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