• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.descriptors;
18 
19 import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
20 import static com.android.SdkConstants.ANDROID_URI;
21 
22 import com.android.ide.eclipse.adt.AdtPlugin;
23 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
25 
26 import org.eclipse.jface.resource.ImageDescriptor;
27 import org.eclipse.swt.graphics.Image;
28 
29 import java.util.Collection;
30 import java.util.HashSet;
31 import java.util.Set;
32 
33 /**
34  * {@link ElementDescriptor} describes the properties expected for a given XML element node.
35  *
36  * {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url,
37  * an attributes list and a children list.
38  *
39  * An UI node can be "mandatory", meaning the UI node is never deleted and it may lack
40  * an actual XML node attached. A non-mandatory UI node MUST have an XML node attached
41  * and it will cease to exist when the XML node ceases to exist.
42  */
43 public class ElementDescriptor implements Comparable<ElementDescriptor> {
44     private static final String ELEMENT_ICON_FILENAME = "element"; //$NON-NLS-1$
45 
46     /** The XML element node name. Case sensitive. */
47     protected final String mXmlName;
48     /** The XML element name for the user interface, typically capitalized. */
49     private final String mUiName;
50     /** The list of allowed attributes. */
51     private AttributeDescriptor[] mAttributes;
52     /** The list of allowed children */
53     private ElementDescriptor[] mChildren;
54     /* An optional tooltip. Can be empty. */
55     private String mTooltip;
56     /** An optional SKD URL. Can be empty. */
57     private String mSdkUrl;
58     /** Whether this UI node must always exist (even for empty models). */
59     private final Mandatory mMandatory;
60 
61     public enum Mandatory {
62         NOT_MANDATORY,
63         MANDATORY,
64         MANDATORY_LAST
65     }
66 
67     /**
68      * Constructs a new {@link ElementDescriptor} based on its XML name, UI name,
69      * tooltip, SDK url, attributes list, children list and mandatory.
70      *
71      * @param xml_name The XML element node name. Case sensitive.
72      * @param ui_name The XML element name for the user interface, typically capitalized.
73      * @param tooltip An optional tooltip. Can be null or empty.
74      * @param sdk_url An optional SKD URL. Can be null or empty.
75      * @param attributes The list of allowed attributes. Can be null or empty.
76      * @param children The list of allowed children. Can be null or empty.
77      * @param mandatory Whether this node must always exist (even for empty models). A mandatory
78      *  UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
79      *  UI node MUST have an XML node attached and it will cease to exist when the XML node
80      *  ceases to exist.
81      */
ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, AttributeDescriptor[] attributes, ElementDescriptor[] children, Mandatory mandatory)82     public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url,
83             AttributeDescriptor[] attributes,
84             ElementDescriptor[] children,
85             Mandatory mandatory) {
86         mMandatory = mandatory;
87         mXmlName = xml_name;
88         mUiName = ui_name;
89         mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null;
90         mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null;
91         setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{});
92         mChildren = children != null ? children : new ElementDescriptor[]{};
93     }
94 
95     /**
96      * Constructs a new {@link ElementDescriptor} based on its XML name, UI name,
97      * tooltip, SDK url, attributes list, children list and mandatory.
98      *
99      * @param xml_name The XML element node name. Case sensitive.
100      * @param ui_name The XML element name for the user interface, typically capitalized.
101      * @param tooltip An optional tooltip. Can be null or empty.
102      * @param sdk_url An optional SKD URL. Can be null or empty.
103      * @param attributes The list of allowed attributes. Can be null or empty.
104      * @param children The list of allowed children. Can be null or empty.
105      * @param mandatory Whether this node must always exist (even for empty models). A mandatory
106      *  UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
107      *  UI node MUST have an XML node attached and it will cease to exist when the XML node
108      *  ceases to exist.
109      */
ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, AttributeDescriptor[] attributes, ElementDescriptor[] children, boolean mandatory)110     public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url,
111             AttributeDescriptor[] attributes,
112             ElementDescriptor[] children,
113             boolean mandatory) {
114         mMandatory = mandatory ? Mandatory.MANDATORY : Mandatory.NOT_MANDATORY;
115         mXmlName = xml_name;
116         mUiName = ui_name;
117         mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null;
118         mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null;
119         setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{});
120         mChildren = children != null ? children : new ElementDescriptor[]{};
121     }
122 
123     /**
124      * Constructs a new {@link ElementDescriptor} based on its XML name and children list.
125      * The UI name is build by capitalizing the XML name.
126      * The UI nodes will be non-mandatory.
127      *
128      * @param xml_name The XML element node name. Case sensitive.
129      * @param children The list of allowed children. Can be null or empty.
130      * @param mandatory Whether this node must always exist (even for empty models). A mandatory
131      *  UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
132      *  UI node MUST have an XML node attached and it will cease to exist when the XML node
133      *  ceases to exist.
134      */
ElementDescriptor(String xml_name, ElementDescriptor[] children, Mandatory mandatory)135     public ElementDescriptor(String xml_name, ElementDescriptor[] children, Mandatory mandatory) {
136         this(xml_name, prettyName(xml_name), null, null, null, children, mandatory);
137     }
138 
139     /**
140      * Constructs a new {@link ElementDescriptor} based on its XML name and children list.
141      * The UI name is build by capitalizing the XML name.
142      * The UI nodes will be non-mandatory.
143      *
144      * @param xml_name The XML element node name. Case sensitive.
145      * @param children The list of allowed children. Can be null or empty.
146      */
ElementDescriptor(String xml_name, ElementDescriptor[] children)147     public ElementDescriptor(String xml_name, ElementDescriptor[] children) {
148         this(xml_name, prettyName(xml_name), null, null, null, children, false);
149     }
150 
151     /**
152      * Constructs a new {@link ElementDescriptor} based on its XML name.
153      * The UI name is build by capitalizing the XML name.
154      * The UI nodes will be non-mandatory.
155      *
156      * @param xml_name The XML element node name. Case sensitive.
157      */
ElementDescriptor(String xml_name)158     public ElementDescriptor(String xml_name) {
159         this(xml_name, prettyName(xml_name), null, null, null, null, false);
160     }
161 
162     /** Returns whether this node must always exist (even for empty models) */
getMandatory()163     public Mandatory getMandatory() {
164         return mMandatory;
165     }
166 
167     @Override
toString()168     public String toString() {
169         return String.format("%s [%s, attr %d, children %d%s]",    //$NON-NLS-1$
170                 this.getClass().getSimpleName(),
171                 mXmlName,
172                 mAttributes != null ? mAttributes.length : 0,
173                 mChildren != null ? mChildren.length : 0,
174                 mMandatory != Mandatory.NOT_MANDATORY ? ", " + mMandatory.toString() : "" //$NON-NLS-1$ //$NON-NLS-2$
175                 );
176     }
177 
178     /**
179      * Returns the XML element node local name (case sensitive)
180      */
getXmlLocalName()181     public final String getXmlLocalName() {
182         int pos = mXmlName.indexOf(':');
183         if (pos != -1) {
184             return mXmlName.substring(pos+1);
185         }
186         return mXmlName;
187     }
188 
189     /**
190      * Returns the XML element node name, including the prefix.
191      * Case sensitive.
192      * <p/>
193      * In Android resources, the element node name for Android resources typically does not
194      * have a prefix and is typically the simple Java class name (e.g. "View"), whereas for
195      * custom views it is generally the fully qualified class name of the view (e.g.
196      * "com.mycompany.myapp.MyView").
197      * <p/>
198      * Most of the time you'll probably want to use {@link #getXmlLocalName()} to get a local
199      * name guaranteed without a prefix.
200      * <p/>
201      * Note that the prefix that <em>may</em> be available in this descriptor has nothing to
202      * do with the actual prefix the node might have (or needs to have) in the actual XML file
203      * since descriptors are fixed and do not depend on any current namespace defined in the
204      * target XML.
205      */
getXmlName()206     public String getXmlName() {
207         return mXmlName;
208     }
209 
210     /**
211      * Returns the namespace of the attribute.
212      */
getNamespace()213     public final String getNamespace() {
214         // For now we hard-code the prefix as being "android"
215         if (mXmlName.startsWith(ANDROID_NS_NAME_PREFIX)) {
216             return ANDROID_URI;
217         }
218 
219         return ""; //$NON-NLs-1$
220     }
221 
222 
223     /** Returns the XML element name for the user interface, typically capitalized. */
getUiName()224     public String getUiName() {
225         return mUiName;
226     }
227 
228     /**
229      * Returns an icon for the element.
230      * This icon is generic, that is all element descriptors have the same icon
231      * no matter what they represent.
232      *
233      * @return An icon for this element or null.
234      * @see #getCustomizedIcon()
235      */
getGenericIcon()236     public Image getGenericIcon() {
237         return IconFactory.getInstance().getIcon(ELEMENT_ICON_FILENAME);
238     }
239 
240     /**
241      * Returns an optional icon for the element, typically to be used in XML form trees.
242      * <p/>
243      * This icon is customized to the given descriptor, that is different elements
244      * will have different icons.
245      * <p/>
246      * By default this tries to return an icon based on the XML name of the element.
247      * If this fails, it tries to return the default Android logo as defined in the
248      * plugin. If all fails, it returns null.
249      *
250      * @return An icon for this element. This is never null.
251      */
getCustomizedIcon()252     public Image getCustomizedIcon() {
253         IconFactory factory = IconFactory.getInstance();
254         int color = hasChildren() ? IconFactory.COLOR_BLUE
255                 : IconFactory.COLOR_GREEN;
256         int shape = hasChildren() ? IconFactory.SHAPE_RECT
257                 : IconFactory.SHAPE_CIRCLE;
258         String name = mXmlName;
259 
260         int pos = name.lastIndexOf('.');
261         if (pos != -1) {
262             // If the user uses a fully qualified name, such as
263             // "android.gesture.GestureOverlayView" in their XML, we need to
264             // look up only by basename
265             name = name.substring(pos + 1);
266         }
267         Image icon = factory.getIcon(name, color, shape);
268         if (icon == null) {
269             icon = getGenericIcon();
270         }
271         if (icon == null) {
272             icon = AdtPlugin.getAndroidLogo();
273         }
274         return icon;
275     }
276 
277     /**
278      * Returns an optional ImageDescriptor for the element.
279      * <p/>
280      * By default this tries to return an image based on the XML name of the element.
281      * If this fails, it tries to return the default Android logo as defined in the
282      * plugin. If all fails, it returns null.
283      *
284      * @return An ImageDescriptor for this element or null.
285      */
getImageDescriptor()286     public ImageDescriptor getImageDescriptor() {
287         IconFactory factory = IconFactory.getInstance();
288         int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN;
289         int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE;
290         ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape);
291         return id != null ? id : AdtPlugin.getAndroidLogoDesc();
292     }
293 
294     /* Returns the list of allowed attributes. */
getAttributes()295     public AttributeDescriptor[] getAttributes() {
296         return mAttributes;
297     }
298 
299     /** Sets the list of allowed attributes. */
setAttributes(AttributeDescriptor[] attributes)300     public void setAttributes(AttributeDescriptor[] attributes) {
301         mAttributes = attributes;
302         for (AttributeDescriptor attribute : attributes) {
303             attribute.setParent(this);
304         }
305     }
306 
307     /** Returns the list of allowed children */
getChildren()308     public ElementDescriptor[] getChildren() {
309         return mChildren;
310     }
311 
312     /** @return True if this descriptor has children available */
hasChildren()313     public boolean hasChildren() {
314         return mChildren.length > 0;
315     }
316 
317     /**
318      * Checks whether this descriptor can accept the given descriptor type
319      * as a direct child.
320      *
321      * @return True if this descriptor can accept children of the given descriptor type.
322      *   False if not accepted, no children allowed, or target is null.
323      */
acceptChild(ElementDescriptor target)324     public boolean acceptChild(ElementDescriptor target) {
325         if (target != null && mChildren.length > 0) {
326             String targetXmlName = target.getXmlName();
327             for (ElementDescriptor child : mChildren) {
328                 if (child.getXmlName().equals(targetXmlName)) {
329                     return true;
330                 }
331             }
332         }
333 
334         return false;
335     }
336 
337     /** Sets the list of allowed children. */
setChildren(ElementDescriptor[] newChildren)338     public void setChildren(ElementDescriptor[] newChildren) {
339         mChildren = newChildren;
340     }
341 
342     /**
343      * Sets the list of allowed children.
344      * <p/>
345      * This is just a convenience method that converts a Collection into an array and
346      * calls {@link #setChildren(ElementDescriptor[])}.
347      * <p/>
348      * This means a <em>copy</em> of the collection is made. The collection is not
349      * stored by the recipient and can thus be altered by the caller.
350      */
setChildren(Collection<ElementDescriptor> newChildren)351     public void setChildren(Collection<ElementDescriptor> newChildren) {
352         setChildren(newChildren.toArray(new ElementDescriptor[newChildren.size()]));
353     }
354 
355     /**
356      * Returns an optional tooltip. Will be null if not present.
357      * <p/>
358      * The tooltip is based on the Javadoc of the element and already processed via
359      * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as
360      * a UI tooltip.
361      */
getTooltip()362     public String getTooltip() {
363         return mTooltip;
364     }
365 
366     /** Returns an optional SKD URL. Will be null if not present. */
getSdkUrl()367     public String getSdkUrl() {
368         return mSdkUrl;
369     }
370 
371     /** Sets the optional tooltip. Can be null or empty. */
setTooltip(String tooltip)372     public void setTooltip(String tooltip) {
373         mTooltip = tooltip;
374     }
375 
376     /** Sets the optional SDK URL. Can be null or empty. */
setSdkUrl(String sdkUrl)377     public void setSdkUrl(String sdkUrl) {
378         mSdkUrl = sdkUrl;
379     }
380 
381     /**
382      * @return A new {@link UiElementNode} linked to this descriptor.
383      */
createUiNode()384     public UiElementNode createUiNode() {
385         return new UiElementNode(this);
386     }
387 
388     /**
389      * Returns the first children of this descriptor that describes the given XML element name.
390      * <p/>
391      * In recursive mode, searches the direct children first before descending in the hierarchy.
392      *
393      * @return The ElementDescriptor matching the requested XML node element name or null.
394      */
findChildrenDescriptor(String element_name, boolean recursive)395     public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) {
396         return findChildrenDescriptorInternal(element_name, recursive, null);
397     }
398 
findChildrenDescriptorInternal(String element_name, boolean recursive, Set<ElementDescriptor> visited)399     private ElementDescriptor findChildrenDescriptorInternal(String element_name,
400             boolean recursive,
401             Set<ElementDescriptor> visited) {
402         if (recursive && visited == null) {
403             visited = new HashSet<ElementDescriptor>();
404         }
405 
406         for (ElementDescriptor e : getChildren()) {
407             if (e.getXmlName().equals(element_name)) {
408                 return e;
409             }
410         }
411 
412         if (visited != null) {
413             visited.add(this);
414         }
415 
416         if (recursive) {
417             for (ElementDescriptor e : getChildren()) {
418                 if (visited != null) {
419                     if (!visited.add(e)) {  // Set.add() returns false if element is already present
420                         continue;
421                     }
422                 }
423                 ElementDescriptor f = e.findChildrenDescriptorInternal(element_name,
424                         recursive, visited);
425                 if (f != null) {
426                     return f;
427                 }
428             }
429         }
430 
431         return null;
432     }
433 
434     /**
435      * Utility helper than pretty-formats an XML Name for the UI.
436      * This is used by the simplified constructor that takes only an XML element name.
437      *
438      * @param xml_name The XML name to convert.
439      * @return The XML name with dashes replaced by spaces and capitalized.
440      */
prettyName(String xml_name)441     private static String prettyName(String xml_name) {
442         char c[] = xml_name.toCharArray();
443         if (c.length > 0) {
444             c[0] = Character.toUpperCase(c[0]);
445         }
446         return new String(c).replace("-", " ");  //$NON-NLS-1$  //$NON-NLS-2$
447     }
448 
449     /**
450      * Returns true if this node defines the given attribute
451      *
452      * @param namespaceUri the namespace URI of the target attribute
453      * @param attributeName the attribute name
454      * @return true if this element defines an attribute of the given name and namespace
455      */
definesAttribute(String namespaceUri, String attributeName)456     public boolean definesAttribute(String namespaceUri, String attributeName) {
457         for (AttributeDescriptor desc : mAttributes) {
458             if (desc.getXmlLocalName().equals(attributeName) &&
459                     desc.getNamespaceUri().equals(namespaceUri)) {
460                 return true;
461             }
462         }
463 
464         return false;
465     }
466 
467     // Implements Comparable<ElementDescriptor>:
468     @Override
compareTo(ElementDescriptor o)469     public int compareTo(ElementDescriptor o) {
470         return mUiName.compareToIgnoreCase(o.mUiName);
471     }
472 
473     /**
474      * Ensures that this view descriptor's attribute list is up to date. This is
475      * always the case for all the builtin descriptors, but for example for a
476      * custom view, it could be changing dynamically so caches may have to be
477      * recomputed. This method will return true if nothing changed, and false if
478      * it recomputed its info.
479      *
480      * @return true if the attributes are already up to date and nothing changed
481      */
syncAttributes()482     public boolean syncAttributes() {
483         return true;
484     }
485 }
486