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