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