aboutsummaryrefslogtreecommitdiffstats
path: root/ide_common
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2011-03-17 18:52:22 -0700
committerXavier Ducrohet <xav@android.com>2011-03-17 19:09:22 -0700
commit026ba97e98e0527d910e15c4e1512893a777a8d2 (patch)
treefb4dd61fcdf82f41aaadd5528091c38c79604454 /ide_common
parent6aa6ce67dc85ea1d928a607d292d2c19bc2e2307 (diff)
downloadsdk-026ba97e98e0527d910e15c4e1512893a777a8d2.zip
sdk-026ba97e98e0527d910e15c4e1512893a777a8d2.tar.gz
sdk-026ba97e98e0527d910e15c4e1512893a777a8d2.tar.bz2
Move classes to ide-common
ResourceFolder/File and children classes (single/multi file) ResourceItem and children classes ResourceRepository (base and framework, project stays in ADT for now) All the ResourceQualifiers and FolderConfiguration Change-Id: I5adc9bdc4886a8fa0ab44860763d594cf7af4bd5
Diffstat (limited to 'ide_common')
-rw-r--r--ide_common/src/com/android/ide/common/resources/FrameworkResourceItem.java40
-rw-r--r--ide_common/src/com/android/ide/common/resources/FrameworkResources.java202
-rw-r--r--ide_common/src/com/android/ide/common/resources/InlineResourceItem.java71
-rw-r--r--ide_common/src/com/android/ide/common/resources/IntArrayWrapper.java53
-rw-r--r--ide_common/src/com/android/ide/common/resources/MultiResourceFile.java175
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceDeltaKind.java26
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceFile.java97
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceFolder.java250
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceItem.java237
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceRepository.java546
-rw-r--r--ide_common/src/com/android/ide/common/resources/SingleResourceFile.java136
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/Configurable.java28
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java153
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/DockModeQualifier.java103
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java92
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java771
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java107
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java159
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java69
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java70
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java164
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java69
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/PixelDensityQualifier.java125
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java159
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java121
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java162
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java68
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java65
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java69
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java71
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java71
-rw-r--r--ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java189
32 files changed, 4718 insertions, 0 deletions
diff --git a/ide_common/src/com/android/ide/common/resources/FrameworkResourceItem.java b/ide_common/src/com/android/ide/common/resources/FrameworkResourceItem.java
new file mode 100644
index 0000000..70bbcef
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/FrameworkResourceItem.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+/**
+ * A custom {@link ResourceItem} for resources provided by the framework.
+ *
+ * The main change is that {@link #isEditableDirectly()} returns false.
+ */
+class FrameworkResourceItem extends ResourceItem {
+
+ FrameworkResourceItem(String name) {
+ super(name);
+ }
+
+ @Override
+ public boolean isEditableDirectly() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "FrameworkResourceItem [mName=" + getName() + ", mFiles=" //$NON-NLS-1$ //$NON-NLS-2$
+ + getSourceFileList() + "]"; //$NON-NLS-1$
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/FrameworkResources.java b/ide_common/src/com/android/ide/common/resources/FrameworkResources.java
new file mode 100644
index 0000000..31dc137
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/FrameworkResources.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import static com.android.AndroidConstants.FD_RES_VALUES;
+
+import com.android.ide.common.log.ILogger;
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.resources.ResourceType;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * Framework resources repository.
+ *
+ * This behaves the same as {@link ResourceRepository} except that it differentiates between
+ * resources that are public and non public.
+ * {@link #getResources(ResourceType)} and {@link #hasResourcesOfType(ResourceType)} only return
+ * public resources. This is typically used to display resource lists in the UI.
+ *
+ * {@link #getConfiguredResources(com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration)}
+ * returns all resources, even the non public ones so that this can be used for rendering.
+ */
+public class FrameworkResources extends ResourceRepository {
+
+ /**
+ * Map of {@link ResourceType} to list of items. It is guaranteed to contain a list for all
+ * possible values of ResourceType.
+ */
+ protected final Map<ResourceType, List<ResourceItem>> mPublicResourceMap =
+ new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class);
+
+ public FrameworkResources() {
+ super(true /*isFrameworkRepository*/);
+ }
+
+ /**
+ * Returns a {@link Collection} (always non null, but can be empty) of <b>public</b>
+ * {@link ResourceItem} matching a given {@link ResourceType}.
+ *
+ * @param type the type of the resources to return
+ * @return a collection of items, possible empty.
+ */
+ @Override
+ public List<ResourceItem> getResourceItemsOfType(ResourceType type) {
+ return mPublicResourceMap.get(type);
+ }
+
+ /**
+ * Returns whether the repository has <b>public</b> resources of a given {@link ResourceType}.
+ * @param type the type of resource to check.
+ * @return true if the repository contains resources of the given type, false otherwise.
+ */
+ @Override
+ public boolean hasResourcesOfType(ResourceType type) {
+ return mPublicResourceMap.get(type).size() > 0;
+ }
+
+ @Override
+ protected ResourceItem createResourceItem(String name) {
+ return new FrameworkResourceItem(name);
+ }
+
+ /**
+ * Reads the public.xml file in data/res/values/ for a given resource folder and builds up
+ * a map of public resources.
+ *
+ * This map is a subset of the full resource map that only contains framework resources
+ * that are public.
+ *
+ * @param osFrameworkResourcePath The root folder of the resources
+ */
+ public void loadPublicResources(IAbstractFolder resFolder, ILogger logger) {
+ IAbstractFolder valueFolder = resFolder.getFolder(FD_RES_VALUES);
+ if (valueFolder.exists() == false) {
+ return;
+ }
+
+ IAbstractFile publicXmlFile = valueFolder.getFile("public.xml"); //$NON-NLS-1$
+ if (publicXmlFile.exists()) {
+ Document document = null;
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Reader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(publicXmlFile.getContents()));
+ InputSource is = new InputSource(reader);
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ document = builder.parse(is);
+
+ ResourceType lastType = null;
+ String lastTypeName = "";
+
+ NodeList children = document.getDocumentElement().getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node node = children.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ String name = element.getAttribute("name"); //$NON-NLS-1$
+ if (name.length() > 0) {
+ String typeName = element.getAttribute("type"); //$NON-NLS-1$
+ ResourceType type = null;
+ if (typeName.equals(lastTypeName)) {
+ type = lastType;
+ } else {
+ type = ResourceType.getEnum(typeName);
+ lastType = type;
+ lastTypeName = typeName;
+ }
+ if (type != null) {
+ List<ResourceItem> typeList = mResourceMap.get(type);
+
+ ResourceItem match = null;
+ if (typeList != null) {
+ for (ResourceItem item : typeList) {
+ if (name.equals(item.getName())) {
+ match = item;
+ break;
+ }
+ }
+ }
+
+ if (match != null) {
+ List<ResourceItem> publicList = mPublicResourceMap.get(type);
+ if (publicList == null) {
+ publicList = new ArrayList<ResourceItem>();
+ mPublicResourceMap.put(type, publicList);
+ }
+
+ publicList.add(match);
+ } else {
+ // log that there's a public resource that doesn't actually
+ // exist?
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ if (logger != null) {
+ logger.error(e, "Can't read and parse public attribute list");
+ }
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // Nothing to be done here - we don't care if it closed or not.
+ }
+ }
+ }
+ }
+
+ // put unmodifiable list for all res type in the public resource map
+ // this will simplify access
+ for (ResourceType type : ResourceType.values()) {
+ List<ResourceItem> list = mPublicResourceMap.get(type);
+ if (list == null) {
+ list = Collections.emptyList();
+ } else {
+ list = Collections.unmodifiableList(list);
+ }
+
+ // put the new list in the map
+ mPublicResourceMap.put(type, list);
+ }
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/InlineResourceItem.java b/ide_common/src/com/android/ide/common/resources/InlineResourceItem.java
new file mode 100644
index 0000000..37fdc81
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/InlineResourceItem.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+
+
+/**
+ * Represents a resource item that has been declared inline in another resource file.
+ *
+ * This covers the typical ID declaration of "@+id/foo", but does not cover normal value
+ * resources declared in strings.xml or other similar value files.
+ *
+ * This resource will return {@code true} for {@link #isDeclaredInline()} and {@code false} for
+ * {@link #isEditableDirectly()}.
+ */
+public class InlineResourceItem extends ResourceItem {
+
+ private ResourceValue mValue = null;
+
+ /**
+ * Constructs a new inline ResourceItem.
+ * @param name the name of the resource as it appears in the XML and R.java files.
+ */
+ public InlineResourceItem(String name) {
+ super(name);
+ }
+
+ @Override
+ public boolean isDeclaredInline() {
+ return true;
+ }
+
+ @Override
+ public boolean isEditableDirectly() {
+ return false;
+ }
+
+ @Override
+ public ResourceValue getResourceValue(ResourceType type, FolderConfiguration referenceConfig,
+ boolean isFramework) {
+ assert type == ResourceType.ID;
+ if (mValue == null) {
+ mValue = new ResourceValue(type, getName(), isFramework);
+ }
+
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return "InlineResourceItem [mName=" + getName() + ", mFiles=" //$NON-NLS-1$ //$NON-NLS-2$
+ + getSourceFileList() + "]"; //$NON-NLS-1$
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/IntArrayWrapper.java b/ide_common/src/com/android/ide/common/resources/IntArrayWrapper.java
new file mode 100644
index 0000000..668c677
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/IntArrayWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import java.util.Arrays;
+
+
+/**
+ * Wrapper around a int[] to provide hashCode/equals support.
+ */
+public final class IntArrayWrapper {
+
+ private int[] mData;
+
+ public IntArrayWrapper(int[] data) {
+ mData = data;
+ }
+
+ public void set(int[] data) {
+ mData = data;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass().equals(obj.getClass())) {
+ return Arrays.equals(mData, ((IntArrayWrapper)obj).mData);
+ }
+
+ return super.equals(obj);
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java
new file mode 100644
index 0000000..c6bfeff
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
+import com.android.io.IAbstractFile;
+import com.android.io.StreamException;
+import com.android.resources.ResourceType;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Represents a resource file able to declare multiple resources, which could be of
+ * different {@link ResourceType}.
+ * <p/>
+ * This is typically an XML file inside res/values.
+ */
+public final class MultiResourceFile extends ResourceFile implements IValueResourceRepository {
+
+ private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance();
+
+ private final Map<ResourceType, Map<String, ResourceValue>> mResourceItems =
+ new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
+
+ private Collection<ResourceType> mResourceTypeList = null;
+
+ public MultiResourceFile(IAbstractFile file, ResourceFolder folder) {
+ super(file, folder);
+ }
+
+ @Override
+ protected void load() {
+ // need to parse the file and find the content.
+ parseFile();
+
+ // create new ResourceItems for the new content.
+ mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet());
+
+ // create/update the resource items.
+ updateResourceItems();
+ }
+
+ @Override
+ protected void update() {
+ // remove this file from all existing ResourceItem.
+ getFolder().getRepository().removeFile(mResourceTypeList, this);
+
+ // reset current content.
+ mResourceItems.clear();
+
+ // need to parse the file and find the content.
+ parseFile();
+
+ // create new ResourceItems for the new content.
+ mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet());
+
+ // create/update the resource items.
+ updateResourceItems();
+ }
+
+ @Override
+ protected void dispose() {
+ // only remove this file from all existing ResourceItem.
+ getFolder().getRepository().removeFile(mResourceTypeList, this);
+
+ // don't need to touch the content, it'll get reclaimed as this objects disappear.
+ // In the mean time other objects may need to access it.
+ }
+
+ @Override
+ public Collection<ResourceType> getResourceTypes() {
+ return mResourceTypeList;
+ }
+
+ @Override
+ public boolean hasResources(ResourceType type) {
+ Map<String, ResourceValue> list = mResourceItems.get(type);
+ return (list != null && list.size() > 0);
+ }
+
+ private void updateResourceItems() {
+ ResourceRepository repository = getRepository();
+ for (ResourceType type : mResourceTypeList) {
+ Map<String, ResourceValue> list = mResourceItems.get(type);
+
+ if (list != null) {
+ Collection<ResourceValue> values = list.values();
+ for (ResourceValue res : values) {
+ ResourceItem item = repository.getResourceItem(type, res.getName());
+
+ // add this file to the list of files generating this resource item.
+ item.add(this);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses the file and creates a list of {@link ResourceType}.
+ */
+ private void parseFile() {
+ try {
+ SAXParser parser = sParserFactory.newSAXParser();
+ parser.parse(getFile().getContents(), new ValueResourceParser(this, isFramework()));
+ } catch (ParserConfigurationException e) {
+ } catch (SAXException e) {
+ } catch (IOException e) {
+ } catch (StreamException e) {
+ }
+ }
+
+ /**
+ * Adds a resource item to the list
+ * @param resType The type of the resource
+ * @param value The value of the resource.
+ */
+ public void addResourceValue(ResourceType resType, ResourceValue value) {
+ Map<String, ResourceValue> list = mResourceItems.get(resType);
+
+ // if the list does not exist, create it.
+ if (list == null) {
+ list = new HashMap<String, ResourceValue>();
+ mResourceItems.put(resType, list);
+ } else {
+ // look for a possible value already existing.
+ ResourceValue oldValue = list.get(value.getName());
+
+ if (oldValue != null) {
+ oldValue.replaceWith(value);
+ return;
+ }
+ }
+
+ // empty list or no match found? add the given resource
+ list.put(value.getName(), value);
+ }
+
+ @Override
+ public ResourceValue getValue(ResourceType type, String name) {
+ // get the list for the given type
+ Map<String, ResourceValue> list = mResourceItems.get(type);
+
+ if (list != null) {
+ return list.get(name);
+ }
+
+ return null;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceDeltaKind.java b/ide_common/src/com/android/ide/common/resources/ResourceDeltaKind.java
new file mode 100644
index 0000000..769b6ea
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/ResourceDeltaKind.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+/**
+ * Enum indicating a type of resource change.
+ *
+ * This is similar, and can be easily mapped to Eclipse's integer constants in IResourceDelta.
+ */
+public enum ResourceDeltaKind {
+ CHANGED, ADDED, REMOVED;
+}
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceFile.java b/ide_common/src/com/android/ide/common/resources/ResourceFile.java
new file mode 100644
index 0000000..03f0b34
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/ResourceFile.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.Configurable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.io.IAbstractFile;
+import com.android.resources.ResourceType;
+
+import java.util.Collection;
+
+/**
+ * Represents a Resource file (a file under $Project/res/)
+ */
+public abstract class ResourceFile implements Configurable {
+
+ private final IAbstractFile mFile;
+ private final ResourceFolder mFolder;
+
+ protected ResourceFile(IAbstractFile file, ResourceFolder folder) {
+ mFile = file;
+ mFolder = folder;
+ }
+
+ protected abstract void load();
+ protected abstract void update();
+ protected abstract void dispose();
+
+ public FolderConfiguration getConfiguration() {
+ return mFolder.getConfiguration();
+ }
+
+ /**
+ * Returns the IFile associated with the ResourceFile.
+ */
+ public final IAbstractFile getFile() {
+ return mFile;
+ }
+
+ /**
+ * Returns the parent folder as a {@link ResourceFolder}.
+ */
+ public final ResourceFolder getFolder() {
+ return mFolder;
+ }
+
+ public final ResourceRepository getRepository() {
+ return mFolder.getRepository();
+ }
+
+ /**
+ * Returns whether the resource is a framework resource.
+ */
+ public final boolean isFramework() {
+ return mFolder.getRepository().isFrameworkRepository();
+ }
+
+ /**
+ * Returns the list of {@link ResourceType} generated by the file. This is never null.
+ */
+ public abstract Collection<ResourceType> getResourceTypes();
+
+ /**
+ * Returns whether the file generated a resource of a specific type.
+ * @param type The {@link ResourceType}
+ */
+ public abstract boolean hasResources(ResourceType type);
+
+ /**
+ * Returns the value of a resource generated by this file by {@link ResourceType} and name.
+ * <p/>If no resource match, <code>null</code> is returned.
+ * @param type the type of the resource.
+ * @param name the name of the resource.
+ */
+ public abstract ResourceValue getValue(ResourceType type, String name);
+
+ @Override
+ public String toString() {
+ return mFile.toString();
+ }
+}
+
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceFolder.java b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java
new file mode 100644
index 0000000..abdf200
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.ide.common.resources.configuration.Configurable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Resource Folder class. Contains list of {@link ResourceFile}s,
+ * the {@link FolderConfiguration}, and a link to the {@link IAbstractFolder} object.
+ */
+public final class ResourceFolder implements Configurable {
+ final ResourceFolderType mType;
+ final FolderConfiguration mConfiguration;
+ IAbstractFolder mFolder;
+ ArrayList<ResourceFile> mFiles = null;
+ private final ResourceRepository mRepository;
+
+
+ /**
+ * Creates a new {@link ResourceFolder}
+ * @param type The type of the folder
+ * @param config The configuration of the folder
+ * @param folder The associated {@link IAbstractFolder} object.
+ * @param isFrameworkRepository
+ */
+ protected ResourceFolder(ResourceFolderType type, FolderConfiguration config,
+ IAbstractFolder folder, ResourceRepository repository) {
+ mType = type;
+ mConfiguration = config;
+ mFolder = folder;
+ mRepository = repository;
+ }
+
+ /**
+ * Processes a file and adds it to its parent folder resource.
+ * @param file the underlying resource file.
+ * @param folder the parent of the resource file.
+ * @param kind the file change kind.
+ * @return the {@link ResourceFile} that was created.
+ */
+ public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind) {
+ // look for this file if it's already been created
+ ResourceFile resFile = getFile(file);
+
+ if (resFile == null) {
+ if (kind != ResourceDeltaKind.REMOVED) {
+ // create a ResourceFile for it.
+
+ // check if that's a single or multi resource type folder. For now we define this by
+ // the number of possible resource type output by files in the folder. This does
+ // not make the difference between several resource types from a single file or
+ // the ability to have 2 files in the same folder generating 2 different types of
+ // resource. The former is handled by MultiResourceFile properly while we don't
+ // handle the latter. If we were to add this behavior we'd have to change this call.
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(mType);
+
+ if (types.size() == 1) {
+ resFile = new SingleResourceFile(file, this);
+ } else {
+ resFile = new MultiResourceFile(file, this);
+ }
+
+ resFile.load();
+
+ // add it to the folder
+ addFile(resFile);
+ }
+ } else {
+ if (kind == ResourceDeltaKind.REMOVED) {
+ removeFile(resFile);
+ } else {
+ resFile.update();
+ }
+ }
+
+ return resFile;
+ }
+
+
+ /**
+ * Adds a {@link ResourceFile} to the folder.
+ * @param file The {@link ResourceFile}.
+ */
+ @VisibleForTesting(visibility=Visibility.PROTECTED)
+ public void addFile(ResourceFile file) {
+ if (mFiles == null) {
+ mFiles = new ArrayList<ResourceFile>();
+ }
+
+ mFiles.add(file);
+ }
+
+ protected void removeFile(ResourceFile file) {
+ file.dispose();
+ mFiles.remove(file);
+ }
+
+ protected void dispose() {
+ for (ResourceFile file : mFiles) {
+ file.dispose();
+ }
+
+ mFiles.clear();
+ }
+
+ /**
+ * Returns the {@link IAbstractFolder} associated with this object.
+ */
+ public IAbstractFolder getFolder() {
+ return mFolder;
+ }
+
+ /**
+ * Returns the {@link ResourceFolderType} of this object.
+ */
+ public ResourceFolderType getType() {
+ return mType;
+ }
+
+ public ResourceRepository getRepository() {
+ return mRepository;
+ }
+
+ /**
+ * Returns the list of {@link ResourceType}s generated by the files inside this folder.
+ */
+ public Collection<ResourceType> getResourceTypes() {
+ ArrayList<ResourceType> list = new ArrayList<ResourceType>();
+
+ if (mFiles != null) {
+ for (ResourceFile file : mFiles) {
+ Collection<ResourceType> types = file.getResourceTypes();
+
+ // loop through those and add them to the main list,
+ // if they are not already present
+ for (ResourceType resType : types) {
+ if (list.indexOf(resType) == -1) {
+ list.add(resType);
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+
+ public FolderConfiguration getConfiguration() {
+ return mConfiguration;
+ }
+
+ /**
+ * Returns whether the folder contains a file with the given name.
+ * @param name the name of the file.
+ */
+ public boolean hasFile(String name) {
+ return mFolder.hasFile(name);
+ }
+
+ /**
+ * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object.
+ * @param file The {@link IAbstractFile} object.
+ * @return the {@link ResourceFile} or null if no match was found.
+ */
+ private ResourceFile getFile(IAbstractFile file) {
+ if (mFiles != null) {
+ for (ResourceFile f : mFiles) {
+ if (f.getFile().equals(file)) {
+ return f;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the {@link ResourceFile} matching a given name.
+ * @param filename The name of the file to return.
+ * @return the {@link ResourceFile} or <code>null</code> if no match was found.
+ */
+ public ResourceFile getFile(String filename) {
+ if (mFiles != null) {
+ for (ResourceFile f : mFiles) {
+ if (f.getFile().getName().equals(filename)) {
+ return f;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether a file in the folder is generating a resource of a specified type.
+ * @param type The {@link ResourceType} being looked up.
+ */
+ public boolean hasResources(ResourceType type) {
+ // Check if the folder type is able to generate resource of the type that was asked.
+ // this is a first check to avoid going through the files.
+ List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);
+
+ boolean valid = false;
+ for (ResourceFolderType rft : folderTypes) {
+ if (rft == mType) {
+ valid = true;
+ break;
+ }
+ }
+
+ if (valid) {
+ if (mFiles != null) {
+ for (ResourceFile f : mFiles) {
+ if (f.hasResources(type)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return mFolder.toString();
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceItem.java b/ide_common/src/com/android/ide/common/resources/ResourceItem.java
new file mode 100644
index 0000000..dd28a9a
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/ResourceItem.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * An android resource.
+ *
+ * This is a representation of the resource, not of its value(s). It gives access to all
+ * the source files that generate this particular resource which then can be used to access
+ * the actual value(s).
+ *
+ * @see ResourceFile#getResources(ResourceType, ResourceRepository)
+ */
+public class ResourceItem implements Comparable<ResourceItem> {
+
+ private final static Comparator<ResourceFile> sComparator = new Comparator<ResourceFile>() {
+ public int compare(ResourceFile file1, ResourceFile file2) {
+ // get both FolderConfiguration and compare them
+ FolderConfiguration fc1 = file1.getFolder().getConfiguration();
+ FolderConfiguration fc2 = file2.getFolder().getConfiguration();
+
+ return fc1.compareTo(fc2);
+ }
+ };
+
+ private final String mName;
+
+ /**
+ * List of files generating this ResourceItem.
+ */
+ private final List<ResourceFile> mFiles = new ArrayList<ResourceFile>();
+
+ /**
+ * Constructs a new ResourceItem.
+ * @param name the name of the resource as it appears in the XML and R.java files.
+ */
+ public ResourceItem(String name) {
+ mName = name;
+ }
+
+ /**
+ * Returns the name of the resource.
+ */
+ public final String getName() {
+ return mName;
+ }
+
+ /**
+ * Compares the {@link ResourceItem} to another.
+ * @param other the ResourceItem to be compared to.
+ */
+ public int compareTo(ResourceItem other) {
+ return mName.compareTo(other.mName);
+ }
+
+ /**
+ * Returns whether the resource is editable directly.
+ * <p/>
+ * This is typically the case for resources that don't have alternate versions, or resources
+ * of type {@link ResourceType#ID} that aren't declared inline.
+ */
+ public boolean isEditableDirectly() {
+ return hasAlternates() == false;
+ }
+
+ /**
+ * Returns whether the ID resource has been declared inline inside another resource XML file.
+ * If the resource type is not {@link ResourceType#ID}, this will always return {@code false}.
+ */
+ public boolean isDeclaredInline() {
+ return false;
+ }
+
+ /**
+ * Returns a {@link ResourceValue} for this item based on the given configuration.
+ * If the ResourceItem has several source files, one will be selected based on the config.
+ * @param type the type of the resource. This is necessary because ResourceItem doesn't embed
+ * its type, but ResourceValue does.
+ * @param referenceConfig the config of the resource item.
+ * @param isFramework whether the resource is a framework value. Same as the type.
+ * @return a ResourceValue or null if none match the config.
+ */
+ public ResourceValue getResourceValue(ResourceType type, FolderConfiguration referenceConfig,
+ boolean isFramework) {
+ // look for the best match for the given configuration
+ // the match has to be of type ResourceFile since that's what the input list contains
+ ResourceFile match = (ResourceFile) referenceConfig.findMatchingConfigurable(mFiles);
+
+ if (match != null) {
+ // get the value of this configured resource.
+ return match.getValue(type, mName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds a new source file.
+ * @param file the source file.
+ */
+ protected void add(ResourceFile file) {
+ mFiles.add(file);
+ }
+
+ /**
+ * Removes a file from the list of source files.
+ * @param file the file to remove
+ */
+ protected void removeFile(ResourceFile file) {
+ mFiles.remove(file);
+ }
+
+ /**
+ * Returns {@code true} if the item has no source file.
+ * @return
+ */
+ protected boolean hasNoSourceFile() {
+ return mFiles.size() == 0;
+ }
+
+ /**
+ * Reset the item by emptying its source file list.
+ */
+ protected void reset() {
+ mFiles.clear();
+ }
+
+ /**
+ * Returns the sorted list of {@link ResourceItem} objects for this resource item.
+ */
+ public ResourceFile[] getSourceFileArray() {
+ ArrayList<ResourceFile> list = new ArrayList<ResourceFile>();
+ list.addAll(mFiles);
+
+ Collections.sort(list, sComparator);
+
+ return list.toArray(new ResourceFile[list.size()]);
+ }
+
+ /**
+ * Returns the list of source file for this resource.
+ */
+ public List<ResourceFile> getSourceFileList() {
+ return Collections.unmodifiableList(mFiles);
+ }
+
+ /**
+ * Returns if the resource has at least one non-default version.
+ *
+ * @see ResourceFile#getConfiguration()
+ * @see FolderConfiguration#isDefault()
+ */
+ public boolean hasAlternates() {
+ for (ResourceFile file : mFiles) {
+ if (file.getFolder().getConfiguration().isDefault() == false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the resource has a default version, with no qualifier.
+ *
+ * @see ResourceFile#getConfiguration()
+ * @see FolderConfiguration#isDefault()
+ */
+ public boolean hasDefault() {
+ for (ResourceFile file : mFiles) {
+ if (file.getFolder().getConfiguration().isDefault()) {
+ return true;
+ }
+ }
+
+ // We only want to return false if there's no default and more than 0 items.
+ return (mFiles.size() == 0);
+ }
+
+ /**
+ * Returns the number of alternate versions for this resource.
+ *
+ * @see ResourceFile#getConfiguration()
+ * @see FolderConfiguration#isDefault()
+ */
+ public int getAlternateCount() {
+ int count = 0;
+ for (ResourceFile file : mFiles) {
+ if (file.getFolder().getConfiguration().isDefault() == false) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * Returns a formatted string usable in an XML to use for the {@link ResourceItem}.
+ * @param system Whether this is a system resource or a project resource.
+ * @return a string in the format @[type]/[name]
+ */
+ public String getXmlString(ResourceType type, boolean system) {
+ if (type == ResourceType.ID && isDeclaredInline()) {
+ return (system ? "@android:" : "@+") + type.getName() + "/" + mName; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ return (system ? "@android:" : "@") + type.getName() + "/" + mName; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceItem [mName=" + mName + ", mFiles=" + mFiles + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java
new file mode 100644
index 0000000..41e4f89
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.AndroidConstants;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.Configurable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.android.io.IAbstractFolder;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Base class for resource repository.
+ *
+ * A repository is both a file representation of a resource folder and a representation
+ * of the generated resources, organized by type.
+ *
+ * {@link #getResourceFolder(IAbstractFolder)} and {@link #getSourceFiles(ResourceType, String, FolderConfiguration)}
+ * give access to the folders and files of the resource folder.
+ *
+ * {@link #getResources(ResourceType)} gives access to the resources directly.
+ *
+ */
+public abstract class ResourceRepository {
+
+ protected final Map<ResourceFolderType, List<ResourceFolder>> mFolderMap =
+ new EnumMap<ResourceFolderType, List<ResourceFolder>>(ResourceFolderType.class);
+
+ protected final Map<ResourceType, List<ResourceItem>> mResourceMap =
+ new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class);
+
+ private final Map<List<ResourceItem>, List<ResourceItem>> mReadOnlyListMap =
+ new IdentityHashMap<List<ResourceItem>, List<ResourceItem>>();
+
+ private final boolean mFrameworkRepository;
+
+ protected final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
+
+
+ /**
+ * Makes a resource repository
+ * @param isFrameworkRepository whether the repository is for framework resources.
+ */
+ protected ResourceRepository(boolean isFrameworkRepository) {
+ mFrameworkRepository = isFrameworkRepository;
+ }
+
+ public boolean isFrameworkRepository() {
+ return mFrameworkRepository;
+ }
+
+ /**
+ * Adds a Folder Configuration to the project.
+ * @param type The resource type.
+ * @param config The resource configuration.
+ * @param folder The workspace folder object.
+ * @return the {@link ResourceFolder} object associated to this folder.
+ */
+ private ResourceFolder add(ResourceFolderType type, FolderConfiguration config,
+ IAbstractFolder folder) {
+ // get the list for the resource type
+ List<ResourceFolder> list = mFolderMap.get(type);
+
+ if (list == null) {
+ list = new ArrayList<ResourceFolder>();
+
+ ResourceFolder cf = new ResourceFolder(type, config, folder, this);
+ list.add(cf);
+
+ mFolderMap.put(type, list);
+
+ return cf;
+ }
+
+ // look for an already existing folder configuration.
+ for (ResourceFolder cFolder : list) {
+ if (cFolder.mConfiguration.equals(config)) {
+ // config already exist. Nothing to be done really, besides making sure
+ // the IAbstractFolder object is up to date.
+ cFolder.mFolder = folder;
+ return cFolder;
+ }
+ }
+
+ // If we arrive here, this means we didn't find a matching configuration.
+ // So we add one.
+ ResourceFolder cf = new ResourceFolder(type, config, folder, this);
+ list.add(cf);
+
+ return cf;
+ }
+
+ /**
+ * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}.
+ * @param type The type of the folder
+ * @param removedFolder the IAbstractFolder object.
+ * @return the {@link ResourceFolder} that was removed, or null if no matches were found.
+ */
+ public ResourceFolder removeFolder(ResourceFolderType type, IAbstractFolder removedFolder) {
+ // get the list of folders for the resource type.
+ List<ResourceFolder> list = mFolderMap.get(type);
+
+ if (list != null) {
+ int count = list.size();
+ for (int i = 0 ; i < count ; i++) {
+ ResourceFolder resFolder = list.get(i);
+ IAbstractFolder folder = resFolder.getFolder();
+ if (removedFolder.equals(folder)) {
+ // we found the matching ResourceFolder. we need to remove it.
+ list.remove(i);
+
+ // remove its content
+ resFolder.dispose();
+
+ return resFolder;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a {@link ResourceItem} matching the given {@link ResourceType} and name. If none
+ * exist, it creates one.
+ *
+ * @param type the resource type
+ * @param name the name of the resource.
+ * @return A resource item matching the type and name.
+ */
+ protected ResourceItem getResourceItem(ResourceType type, String name) {
+ // looking for an existing ResourceItem with this type and name
+ ResourceItem item = findDeclaredResourceItem(type, name);
+
+ // create one if there isn't one already, or if the existing one is inlined, since
+ // clearly we need a non inlined one (the inline one is removed too)
+ if (item == null || item.isDeclaredInline()) {
+ ResourceItem oldItem = item != null && item.isDeclaredInline() ? item : null;
+
+ item = createResourceItem(name);
+
+ List<ResourceItem> list = mResourceMap.get(type);
+ if (list == null) {
+ list = new ArrayList<ResourceItem>();
+ mResourceMap.put(type, list);
+ }
+
+ list.add(item);
+
+ if (oldItem != null) {
+ list.remove(oldItem);
+ }
+ }
+
+ return item;
+ }
+
+ /**
+ * Creates a resource item with the given name.
+ * @param name the name of the resource
+ * @return a new ResourceItem (or child class) instance.
+ */
+ protected abstract ResourceItem createResourceItem(String name);
+
+ /**
+ * Processes a folder and adds it to the list of existing folders.
+ * @param folder the folder to process
+ * @return the ResourceFolder created from this folder, or null if the process failed.
+ */
+ public ResourceFolder processFolder(IAbstractFolder folder) {
+ // split the name of the folder in segments.
+ String[] folderSegments = folder.getName().split(AndroidConstants.RES_QUALIFIER_SEP);
+
+ // get the enum for the resource type.
+ ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);
+
+ if (type != null) {
+ // get the folder configuration.
+ FolderConfiguration config = FolderConfiguration.getConfig(folderSegments);
+
+ if (config != null) {
+ return add(type, config, folder);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}.
+ * @param type The {@link ResourceFolderType}
+ */
+ public List<ResourceFolder> getFolders(ResourceFolderType type) {
+ return mFolderMap.get(type);
+ }
+
+ public List<ResourceType> getAvailableResourceTypes() {
+ List<ResourceType> list = new ArrayList<ResourceType>();
+
+ // For each key, we check if there's a single ResourceType match.
+ // If not, we look for the actual content to give us the resource type.
+
+ for (ResourceFolderType folderType : mFolderMap.keySet()) {
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType);
+ if (types.size() == 1) {
+ // before we add it we check if it's not already present, since a ResourceType
+ // could be created from multiple folders, even for the folders that only create
+ // one type of resource (drawable for instance, can be created from drawable/ and
+ // values/)
+ if (list.contains(types.get(0)) == false) {
+ list.add(types.get(0));
+ }
+ } else {
+ // there isn't a single resource type out of this folder, so we look for all
+ // content.
+ List<ResourceFolder> folders = mFolderMap.get(folderType);
+ if (folders != null) {
+ for (ResourceFolder folder : folders) {
+ Collection<ResourceType> folderContent = folder.getResourceTypes();
+
+ // then we add them, but only if they aren't already in the list.
+ for (ResourceType folderResType : folderContent) {
+ if (list.contains(folderResType) == false) {
+ list.add(folderResType);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns a list of {@link ResourceItem} matching a given {@link ResourceType}.
+ * @param type the type of the resource items to return
+ * @return a non null collection of resource items
+ */
+ public Collection<ResourceItem> getResourceItemsOfType(ResourceType type) {
+ List<ResourceItem> list = mResourceMap.get(type);
+
+ if (list == null) {
+ return Collections.emptyList();
+ }
+
+ List<ResourceItem> roList = mReadOnlyListMap.get(list);
+ if (roList == null) {
+ roList = Collections.unmodifiableList(list);
+ mReadOnlyListMap.put(list, roList);
+ }
+
+ return roList;
+ }
+
+ /**
+ * Returns whether the repository has resources of a given {@link ResourceType}.
+ * @param type the type of resource to check.
+ * @return true if the repository contains resources of the given type, false otherwise.
+ */
+ public boolean hasResourcesOfType(ResourceType type) {
+ List<ResourceItem> items = mResourceMap.get(type);
+ return (items != null && items.size() > 0);
+ }
+
+ /**
+ * Returns the {@link ResourceFolder} associated with a {@link IAbstractFolder}.
+ * @param folder The {@link IAbstractFolder} object.
+ * @return the {@link ResourceFolder} or null if it was not found.
+ */
+ public ResourceFolder getResourceFolder(IAbstractFolder folder) {
+ for (List<ResourceFolder> list : mFolderMap.values()) {
+ for (ResourceFolder resFolder : list) {
+ IAbstractFolder wrapper = resFolder.getFolder();
+ if (wrapper.equals(folder)) {
+ return resFolder;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and
+ * configuration.
+ * <p/>This only works with files generating one resource named after the file (for instance,
+ * layouts, bitmap based drawable, xml, anims).
+ * @return the matching file or <code>null</code> if no match was found.
+ */
+ public ResourceFile getMatchingFile(String name, ResourceFolderType type,
+ FolderConfiguration config) {
+ // get the folders for the given type
+ List<ResourceFolder> folders = mFolderMap.get(type);
+
+ // look for folders containing a file with the given name.
+ ArrayList<ResourceFolder> matchingFolders = new ArrayList<ResourceFolder>(folders.size());
+
+ // remove the folders that do not have a file with the given name.
+ for (int i = 0 ; i < folders.size(); i++) {
+ ResourceFolder folder = folders.get(i);
+
+ if (folder.hasFile(name) == true) {
+ matchingFolders.add(folder);
+ }
+ }
+
+ // from those, get the folder with a config matching the given reference configuration.
+ Configurable match = config.findMatchingConfigurable(matchingFolders);
+
+ // do we have a matching folder?
+ if (match instanceof ResourceFolder) {
+ // get the ResourceFile from the filename
+ return ((ResourceFolder)match).getFile(name);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the list of source files for a given resource.
+ * Optionally, if a {@link FolderConfiguration} is given, then only the best
+ * match for this config is returned.
+ *
+ * @param type the type of the resource.
+ * @param name the name of the resource.
+ * @param referenceConfig an optional config for which only the best match will be returned.
+ *
+ * @return a list of files generating this resource or null if it was not found.
+ */
+ public List<ResourceFile> getSourceFiles(ResourceType type, String name,
+ FolderConfiguration referenceConfig) {
+
+ Collection<ResourceItem> items = getResourceItemsOfType(type);
+
+ for (ResourceItem item : items) {
+ if (name.equals(item.getName())) {
+ if (referenceConfig != null) {
+ Configurable match = referenceConfig.findMatchingConfigurable(
+ item.getSourceFileList());
+
+ if (match instanceof ResourceFile) {
+ return Collections.singletonList((ResourceFile) match);
+ }
+
+ return null;
+ }
+ return item.getSourceFileList();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the resources values matching a given {@link FolderConfiguration}.
+ *
+ * @param referenceConfig the configuration that each value must match.
+ * @return a map with guaranteed to contain an entry for each {@link ResourceType}
+ */
+ public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
+ FolderConfiguration referenceConfig) {
+ return doGetConfiguredResources(referenceConfig);
+ }
+
+ /**
+ * Returns the resources values matching a given {@link FolderConfiguration} for the current
+ * project.
+ *
+ * @param referenceConfig the configuration that each value must match.
+ * @return a map with guaranteed to contain an entry for each {@link ResourceType}
+ */
+ protected final Map<ResourceType, Map<String, ResourceValue>> doGetConfiguredResources(
+ FolderConfiguration referenceConfig) {
+
+ Map<ResourceType, Map<String, ResourceValue>> map =
+ new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
+
+ for (ResourceType key : ResourceType.values()) {
+ // get the local results and put them in the map
+ map.put(key, getConfiguredResource(key, referenceConfig));
+ }
+
+ return map;
+ }
+
+ /**
+ * Returns the sorted list of languages used in the resources.
+ */
+ public SortedSet<String> getLanguages() {
+ SortedSet<String> set = new TreeSet<String>();
+
+ Collection<List<ResourceFolder>> folderList = mFolderMap.values();
+ for (List<ResourceFolder> folderSubList : folderList) {
+ for (ResourceFolder folder : folderSubList) {
+ FolderConfiguration config = folder.getConfiguration();
+ LanguageQualifier lang = config.getLanguageQualifier();
+ if (lang != null) {
+ set.add(lang.getShortDisplayValue());
+ }
+ }
+ }
+
+ return set;
+ }
+
+ /**
+ * Returns the sorted list of regions used in the resources with the given language.
+ * @param currentLanguage the current language the region must be associated with.
+ */
+ public SortedSet<String> getRegions(String currentLanguage) {
+ SortedSet<String> set = new TreeSet<String>();
+
+ Collection<List<ResourceFolder>> folderList = mFolderMap.values();
+ for (List<ResourceFolder> folderSubList : folderList) {
+ for (ResourceFolder folder : folderSubList) {
+ FolderConfiguration config = folder.getConfiguration();
+
+ // get the language
+ LanguageQualifier lang = config.getLanguageQualifier();
+ if (lang != null && lang.getShortDisplayValue().equals(currentLanguage)) {
+ RegionQualifier region = config.getRegionQualifier();
+ if (region != null) {
+ set.add(region.getShortDisplayValue());
+ }
+ }
+ }
+ }
+
+ return set;
+ }
+
+ protected void removeFile(Collection<ResourceType> types, ResourceFile file) {
+ for (ResourceType type : types) {
+ removeFile(type, file);
+ }
+ }
+
+ protected void removeFile(ResourceType type, ResourceFile file) {
+ List<ResourceItem> list = mResourceMap.get(type);
+ for (int i = 0 ; i < list.size(); i++) {
+ ResourceItem item = list.get(i);
+ item.removeFile(file);
+ }
+ }
+
+ /**
+ * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
+ * <p/>The values returned are taken from the resource files best matching a given
+ * {@link FolderConfiguration}.
+ * @param type the type of the resources.
+ * @param referenceConfig the configuration to best match.
+ */
+ private Map<String, ResourceValue> getConfiguredResource(ResourceType type,
+ FolderConfiguration referenceConfig) {
+ // get the resource item for the given type
+ List<ResourceItem> items = mResourceMap.get(type);
+ if (items == null) {
+ return Collections.emptyMap();
+ }
+
+ // create the map
+ HashMap<String, ResourceValue> map = new HashMap<String, ResourceValue>(items.size());
+
+ for (ResourceItem item : items) {
+ ResourceValue value = item.getResourceValue(type, referenceConfig,
+ isFrameworkRepository());
+ if (value != null) {
+ map.put(item.getName(), value);
+ }
+ }
+
+ return map;
+ }
+
+
+ /**
+ * Called after a resource change event, when the resource delta has been processed.
+ */
+ protected void postUpdate() {
+ // Since removed files/folders remove source files from existing ResourceItem, loop through
+ // all resource items and remove the ones that have no source files.
+
+ Collection<List<ResourceItem>> lists = mResourceMap.values();
+ for (List<ResourceItem> list : lists) {
+ for (int i = 0 ; i < list.size() ;) {
+ if (list.get(i).hasNoSourceFile()) {
+ list.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Looks up an existing {@link ResourceItem} by {@link ResourceType} and name. This
+ * ignores inline resources.
+ * @param type the Resource Type.
+ * @param name the Resource name.
+ * @return the existing ResourceItem or null if no match was found.
+ */
+ private ResourceItem findDeclaredResourceItem(ResourceType type, String name) {
+ List<ResourceItem> list = mResourceMap.get(type);
+
+ if (list != null) {
+ for (ResourceItem item : list) {
+ // ignore inline
+ if (name.equals(item.getName()) && item.isDeclaredInline() == false) {
+ return item;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java
new file mode 100644
index 0000000..cd2b627
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.PixelDensityQualifier;
+import com.android.io.IAbstractFile;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceType;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Represents a resource file describing a single resource.
+ * <p/>
+ * This is typically an XML file inside res/anim, res/layout, or res/menu or an image file
+ * under res/drawable.
+ */
+public class SingleResourceFile extends ResourceFile {
+
+ private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance();
+ static {
+ sParserFactory.setNamespaceAware(true);
+ }
+
+ private String mResourceName;
+ private ResourceType mType;
+ private ResourceValue mValue;
+
+ public SingleResourceFile(IAbstractFile file, ResourceFolder folder) {
+ super(file, folder);
+
+ // we need to infer the type of the resource from the folder type.
+ // This is easy since this is a single Resource file.
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folder.getType());
+ mType = types.get(0);
+
+ // compute the resource name
+ mResourceName = getResourceName(mType);
+
+ // test if there's a density qualifier associated with the resource
+ PixelDensityQualifier qualifier = folder.getConfiguration().getPixelDensityQualifier();
+
+ if (qualifier == null) {
+ mValue = new ResourceValue(mType, getResourceName(mType),
+ file.getOsLocation(), isFramework());
+ } else {
+ mValue = new DensityBasedResourceValue(
+ mType,
+ getResourceName(mType),
+ file.getOsLocation(),
+ qualifier.getValue(),
+ isFramework());
+ }
+ }
+
+ @Override
+ protected void load() {
+ // get a resource item matching the given type and name
+ ResourceItem item = getRepository().getResourceItem(mType, mResourceName);
+
+ // add this file to the list of files generating this resource item.
+ item.add(this);
+ }
+
+ @Override
+ protected void update() {
+ // when this happens, nothing needs to be done since the file only generates
+ // a single resources that doesn't actually change (its content is the file path)
+ }
+
+ @Override
+ protected void dispose() {
+ // only remove this file from the existing ResourceItem.
+ getFolder().getRepository().removeFile(mType, this);
+
+ // don't need to touch the content, it'll get reclaimed as this objects disappear.
+ // In the mean time other objects may need to access it.
+ }
+
+ @Override
+ public Collection<ResourceType> getResourceTypes() {
+ return FolderTypeRelationship.getRelatedResourceTypes(getFolder().getType());
+ }
+
+ @Override
+ public boolean hasResources(ResourceType type) {
+ return FolderTypeRelationship.match(type, getFolder().getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ide.eclipse.editors.resources.manager.ResourceFile#getValue(com.android.ide.eclipse.common.resources.ResourceType, java.lang.String)
+ *
+ * This particular implementation does not care about the type or name since a
+ * SingleResourceFile represents a file generating only one resource.
+ * The value returned is the full absolute path of the file in OS form.
+ */
+ @Override
+ public ResourceValue getValue(ResourceType type, String name) {
+ return mValue;
+ }
+
+ /**
+ * Returns the name of the resources.
+ */
+ private String getResourceName(ResourceType type) {
+ // get the name from the filename.
+ String name = getFile().getName();
+
+ int pos = name.indexOf('.');
+ if (pos != -1) {
+ name = name.substring(0, pos);
+ }
+
+ return name;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/Configurable.java b/ide_common/src/com/android/ide/common/resources/configuration/Configurable.java
new file mode 100644
index 0000000..5e7f910
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/Configurable.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+
+/**
+ * An object that is associated with a {@link FolderConfiguration}.
+ */
+public interface Configurable {
+ /**
+ * Returns the {@link FolderConfiguration} for this object.
+ */
+ public FolderConfiguration getConfiguration();
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
new file mode 100644
index 0000000..7195ba5
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Mobile Country Code.
+ */
+public final class CountryCodeQualifier extends ResourceQualifier {
+ /** Default pixel density value. This means the property is not set. */
+ private final static int DEFAULT_CODE = -1;
+
+ private final static Pattern sCountryCodePattern = Pattern.compile("^mcc(\\d{3})$");//$NON-NLS-1$
+
+ private final int mCode;
+
+ public static final String NAME = "Mobile Country Code";
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link CountryCodeQualifier} object or <code>null</code>
+ */
+ public static CountryCodeQualifier getQualifier(String segment) {
+ Matcher m = sCountryCodePattern.matcher(segment);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return null;
+ }
+
+ CountryCodeQualifier qualifier = new CountryCodeQualifier(code);
+ return qualifier;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link CountryCodeQualifier} object.
+ * @param code the value of the qualifier, as returned by {@link #getCode()}.
+ */
+ public static String getFolderSegment(int code) {
+ if (code != DEFAULT_CODE && code >= 100 && code <=999) { // code is 3 digit.) {
+ return String.format("mcc%1$d", code); //$NON-NLS-1$
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public CountryCodeQualifier() {
+ this(DEFAULT_CODE);
+ }
+
+ public CountryCodeQualifier(int code) {
+ mCode = code;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Country Code";
+ }
+
+ @Override
+ public boolean isValid() {
+ return mCode != DEFAULT_CODE;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ CountryCodeQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setCountryCodeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof CountryCodeQualifier) {
+ return mCode == ((CountryCodeQualifier)qualifier).mCode;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCode;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mCode);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mCode != DEFAULT_CODE) {
+ return String.format("MCC %1$d", mCode);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ return getShortDisplayValue();
+ }
+
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/DockModeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/DockModeQualifier.java
new file mode 100644
index 0000000..2c832eb
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/DockModeQualifier.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.DockMode;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for Navigation Method.
+ */
+public final class DockModeQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Dock Mode";
+
+ private DockMode mValue;
+
+ public DockModeQualifier() {
+ // pass
+ }
+
+ public DockModeQualifier(DockMode value) {
+ mValue = value;
+ }
+
+ public DockMode getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Dock Mode";
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ DockMode mode = DockMode.getEnum(value);
+ if (mode != null) {
+ DockModeQualifier qualifier = new DockModeQualifier(mode);
+ config.setDockModeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ // only NONE is a match other DockModes
+ if (mValue == DockMode.NONE) {
+ return true;
+ }
+
+ // others must be an exact match
+ return ((DockModeQualifier)qualifier).mValue == mValue;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ DockModeQualifier compareQualifier = (DockModeQualifier)compareTo;
+ DockModeQualifier referenceQualifier = (DockModeQualifier)reference;
+
+ if (compareQualifier.getValue() == referenceQualifier.getValue()) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mValue == referenceQualifier.mValue) {
+ // got new exact value, this is the best!
+ return true;
+ } else if (mValue == DockMode.NONE) {
+ // else "none" can be a match in case there's no exact match
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java
new file mode 100644
index 0000000..7bfda2d
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+
+/**
+ * Base class for {@link ResourceQualifier} whose value is an {@link ResourceEnum}.
+ *
+ */
+abstract class EnumBasedResourceQualifier extends ResourceQualifier {
+
+ abstract ResourceEnum getEnumValue();
+
+ @Override
+ public boolean isValid() {
+ return getEnumValue() != null;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return getEnumValue().isFakeValue();
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof EnumBasedResourceQualifier) {
+ return getEnumValue() == ((EnumBasedResourceQualifier)qualifier).getEnumValue();
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.hashCode();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public final String getFolderSegment() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.getResourceValue();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+
+ @Override
+ public String getShortDisplayValue() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.getShortDisplayValue();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.getLongDisplayValue();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java b/ide_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java
new file mode 100644
index 0000000..09cf9e4
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java
@@ -0,0 +1,771 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.AndroidConstants;
+import com.android.resources.ResourceFolderType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Represents the configuration for Resource Folders. All the properties have a default
+ * value which means that the property is not set.
+ */
+public final class FolderConfiguration implements Comparable<FolderConfiguration> {
+
+ private final static ResourceQualifier[] DEFAULT_QUALIFIERS;
+
+ static {
+ // get the default qualifiers.
+ FolderConfiguration defaultConfig = new FolderConfiguration();
+ defaultConfig.createDefault();
+ DEFAULT_QUALIFIERS = defaultConfig.getQualifiers();
+ }
+
+
+ private final ResourceQualifier[] mQualifiers = new ResourceQualifier[INDEX_COUNT];
+
+ private final static int INDEX_COUNTRY_CODE = 0;
+ private final static int INDEX_NETWORK_CODE = 1;
+ private final static int INDEX_LANGUAGE = 2;
+ private final static int INDEX_REGION = 3;
+ private final static int INDEX_SCREEN_SIZE = 4;
+ private final static int INDEX_SCREEN_RATIO = 5;
+ private final static int INDEX_SCREEN_ORIENTATION = 6;
+ private final static int INDEX_DOCK_MODE = 7;
+ private final static int INDEX_NIGHT_MODE = 8;
+ private final static int INDEX_PIXEL_DENSITY = 9;
+ private final static int INDEX_TOUCH_TYPE = 10;
+ private final static int INDEX_KEYBOARD_STATE = 11;
+ private final static int INDEX_TEXT_INPUT_METHOD = 12;
+ private final static int INDEX_NAVIGATION_STATE = 13;
+ private final static int INDEX_NAVIGATION_METHOD = 14;
+ private final static int INDEX_SCREEN_DIMENSION = 15;
+ private final static int INDEX_VERSION = 16;
+ private final static int INDEX_COUNT = 17;
+
+ /**
+ * Creates a {@link FolderConfiguration} matching the folder segments.
+ * @param folderSegments The segments of the folder name. The first segments should contain
+ * the name of the folder
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ */
+ public static FolderConfiguration getConfig(String[] folderSegments) {
+ FolderConfiguration config = new FolderConfiguration();
+
+ // we are going to loop through the segments, and match them with the first
+ // available qualifier. If the segment doesn't match we try with the next qualifier.
+ // Because the order of the qualifier is fixed, we do not reset the first qualifier
+ // after each successful segment.
+ // If we run out of qualifier before processing all the segments, we fail.
+
+ int qualifierIndex = 0;
+ int qualifierCount = DEFAULT_QUALIFIERS.length;
+
+ for (int i = 1 ; i < folderSegments.length; i++) {
+ String seg = folderSegments[i];
+ if (seg.length() > 0) {
+ while (qualifierIndex < qualifierCount &&
+ DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config) == false) {
+ qualifierIndex++;
+ }
+
+ // if we reached the end of the qualifier we didn't find a matching qualifier.
+ if (qualifierIndex == qualifierCount) {
+ return null;
+ }
+
+ } else {
+ return null;
+ }
+ }
+
+ return config;
+ }
+
+ /**
+ * Returns the number of {@link ResourceQualifier} that make up a Folder configuration.
+ */
+ public static int getQualifierCount() {
+ return INDEX_COUNT;
+ }
+
+ /**
+ * Sets the config from the qualifiers of a given <var>config</var>.
+ * <p/>This is equivalent to <code>set(config, false)</code>
+ * @param config the configuration to set
+ *
+ * @see #set(FolderConfiguration, boolean)
+ */
+ public void set(FolderConfiguration config) {
+ set(config, false /*nonFakeValuesOnly*/);
+ }
+
+ /**
+ * Sets the config from the qualifiers of a given <var>config</var>.
+ * @param config the configuration to set
+ * @param nonFakeValuesOnly if set to true this ignore qualifiers for which the
+ * current value is a fake value.
+ *
+ * @see ResourceQualifier#hasFakeValue()
+ */
+ public void set(FolderConfiguration config, boolean nonFakeValuesOnly) {
+ if (config != null) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier q = config.mQualifiers[i];
+ if (nonFakeValuesOnly == false || q == null || q.hasFakeValue() == false) {
+ mQualifiers[i] = q;
+ }
+ }
+ }
+ }
+
+ /**
+ * Reset the config.
+ * <p/>This makes qualifiers at all indices <code>null</code>.
+ */
+ public void reset() {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ mQualifiers[i] = null;
+ }
+ }
+
+ /**
+ * Removes the qualifiers from the receiver if they are present (and valid)
+ * in the given configuration.
+ */
+ public void substract(FolderConfiguration config) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (config.mQualifiers[i] != null && config.mQualifiers[i].isValid()) {
+ mQualifiers[i] = null;
+ }
+ }
+ }
+
+ /**
+ * Adds the non-qualifiers from the given config.
+ * Qualifiers that are null in the given config do not change in the receiver.
+ */
+ public void add(FolderConfiguration config) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (config.mQualifiers[i] != null) {
+ mQualifiers[i] = config.mQualifiers[i];
+ }
+ }
+ }
+
+ /**
+ * Returns the first invalid qualifier, or <code>null<code> if they are all valid (or if none
+ * exists).
+ */
+ public ResourceQualifier getInvalidQualifier() {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null && mQualifiers[i].isValid() == false) {
+ return mQualifiers[i];
+ }
+ }
+
+ // all allocated qualifiers are valid, we return null.
+ return null;
+ }
+
+ /**
+ * Returns whether the Region qualifier is valid. Region qualifier can only be present if a
+ * Language qualifier is present as well.
+ * @return true if the Region qualifier is valid.
+ */
+ public boolean checkRegion() {
+ if (mQualifiers[INDEX_LANGUAGE] == null && mQualifiers[INDEX_REGION] != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds a qualifier to the {@link FolderConfiguration}
+ * @param qualifier the {@link ResourceQualifier} to add.
+ */
+ public void addQualifier(ResourceQualifier qualifier) {
+ if (qualifier instanceof CountryCodeQualifier) {
+ mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
+ } else if (qualifier instanceof NetworkCodeQualifier) {
+ mQualifiers[INDEX_NETWORK_CODE] = qualifier;
+ } else if (qualifier instanceof LanguageQualifier) {
+ mQualifiers[INDEX_LANGUAGE] = qualifier;
+ } else if (qualifier instanceof RegionQualifier) {
+ mQualifiers[INDEX_REGION] = qualifier;
+ } else if (qualifier instanceof ScreenSizeQualifier) {
+ mQualifiers[INDEX_SCREEN_SIZE] = qualifier;
+ } else if (qualifier instanceof ScreenRatioQualifier) {
+ mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
+ } else if (qualifier instanceof ScreenOrientationQualifier) {
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
+ } else if (qualifier instanceof DockModeQualifier) {
+ mQualifiers[INDEX_DOCK_MODE] = qualifier;
+ } else if (qualifier instanceof NightModeQualifier) {
+ mQualifiers[INDEX_NIGHT_MODE] = qualifier;
+ } else if (qualifier instanceof PixelDensityQualifier) {
+ mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
+ } else if (qualifier instanceof TouchScreenQualifier) {
+ mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
+ } else if (qualifier instanceof KeyboardStateQualifier) {
+ mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
+ } else if (qualifier instanceof TextInputMethodQualifier) {
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
+ } else if (qualifier instanceof NavigationStateQualifier) {
+ mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
+ } else if (qualifier instanceof NavigationMethodQualifier) {
+ mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
+ } else if (qualifier instanceof ScreenDimensionQualifier) {
+ mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
+ } else if (qualifier instanceof VersionQualifier) {
+ mQualifiers[INDEX_VERSION] = qualifier;
+ }
+ }
+
+ /**
+ * Removes a given qualifier from the {@link FolderConfiguration}.
+ * @param qualifier the {@link ResourceQualifier} to remove.
+ */
+ public void removeQualifier(ResourceQualifier qualifier) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] == qualifier) {
+ mQualifiers[i] = null;
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns a qualifier by its index. The total number of qualifiers can be accessed by
+ * {@link #getQualifierCount()}.
+ * @param index the index of the qualifier to return.
+ * @return the qualifier or null if there are none at the index.
+ */
+ public ResourceQualifier getQualifier(int index) {
+ return mQualifiers[index];
+ }
+
+ public void setCountryCodeQualifier(CountryCodeQualifier qualifier) {
+ mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
+ }
+
+ public CountryCodeQualifier getCountryCodeQualifier() {
+ return (CountryCodeQualifier)mQualifiers[INDEX_COUNTRY_CODE];
+ }
+
+ public void setNetworkCodeQualifier(NetworkCodeQualifier qualifier) {
+ mQualifiers[INDEX_NETWORK_CODE] = qualifier;
+ }
+
+ public NetworkCodeQualifier getNetworkCodeQualifier() {
+ return (NetworkCodeQualifier)mQualifiers[INDEX_NETWORK_CODE];
+ }
+
+ public void setLanguageQualifier(LanguageQualifier qualifier) {
+ mQualifiers[INDEX_LANGUAGE] = qualifier;
+ }
+
+ public LanguageQualifier getLanguageQualifier() {
+ return (LanguageQualifier)mQualifiers[INDEX_LANGUAGE];
+ }
+
+ public void setRegionQualifier(RegionQualifier qualifier) {
+ mQualifiers[INDEX_REGION] = qualifier;
+ }
+
+ public RegionQualifier getRegionQualifier() {
+ return (RegionQualifier)mQualifiers[INDEX_REGION];
+ }
+
+ public void setScreenSizeQualifier(ScreenSizeQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_SIZE] = qualifier;
+ }
+
+ public ScreenSizeQualifier getScreenSizeQualifier() {
+ return (ScreenSizeQualifier)mQualifiers[INDEX_SCREEN_SIZE];
+ }
+
+ public void setScreenRatioQualifier(ScreenRatioQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
+ }
+
+ public ScreenRatioQualifier getScreenRatioQualifier() {
+ return (ScreenRatioQualifier)mQualifiers[INDEX_SCREEN_RATIO];
+ }
+
+ public void setScreenOrientationQualifier(ScreenOrientationQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
+ }
+
+ public ScreenOrientationQualifier getScreenOrientationQualifier() {
+ return (ScreenOrientationQualifier)mQualifiers[INDEX_SCREEN_ORIENTATION];
+ }
+
+ public void setDockModeQualifier(DockModeQualifier qualifier) {
+ mQualifiers[INDEX_DOCK_MODE] = qualifier;
+ }
+
+ public DockModeQualifier getDockModeQualifier() {
+ return (DockModeQualifier)mQualifiers[INDEX_DOCK_MODE];
+ }
+
+ public void setNightModeQualifier(NightModeQualifier qualifier) {
+ mQualifiers[INDEX_NIGHT_MODE] = qualifier;
+ }
+
+ public NightModeQualifier getNightModeQualifier() {
+ return (NightModeQualifier)mQualifiers[INDEX_NIGHT_MODE];
+ }
+
+ public void setPixelDensityQualifier(PixelDensityQualifier qualifier) {
+ mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
+ }
+
+ public PixelDensityQualifier getPixelDensityQualifier() {
+ return (PixelDensityQualifier)mQualifiers[INDEX_PIXEL_DENSITY];
+ }
+
+ public void setTouchTypeQualifier(TouchScreenQualifier qualifier) {
+ mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
+ }
+
+ public TouchScreenQualifier getTouchTypeQualifier() {
+ return (TouchScreenQualifier)mQualifiers[INDEX_TOUCH_TYPE];
+ }
+
+ public void setKeyboardStateQualifier(KeyboardStateQualifier qualifier) {
+ mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
+ }
+
+ public KeyboardStateQualifier getKeyboardStateQualifier() {
+ return (KeyboardStateQualifier)mQualifiers[INDEX_KEYBOARD_STATE];
+ }
+
+ public void setTextInputMethodQualifier(TextInputMethodQualifier qualifier) {
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
+ }
+
+ public TextInputMethodQualifier getTextInputMethodQualifier() {
+ return (TextInputMethodQualifier)mQualifiers[INDEX_TEXT_INPUT_METHOD];
+ }
+
+ public void setNavigationStateQualifier(NavigationStateQualifier qualifier) {
+ mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
+ }
+
+ public NavigationStateQualifier getNavigationStateQualifier() {
+ return (NavigationStateQualifier)mQualifiers[INDEX_NAVIGATION_STATE];
+ }
+
+ public void setNavigationMethodQualifier(NavigationMethodQualifier qualifier) {
+ mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
+ }
+
+ public NavigationMethodQualifier getNavigationMethodQualifier() {
+ return (NavigationMethodQualifier)mQualifiers[INDEX_NAVIGATION_METHOD];
+ }
+
+ public void setScreenDimensionQualifier(ScreenDimensionQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
+ }
+
+ public ScreenDimensionQualifier getScreenDimensionQualifier() {
+ return (ScreenDimensionQualifier)mQualifiers[INDEX_SCREEN_DIMENSION];
+ }
+
+ public void setVersionQualifier(VersionQualifier qualifier) {
+ mQualifiers[INDEX_VERSION] = qualifier;
+ }
+
+ public VersionQualifier getVersionQualifier() {
+ return (VersionQualifier)mQualifiers[INDEX_VERSION];
+ }
+
+ /**
+ * Returns whether an object is equals to the receiver.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof FolderConfiguration) {
+ FolderConfiguration fc = (FolderConfiguration)obj;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier qualifier = mQualifiers[i];
+ ResourceQualifier fcQualifier = fc.mQualifiers[i];
+ if (qualifier != null) {
+ if (qualifier.equals(fcQualifier) == false) {
+ return false;
+ }
+ } else if (fcQualifier != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * Returns whether the Configuration has only default values.
+ */
+ public boolean isDefault() {
+ for (ResourceQualifier irq : mQualifiers) {
+ if (irq != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the name of a folder with the configuration.
+ */
+ public String getFolderName(ResourceFolderType folder) {
+ StringBuilder result = new StringBuilder(folder.getName());
+
+ for (ResourceQualifier qualifier : mQualifiers) {
+ if (qualifier != null) {
+ String segment = qualifier.getFolderSegment();
+ if (segment != null && segment.length() > 0) {
+ result.append(AndroidConstants.RES_QUALIFIER_SEP);
+ result.append(segment);
+ }
+ }
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Returns {@link #toDisplayString()}.
+ */
+ @Override
+ public String toString() {
+ return toDisplayString();
+ }
+
+ /**
+ * Returns a string valid for display purpose.
+ */
+ public String toDisplayString() {
+ if (isDefault()) {
+ return "default";
+ }
+
+ StringBuilder result = null;
+ int index = 0;
+ ResourceQualifier qualifier = null;
+
+ // pre- language/region qualifiers
+ while (index < INDEX_LANGUAGE) {
+ qualifier = mQualifiers[index++];
+ if (qualifier != null) {
+ if (result == null) {
+ result = new StringBuilder();
+ } else {
+ result.append(", "); //$NON-NLS-1$
+ }
+ result.append(qualifier.getLongDisplayValue());
+
+ }
+ }
+
+ // process the language/region qualifier in a custom way, if there are both non null.
+ if (mQualifiers[INDEX_LANGUAGE] != null && mQualifiers[INDEX_REGION] != null) {
+ String language = mQualifiers[INDEX_LANGUAGE].getLongDisplayValue();
+ String region = mQualifiers[INDEX_REGION].getLongDisplayValue();
+
+ if (result == null) {
+ result = new StringBuilder();
+ } else {
+ result.append(", "); //$NON-NLS-1$
+ }
+ result.append(String.format("Locale %s_%s", language, region)); //$NON-NLS-1$
+
+ index += 2;
+ }
+
+ // post language/region qualifiers.
+ while (index < INDEX_COUNT) {
+ qualifier = mQualifiers[index++];
+ if (qualifier != null) {
+ if (result == null) {
+ result = new StringBuilder();
+ } else {
+ result.append(", "); //$NON-NLS-1$
+ }
+ result.append(qualifier.getLongDisplayValue());
+
+ }
+ }
+
+ return result == null ? null : result.toString();
+ }
+
+ public int compareTo(FolderConfiguration folderConfig) {
+ // default are always at the top.
+ if (isDefault()) {
+ if (folderConfig.isDefault()) {
+ return 0;
+ }
+ return -1;
+ }
+
+ // now we compare the qualifiers
+ for (int i = 0 ; i < INDEX_COUNT; i++) {
+ ResourceQualifier qualifier1 = mQualifiers[i];
+ ResourceQualifier qualifier2 = folderConfig.mQualifiers[i];
+
+ if (qualifier1 == null) {
+ if (qualifier2 == null) {
+ continue;
+ } else {
+ return -1;
+ }
+ } else {
+ if (qualifier2 == null) {
+ return 1;
+ } else {
+ int result = qualifier1.compareTo(qualifier2);
+
+ if (result == 0) {
+ continue;
+ }
+
+ return result;
+ }
+ }
+ }
+
+ // if we arrive here, all the qualifier matches
+ return 0;
+ }
+
+ /**
+ * Returns the best matching {@link Configurable} for this configuration.
+ *
+ * @param configurables the list of {@link Configurable} to choose from.
+ *
+ * @return an item from the given list of {@link Configurable} or null.
+ *
+ * @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
+ */
+ public Configurable findMatchingConfigurable(List<? extends Configurable> configurables) {
+ //
+ // 1: eliminate resources that contradict the reference configuration
+ // 2: pick next qualifier type
+ // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4.
+ // 4: eliminate resources that don't use this qualifier.
+ // 5: if more than one resource left, go back to 2.
+ //
+ // The precedence of the qualifiers is more important than the number of qualifiers that
+ // exactly match the device.
+
+ // 1: eliminate resources that contradict
+ ArrayList<Configurable> matchingConfigurables = new ArrayList<Configurable>();
+ for (int i = 0 ; i < configurables.size(); i++) {
+ Configurable res = configurables.get(i);
+
+ if (res.getConfiguration().isMatchFor(this)) {
+ matchingConfigurables.add(res);
+ }
+ }
+
+ // if there is only one match, just take it
+ if (matchingConfigurables.size() == 1) {
+ return matchingConfigurables.get(0);
+ } else if (matchingConfigurables.size() == 0) {
+ return null;
+ }
+
+ // 2. Loop on the qualifiers, and eliminate matches
+ final int count = FolderConfiguration.getQualifierCount();
+ for (int q = 0 ; q < count ; q++) {
+ // look to see if one configurable has this qualifier.
+ // At the same time also record the best match value for the qualifier (if applicable).
+
+ // The reference value, to find the best match.
+ // Note that this qualifier could be null. In which case any qualifier found in the
+ // possible match, will all be considered best match.
+ ResourceQualifier referenceQualifier = getQualifier(q);
+
+ boolean found = false;
+ ResourceQualifier bestMatch = null; // this is to store the best match.
+ for (Configurable configurable : matchingConfigurables) {
+ ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q);
+ if (qualifier != null) {
+ // set the flag.
+ found = true;
+
+ // Now check for a best match. If the reference qualifier is null ,
+ // any qualifier is a "best" match (we don't need to record all of them.
+ // Instead the non compatible ones are removed below)
+ if (referenceQualifier != null) {
+ if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
+ bestMatch = qualifier;
+ }
+ }
+ }
+ }
+
+ // 4. If a configurable has a qualifier at the current index, remove all the ones that
+ // do not have one, or whose qualifier value does not equal the best match found above
+ // unless there's no reference qualifier, in which case they are all considered
+ // "best" match.
+ if (found) {
+ for (int i = 0 ; i < matchingConfigurables.size(); ) {
+ Configurable configurable = matchingConfigurables.get(i);
+ ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q);
+
+ if (qualifier == null) {
+ // this resources has no qualifier of this type: rejected.
+ matchingConfigurables.remove(configurable);
+ } else if (referenceQualifier != null && bestMatch != null &&
+ bestMatch.equals(qualifier) == false) {
+ // there's a reference qualifier and there is a better match for it than
+ // this resource, so we reject it.
+ matchingConfigurables.remove(configurable);
+ } else {
+ // looks like we keep this resource, move on to the next one.
+ i++;
+ }
+ }
+
+ // at this point we may have run out of matching resources before going
+ // through all the qualifiers.
+ if (matchingConfigurables.size() < 2) {
+ break;
+ }
+ }
+ }
+
+ // Because we accept resources whose configuration have qualifiers where the reference
+ // configuration doesn't, we can end up with more than one match. In this case, we just
+ // take the first one.
+ if (matchingConfigurables.size() == 0) {
+ return null;
+ }
+ return matchingConfigurables.get(0);
+ }
+
+
+ /**
+ * Returns whether the configuration is a match for the given reference config.
+ * <p/>A match means that, for each qualifier of this config
+ * <ul>
+ * <li>The reference config has no value set
+ * <li>or, the qualifier of the reference config is a match. Depending on the qualifier type
+ * this does not mean the same exact value.</li>
+ * </ul>
+ * @param referenceConfig The reference configuration to test against.
+ * @return true if the configuration matches.
+ */
+ public boolean isMatchFor(FolderConfiguration referenceConfig) {
+ if (referenceConfig == null) {
+ return false;
+ }
+
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier testQualifier = mQualifiers[i];
+ ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
+
+ // it's only a non match if both qualifiers are non-null, and they don't match.
+ if (testQualifier != null && referenceQualifier != null &&
+ testQualifier.isMatchFor(referenceQualifier) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the index of the first non null {@link ResourceQualifier} starting at index
+ * <var>startIndex</var>
+ * @param startIndex
+ * @return -1 if no qualifier was found.
+ */
+ public int getHighestPriorityQualifier(int startIndex) {
+ for (int i = startIndex ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create default qualifiers.
+ * <p/>This creates qualifiers with no values for all indices.
+ */
+ public void createDefault() {
+ mQualifiers[INDEX_COUNTRY_CODE] = new CountryCodeQualifier();
+ mQualifiers[INDEX_NETWORK_CODE] = new NetworkCodeQualifier();
+ mQualifiers[INDEX_LANGUAGE] = new LanguageQualifier();
+ mQualifiers[INDEX_REGION] = new RegionQualifier();
+ mQualifiers[INDEX_SCREEN_SIZE] = new ScreenSizeQualifier();
+ mQualifiers[INDEX_SCREEN_RATIO] = new ScreenRatioQualifier();
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = new ScreenOrientationQualifier();
+ mQualifiers[INDEX_DOCK_MODE] = new DockModeQualifier();
+ mQualifiers[INDEX_NIGHT_MODE] = new NightModeQualifier();
+ mQualifiers[INDEX_PIXEL_DENSITY] = new PixelDensityQualifier();
+ mQualifiers[INDEX_TOUCH_TYPE] = new TouchScreenQualifier();
+ mQualifiers[INDEX_KEYBOARD_STATE] = new KeyboardStateQualifier();
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = new TextInputMethodQualifier();
+ mQualifiers[INDEX_NAVIGATION_STATE] = new NavigationStateQualifier();
+ mQualifiers[INDEX_NAVIGATION_METHOD] = new NavigationMethodQualifier();
+ mQualifiers[INDEX_SCREEN_DIMENSION] = new ScreenDimensionQualifier();
+ mQualifiers[INDEX_VERSION] = new VersionQualifier();
+ }
+
+ /**
+ * Returns an array of all the non null qualifiers.
+ */
+ public ResourceQualifier[] getQualifiers() {
+ int count = 0;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ count++;
+ }
+ }
+
+ ResourceQualifier[] array = new ResourceQualifier[count];
+ int index = 0;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ array[index++] = mQualifiers[i];
+ }
+ }
+
+ return array;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java
new file mode 100644
index 0000000..1ca5dad
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.KeyboardState;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for keyboard state.
+ */
+public final class KeyboardStateQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Keyboard State";
+
+ private KeyboardState mValue = null;
+
+ public KeyboardStateQualifier() {
+ // pass
+ }
+
+ public KeyboardStateQualifier(KeyboardState value) {
+ mValue = value;
+ }
+
+ public KeyboardState getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Keyboard";
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ KeyboardState orientation = KeyboardState.getEnum(value);
+ if (orientation != null) {
+ KeyboardStateQualifier qualifier = new KeyboardStateQualifier();
+ qualifier.mValue = orientation;
+ config.setKeyboardStateQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof KeyboardStateQualifier) {
+ KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)qualifier;
+
+ // special case where EXPOSED can be used for SOFT
+ if (referenceQualifier.mValue == KeyboardState.SOFT &&
+ mValue == KeyboardState.EXPOSED) {
+ return true;
+ }
+
+ return referenceQualifier.mValue == mValue;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ KeyboardStateQualifier compareQualifier = (KeyboardStateQualifier)compareTo;
+ KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)reference;
+
+ if (referenceQualifier.mValue == KeyboardState.SOFT) { // only case where there could be a
+ // better qualifier
+ // only return true if it's a better value.
+ if (compareQualifier.mValue == KeyboardState.EXPOSED && mValue == KeyboardState.SOFT) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java
new file mode 100644
index 0000000..ff18bdc
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Language.
+ */
+public final class LanguageQualifier extends ResourceQualifier {
+ private final static Pattern sLanguagePattern = Pattern.compile("^[a-z]{2}$"); //$NON-NLS-1$
+
+ public static final String FAKE_LANG_VALUE = "__"; //$NON-NLS-1$
+ public static final String NAME = "Language";
+
+ private String mValue;
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link LanguageQualifier} object or <code>null</code>
+ */
+ public static LanguageQualifier getQualifier(String segment) {
+ if (sLanguagePattern.matcher(segment).matches()) {
+ LanguageQualifier qualifier = new LanguageQualifier();
+ qualifier.mValue = segment;
+
+ return qualifier;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link LanguageQualifier} object.
+ * @param value the value of the qualifier, as returned by {@link #getValue()}.
+ */
+ public static String getFolderSegment(String value) {
+ String segment = value.toLowerCase();
+ if (sLanguagePattern.matcher(segment).matches()) {
+ return segment;
+ }
+
+ return null;
+ }
+
+ public LanguageQualifier() {
+
+ }
+
+ public LanguageQualifier(String value) {
+ mValue = value;
+ }
+
+ public String getValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue != null;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return FAKE_LANG_VALUE.equals(mValue);
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ LanguageQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setLanguageQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof LanguageQualifier) {
+ if (mValue == null) {
+ return ((LanguageQualifier)qualifier).mValue == null;
+ }
+ return mValue.equals(((LanguageQualifier)qualifier).mValue);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mValue != null) {
+ return mValue.hashCode();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ if (mValue != null) {
+ return getFolderSegment(mValue);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mValue != null) {
+ return String.format("Language %s", mValue);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java
new file mode 100644
index 0000000..6c7e31f
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.Navigation;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for Navigation Method.
+ */
+public final class NavigationMethodQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Navigation Method";
+
+ private Navigation mValue;
+
+ public NavigationMethodQualifier() {
+ // pass
+ }
+
+ public NavigationMethodQualifier(Navigation value) {
+ mValue = value;
+ }
+
+ public Navigation getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Navigation method = Navigation.getEnum(value);
+ if (method != null) {
+ NavigationMethodQualifier qualifier = new NavigationMethodQualifier(method);
+ config.setNavigationMethodQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java
new file mode 100644
index 0000000..9b1e07e
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.NavigationState;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for navigation state.
+ */
+public final class NavigationStateQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Navigation State";
+
+ private NavigationState mValue = null;
+
+ public NavigationStateQualifier() {
+ // pass
+ }
+
+ public NavigationStateQualifier(NavigationState value) {
+ mValue = value;
+ }
+
+ public NavigationState getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ NavigationState state = NavigationState.getEnum(value);
+ if (state != null) {
+ NavigationStateQualifier qualifier = new NavigationStateQualifier();
+ qualifier.mValue = state;
+ config.setNavigationStateQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
new file mode 100644
index 0000000..295e8ab
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Mobile Network Code Pixel Density.
+ */
+public final class NetworkCodeQualifier extends ResourceQualifier {
+ /** Default pixel density value. This means the property is not set. */
+ private final static int DEFAULT_CODE = -1;
+
+ private final static Pattern sNetworkCodePattern = Pattern.compile("^mnc(\\d{1,3})$"); //$NON-NLS-1$
+
+ private final int mCode;
+
+ public final static String NAME = "Mobile Network Code";
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link CountryCodeQualifier} object or <code>null</code>
+ */
+ public static NetworkCodeQualifier getQualifier(String segment) {
+ Matcher m = sNetworkCodePattern.matcher(segment);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return null;
+ }
+
+ NetworkCodeQualifier qualifier = new NetworkCodeQualifier(code);
+ return qualifier;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link NetworkCodeQualifier} object.
+ * @param code the value of the qualifier, as returned by {@link #getCode()}.
+ */
+ public static String getFolderSegment(int code) {
+ if (code != DEFAULT_CODE && code >= 1 && code <= 999) { // code is 1-3 digit.
+ return String.format("mnc%1$d", code); //$NON-NLS-1$
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public NetworkCodeQualifier() {
+ this(DEFAULT_CODE);
+ }
+
+ public NetworkCodeQualifier(int code) {
+ mCode = code;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Network Code";
+ }
+
+ @Override
+ public boolean isValid() {
+ return mCode != DEFAULT_CODE;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Matcher m = sNetworkCodePattern.matcher(value);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return false;
+ }
+
+ NetworkCodeQualifier qualifier = new NetworkCodeQualifier(code);
+ config.setNetworkCodeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof NetworkCodeQualifier) {
+ return mCode == ((NetworkCodeQualifier)qualifier).mCode;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCode;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mCode);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mCode != DEFAULT_CODE) {
+ return String.format("MNC %1$d", mCode);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ return getShortDisplayValue();
+ }
+
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java
new file mode 100644
index 0000000..9e49091
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.NightMode;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for Navigation Method.
+ */
+public final class NightModeQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Night Mode";
+
+ private NightMode mValue;
+
+ public NightModeQualifier() {
+ // pass
+ }
+
+ public NightModeQualifier(NightMode value) {
+ mValue = value;
+ }
+
+ public NightMode getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Night Mode";
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ NightMode mode = NightMode.getEnum(value);
+ if (mode != null) {
+ NightModeQualifier qualifier = new NightModeQualifier(mode);
+ config.setNightModeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/PixelDensityQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/PixelDensityQualifier.java
new file mode 100644
index 0000000..80842a8
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/PixelDensityQualifier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.Density;
+import com.android.resources.ResourceEnum;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Screen Pixel Density.
+ */
+public final class PixelDensityQualifier extends EnumBasedResourceQualifier {
+ private final static Pattern sDensityLegacyPattern = Pattern.compile("^(\\d+)dpi$");//$NON-NLS-1$
+
+ public static final String NAME = "Pixel Density";
+
+ private Density mValue = Density.MEDIUM;
+
+ public PixelDensityQualifier() {
+ // pass
+ }
+
+ public PixelDensityQualifier(Density value) {
+ mValue = value;
+ }
+
+ public Density getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Density density = Density.getEnum(value);
+ if (density == null) {
+
+ // attempt to read a legacy value.
+ Matcher m = sDensityLegacyPattern.matcher(value);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ try {
+ density = Density.getEnum(Integer.parseInt(v));
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number
+ // which really shouldn't happen since the regexp would have failed.
+ }
+ }
+ }
+
+ if (density != null) {
+ PixelDensityQualifier qualifier = new PixelDensityQualifier();
+ qualifier.mValue = density;
+ config.setPixelDensityQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof PixelDensityQualifier) {
+ // as long as there's a density qualifier, it's always a match.
+ // The best match will be found later.
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ PixelDensityQualifier compareQ = (PixelDensityQualifier)compareTo;
+ PixelDensityQualifier referenceQ = (PixelDensityQualifier)reference;
+
+ if (compareQ.mValue == referenceQ.mValue) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mValue == referenceQ.mValue) {
+ // got new exact value, this is the best!
+ return true;
+ } else {
+ // in all case we're going to prefer the higher dpi.
+ // if reference is high, we want highest dpi.
+ // if reference is medium, we'll prefer to scale down high dpi, than scale up low dpi
+ // if reference if low, we'll prefer to scale down high than medium (2:1 over 4:3)
+ return mValue.getDpiValue() > compareQ.mValue.getDpiValue();
+ }
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java
new file mode 100644
index 0000000..7e8ca9a
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Region.
+ */
+public final class RegionQualifier extends ResourceQualifier {
+ private final static Pattern sRegionPattern = Pattern.compile("^r([A-Z]{2})$"); //$NON-NLS-1$
+
+ public static final String FAKE_REGION_VALUE = "__"; //$NON-NLS-1$
+ public static final String NAME = "Region";
+
+ private String mValue;
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link RegionQualifier} object or <code>null</code>
+ */
+ public static RegionQualifier getQualifier(String segment) {
+ Matcher m = sRegionPattern.matcher(segment);
+ if (m.matches()) {
+ RegionQualifier qualifier = new RegionQualifier();
+ qualifier.mValue = m.group(1);
+
+ return qualifier;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link RegionQualifier} object.
+ * @param value the value of the qualifier, as returned by {@link #getValue()}.
+ */
+ public static String getFolderSegment(String value) {
+ if (value != null) {
+ String segment = "r" + value.toUpperCase(); //$NON-NLS-1$
+ if (sRegionPattern.matcher(segment).matches()) {
+ return segment;
+ }
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public RegionQualifier() {
+
+ }
+
+ public RegionQualifier(String value) {
+ mValue = value;
+ }
+
+ public String getValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue != null;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return FAKE_REGION_VALUE.equals(mValue);
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ RegionQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setRegionQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof RegionQualifier) {
+ if (mValue == null) {
+ return ((RegionQualifier)qualifier).mValue == null;
+ }
+ return mValue.equals(((RegionQualifier)qualifier).mValue);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mValue != null) {
+ return mValue.hashCode();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mValue);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mValue != null) {
+ return String.format("Region %s", mValue);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java
new file mode 100644
index 0000000..6abac4e
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+
+/**
+ * Base class for resource qualifiers.
+ * <p/>The resource qualifier classes are designed as immutable.
+ */
+public abstract class ResourceQualifier implements Comparable<ResourceQualifier> {
+
+ /**
+ * Returns the human readable name of the qualifier.
+ */
+ public abstract String getName();
+
+ /**
+ * Returns a shorter human readable name for the qualifier.
+ * @see #getName()
+ */
+ public abstract String getShortName();
+
+ /**
+ * Returns whether the qualifier has a valid filter value.
+ */
+ public abstract boolean isValid();
+
+ /**
+ * Returns whether the qualifier has a fake value.
+ * <p/>Fake values are used internally and should not be used as real qualifier value.
+ */
+ public abstract boolean hasFakeValue();
+
+ /**
+ * Check if the value is valid for this qualifier, and if so sets the value
+ * into a Folder Configuration.
+ * @param value The value to check and set. Must not be null.
+ * @param config The folder configuration to receive the value. Must not be null.
+ * @return true if the value was valid and was set.
+ */
+ public abstract boolean checkAndSet(String value, FolderConfiguration config);
+
+ /**
+ * Returns a string formated to be used in a folder name.
+ * <p/>This is declared as abstract to force children classes to implement it.
+ */
+ public abstract String getFolderSegment();
+
+ /**
+ * Returns whether the given qualifier is a match for the receiver.
+ * <p/>The default implementation returns the result of {@link #equals(Object)}.
+ * <p/>Children class that re-implements this must implement
+ * {@link #isBetterMatchThan(ResourceQualifier, ResourceQualifier)} too.
+ * @param qualifier the reference qualifier
+ * @return true if the receiver is a match.
+ */
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ return equals(qualifier);
+ }
+
+ /**
+ * Returns true if the receiver is a better match for the given <var>reference</var> than
+ * the given <var>compareTo</var> comparable.
+ * @param compareTo The {@link ResourceQualifier} to compare to. Can be null, in which
+ * case the method must return <code>true</code>.
+ * @param reference The reference qualifier value for which the match is.
+ * @return true if the receiver is a better match.
+ */
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ // the default is to always return false. This gives less overhead than always returning
+ // true, as it would only compare same values anyway.
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getFolderSegment();
+ }
+
+ /**
+ * Returns a string formatted for display purpose.
+ */
+ public abstract String getShortDisplayValue();
+
+ /**
+ * Returns a string formatted for display purpose.
+ */
+ public abstract String getLongDisplayValue();
+
+ /**
+ * Returns <code>true</code> if both objects are equal.
+ * <p/>This is declared as abstract to force children classes to implement it.
+ */
+ @Override
+ public abstract boolean equals(Object object);
+
+ /**
+ * Returns a hash code value for the object.
+ * <p/>This is declared as abstract to force children classes to implement it.
+ */
+ @Override
+ public abstract int hashCode();
+
+ public final int compareTo(ResourceQualifier o) {
+ return toString().compareTo(o.toString());
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java
new file mode 100644
index 0000000..a58789a
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Screen Dimension.
+ */
+public final class ScreenDimensionQualifier extends ResourceQualifier {
+ /** Default screen size value. This means the property is not set */
+ final static int DEFAULT_SIZE = -1;
+
+ private final static Pattern sDimensionPattern = Pattern.compile(
+ "^(\\d+)x(\\d+)$"); //$NON-NLS-1$
+
+ public static final String NAME = "Screen Dimension";
+
+ /** Screen size 1 value. This is not size X or Y because the folder name always
+ * contains the biggest size first. So if the qualifier is 400x200, size 1 will always be
+ * 400 but that'll be X in landscape and Y in portrait.
+ * Default value is <code>DEFAULT_SIZE</code> */
+ private int mValue1 = DEFAULT_SIZE;
+
+ /** Screen size 2 value. This is not size X or Y because the folder name always
+ * contains the biggest size first. So if the qualifier is 400x200, size 2 will always be
+ * 200 but that'll be Y in landscape and X in portrait.
+ * Default value is <code>DEFAULT_SIZE</code> */
+ private int mValue2 = DEFAULT_SIZE;
+
+ public ScreenDimensionQualifier() {
+ // pass
+ }
+
+ public ScreenDimensionQualifier(int value1, int value2) {
+ mValue1 = value1;
+ mValue2 = value2;
+ }
+
+ public int getValue1() {
+ return mValue1;
+ }
+
+ public int getValue2() {
+ return mValue2;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Dimension";
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue1 != DEFAULT_SIZE && mValue2 != DEFAULT_SIZE;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Matcher m = sDimensionPattern.matcher(value);
+ if (m.matches()) {
+ String d1 = m.group(1);
+ String d2 = m.group(2);
+
+ ScreenDimensionQualifier qualifier = getQualifier(d1, d2);
+ if (qualifier != null) {
+ config.setScreenDimensionQualifier(qualifier);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof ScreenDimensionQualifier) {
+ ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier;
+ return (mValue1 == q.mValue1 && mValue2 == q.mValue2);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ public static ScreenDimensionQualifier getQualifier(String size1, String size2) {
+ try {
+ int s1 = Integer.parseInt(size1);
+ int s2 = Integer.parseInt(size2);
+
+ ScreenDimensionQualifier qualifier = new ScreenDimensionQualifier();
+
+ if (s1 > s2) {
+ qualifier.mValue1 = s1;
+ qualifier.mValue2 = s2;
+ } else {
+ qualifier.mValue1 = s2;
+ qualifier.mValue2 = s1;
+ }
+
+ return qualifier;
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return String.format("%1$dx%2$d", mValue1, mValue2); //$NON-NLS-1$
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mValue1 != -1 && mValue2 != -1) {
+ return String.format("%1$dx%2$d", mValue1, mValue2);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mValue1 != -1 && mValue2 != -1) {
+ return String.format("Screen resolution %1$dx%2$d", mValue1, mValue2);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java
new file mode 100644
index 0000000..c26a6f4
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.ScreenOrientation;
+
+/**
+ * Resource Qualifier for Screen Orientation.
+ */
+public final class ScreenOrientationQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Screen Orientation";
+
+ private ScreenOrientation mValue = null;
+
+ public ScreenOrientationQualifier() {
+ }
+
+ public ScreenOrientationQualifier(ScreenOrientation value) {
+ mValue = value;
+ }
+
+ public ScreenOrientation getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Orientation";
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ ScreenOrientation orientation = ScreenOrientation.getEnum(value);
+ if (orientation != null) {
+ ScreenOrientationQualifier qualifier = new ScreenOrientationQualifier(orientation);
+ config.setScreenOrientationQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java
new file mode 100644
index 0000000..4cbf0a4
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.ScreenRatio;
+
+public class ScreenRatioQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Screen Ratio";
+
+ private ScreenRatio mValue = null;
+
+ public ScreenRatioQualifier() {
+ }
+
+ public ScreenRatioQualifier(ScreenRatio value) {
+ mValue = value;
+ }
+
+ public ScreenRatio getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Ratio";
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ ScreenRatio size = ScreenRatio.getEnum(value);
+ if (size != null) {
+ ScreenRatioQualifier qualifier = new ScreenRatioQualifier(size);
+ config.setScreenRatioQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
new file mode 100644
index 0000000..7ab6dd8
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.ScreenSize;
+
+/**
+ * Resource Qualifier for Screen Size. Size can be "small", "normal", "large" and "x-large"
+ */
+public class ScreenSizeQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Screen Size";
+
+ private ScreenSize mValue = null;
+
+
+ public ScreenSizeQualifier() {
+ }
+
+ public ScreenSizeQualifier(ScreenSize value) {
+ mValue = value;
+ }
+
+ public ScreenSize getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Size";
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ ScreenSize size = ScreenSize.getEnum(value);
+ if (size != null) {
+ ScreenSizeQualifier qualifier = new ScreenSizeQualifier(size);
+ config.setScreenSizeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java
new file mode 100644
index 0000000..3d772aa
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.Keyboard;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for Text Input Method.
+ */
+public final class TextInputMethodQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Text Input Method";
+
+ private Keyboard mValue;
+
+
+ public TextInputMethodQualifier() {
+ // pass
+ }
+
+ public TextInputMethodQualifier(Keyboard value) {
+ mValue = value;
+ }
+
+ public Keyboard getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Text Input";
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Keyboard method = Keyboard.getEnum(value);
+ if (method != null) {
+ TextInputMethodQualifier qualifier = new TextInputMethodQualifier();
+ qualifier.mValue = method;
+ config.setTextInputMethodQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java
new file mode 100644
index 0000000..eeb68d2
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.TouchScreen;
+
+
+/**
+ * Resource Qualifier for Touch Screen type.
+ */
+public final class TouchScreenQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Touch Screen";
+
+ private TouchScreen mValue;
+
+ public TouchScreenQualifier() {
+ // pass
+ }
+
+ public TouchScreenQualifier(TouchScreen touchValue) {
+ mValue = touchValue;
+ }
+
+ public TouchScreen getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ TouchScreen type = TouchScreen.getEnum(value);
+ if (type != null) {
+ TouchScreenQualifier qualifier = new TouchScreenQualifier();
+ qualifier.mValue = type;
+ config.setTouchTypeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java
new file mode 100644
index 0000000..c7cef97
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Platform Version.
+ */
+public final class VersionQualifier extends ResourceQualifier {
+ /** Default pixel density value. This means the property is not set. */
+ private final static int DEFAULT_VERSION = -1;
+
+ private final static Pattern sCountryCodePattern = Pattern.compile("^v(\\d+)$");//$NON-NLS-1$
+
+ private int mVersion = DEFAULT_VERSION;
+
+ public static final String NAME = "Platform Version";
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link VersionQualifier} object or <code>null</code>
+ */
+ public static VersionQualifier getQualifier(String segment) {
+ Matcher m = sCountryCodePattern.matcher(segment);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return null;
+ }
+
+ VersionQualifier qualifier = new VersionQualifier();
+ qualifier.mVersion = code;
+ return qualifier;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link VersionQualifier} object.
+ * @param version the value of the qualifier, as returned by {@link #getVersion()}.
+ */
+ public static String getFolderSegment(int version) {
+ if (version != DEFAULT_VERSION) {
+ return String.format("v%1$d", version); //$NON-NLS-1$
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public VersionQualifier(int apiLevel) {
+ mVersion = apiLevel;
+ }
+
+ public VersionQualifier() {
+ //pass
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Version";
+ }
+
+ @Override
+ public boolean isValid() {
+ return mVersion != DEFAULT_VERSION;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ VersionQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setVersionQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof VersionQualifier) {
+ return mVersion == ((VersionQualifier)qualifier).mVersion;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof VersionQualifier) {
+ // it is considered a match if the api level is equal or lower to the given qualifier
+ return mVersion <= ((VersionQualifier) qualifier).mVersion;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ VersionQualifier compareQ = (VersionQualifier)compareTo;
+ VersionQualifier referenceQ = (VersionQualifier)reference;
+
+ if (compareQ.mVersion == referenceQ.mVersion) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mVersion == referenceQ.mVersion) {
+ // got new exact value, this is the best!
+ return true;
+ } else {
+ // in all case we're going to prefer the higher version (since they have been filtered
+ // to not be too high
+ return mVersion > compareQ.mVersion;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mVersion;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mVersion);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mVersion != DEFAULT_VERSION) {
+ return String.format("API %1$d", mVersion);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mVersion != DEFAULT_VERSION) {
+ return String.format("API Level %1$d", mVersion);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}