diff options
author | Xavier Ducrohet <xav@android.com> | 2011-03-17 18:52:22 -0700 |
---|---|---|
committer | Xavier Ducrohet <xav@android.com> | 2011-03-17 19:09:22 -0700 |
commit | 026ba97e98e0527d910e15c4e1512893a777a8d2 (patch) | |
tree | fb4dd61fcdf82f41aaadd5528091c38c79604454 /ide_common | |
parent | 6aa6ce67dc85ea1d928a607d292d2c19bc2e2307 (diff) | |
download | sdk-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')
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$ + } +} |