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.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      */
     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      */
    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      */
    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      */
    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      */
    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) */
    163     public Mandatory getMandatory() {
    164         return mMandatory;
    165     }
    166 
    167     @Override
    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      */
    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      */
    206     public String getXmlName() {
    207         return mXmlName;
    208     }
    209 
    210     /**
    211      * Returns the namespace of the attribute.
    212      */
    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. */
    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      */
    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      */
    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      */
    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. */
    295     public AttributeDescriptor[] getAttributes() {
    296         return mAttributes;
    297     }
    298 
    299     /** Sets the list of allowed 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 */
    308     public ElementDescriptor[] getChildren() {
    309         return mChildren;
    310     }
    311 
    312     /** @return True if this descriptor has children available */
    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      */
    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. */
    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      */
    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      */
    362     public String getTooltip() {
    363         return mTooltip;
    364     }
    365 
    366     /** Returns an optional SKD URL. Will be null if not present. */
    367     public String getSdkUrl() {
    368         return mSdkUrl;
    369     }
    370 
    371     /** Sets the optional tooltip. Can be null or empty. */
    372     public void setTooltip(String tooltip) {
    373         mTooltip = tooltip;
    374     }
    375 
    376     /** Sets the optional SDK URL. Can be null or empty. */
    377     public void setSdkUrl(String sdkUrl) {
    378         mSdkUrl = sdkUrl;
    379     }
    380 
    381     /**
    382      * @return A new {@link UiElementNode} linked to this descriptor.
    383      */
    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      */
    395     public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) {
    396         return findChildrenDescriptorInternal(element_name, recursive, null);
    397     }
    398 
    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      */
    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      */
    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
    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      */
    482     public boolean syncAttributes() {
    483         return true;
    484     }
    485 }
    486