/*
* 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.editors.descriptors;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.sdklib.SdkConstants;
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 {
/** The XML element node name. Case sensitive. */
private String mXmlName;
/** The XML element name for the user interface, typically capitalized. */
private 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 boolean mMandatory;
/**
* 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;
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, boolean 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 boolean isMandatory() {
return mMandatory;
}
/**
* 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. Case sensitive. */
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:")) { //$NON-NLs-1$
return SdkConstants.NS_RESOURCES;
}
return ""; //$NON-NLs-1$
}
/** Returns the XML element name for the user interface, typically capitalized. */
public String getUiName() {
return mUiName;
}
/**
* Returns an optional icon for the element.
* <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 or null.
*/
public Image getIcon() {
IconFactory factory = IconFactory.getInstance();
int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN;
int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE;
Image icon = factory.getIcon(mXmlName, color, shape);
return icon != null ? icon : AdtPlugin.getAndroidLogo();
}
/**
* 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;
}
/** 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$
}
}