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