• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ResourceRepository;
38 import com.android.ide.common.resources.ResourceResolver;
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();
sIconMap.put(CountryCodeQualifier.class, factory.getIcon("mcc"))128             sIconMap.put(CountryCodeQualifier.class,        factory.getIcon("mcc")); //$NON-NLS-1$
sIconMap.put(NetworkCodeQualifier.class, factory.getIcon("mnc"))129             sIconMap.put(NetworkCodeQualifier.class,        factory.getIcon("mnc")); //$NON-NLS-1$
sIconMap.put(LanguageQualifier.class, factory.getIcon("language"))130             sIconMap.put(LanguageQualifier.class,           factory.getIcon("language")); //$NON-NLS-1$
sIconMap.put(RegionQualifier.class, factory.getIcon("region"))131             sIconMap.put(RegionQualifier.class,             factory.getIcon("region")); //$NON-NLS-1$
sIconMap.put(LayoutDirectionQualifier.class, factory.getIcon("bidi"))132             sIconMap.put(LayoutDirectionQualifier.class,    factory.getIcon("bidi")); //$NON-NLS-1$
sIconMap.put(ScreenSizeQualifier.class, factory.getIcon("size"))133             sIconMap.put(ScreenSizeQualifier.class,         factory.getIcon("size")); //$NON-NLS-1$
sIconMap.put(ScreenRatioQualifier.class, factory.getIcon("ratio"))134             sIconMap.put(ScreenRatioQualifier.class,        factory.getIcon("ratio")); //$NON-NLS-1$
sIconMap.put(ScreenOrientationQualifier.class, factory.getIcon("orientation"))135             sIconMap.put(ScreenOrientationQualifier.class,  factory.getIcon("orientation")); //$NON-NLS-1$
sIconMap.put(UiModeQualifier.class, factory.getIcon("dockmode"))136             sIconMap.put(UiModeQualifier.class,             factory.getIcon("dockmode")); //$NON-NLS-1$
sIconMap.put(NightModeQualifier.class, factory.getIcon("nightmode"))137             sIconMap.put(NightModeQualifier.class,          factory.getIcon("nightmode")); //$NON-NLS-1$
sIconMap.put(DensityQualifier.class, factory.getIcon("dpi"))138             sIconMap.put(DensityQualifier.class,            factory.getIcon("dpi")); //$NON-NLS-1$
sIconMap.put(TouchScreenQualifier.class, factory.getIcon("touch"))139             sIconMap.put(TouchScreenQualifier.class,        factory.getIcon("touch")); //$NON-NLS-1$
sIconMap.put(KeyboardStateQualifier.class, factory.getIcon("keyboard"))140             sIconMap.put(KeyboardStateQualifier.class,      factory.getIcon("keyboard")); //$NON-NLS-1$
sIconMap.put(TextInputMethodQualifier.class, factory.getIcon("text_input"))141             sIconMap.put(TextInputMethodQualifier.class,    factory.getIcon("text_input")); //$NON-NLS-1$
sIconMap.put(NavigationStateQualifier.class, factory.getIcon("navpad"))142             sIconMap.put(NavigationStateQualifier.class,    factory.getIcon("navpad")); //$NON-NLS-1$
sIconMap.put(NavigationMethodQualifier.class, factory.getIcon("navpad"))143             sIconMap.put(NavigationMethodQualifier.class,   factory.getIcon("navpad")); //$NON-NLS-1$
sIconMap.put(ScreenDimensionQualifier.class, factory.getIcon("dimension"))144             sIconMap.put(ScreenDimensionQualifier.class,    factory.getIcon("dimension")); //$NON-NLS-1$
sIconMap.put(VersionQualifier.class, factory.getIcon("version"))145             sIconMap.put(VersionQualifier.class,            factory.getIcon("version")); //$NON-NLS-1$
sIconMap.put(ScreenWidthQualifier.class, factory.getIcon("width"))146             sIconMap.put(ScreenWidthQualifier.class,        factory.getIcon("width")); //$NON-NLS-1$
sIconMap.put(ScreenHeightQualifier.class, factory.getIcon("height"))147             sIconMap.put(ScreenHeightQualifier.class,       factory.getIcon("height")); //$NON-NLS-1$
sIconMap.put(SmallestScreenWidthQualifier.class,factory.getIcon("swidth"))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      */
getIcon(Class<? extends ResourceQualifier> theClass)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      */
getResourceDeltaKind(int kind)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      */
isValueBasedResourceType(ResourceType type)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      */
isFileBasedResourceType(ResourceType type)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      */
canCreateResource(String resource)246     public static boolean canCreateResource(String resource) {
247         // Cannot create framework resources
248         if (resource.startsWith(ANDROID_PREFIX)) {
249             return false;
250         }
251 
252         Pair<ResourceType,String> parsed = ResourceRepository.parseResource(resource);
253         if (parsed != null) {
254             ResourceType type = parsed.getFirst();
255             String name = parsed.getSecond();
256 
257             // Make sure the name is valid
258             ResourceNameValidator validator =
259                 ResourceNameValidator.create(false, (Set<String>) null /* existing */, type);
260             if (validator.isValid(name) != null) {
261                 return false;
262             }
263 
264             return canCreateResourceType(type);
265         }
266 
267         return false;
268     }
269 
270     /**
271      * Returns true if this class can create resources of the given resource
272      * type
273      *
274      * @param type the type of resource to be created
275      * @return true if the {@link #createResource} method can create resources
276      *         of this type (provided the name parameter is also valid)
277      */
canCreateResourceType(ResourceType type)278     public static boolean canCreateResourceType(ResourceType type) {
279         // We can create all value types
280         if (isValueBasedResourceType(type)) {
281             return true;
282         }
283 
284         // We can create -some- file-based types - those supported by the New XML wizard:
285         for (ResourceFolderType folderType : FolderTypeRelationship.getRelatedFolders(type)) {
286             if (NewXmlFileWizard.canCreateXmlFile(folderType)) {
287                 return true;
288             }
289         }
290 
291         return false;
292     }
293 
294     /** Creates a file-based resource, like a layout. Used by {@link #createResource} */
createFileResource(IProject project, ResourceType type, String name)295     private static Pair<IFile,IRegion> createFileResource(IProject project, ResourceType type,
296             String name) {
297 
298         ResourceFolderType folderType = null;
299         for (ResourceFolderType f : FolderTypeRelationship.getRelatedFolders(type)) {
300             if (NewXmlFileWizard.canCreateXmlFile(f)) {
301                 folderType = f;
302                 break;
303             }
304         }
305         if (folderType == null) {
306             return null;
307         }
308 
309         // Find "dimens.xml" file in res/values/ (or corresponding name for other
310         // value types)
311         IPath projectPath = new Path(FD_RESOURCES + WS_SEP + folderType.getName() + WS_SEP
312             + name + '.' + EXT_XML);
313         IFile file = project.getFile(projectPath);
314         return NewXmlFileWizard.createXmlFile(project, file, folderType);
315     }
316 
317     /**
318      * Creates a resource of a given type, name and (if applicable) value
319      *
320      * @param project the project to contain the resource
321      * @param type the type of resource
322      * @param name the name of the resource
323      * @param value the value of the resource, if it is a value-type resource
324      * @return a pair of the file containing the resource and a region where the value
325      *         appears
326      */
createResource(IProject project, ResourceType type, String name, String value)327     public static Pair<IFile,IRegion> createResource(IProject project, ResourceType type,
328             String name, String value) {
329         if (!isValueBasedResourceType(type)) {
330             return createFileResource(project, type, name);
331         }
332 
333         // Find "dimens.xml" file in res/values/ (or corresponding name for other
334         // value types)
335         String typeName = type.getName();
336         String fileName = typeName + 's';
337         String projectPath = FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP
338             + fileName + '.' + EXT_XML;
339         Object editRequester = project;
340         IResource member = project.findMember(projectPath);
341         String tagName = Hyperlinks.getTagName(type);
342         boolean createEmptyTag = type == ResourceType.ID;
343         if (member != null) {
344             if (member instanceof IFile) {
345                 IFile file = (IFile) member;
346                 // File exists: Must add item to the XML
347                 IModelManager manager = StructuredModelManager.getModelManager();
348                 IStructuredModel model = null;
349                 try {
350                     model = manager.getExistingModelForEdit(file);
351                     if (model == null) {
352                         model = manager.getModelForEdit(file);
353                     }
354                     if (model instanceof IDOMModel) {
355                         model.beginRecording(editRequester, String.format("Add %1$s",
356                                 type.getDisplayName()));
357                         IDOMModel domModel = (IDOMModel) model;
358                         Document document = domModel.getDocument();
359                         Element root = document.getDocumentElement();
360                         IStructuredDocument structuredDocument = model.getStructuredDocument();
361                         Node lastElement = null;
362                         NodeList childNodes = root.getChildNodes();
363                         String indent = null;
364                         for (int i = childNodes.getLength() - 1; i >= 0; i--) {
365                             Node node = childNodes.item(i);
366                             if (node.getNodeType() == Node.ELEMENT_NODE) {
367                                 lastElement = node;
368                                 indent = AndroidXmlEditor.getIndent(structuredDocument, node);
369                                 break;
370                             }
371                         }
372                         if (indent == null || indent.length() == 0) {
373                             indent = "    "; //$NON-NLS-1$
374                         }
375                         Node nextChild = lastElement != null ? lastElement.getNextSibling() : null;
376                         Text indentNode = document.createTextNode('\n' + indent);
377                         root.insertBefore(indentNode, nextChild);
378                         Element element = document.createElement(tagName);
379                         if (createEmptyTag) {
380                             if (element instanceof ElementImpl) {
381                                 ElementImpl elementImpl = (ElementImpl) element;
382                                 elementImpl.setEmptyTag(true);
383                             }
384                         }
385                         element.setAttribute(ATTR_NAME, name);
386                         if (!tagName.equals(typeName)) {
387                             element.setAttribute(ATTR_TYPE, typeName);
388                         }
389                         root.insertBefore(element, nextChild);
390                         IRegion region = null;
391 
392                         if (createEmptyTag) {
393                             IndexedRegion domRegion = VisualRefactoring.getRegion(element);
394                             int endOffset = domRegion.getEndOffset();
395                             region = new Region(endOffset, 0);
396                         } else {
397                             Node valueNode = document.createTextNode(value);
398                             element.appendChild(valueNode);
399 
400                             IndexedRegion domRegion = VisualRefactoring.getRegion(valueNode);
401                             int startOffset = domRegion.getStartOffset();
402                             int length = domRegion.getLength();
403                             region = new Region(startOffset, length);
404                         }
405                         model.save();
406                         return Pair.of(file, region);
407                     }
408                 } catch (Exception e) {
409                     AdtPlugin.log(e, "Cannot access XML value model");
410                 } finally {
411                     if (model != null) {
412                         model.endRecording(editRequester);
413                         model.releaseFromEdit();
414                     }
415                 }
416             }
417 
418             return null;
419         } else {
420             // No such file exists: just create it
421             String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$
422             StringBuilder sb = new StringBuilder(prolog);
423 
424             String root = TAG_RESOURCES;
425             sb.append('<').append(root).append('>').append('\n');
426             sb.append("    "); //$NON-NLS-1$
427             sb.append('<');
428             sb.append(tagName);
429             sb.append(" name=\""); //$NON-NLS-1$
430             sb.append(name);
431             sb.append('"');
432             if (!tagName.equals(typeName)) {
433                 sb.append(" type=\""); //$NON-NLS-1$
434                 sb.append(typeName);
435                 sb.append('"');
436             }
437             int start, end;
438             if (createEmptyTag) {
439                 sb.append("/>");                             //$NON-NLS-1$
440                 start = sb.length();
441                 end = sb.length();
442             } else {
443                 sb.append('>');
444                 start = sb.length();
445                 sb.append(value);
446                 end = sb.length();
447                 sb.append('<').append('/');
448                 sb.append(tagName);
449                 sb.append('>');
450             }
451             sb.append('\n').append('<').append('/').append(root).append('>').append('\n');
452             String result = sb.toString();
453             // TODO: Pretty print string (wait until that CL is integrated)
454             String error = null;
455             try {
456                 byte[] buf = result.getBytes("UTF8");    //$NON-NLS-1$
457                 InputStream stream = new ByteArrayInputStream(buf);
458                 IFile file = project.getFile(new Path(projectPath));
459                 file.create(stream, true /*force*/, null /*progress*/);
460                 IRegion region = new Region(start, end - start);
461                 return Pair.of(file, region);
462             } catch (UnsupportedEncodingException e) {
463                 error = e.getMessage();
464             } catch (CoreException e) {
465                 error = e.getMessage();
466             }
467 
468             error = String.format("Failed to generate %1$s: %2$s", name, error);
469             AdtPlugin.displayError("New Android XML File", error);
470         }
471         return null;
472     }
473 
474     /**
475      * Returns the theme name to be shown for theme styles, e.g. for "@style/Theme" it
476      * returns "Theme"
477      *
478      * @param style a theme style string
479      * @return the user visible theme name
480      */
styleToTheme(String style)481     public static String styleToTheme(String style) {
482         if (style.startsWith(STYLE_RESOURCE_PREFIX)) {
483             style = style.substring(STYLE_RESOURCE_PREFIX.length());
484         } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) {
485             style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length());
486         } else if (style.startsWith(PREFIX_RESOURCE_REF)) {
487             // @package:style/foo
488             int index = style.indexOf('/');
489             if (index != -1) {
490                 style = style.substring(index + 1);
491             }
492         }
493         return style;
494     }
495 
496     /**
497      * Returns true if the given style represents a project theme
498      *
499      * @param style a theme style string
500      * @return true if the style string represents a project theme, as opposed
501      *         to a framework theme
502      */
isProjectStyle(String style)503     public static boolean isProjectStyle(String style) {
504         assert style.startsWith(STYLE_RESOURCE_PREFIX)
505             || style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX) : style;
506 
507         return style.startsWith(STYLE_RESOURCE_PREFIX);
508     }
509 
510     /**
511      * Returns the layout resource name for the given layout file, e.g. for
512      * /res/layout/foo.xml returns foo.
513      *
514      * @param layoutFile the layout file whose name we want to look up
515      * @return the layout name
516      */
getLayoutName(IFile layoutFile)517     public static String getLayoutName(IFile layoutFile) {
518         String layoutName = layoutFile.getName();
519         int dotIndex = layoutName.indexOf('.');
520         if (dotIndex != -1) {
521             layoutName = layoutName.substring(0, dotIndex);
522         }
523         return layoutName;
524     }
525 
526     /**
527      * Tries to resolve the given resource value to an actual RGB color. For state lists
528      * it will pick the simplest/fallback color.
529      *
530      * @param resources the resource resolver to use to follow color references
531      * @param color the color to resolve
532      * @return the corresponding {@link RGB} color, or null
533      */
resolveColor(ResourceResolver resources, ResourceValue color)534     public static RGB resolveColor(ResourceResolver resources, ResourceValue color) {
535         color = resources.resolveResValue(color);
536         if (color == null) {
537             return null;
538         }
539         String value = color.getValue();
540 
541         while (value != null) {
542             if (value.startsWith("#")) { //$NON-NLS-1$
543                 try {
544                     int rgba = ImageUtils.getColor(value);
545                     // Drop alpha channel
546                     return ImageUtils.intToRgb(rgba);
547                 } catch (NumberFormatException nfe) {
548                     // Pass
549                 }
550                 return null;
551             }
552             if (value.startsWith(PREFIX_RESOURCE_REF)) {
553                 boolean isFramework = color.isFramework();
554                 color = resources.findResValue(value, isFramework);
555                 if (color != null) {
556                     value = color.getValue();
557                 } else {
558                     break;
559                 }
560             } else {
561                 File file = new File(value);
562                 if (file.exists() && file.getName().endsWith(DOT_XML)) {
563                     // Parse
564                     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
565                     BufferedInputStream bis = null;
566                     try {
567                         bis = new BufferedInputStream(new FileInputStream(file));
568                         InputSource is = new InputSource(bis);
569                         factory.setNamespaceAware(true);
570                         factory.setValidating(false);
571                         DocumentBuilder builder = factory.newDocumentBuilder();
572                         Document document = builder.parse(is);
573                         NodeList items = document.getElementsByTagName(TAG_ITEM);
574 
575                         value = findColorValue(items);
576                         continue;
577                     } catch (Exception e) {
578                         AdtPlugin.log(e, "Failed parsing color file %1$s", file.getName());
579                     } finally {
580                         if (bis != null) {
581                             try {
582                                 bis.close();
583                             } catch (IOException e) {
584                                 // Nothing useful can be done here
585                             }
586                         }
587                     }
588                 }
589 
590                 return null;
591             }
592         }
593 
594         return null;
595     }
596 
597     /**
598      * Searches a color XML file for the color definition element that does not
599      * have an associated state and returns its color
600      */
findColorValue(NodeList items)601     private static String findColorValue(NodeList items) {
602         for (int i = 0, n = items.getLength(); i < n; i++) {
603             // Find non-state color definition
604             Node item = items.item(i);
605             boolean hasState = false;
606             if (item.getNodeType() == Node.ELEMENT_NODE) {
607                 Element element = (Element) item;
608                 if (element.hasAttributeNS(ANDROID_URI, ATTR_COLOR)) {
609                     NamedNodeMap attributes = element.getAttributes();
610                     for (int j = 0, m = attributes.getLength(); j < m; j++) {
611                         Attr attribute = (Attr) attributes.item(j);
612                         if (attribute.getLocalName().startsWith("state_")) { //$NON-NLS-1$
613                             hasState = true;
614                             break;
615                         }
616                     }
617 
618                     if (!hasState) {
619                         return element.getAttributeNS(ANDROID_URI, ATTR_COLOR);
620                     }
621                 }
622             }
623         }
624 
625         return null;
626     }
627 }
628