• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.editors.manifest.descriptors;
18 
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.editors.layout.gscripts.IAttributeInfo;
21 import com.android.ide.eclipse.adt.editors.layout.gscripts.IAttributeInfo.Format;
22 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
23 import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
24 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
25 import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
26 import com.android.ide.eclipse.adt.internal.editors.descriptors.ListAttributeDescriptor;
27 import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor;
28 import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
29 import com.android.ide.eclipse.adt.internal.resources.AttributeInfo;
30 import com.android.ide.eclipse.adt.internal.resources.DeclareStyleableInfo;
31 import com.android.ide.eclipse.adt.internal.resources.ResourceType;
32 import com.android.sdklib.SdkConstants;
33 
34 import org.eclipse.core.runtime.IStatus;
35 
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.Iterator;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.TreeSet;
43 import java.util.Map.Entry;
44 
45 
46 /**
47  * Complete description of the AndroidManifest.xml structure.
48  * <p/>
49  * The root element are static instances which always exists.
50  * However their sub-elements and attributes are created only when the SDK changes or is
51  * loaded the first time.
52  */
53 public final class AndroidManifestDescriptors implements IDescriptorProvider {
54 
55     private static final String MANIFEST_NODE_NAME = "manifest";                //$NON-NLS-1$
56     private static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest"; //$NON-NLS-1$
57 
58     // Public attributes names, attributes descriptors and elements descriptors
59 
60     public static final String ANDROID_LABEL_ATTR = "label";    //$NON-NLS-1$
61     public static final String ANDROID_NAME_ATTR  = "name";     //$NON-NLS-1$
62     public static final String PACKAGE_ATTR       = "package";  //$NON-NLS-1$
63 
64     /** The {@link ElementDescriptor} for the root Manifest element. */
65     private final ElementDescriptor MANIFEST_ELEMENT;
66     /** The {@link ElementDescriptor} for the root Application element. */
67     private final ElementDescriptor APPLICATION_ELEMENT;
68 
69     /** The {@link ElementDescriptor} for the root Instrumentation element. */
70     private final ElementDescriptor INTRUMENTATION_ELEMENT;
71     /** The {@link ElementDescriptor} for the root Permission element. */
72     private final ElementDescriptor PERMISSION_ELEMENT;
73     /** The {@link ElementDescriptor} for the root UsesPermission element. */
74     private final ElementDescriptor USES_PERMISSION_ELEMENT;
75     /** The {@link ElementDescriptor} for the root UsesSdk element. */
76     private final ElementDescriptor USES_SDK_ELEMENT;
77 
78     /** The {@link ElementDescriptor} for the root PermissionGroup element. */
79     private final ElementDescriptor PERMISSION_GROUP_ELEMENT;
80     /** The {@link ElementDescriptor} for the root PermissionTree element. */
81     private final ElementDescriptor PERMISSION_TREE_ELEMENT;
82 
83     /** Private package attribute for the manifest element. Needs to be handled manually. */
84     private final TextAttributeDescriptor PACKAGE_ATTR_DESC;
85 
AndroidManifestDescriptors()86     public AndroidManifestDescriptors() {
87         APPLICATION_ELEMENT = createElement("application", null, true); //$NON-NLS-1$ + no child & mandatory
88         INTRUMENTATION_ELEMENT = createElement("instrumentation"); //$NON-NLS-1$
89 
90         PERMISSION_ELEMENT = createElement("permission"); //$NON-NLS-1$
91         USES_PERMISSION_ELEMENT = createElement("uses-permission"); //$NON-NLS-1$
92         USES_SDK_ELEMENT = createElement("uses-sdk", null, true); //$NON-NLS-1$ + no child & mandatory
93 
94         PERMISSION_GROUP_ELEMENT = createElement("permission-group"); //$NON-NLS-1$
95         PERMISSION_TREE_ELEMENT = createElement("permission-tree"); //$NON-NLS-1$
96 
97         MANIFEST_ELEMENT = createElement(
98                         MANIFEST_NODE_NAME, // xml name
99                         new ElementDescriptor[] {
100                                         APPLICATION_ELEMENT,
101                                         INTRUMENTATION_ELEMENT,
102                                         PERMISSION_ELEMENT,
103                                         USES_PERMISSION_ELEMENT,
104                                         PERMISSION_GROUP_ELEMENT,
105                                         PERMISSION_TREE_ELEMENT,
106                                         USES_SDK_ELEMENT,
107                         },
108                         true /* mandatory */);
109 
110         // The "package" attribute is treated differently as it doesn't have the standard
111         // Android XML namespace.
112         PACKAGE_ATTR_DESC = new PackageAttributeDescriptor(PACKAGE_ATTR,
113                 "Package",
114                 null /* nsUri */,
115                 "This attribute gives a unique name for the package, using a Java-style naming convention to avoid name collisions.\nFor example, applications published by Google could have names of the form com.google.app.appname",
116                 new AttributeInfo(PACKAGE_ATTR, new Format[] { Format.REFERENCE }) );
117     }
118 
getRootElementDescriptors()119     public ElementDescriptor[] getRootElementDescriptors() {
120         return new ElementDescriptor[] { MANIFEST_ELEMENT };
121     }
122 
getDescriptor()123     public ElementDescriptor getDescriptor() {
124         return getManifestElement();
125     }
126 
getApplicationElement()127     public ElementDescriptor getApplicationElement() {
128         return APPLICATION_ELEMENT;
129     }
130 
getManifestElement()131     public ElementDescriptor getManifestElement() {
132         return MANIFEST_ELEMENT;
133     }
134 
getUsesSdkElement()135     public ElementDescriptor getUsesSdkElement() {
136         return USES_SDK_ELEMENT;
137     }
138 
getInstrumentationElement()139     public ElementDescriptor getInstrumentationElement() {
140         return INTRUMENTATION_ELEMENT;
141     }
142 
getPermissionElement()143     public ElementDescriptor getPermissionElement() {
144         return PERMISSION_ELEMENT;
145     }
146 
getUsesPermissionElement()147     public ElementDescriptor getUsesPermissionElement() {
148         return USES_PERMISSION_ELEMENT;
149     }
150 
getPermissionGroupElement()151     public ElementDescriptor getPermissionGroupElement() {
152         return PERMISSION_GROUP_ELEMENT;
153     }
154 
getPermissionTreeElement()155     public ElementDescriptor getPermissionTreeElement() {
156         return PERMISSION_TREE_ELEMENT;
157     }
158 
159     /**
160      * Updates the document descriptor.
161      * <p/>
162      * It first computes the new children of the descriptor and then updates them
163      * all at once.
164      *
165      * @param manifestMap The map style => attributes from the attrs_manifest.xml file
166      */
updateDescriptors( Map<String, DeclareStyleableInfo> manifestMap)167     public synchronized void updateDescriptors(
168             Map<String, DeclareStyleableInfo> manifestMap) {
169 
170         // -- setup the required attributes overrides --
171 
172         Set<String> required = new HashSet<String>();
173         required.add("provider/authorities");  //$NON-NLS-1$
174 
175         // -- setup the various attribute format overrides --
176 
177         // The key for each override is "element1,element2,.../attr-xml-local-name" or
178         // "*/attr-xml-local-name" to match the attribute in any element.
179 
180         Map<String, Object> overrides = new HashMap<String, Object>();
181 
182         overrides.put("*/icon", new DescriptorsUtils.ITextAttributeCreator() { //$NON-NLS-1$
183             public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri,
184                     String tooltip) {
185                 return new ReferenceAttributeDescriptor(
186                         ResourceType.DRAWABLE,
187                         xmlName, uiName, nsUri,
188                         tooltip,
189                         new AttributeInfo(xmlName, new Format[] { Format.REFERENCE }) );
190             }
191         });
192 
193         overrides.put("*/theme",         ThemeAttributeDescriptor.class);   //$NON-NLS-1$
194         overrides.put("*/permission",    ListAttributeDescriptor.class);    //$NON-NLS-1$
195         overrides.put("*/targetPackage", ManifestPkgAttrDescriptor.class);  //$NON-NLS-1$
196 
197         overrides.put("uses-library/name", ListAttributeDescriptor.class);       //$NON-NLS-1$
198 
199         overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR,    //$NON-NLS-1$
200                       ListAttributeDescriptor.class);
201         overrides.put("application/" + ANDROID_NAME_ATTR, ApplicationAttributeDescriptor.class);  //$NON-NLS-1$
202 
203         overrideClassName(overrides, "activity", SdkConstants.CLASS_ACTIVITY);           //$NON-NLS-1$
204         overrideClassName(overrides, "receiver", SdkConstants.CLASS_BROADCASTRECEIVER);  //$NON-NLS-1$
205         overrideClassName(overrides, "service", SdkConstants.CLASS_SERVICE);             //$NON-NLS-1$
206         overrideClassName(overrides, "provider", SdkConstants.CLASS_CONTENTPROVIDER);    //$NON-NLS-1$
207         overrideClassName(overrides, "instrumentation", SdkConstants.CLASS_INSTRUMENTATION);    //$NON-NLS-1$
208 
209         // -- list element nodes already created --
210         // These elements are referenced by already opened editors, so we want to update them
211         // but not re-create them when reloading an SDK on the fly.
212 
213         HashMap<String, ElementDescriptor> elementDescs =
214             new HashMap<String, ElementDescriptor>();
215         elementDescs.put(MANIFEST_ELEMENT.getXmlLocalName(),         MANIFEST_ELEMENT);
216         elementDescs.put(APPLICATION_ELEMENT.getXmlLocalName(),      APPLICATION_ELEMENT);
217         elementDescs.put(INTRUMENTATION_ELEMENT.getXmlLocalName(),   INTRUMENTATION_ELEMENT);
218         elementDescs.put(PERMISSION_ELEMENT.getXmlLocalName(),       PERMISSION_ELEMENT);
219         elementDescs.put(USES_PERMISSION_ELEMENT.getXmlLocalName(),  USES_PERMISSION_ELEMENT);
220         elementDescs.put(USES_SDK_ELEMENT.getXmlLocalName(),         USES_SDK_ELEMENT);
221         elementDescs.put(PERMISSION_GROUP_ELEMENT.getXmlLocalName(), PERMISSION_GROUP_ELEMENT);
222         elementDescs.put(PERMISSION_TREE_ELEMENT.getXmlLocalName(),  PERMISSION_TREE_ELEMENT);
223 
224         // --
225 
226         inflateElement(manifestMap,
227                 overrides,
228                 required,
229                 elementDescs,
230                 MANIFEST_ELEMENT,
231                 "AndroidManifest"); //$NON-NLS-1$
232         insertAttribute(MANIFEST_ELEMENT, PACKAGE_ATTR_DESC);
233 
234         sanityCheck(manifestMap, MANIFEST_ELEMENT);
235     }
236 
237     /**
238      * Sets up an attribute override for ANDROID_NAME_ATTR using a ClassAttributeDescriptor
239      * with the specified class name.
240      */
overrideClassName(Map<String, Object> overrides, String elementName, final String className)241     private static void overrideClassName(Map<String, Object> overrides,
242             String elementName, final String className) {
243         overrides.put(elementName + "/" + ANDROID_NAME_ATTR,
244                 new DescriptorsUtils.ITextAttributeCreator() {
245             public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri,
246                     String tooltip) {
247                 uiName += "*";  //$NON-NLS-1$
248 
249                 IAttributeInfo attrInfo = new AttributeInfo(xmlName,
250                         new Format[] { Format.STRING } );
251 
252                 if (SdkConstants.CLASS_ACTIVITY.equals(className)) {
253                     return new ClassAttributeDescriptor(
254                             className,
255                             PostActivityCreationAction.getAction(),
256                             xmlName,
257                             uiName,
258                             nsUri,
259                             tooltip,
260                             attrInfo,
261                             true /*mandatory */,
262                             true /*defaultToProjectOnly*/);
263                 } else if (SdkConstants.CLASS_BROADCASTRECEIVER.equals(className)) {
264                     return new ClassAttributeDescriptor(
265                             className,
266                             PostReceiverCreationAction.getAction(),
267                             xmlName,
268                             uiName,
269                             nsUri,
270                             tooltip,
271                             attrInfo,
272                             true /*mandatory */,
273                             true /*defaultToProjectOnly*/);
274                 } else if (SdkConstants.CLASS_INSTRUMENTATION.equals(className)) {
275                     return new ClassAttributeDescriptor(
276                             className,
277                             null, // no post action
278                             xmlName,
279                             uiName,
280                             nsUri,
281                             tooltip,
282                             attrInfo,
283                             true /*mandatory */,
284                             false /*defaultToProjectOnly*/);
285                 } else {
286                     return new ClassAttributeDescriptor(
287                             className,
288                             xmlName,
289                             uiName,
290                             nsUri,
291                             tooltip,
292                             attrInfo,
293                             true /*mandatory */);
294                 }
295             }
296         });
297     }
298 
299     /**
300      * Returns a new ElementDescriptor constructed from the information given here
301      * and the javadoc & attributes extracted from the style map if any.
302      * <p/>
303      * Creates an element with no attribute overrides.
304      */
createElement( String xmlName, ElementDescriptor[] childrenElements, boolean mandatory)305     private ElementDescriptor createElement(
306             String xmlName,
307             ElementDescriptor[] childrenElements,
308             boolean mandatory) {
309         // Creates an element with no attribute overrides.
310         String styleName = guessStyleName(xmlName);
311         String sdkUrl = DescriptorsUtils.MANIFEST_SDK_URL + styleName;
312         String uiName = getUiName(xmlName);
313 
314         ElementDescriptor element = new ManifestElementDescriptor(xmlName, uiName, null, sdkUrl,
315                 null, childrenElements, mandatory);
316 
317         return element;
318     }
319 
320     /**
321      * Returns a new ElementDescriptor constructed from its XML local name.
322      * <p/>
323      * This version creates an element not mandatory.
324      */
createElement(String xmlName)325     private ElementDescriptor createElement(String xmlName) {
326         // Creates an element with no child and not mandatory
327         return createElement(xmlName, null, false);
328     }
329 
330     /**
331      * Inserts an attribute in this element attribute list if it is not present there yet
332      * (based on the attribute XML name.)
333      * The attribute is inserted at the beginning of the attribute list.
334      */
insertAttribute(ElementDescriptor element, AttributeDescriptor newAttr)335     private void insertAttribute(ElementDescriptor element, AttributeDescriptor newAttr) {
336         AttributeDescriptor[] attributes = element.getAttributes();
337         for (AttributeDescriptor attr : attributes) {
338             if (attr.getXmlLocalName().equals(newAttr.getXmlLocalName())) {
339                 return;
340             }
341         }
342 
343         AttributeDescriptor[] newArray = new AttributeDescriptor[attributes.length + 1];
344         newArray[0] = newAttr;
345         System.arraycopy(attributes, 0, newArray, 1, attributes.length);
346         element.setAttributes(newArray);
347     }
348 
349     /**
350      * "Inflates" the properties of an {@link ElementDescriptor} from the styleable declaration.
351      * <p/>
352      * This first creates all the attributes for the given ElementDescriptor.
353      * It then finds all children of the descriptor, inflate them recursively and set them
354      * as child to this ElementDescriptor.
355      *
356      * @param styleMap The input styleable map for manifest elements & attributes.
357      * @param overrides A list of attribute overrides (to customize the type of the attribute
358      *          descriptors).
359      * @param requiredAttributes Set of attributes to be marked as required.
360      * @param existingElementDescs A map of already created element descriptors, keyed by
361      *          XML local name. This is used to use the static elements created initially by this
362      *          class, which are referenced directly by editors (so that reloading an SDK won't
363      *          break these references).
364      * @param elemDesc The current {@link ElementDescriptor} to inflate.
365      * @param styleName The name of the {@link ElementDescriptor} to inflate. Its XML local name
366      *          will be guessed automatically from the style name.
367      */
inflateElement( Map<String, DeclareStyleableInfo> styleMap, Map<String, Object> overrides, Set<String> requiredAttributes, HashMap<String, ElementDescriptor> existingElementDescs, ElementDescriptor elemDesc, String styleName)368     private void inflateElement(
369             Map<String, DeclareStyleableInfo> styleMap,
370             Map<String, Object> overrides,
371             Set<String> requiredAttributes,
372             HashMap<String, ElementDescriptor> existingElementDescs,
373             ElementDescriptor elemDesc,
374             String styleName) {
375         assert elemDesc != null;
376         assert styleName != null;
377         assert styleMap != null;
378 
379         // define attributes
380         DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null;
381         if (style != null) {
382             ArrayList<AttributeDescriptor> attrDescs = new ArrayList<AttributeDescriptor>();
383             DescriptorsUtils.appendAttributes(attrDescs,
384                     elemDesc.getXmlLocalName(),
385                     SdkConstants.NS_RESOURCES,
386                     style.getAttributes(),
387                     requiredAttributes,
388                     overrides);
389             elemDesc.setTooltip(style.getJavaDoc());
390             elemDesc.setAttributes(attrDescs.toArray(new AttributeDescriptor[attrDescs.size()]));
391         }
392 
393         // find all elements that have this one as parent
394         ArrayList<ElementDescriptor> children = new ArrayList<ElementDescriptor>();
395         for (Entry<String, DeclareStyleableInfo> entry : styleMap.entrySet()) {
396             DeclareStyleableInfo childStyle = entry.getValue();
397             boolean isParent = false;
398             String[] parents = childStyle.getParents();
399             if (parents != null) {
400                 for (String parent: parents) {
401                     if (styleName.equals(parent)) {
402                         isParent = true;
403                         break;
404                     }
405                 }
406             }
407             if (isParent) {
408                 String childStyleName = entry.getKey();
409                 String childXmlName = guessXmlName(childStyleName);
410 
411                 // create or re-use element
412                 ElementDescriptor child = existingElementDescs.get(childXmlName);
413                 if (child == null) {
414                     child = createElement(childXmlName);
415                     existingElementDescs.put(childXmlName, child);
416                 }
417                 children.add(child);
418 
419                 inflateElement(styleMap,
420                         overrides,
421                         requiredAttributes,
422                         existingElementDescs,
423                         child,
424                         childStyleName);
425             }
426         }
427         elemDesc.setChildren(children.toArray(new ElementDescriptor[children.size()]));
428     }
429 
430     /**
431      * Get an UI name from the element XML name.
432      * <p/>
433      * Capitalizes the first letter and replace non-alphabet by a space followed by a capital.
434      */
getUiName(String xmlName)435     private String getUiName(String xmlName) {
436         StringBuilder sb = new StringBuilder();
437 
438         boolean capitalize = true;
439         for (char c : xmlName.toCharArray()) {
440             if (capitalize && c >= 'a' && c <= 'z') {
441                 sb.append((char)(c + 'A' - 'a'));
442                 capitalize = false;
443             } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
444                 sb.append(' ');
445                 capitalize = true;
446             } else {
447                 sb.append(c);
448             }
449         }
450 
451         return sb.toString();
452     }
453 
454     /**
455      * Guesses the style name for a given XML element name.
456      * <p/>
457      * The rules are:
458      * - capitalize the first letter:
459      * - if there's a dash, skip it and capitalize the next one
460      * - prefix AndroidManifest
461      * The exception is "manifest" which just becomes AndroidManifest.
462      * <p/>
463      * Examples:
464      * - manifest        => AndroidManifest
465      * - application     => AndroidManifestApplication
466      * - uses-permission => AndroidManifestUsesPermission
467      */
guessStyleName(String xmlName)468     private String guessStyleName(String xmlName) {
469         StringBuilder sb = new StringBuilder();
470 
471         if (!xmlName.equals(MANIFEST_NODE_NAME)) {
472             boolean capitalize = true;
473             for (char c : xmlName.toCharArray()) {
474                 if (capitalize && c >= 'a' && c <= 'z') {
475                     sb.append((char)(c + 'A' - 'a'));
476                     capitalize = false;
477                 } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
478                     // not a letter -- skip the character and capitalize the next one
479                     capitalize = true;
480                 } else {
481                     sb.append(c);
482                 }
483             }
484         }
485 
486         sb.insert(0, ANDROID_MANIFEST_STYLEABLE);
487         return sb.toString();
488     }
489 
490     /**
491      * This method performs a sanity check to make sure all the styles declared in the
492      * manifestMap are actually defined in the actual element descriptors and reachable from
493      * the manifestElement root node.
494      */
sanityCheck(Map<String, DeclareStyleableInfo> manifestMap, ElementDescriptor manifestElement)495     private void sanityCheck(Map<String, DeclareStyleableInfo> manifestMap,
496             ElementDescriptor manifestElement) {
497         TreeSet<String> elementsDeclared = new TreeSet<String>();
498         findAllElementNames(manifestElement, elementsDeclared);
499 
500         TreeSet<String> stylesDeclared = new TreeSet<String>();
501         for (String styleName : manifestMap.keySet()) {
502             if (styleName.startsWith(ANDROID_MANIFEST_STYLEABLE)) {
503                 stylesDeclared.add(styleName);
504             }
505         }
506 
507         for (Iterator<String> it = elementsDeclared.iterator(); it.hasNext();) {
508             String xmlName = it.next();
509             String styleName = guessStyleName(xmlName);
510             if (stylesDeclared.remove(styleName)) {
511                 it.remove();
512             }
513         }
514 
515         StringBuilder sb = new StringBuilder();
516         if (!stylesDeclared.isEmpty()) {
517             sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by the SDK but unknown to ADT: ");
518             for (String name : stylesDeclared) {
519                 name = guessXmlName(name);
520 
521                 if (name != stylesDeclared.last()) {
522                     sb.append(", ");    //$NON-NLS-1$
523                 }
524             }
525 
526             AdtPlugin.log(IStatus.WARNING, "%s", sb.toString());
527             AdtPlugin.printToConsole((String)null, sb);
528             sb.setLength(0);
529         }
530 
531         if (!elementsDeclared.isEmpty()) {
532             sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by ADT but not by the SDK: ");
533             for (String name : elementsDeclared) {
534                 sb.append(name);
535                 if (name != elementsDeclared.last()) {
536                     sb.append(", ");    //$NON-NLS-1$
537                 }
538             }
539 
540             AdtPlugin.log(IStatus.WARNING, "%s", sb.toString());
541             AdtPlugin.printToConsole((String)null, sb);
542         }
543     }
544 
545     /**
546      * Performs an approximate translation of the style name into a potential
547      * xml name. This is more or less the reverse from guessStyleName().
548      *
549      * @return The XML local name for a given style name.
550      */
guessXmlName(String name)551     private String guessXmlName(String name) {
552         StringBuilder sb = new StringBuilder();
553         if (ANDROID_MANIFEST_STYLEABLE.equals(name)) {
554             sb.append(MANIFEST_NODE_NAME);
555         } else {
556             name = name.replace(ANDROID_MANIFEST_STYLEABLE, "");    //$NON-NLS-1$
557             boolean first_char = true;
558             for (char c : name.toCharArray()) {
559                 if (c >= 'A' && c <= 'Z') {
560                     if (!first_char) {
561                         sb.append('-');
562                     }
563                     c = (char) (c - 'A' + 'a');
564                 }
565                 sb.append(c);
566                 first_char = false;
567             }
568         }
569         return sb.toString();
570     }
571 
572     /**
573      * Helper method used by {@link #sanityCheck(Map, ElementDescriptor)} to find all the
574      * {@link ElementDescriptor} names defined by the tree of descriptors.
575      * <p/>
576      * Note: this assumes no circular reference in the tree of {@link ElementDescriptor}s.
577      */
findAllElementNames(ElementDescriptor element, TreeSet<String> declared)578     private void findAllElementNames(ElementDescriptor element, TreeSet<String> declared) {
579         declared.add(element.getXmlName());
580         for (ElementDescriptor desc : element.getChildren()) {
581             findAllElementNames(desc, declared);
582         }
583     }
584 
585 
586 }
587