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