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