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