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