diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java new file mode 100644 index 000000000..0d62ec00c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.descriptors; + +import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.SdkConstants.ANDROID_URI; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * {@link ElementDescriptor} describes the properties expected for a given XML element node. + * + * {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url, + * an attributes list and a children list. + * + * An UI node can be "mandatory", meaning the UI node is never deleted and it may lack + * an actual XML node attached. A non-mandatory UI node MUST have an XML node attached + * and it will cease to exist when the XML node ceases to exist. + */ +public class ElementDescriptor implements Comparable<ElementDescriptor> { + private static final String ELEMENT_ICON_FILENAME = "element"; //$NON-NLS-1$ + + /** The XML element node name. Case sensitive. */ + protected final String mXmlName; + /** The XML element name for the user interface, typically capitalized. */ + private final String mUiName; + /** The list of allowed attributes. */ + private AttributeDescriptor[] mAttributes; + /** The list of allowed children */ + private ElementDescriptor[] mChildren; + /* An optional tooltip. Can be empty. */ + private String mTooltip; + /** An optional SKD URL. Can be empty. */ + private String mSdkUrl; + /** Whether this UI node must always exist (even for empty models). */ + private final Mandatory mMandatory; + + public enum Mandatory { + NOT_MANDATORY, + MANDATORY, + MANDATORY_LAST + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name, UI name, + * tooltip, SDK url, attributes list, children list and mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + AttributeDescriptor[] attributes, + ElementDescriptor[] children, + Mandatory mandatory) { + mMandatory = mandatory; + mXmlName = xml_name; + mUiName = ui_name; + mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; + mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null; + setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{}); + mChildren = children != null ? children : new ElementDescriptor[]{}; + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name, UI name, + * tooltip, SDK url, attributes list, children list and mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + AttributeDescriptor[] attributes, + ElementDescriptor[] children, + boolean mandatory) { + mMandatory = mandatory ? Mandatory.MANDATORY : Mandatory.NOT_MANDATORY; + mXmlName = xml_name; + mUiName = ui_name; + mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; + mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null; + setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{}); + mChildren = children != null ? children : new ElementDescriptor[]{}; + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name and children list. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ElementDescriptor(String xml_name, ElementDescriptor[] children, Mandatory mandatory) { + this(xml_name, prettyName(xml_name), null, null, null, children, mandatory); + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name and children list. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param children The list of allowed children. Can be null or empty. + */ + public ElementDescriptor(String xml_name, ElementDescriptor[] children) { + this(xml_name, prettyName(xml_name), null, null, null, children, false); + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + */ + public ElementDescriptor(String xml_name) { + this(xml_name, prettyName(xml_name), null, null, null, null, false); + } + + /** Returns whether this node must always exist (even for empty models) */ + public Mandatory getMandatory() { + return mMandatory; + } + + @Override + public String toString() { + return String.format("%s [%s, attr %d, children %d%s]", //$NON-NLS-1$ + this.getClass().getSimpleName(), + mXmlName, + mAttributes != null ? mAttributes.length : 0, + mChildren != null ? mChildren.length : 0, + mMandatory != Mandatory.NOT_MANDATORY ? ", " + mMandatory.toString() : "" //$NON-NLS-1$ //$NON-NLS-2$ + ); + } + + /** + * Returns the XML element node local name (case sensitive) + */ + public final String getXmlLocalName() { + int pos = mXmlName.indexOf(':'); + if (pos != -1) { + return mXmlName.substring(pos+1); + } + return mXmlName; + } + + /** + * Returns the XML element node name, including the prefix. + * Case sensitive. + * <p/> + * In Android resources, the element node name for Android resources typically does not + * have a prefix and is typically the simple Java class name (e.g. "View"), whereas for + * custom views it is generally the fully qualified class name of the view (e.g. + * "com.mycompany.myapp.MyView"). + * <p/> + * Most of the time you'll probably want to use {@link #getXmlLocalName()} to get a local + * name guaranteed without a prefix. + * <p/> + * Note that the prefix that <em>may</em> be available in this descriptor has nothing to + * do with the actual prefix the node might have (or needs to have) in the actual XML file + * since descriptors are fixed and do not depend on any current namespace defined in the + * target XML. + */ + public String getXmlName() { + return mXmlName; + } + + /** + * Returns the namespace of the attribute. + */ + public final String getNamespace() { + // For now we hard-code the prefix as being "android" + if (mXmlName.startsWith(ANDROID_NS_NAME_PREFIX)) { + return ANDROID_URI; + } + + return ""; //$NON-NLs-1$ + } + + + /** Returns the XML element name for the user interface, typically capitalized. */ + public String getUiName() { + return mUiName; + } + + /** + * Returns an icon for the element. + * This icon is generic, that is all element descriptors have the same icon + * no matter what they represent. + * + * @return An icon for this element or null. + * @see #getCustomizedIcon() + */ + public Image getGenericIcon() { + return IconFactory.getInstance().getIcon(ELEMENT_ICON_FILENAME); + } + + /** + * Returns an optional icon for the element, typically to be used in XML form trees. + * <p/> + * This icon is customized to the given descriptor, that is different elements + * will have different icons. + * <p/> + * By default this tries to return an icon based on the XML name of the element. + * If this fails, it tries to return the default Android logo as defined in the + * plugin. If all fails, it returns null. + * + * @return An icon for this element. This is never null. + */ + public Image getCustomizedIcon() { + IconFactory factory = IconFactory.getInstance(); + int color = hasChildren() ? IconFactory.COLOR_BLUE + : IconFactory.COLOR_GREEN; + int shape = hasChildren() ? IconFactory.SHAPE_RECT + : IconFactory.SHAPE_CIRCLE; + String name = mXmlName; + + int pos = name.lastIndexOf('.'); + if (pos != -1) { + // If the user uses a fully qualified name, such as + // "android.gesture.GestureOverlayView" in their XML, we need to + // look up only by basename + name = name.substring(pos + 1); + } + Image icon = factory.getIcon(name, color, shape); + if (icon == null) { + icon = getGenericIcon(); + } + if (icon == null) { + icon = AdtPlugin.getAndroidLogo(); + } + return icon; + } + + /** + * Returns an optional ImageDescriptor for the element. + * <p/> + * By default this tries to return an image based on the XML name of the element. + * If this fails, it tries to return the default Android logo as defined in the + * plugin. If all fails, it returns null. + * + * @return An ImageDescriptor for this element or null. + */ + public ImageDescriptor getImageDescriptor() { + IconFactory factory = IconFactory.getInstance(); + int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; + int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; + ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape); + return id != null ? id : AdtPlugin.getAndroidLogoDesc(); + } + + /* Returns the list of allowed attributes. */ + public AttributeDescriptor[] getAttributes() { + return mAttributes; + } + + /** Sets the list of allowed attributes. */ + public void setAttributes(AttributeDescriptor[] attributes) { + mAttributes = attributes; + for (AttributeDescriptor attribute : attributes) { + attribute.setParent(this); + } + } + + /** Returns the list of allowed children */ + public ElementDescriptor[] getChildren() { + return mChildren; + } + + /** @return True if this descriptor has children available */ + public boolean hasChildren() { + return mChildren.length > 0; + } + + /** + * Checks whether this descriptor can accept the given descriptor type + * as a direct child. + * + * @return True if this descriptor can accept children of the given descriptor type. + * False if not accepted, no children allowed, or target is null. + */ + public boolean acceptChild(ElementDescriptor target) { + if (target != null && mChildren.length > 0) { + String targetXmlName = target.getXmlName(); + for (ElementDescriptor child : mChildren) { + if (child.getXmlName().equals(targetXmlName)) { + return true; + } + } + } + + return false; + } + + /** Sets the list of allowed children. */ + public void setChildren(ElementDescriptor[] newChildren) { + mChildren = newChildren; + } + + /** + * Sets the list of allowed children. + * <p/> + * This is just a convenience method that converts a Collection into an array and + * calls {@link #setChildren(ElementDescriptor[])}. + * <p/> + * This means a <em>copy</em> of the collection is made. The collection is not + * stored by the recipient and can thus be altered by the caller. + */ + public void setChildren(Collection<ElementDescriptor> newChildren) { + setChildren(newChildren.toArray(new ElementDescriptor[newChildren.size()])); + } + + /** + * Returns an optional tooltip. Will be null if not present. + * <p/> + * The tooltip is based on the Javadoc of the element and already processed via + * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as + * a UI tooltip. + */ + public String getTooltip() { + return mTooltip; + } + + /** Returns an optional SKD URL. Will be null if not present. */ + public String getSdkUrl() { + return mSdkUrl; + } + + /** Sets the optional tooltip. Can be null or empty. */ + public void setTooltip(String tooltip) { + mTooltip = tooltip; + } + + /** Sets the optional SDK URL. Can be null or empty. */ + public void setSdkUrl(String sdkUrl) { + mSdkUrl = sdkUrl; + } + + /** + * @return A new {@link UiElementNode} linked to this descriptor. + */ + public UiElementNode createUiNode() { + return new UiElementNode(this); + } + + /** + * Returns the first children of this descriptor that describes the given XML element name. + * <p/> + * In recursive mode, searches the direct children first before descending in the hierarchy. + * + * @return The ElementDescriptor matching the requested XML node element name or null. + */ + public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) { + return findChildrenDescriptorInternal(element_name, recursive, null); + } + + private ElementDescriptor findChildrenDescriptorInternal(String element_name, + boolean recursive, + Set<ElementDescriptor> visited) { + if (recursive && visited == null) { + visited = new HashSet<ElementDescriptor>(); + } + + for (ElementDescriptor e : getChildren()) { + if (e.getXmlName().equals(element_name)) { + return e; + } + } + + if (visited != null) { + visited.add(this); + } + + if (recursive) { + for (ElementDescriptor e : getChildren()) { + if (visited != null) { + if (!visited.add(e)) { // Set.add() returns false if element is already present + continue; + } + } + ElementDescriptor f = e.findChildrenDescriptorInternal(element_name, + recursive, visited); + if (f != null) { + return f; + } + } + } + + return null; + } + + /** + * Utility helper than pretty-formats an XML Name for the UI. + * This is used by the simplified constructor that takes only an XML element name. + * + * @param xml_name The XML name to convert. + * @return The XML name with dashes replaced by spaces and capitalized. + */ + private static String prettyName(String xml_name) { + char c[] = xml_name.toCharArray(); + if (c.length > 0) { + c[0] = Character.toUpperCase(c[0]); + } + return new String(c).replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Returns true if this node defines the given attribute + * + * @param namespaceUri the namespace URI of the target attribute + * @param attributeName the attribute name + * @return true if this element defines an attribute of the given name and namespace + */ + public boolean definesAttribute(String namespaceUri, String attributeName) { + for (AttributeDescriptor desc : mAttributes) { + if (desc.getXmlLocalName().equals(attributeName) && + desc.getNamespaceUri().equals(namespaceUri)) { + return true; + } + } + + return false; + } + + // Implements Comparable<ElementDescriptor>: + @Override + public int compareTo(ElementDescriptor o) { + return mUiName.compareToIgnoreCase(o.mUiName); + } + + /** + * Ensures that this view descriptor's attribute list is up to date. This is + * always the case for all the builtin descriptors, but for example for a + * custom view, it could be changing dynamically so caches may have to be + * recomputed. This method will return true if nothing changed, and false if + * it recomputed its info. + * + * @return true if the attributes are already up to date and nothing changed + */ + public boolean syncAttributes() { + return true; + } +} |