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.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 
     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
    126     public ElementDescriptor[] getRootElementDescriptors() {
    127         return new ElementDescriptor[] { MANIFEST_ELEMENT };
    128     }
    129 
    130     @Override
    131     public ElementDescriptor getDescriptor() {
    132         return getManifestElement();
    133     }
    134 
    135     public ElementDescriptor getApplicationElement() {
    136         return APPLICATION_ELEMENT;
    137     }
    138 
    139     public ElementDescriptor getManifestElement() {
    140         return MANIFEST_ELEMENT;
    141     }
    142 
    143     public ElementDescriptor getUsesSdkElement() {
    144         return USES_SDK_ELEMENT;
    145     }
    146 
    147     public ElementDescriptor getInstrumentationElement() {
    148         return INTRUMENTATION_ELEMENT;
    149     }
    150 
    151     public ElementDescriptor getPermissionElement() {
    152         return PERMISSION_ELEMENT;
    153     }
    154 
    155     public ElementDescriptor getUsesPermissionElement() {
    156         return USES_PERMISSION_ELEMENT;
    157     }
    158 
    159     public ElementDescriptor getPermissionGroupElement() {
    160         return PERMISSION_GROUP_ELEMENT;
    161     }
    162 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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