Home | History | Annotate | Download | only in resources
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.resources;
     18 
     19 import static com.android.AndroidConstants.FD_RES_VALUES;
     20 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
     21 import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE;
     22 import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE;
     23 import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
     24 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
     25 import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;
     26 import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
     27 import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR;
     28 import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.TYPE_ATTR;
     29 import static com.android.sdklib.SdkConstants.FD_RESOURCES;
     30 
     31 import com.android.ide.common.rendering.api.ResourceValue;
     32 import com.android.ide.common.resources.ResourceDeltaKind;
     33 import com.android.ide.common.resources.ResourceResolver;
     34 import com.android.ide.common.resources.configuration.CountryCodeQualifier;
     35 import com.android.ide.common.resources.configuration.DensityQualifier;
     36 import com.android.ide.common.resources.configuration.FolderConfiguration;
     37 import com.android.ide.common.resources.configuration.KeyboardStateQualifier;
     38 import com.android.ide.common.resources.configuration.LanguageQualifier;
     39 import com.android.ide.common.resources.configuration.NavigationMethodQualifier;
     40 import com.android.ide.common.resources.configuration.NavigationStateQualifier;
     41 import com.android.ide.common.resources.configuration.NetworkCodeQualifier;
     42 import com.android.ide.common.resources.configuration.NightModeQualifier;
     43 import com.android.ide.common.resources.configuration.RegionQualifier;
     44 import com.android.ide.common.resources.configuration.ResourceQualifier;
     45 import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
     46 import com.android.ide.common.resources.configuration.ScreenHeightQualifier;
     47 import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
     48 import com.android.ide.common.resources.configuration.ScreenRatioQualifier;
     49 import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
     50 import com.android.ide.common.resources.configuration.ScreenWidthQualifier;
     51 import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier;
     52 import com.android.ide.common.resources.configuration.TextInputMethodQualifier;
     53 import com.android.ide.common.resources.configuration.TouchScreenQualifier;
     54 import com.android.ide.common.resources.configuration.UiModeQualifier;
     55 import com.android.ide.common.resources.configuration.VersionQualifier;
     56 import com.android.ide.eclipse.adt.AdtPlugin;
     57 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     58 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     59 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils;
     60 import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.VisualRefactoring;
     61 import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors;
     62 import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks;
     63 import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard;
     64 import com.android.resources.FolderTypeRelationship;
     65 import com.android.resources.ResourceFolderType;
     66 import com.android.resources.ResourceType;
     67 import com.android.util.Pair;
     68 
     69 import org.eclipse.core.resources.IFile;
     70 import org.eclipse.core.resources.IProject;
     71 import org.eclipse.core.resources.IResource;
     72 import org.eclipse.core.resources.IResourceDelta;
     73 import org.eclipse.core.runtime.CoreException;
     74 import org.eclipse.core.runtime.IPath;
     75 import org.eclipse.core.runtime.Path;
     76 import org.eclipse.jface.text.IRegion;
     77 import org.eclipse.jface.text.Region;
     78 import org.eclipse.swt.graphics.Image;
     79 import org.eclipse.swt.graphics.RGB;
     80 import org.eclipse.wst.sse.core.StructuredModelManager;
     81 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
     82 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     83 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
     84 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
     85 import org.eclipse.wst.xml.core.internal.document.ElementImpl;
     86 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
     87 import org.w3c.dom.Attr;
     88 import org.w3c.dom.Document;
     89 import org.w3c.dom.Element;
     90 import org.w3c.dom.NamedNodeMap;
     91 import org.w3c.dom.Node;
     92 import org.w3c.dom.NodeList;
     93 import org.w3c.dom.Text;
     94 import org.xml.sax.InputSource;
     95 
     96 import java.io.BufferedInputStream;
     97 import java.io.ByteArrayInputStream;
     98 import java.io.File;
     99 import java.io.FileInputStream;
    100 import java.io.IOException;
    101 import java.io.InputStream;
    102 import java.io.UnsupportedEncodingException;
    103 import java.util.HashMap;
    104 import java.util.List;
    105 import java.util.Map;
    106 import java.util.Set;
    107 
    108 import javax.xml.parsers.DocumentBuilder;
    109 import javax.xml.parsers.DocumentBuilderFactory;
    110 
    111 /**
    112  * Helper class to deal with SWT specifics for the resources.
    113  */
    114 @SuppressWarnings("restriction") // XML model
    115 public class ResourceHelper {
    116 
    117     private static final String TAG_ITEM = "item"; //$NON-NLS-1$
    118     private static final String ATTR_COLOR = "color";  //$NON-NLS-1$
    119 
    120     private final static Map<Class<?>, Image> sIconMap = new HashMap<Class<?>, Image>(
    121             FolderConfiguration.getQualifierCount());
    122 
    123     static {
    124         IconFactory factory = IconFactory.getInstance();
    125         sIconMap.put(CountryCodeQualifier.class,        factory.getIcon("mcc")); //$NON-NLS-1$
    126         sIconMap.put(NetworkCodeQualifier.class,        factory.getIcon("mnc")); //$NON-NLS-1$
    127         sIconMap.put(LanguageQualifier.class,           factory.getIcon("language")); //$NON-NLS-1$
    128         sIconMap.put(RegionQualifier.class,             factory.getIcon("region")); //$NON-NLS-1$
    129         sIconMap.put(ScreenSizeQualifier.class,         factory.getIcon("size")); //$NON-NLS-1$
    130         sIconMap.put(ScreenRatioQualifier.class,        factory.getIcon("ratio")); //$NON-NLS-1$
    131         sIconMap.put(ScreenOrientationQualifier.class,  factory.getIcon("orientation")); //$NON-NLS-1$
    132         sIconMap.put(UiModeQualifier.class,             factory.getIcon("dockmode")); //$NON-NLS-1$
    133         sIconMap.put(NightModeQualifier.class,          factory.getIcon("nightmode")); //$NON-NLS-1$
    134         sIconMap.put(DensityQualifier.class,            factory.getIcon("dpi")); //$NON-NLS-1$
    135         sIconMap.put(TouchScreenQualifier.class,        factory.getIcon("touch")); //$NON-NLS-1$
    136         sIconMap.put(KeyboardStateQualifier.class,      factory.getIcon("keyboard")); //$NON-NLS-1$
    137         sIconMap.put(TextInputMethodQualifier.class,    factory.getIcon("text_input")); //$NON-NLS-1$
    138         sIconMap.put(NavigationStateQualifier.class,    factory.getIcon("navpad")); //$NON-NLS-1$
    139         sIconMap.put(NavigationMethodQualifier.class,   factory.getIcon("navpad")); //$NON-NLS-1$
    140         sIconMap.put(ScreenDimensionQualifier.class,    factory.getIcon("dimension")); //$NON-NLS-1$
    141         sIconMap.put(VersionQualifier.class,            factory.getIcon("version")); //$NON-NLS-1$
    142         sIconMap.put(ScreenWidthQualifier.class,        factory.getIcon("width")); //$NON-NLS-1$
    143         sIconMap.put(ScreenHeightQualifier.class,       factory.getIcon("height")); //$NON-NLS-1$
    144         sIconMap.put(SmallestScreenWidthQualifier.class,factory.getIcon("swidth")); //$NON-NLS-1$
    145     }
    146 
    147     /**
    148      * Returns the icon for the qualifier.
    149      */
    150     public static Image getIcon(Class<? extends ResourceQualifier> theClass) {
    151         return sIconMap.get(theClass);
    152     }
    153 
    154     /**
    155      * Returns a {@link ResourceDeltaKind} from an {@link IResourceDelta} value.
    156      * @param kind a {@link IResourceDelta} integer constant.
    157      * @return a matching {@link ResourceDeltaKind} or null.
    158      *
    159      * @see IResourceDelta#ADDED
    160      * @see IResourceDelta#REMOVED
    161      * @see IResourceDelta#CHANGED
    162      */
    163     public static ResourceDeltaKind getResourceDeltaKind(int kind) {
    164         switch (kind) {
    165             case IResourceDelta.ADDED:
    166                 return ResourceDeltaKind.ADDED;
    167             case IResourceDelta.REMOVED:
    168                 return ResourceDeltaKind.REMOVED;
    169             case IResourceDelta.CHANGED:
    170                 return ResourceDeltaKind.CHANGED;
    171         }
    172 
    173         return null;
    174     }
    175 
    176     /**
    177      * Return the resource type of the given url, and the resource name
    178      *
    179      * @param url the resource url to be parsed
    180      * @return a pair of the resource type and the resource name
    181      */
    182     public static Pair<ResourceType,String> parseResource(String url) {
    183         if (!url.startsWith("@")) { //$NON-NLS-1$
    184             return null;
    185         }
    186         int typeEnd = url.indexOf('/', 1);
    187         if (typeEnd == -1) {
    188             return null;
    189         }
    190         int nameBegin = typeEnd + 1;
    191 
    192         // Skip @ and @+
    193         int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
    194 
    195         int colon = url.lastIndexOf(':', typeEnd);
    196         if (colon != -1) {
    197             typeBegin = colon + 1;
    198         }
    199         String typeName = url.substring(typeBegin, typeEnd);
    200         ResourceType type = ResourceType.getEnum(typeName);
    201         if (type == null) {
    202             return null;
    203         }
    204         String name = url.substring(nameBegin);
    205 
    206         return Pair.of(type, name);
    207     }
    208 
    209     /**
    210      * Is this a resource that can be defined in any file within the "values" folder?
    211      * <p>
    212      * Some resource types can be defined <b>both</b> as a separate XML file as well
    213      * as defined within a value XML file. This method will return true for these types
    214      * as well. In other words, a ResourceType can return true for both
    215      * {@link #isValueBasedResourceType} and {@link #isFileBasedResourceType}.
    216      *
    217      * @param type the resource type to check
    218      * @return true if the given resource type can be represented as a value under the
    219      *         values/ folder
    220      */
    221     public static boolean isValueBasedResourceType(ResourceType type) {
    222         List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);
    223         for (ResourceFolderType folderType : folderTypes) {
    224             if (folderType == ResourceFolderType.VALUES) {
    225                 return true;
    226             }
    227         }
    228 
    229         return false;
    230     }
    231 
    232     /**
    233      * Is this a resource that is defined in a file named by the resource plus the XML
    234      * extension?
    235      * <p>
    236      * Some resource types can be defined <b>both</b> as a separate XML file as well as
    237      * defined within a value XML file along with other properties. This method will
    238      * return true for these resource types as well. In other words, a ResourceType can
    239      * return true for both {@link #isValueBasedResourceType} and
    240      * {@link #isFileBasedResourceType}.
    241      *
    242      * @param type the resource type to check
    243      * @return true if the given resource type is stored in a file named by the resource
    244      */
    245     public static boolean isFileBasedResourceType(ResourceType type) {
    246         List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);
    247         for (ResourceFolderType folderType : folderTypes) {
    248             if (folderType != ResourceFolderType.VALUES) {
    249 
    250                 if (type == ResourceType.ID) {
    251                     // The folder types for ID is not only VALUES but also
    252                     // LAYOUT and MENU. However, unlike resources, they are only defined
    253                     // inline there so for the purposes of isFileBasedResourceType
    254                     // (where the intent is to figure out files that are uniquely identified
    255                     // by a resource's name) this method should return false anyway.
    256                     return false;
    257                 }
    258 
    259                 return true;
    260             }
    261         }
    262 
    263         return false;
    264     }
    265 
    266     /**
    267      * Returns true if this class can create the given resource
    268      *
    269      * @param resource the resource to be created
    270      * @return true if the {@link #createResource} method can create this resource
    271      */
    272     public static boolean canCreateResource(String resource) {
    273         // Cannot create framework resources
    274         if (resource.startsWith('@' + ANDROID_PKG + ':')) {
    275             return false;
    276         }
    277 
    278         Pair<ResourceType,String> parsed = parseResource(resource);
    279         if (parsed != null) {
    280             ResourceType type = parsed.getFirst();
    281             String name = parsed.getSecond();
    282 
    283             // Make sure the name is valid
    284             ResourceNameValidator validator =
    285                 ResourceNameValidator.create(false, (Set<String>) null /* existing */, type);
    286             if (validator.isValid(name) != null) {
    287                 return false;
    288             }
    289 
    290             return canCreateResourceType(type);
    291         }
    292 
    293         return false;
    294     }
    295 
    296     /**
    297      * Returns true if this class can create resources of the given resource
    298      * type
    299      *
    300      * @param type the type of resource to be created
    301      * @return true if the {@link #createResource} method can create resources
    302      *         of this type (provided the name parameter is also valid)
    303      */
    304     public static boolean canCreateResourceType(ResourceType type) {
    305         // We can create all value types
    306         if (isValueBasedResourceType(type)) {
    307             return true;
    308         }
    309 
    310         // We can create -some- file-based types - those supported by the New XML wizard:
    311         for (ResourceFolderType folderType : FolderTypeRelationship.getRelatedFolders(type)) {
    312             if (NewXmlFileWizard.canCreateXmlFile(folderType)) {
    313                 return true;
    314             }
    315         }
    316 
    317         return false;
    318     }
    319 
    320     /** Creates a file-based resource, like a layout. Used by {@link #createResource} */
    321     private static Pair<IFile,IRegion> createFileResource(IProject project, ResourceType type,
    322             String name) {
    323 
    324         ResourceFolderType folderType = null;
    325         for (ResourceFolderType f : FolderTypeRelationship.getRelatedFolders(type)) {
    326             if (NewXmlFileWizard.canCreateXmlFile(f)) {
    327                 folderType = f;
    328                 break;
    329             }
    330         }
    331         if (folderType == null) {
    332             return null;
    333         }
    334 
    335         // Find "dimens.xml" file in res/values/ (or corresponding name for other
    336         // value types)
    337         IPath projectPath = new Path(FD_RESOURCES + WS_SEP + folderType.getName() + WS_SEP
    338             + name + '.' + EXT_XML);
    339         IFile file = project.getFile(projectPath);
    340         return NewXmlFileWizard.createXmlFile(project, file, folderType);
    341     }
    342 
    343     /**
    344      * Creates a resource of a given type, name and (if applicable) value
    345      *
    346      * @param project the project to contain the resource
    347      * @param type the type of resource
    348      * @param name the name of the resource
    349      * @param value the value of the resource, if it is a value-type resource
    350      * @return a pair of the file containing the resource and a region where the value
    351      *         appears
    352      */
    353     public static Pair<IFile,IRegion> createResource(IProject project, ResourceType type,
    354             String name, String value) {
    355         if (!isValueBasedResourceType(type)) {
    356             return createFileResource(project, type, name);
    357         }
    358 
    359         // Find "dimens.xml" file in res/values/ (or corresponding name for other
    360         // value types)
    361         String typeName = type.getName();
    362         String fileName = typeName + 's';
    363         String projectPath = FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP
    364             + fileName + '.' + EXT_XML;
    365         Object editRequester = project;
    366         IResource member = project.findMember(projectPath);
    367         String tagName = Hyperlinks.getTagName(type);
    368         boolean createEmptyTag = type == ResourceType.ID;
    369         if (member != null) {
    370             if (member instanceof IFile) {
    371                 IFile file = (IFile) member;
    372                 // File exists: Must add item to the XML
    373                 IModelManager manager = StructuredModelManager.getModelManager();
    374                 IStructuredModel model = null;
    375                 try {
    376                     model = manager.getExistingModelForEdit(file);
    377                     if (model == null) {
    378                         model = manager.getModelForEdit(file);
    379                     }
    380                     if (model instanceof IDOMModel) {
    381                         model.beginRecording(editRequester, String.format("Add %1$s",
    382                                 type.getDisplayName()));
    383                         IDOMModel domModel = (IDOMModel) model;
    384                         Document document = domModel.getDocument();
    385                         Element root = document.getDocumentElement();
    386                         IStructuredDocument structuredDocument = model.getStructuredDocument();
    387                         Node lastElement = null;
    388                         NodeList childNodes = root.getChildNodes();
    389                         String indent = null;
    390                         for (int i = childNodes.getLength() - 1; i >= 0; i--) {
    391                             Node node = childNodes.item(i);
    392                             if (node.getNodeType() == Node.ELEMENT_NODE) {
    393                                 lastElement = node;
    394                                 indent = AndroidXmlEditor.getIndent(structuredDocument, node);
    395                                 break;
    396                             }
    397                         }
    398                         if (indent == null || indent.length() == 0) {
    399                             indent = "    "; //$NON-NLS-1$
    400                         }
    401                         Node nextChild = lastElement != null ? lastElement.getNextSibling() : null;
    402                         Text indentNode = document.createTextNode('\n' + indent);
    403                         root.insertBefore(indentNode, nextChild);
    404                         Element element = document.createElement(tagName);
    405                         if (createEmptyTag) {
    406                             if (element instanceof ElementImpl) {
    407                                 ElementImpl elementImpl = (ElementImpl) element;
    408                                 elementImpl.setEmptyTag(true);
    409                             }
    410                         }
    411                         element.setAttribute(NAME_ATTR, name);
    412                         if (!tagName.equals(typeName)) {
    413                             element.setAttribute(TYPE_ATTR, typeName);
    414                         }
    415                         root.insertBefore(element, nextChild);
    416                         IRegion region = null;
    417 
    418                         if (createEmptyTag) {
    419                             IndexedRegion domRegion = VisualRefactoring.getRegion(element);
    420                             int endOffset = domRegion.getEndOffset();
    421                             region = new Region(endOffset, 0);
    422                         } else {
    423                             Node valueNode = document.createTextNode(value);
    424                             element.appendChild(valueNode);
    425 
    426                             IndexedRegion domRegion = VisualRefactoring.getRegion(valueNode);
    427                             int startOffset = domRegion.getStartOffset();
    428                             int length = domRegion.getLength();
    429                             region = new Region(startOffset, length);
    430                         }
    431                         model.save();
    432                         return Pair.of(file, region);
    433                     }
    434                 } catch (Exception e) {
    435                     AdtPlugin.log(e, "Cannot access XML value model");
    436                 } finally {
    437                     if (model != null) {
    438                         model.endRecording(editRequester);
    439                         model.releaseFromEdit();
    440                     }
    441                 }
    442             }
    443 
    444             return null;
    445         } else {
    446             // No such file exists: just create it
    447             String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$
    448             StringBuilder sb = new StringBuilder(prolog);
    449 
    450             String root = ResourcesDescriptors.ROOT_ELEMENT;
    451             sb.append('<').append(root).append('>').append('\n');
    452             sb.append("    "); //$NON-NLS-1$
    453             sb.append('<');
    454             sb.append(tagName);
    455             sb.append(" name=\""); //$NON-NLS-1$
    456             sb.append(name);
    457             sb.append('"');
    458             if (!tagName.equals(typeName)) {
    459                 sb.append(" type=\""); //$NON-NLS-1$
    460                 sb.append(typeName);
    461                 sb.append('"');
    462             }
    463             int start, end;
    464             if (createEmptyTag) {
    465                 sb.append("/>");                             //$NON-NLS-1$
    466                 start = sb.length();
    467                 end = sb.length();
    468             } else {
    469                 sb.append('>');
    470                 start = sb.length();
    471                 sb.append(value);
    472                 end = sb.length();
    473                 sb.append('<').append('/');
    474                 sb.append(tagName);
    475                 sb.append('>');
    476             }
    477             sb.append('\n').append('<').append('/').append(root).append('>').append('\n');
    478             String result = sb.toString();
    479             // TODO: Pretty print string (wait until that CL is integrated)
    480             String error = null;
    481             try {
    482                 byte[] buf = result.getBytes("UTF8");    //$NON-NLS-1$
    483                 InputStream stream = new ByteArrayInputStream(buf);
    484                 IFile file = project.getFile(new Path(projectPath));
    485                 file.create(stream, true /*force*/, null /*progress*/);
    486                 IRegion region = new Region(start, end - start);
    487                 return Pair.of(file, region);
    488             } catch (UnsupportedEncodingException e) {
    489                 error = e.getMessage();
    490             } catch (CoreException e) {
    491                 error = e.getMessage();
    492             }
    493 
    494             error = String.format("Failed to generate %1$s: %2$s", name, error);
    495             AdtPlugin.displayError("New Android XML File", error);
    496         }
    497         return null;
    498     }
    499 
    500     /**
    501      * Returns the theme name to be shown for theme styles, e.g. for "@style/Theme" it
    502      * returns "Theme"
    503      *
    504      * @param style a theme style string
    505      * @return the user visible theme name
    506      */
    507     public static String styleToTheme(String style) {
    508         if (style.startsWith(PREFIX_STYLE)) {
    509             style = style.substring(PREFIX_STYLE.length());
    510         } else if (style.startsWith(PREFIX_ANDROID_STYLE)) {
    511             style = style.substring(PREFIX_ANDROID_STYLE.length());
    512         }
    513         return style;
    514     }
    515 
    516     /**
    517      * Returns the layout resource name for the given layout file, e.g. for
    518      * /res/layout/foo.xml returns foo.
    519      *
    520      * @param layoutFile the layout file whose name we want to look up
    521      * @return the layout name
    522      */
    523     public static String getLayoutName(IFile layoutFile) {
    524         String layoutName = layoutFile.getName();
    525         int dotIndex = layoutName.indexOf('.');
    526         if (dotIndex != -1) {
    527             layoutName = layoutName.substring(0, dotIndex);
    528         }
    529         return layoutName;
    530     }
    531 
    532     /**
    533      * Tries to resolve the given resource value to an actual RGB color. For state lists
    534      * it will pick the simplest/fallback color.
    535      *
    536      * @param resources the resource resolver to use to follow color references
    537      * @param color the color to resolve
    538      * @return the corresponding {@link RGB} color, or null
    539      */
    540     public static RGB resolveColor(ResourceResolver resources, ResourceValue color) {
    541         color = resources.resolveResValue(color);
    542         if (color == null) {
    543             return null;
    544         }
    545         String value = color.getValue();
    546 
    547         while (value != null) {
    548             if (value.startsWith("#")) { //$NON-NLS-1$
    549                 try {
    550                     int rgba = ImageUtils.getColor(value);
    551                     // Drop alpha channel
    552                     return ImageUtils.intToRgb(rgba);
    553                 } catch (NumberFormatException nfe) {
    554                     // Pass
    555                 }
    556                 return null;
    557             }
    558             if (value.startsWith("@")) { //$NON-NLS-1$
    559                 boolean isFramework = color.isFramework();
    560                 color = resources.findResValue(value, isFramework);
    561                 if (color != null) {
    562                     value = color.getValue();
    563                 } else {
    564                     break;
    565                 }
    566             } else {
    567                 File file = new File(value);
    568                 if (file.exists() && file.getName().endsWith(DOT_XML)) {
    569                     // Parse
    570                     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    571                     BufferedInputStream bis = null;
    572                     try {
    573                         bis = new BufferedInputStream(new FileInputStream(file));
    574                         InputSource is = new InputSource(bis);
    575                         factory.setNamespaceAware(true);
    576                         factory.setValidating(false);
    577                         DocumentBuilder builder = factory.newDocumentBuilder();
    578                         Document document = builder.parse(is);
    579                         NodeList items = document.getElementsByTagName(TAG_ITEM);
    580 
    581                         value = findColorValue(items);
    582                         continue;
    583                     } catch (Exception e) {
    584                         AdtPlugin.log(e, "Failed parsing color file %1$s", file.getName());
    585                     } finally {
    586                         if (bis != null) {
    587                             try {
    588                                 bis.close();
    589                             } catch (IOException e) {
    590                                 // Nothing useful can be done here
    591                             }
    592                         }
    593                     }
    594                 }
    595 
    596                 return null;
    597             }
    598         }
    599 
    600         return null;
    601     }
    602 
    603     /**
    604      * Searches a color XML file for the color definition element that does not
    605      * have an associated state and returns its color
    606      */
    607     private static String findColorValue(NodeList items) {
    608         for (int i = 0, n = items.getLength(); i < n; i++) {
    609             // Find non-state color definition
    610             Node item = items.item(i);
    611             boolean hasState = false;
    612             if (item.getNodeType() == Node.ELEMENT_NODE) {
    613                 Element element = (Element) item;
    614                 if (element.hasAttributeNS(ANDROID_URI, ATTR_COLOR)) {
    615                     NamedNodeMap attributes = element.getAttributes();
    616                     for (int j = 0, m = attributes.getLength(); j < m; j++) {
    617                         Attr attribute = (Attr) attributes.item(j);
    618                         if (attribute.getLocalName().startsWith("state_")) { //$NON-NLS-1$
    619                             hasState = true;
    620                             break;
    621                         }
    622                     }
    623 
    624                     if (!hasState) {
    625                         return element.getAttributeNS(ANDROID_URI, ATTR_COLOR);
    626                     }
    627                 }
    628             }
    629         }
    630 
    631         return null;
    632     }
    633 }
    634