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