| /* |
| * 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; |
| } |
| } |