FileDocCategorySizeDatePackage
UiElementNode.javaAPI DocAndroid 1.5 API61530Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.editors.uimodel

UiElementNode

public class UiElementNode extends Object implements org.eclipse.ui.views.properties.IPropertySource
Represents an XML node that can be modified by the user interface in the XML editor.

Each tree viewer used in the application page's parts needs to keep a model representing each underlying node in the tree. This interface represents the base type for such a node.

Each node acts as an intermediary model between the actual XML model (the real data support) and the tree viewers or the corresponding page parts.

Element nodes don't contain data per se. Their data is contained in their attributes as well as their children's attributes, see {@link UiAttributeNode}.

The structure of a given {@link UiElementNode} is declared by a corresponding {@link ElementDescriptor}.

The class implements {@link IPropertySource}, in order to fill the Eclipse property tab when an element is selected. The {@link AttributeDescriptor} are used property descriptors.

Fields Summary
private static String[]
ID_PREFIXES
List of prefixes removed from android:id strings when creating short descriptions.
private com.android.ide.eclipse.editors.descriptors.ElementDescriptor
mDescriptor
The element descriptor for the node. Always present, never null.
private UiElementNode
mUiParent
The parent element node in the UI model. It is null for a root element or until the node is attached to its parent.
private com.android.ide.eclipse.editors.AndroidEditor
mEditor
The {@link AndroidEditor} handling the UI hierarchy. This is defined only for the root node. All children have the value set to null and query their parent.
private Document
mXmlDocument
The XML {@link Document} model that is being mirror by the UI model. This is defined only for the root node. All children have the value set to null and query their parent.
private Node
mXmlNode
The XML {@link Node} mirror by this UI node. This can be null for mandatory UI node which have no corresponding XML node or for new UI nodes before their XML node is set.
private ArrayList
mUiChildren
The list of all UI children nodes. Can be empty but never null. There's one UI children node per existing XML children node.
private HashMap
mUiAttributes
The list of all UI attributes, as declared in the {@link ElementDescriptor}. The list is always defined and never null. Unlike the UiElementNode children list, this is always defined, even for attributes that do not exist in the XML model -- that's because "missing" attributes in the XML model simply mean a default value is used. Also note that the underlying collection is a map, so order is not respected. To get the desired attribute order, iterate through the {@link ElementDescriptor}'s attribute list.
private HashSet
mUnknownUiAttributes
private List
mReadOnlyUiChildren
A read-only view of the UI children node collection.
private Collection
mReadOnlyUiAttributes
A read-only view of the UI attributes collection.
private Map
mCachedHiddenAttributes
A map of hidden attribute descriptors. Key is the XML name.
private ArrayList
mUiUpdateListeners
An optional list of {@link IUiUpdateListener}. Most element nodes will not have any listeners attached, so the list is only created on demand and can be null.
private boolean
mHasError
Error Flag
private Object
mEditData
Temporary data used by the editors. This data is not sync'ed with the XML
Constructors Summary
public UiElementNode(com.android.ide.eclipse.editors.descriptors.ElementDescriptor elementDescriptor)
Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}.

param
elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null.

    
                                 
       
        mDescriptor = elementDescriptor;
        clearContent();
    
Methods Summary
public voidaddUpdateListener(IUiUpdateListener listener)
Adds a new {@link IUiUpdateListener} to the internal update listener list.

       if (mUiUpdateListeners == null) {
           mUiUpdateListeners = new ArrayList<IUiUpdateListener>();
       }
       if (!mUiUpdateListeners.contains(listener)) {
           mUiUpdateListeners.add(listener);
       }
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodeappendNewUiChild(com.android.ide.eclipse.editors.descriptors.ElementDescriptor descriptor)
Creates a new {@link UiElementNode} from the given {@link ElementDescriptor} and appends it to the end of the element children list.

param
descriptor The {@link ElementDescriptor} that knows how to create the UI node.
return
The new UI node that has been appended

        UiElementNode ui_node;
        ui_node = descriptor.createUiNode();
        mUiChildren.add(ui_node);
        ui_node.setUiParent(this);
        ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED);
        return ui_node;
    
private voidclearAttributes()
Clears the internal list of attributes, the read-only cached version of it and the read-only cached hidden attribute list.

        mUiAttributes = null;
        mReadOnlyUiAttributes = null;
        mCachedHiddenAttributes = null;
        mUnknownUiAttributes = new HashSet<UiAttributeNode>();
    
voidclearContent()
Clears the {@link UiElementNode} by resetting the children list and the {@link UiAttributeNode}s list. Also resets the attached XML node, document, editor if any.

The parent {@link UiElementNode} node is not reset so that it's position in the hierarchy be left intact, if any.

        mXmlNode = null;
        mXmlDocument = null;
        mEditor = null;
        clearAttributes();
        mReadOnlyUiChildren = null;
        if (mUiChildren == null) {
            mUiChildren = new ArrayList<UiElementNode>();
        } else {
            // We can't remove mandatory nodes, we just clear them.
            for (int i = mUiChildren.size() - 1; i >= 0; --i) {
                removeUiChildAtIndex(i);
            }
        }
    
public voidcommit()
Commits the attributes (all internal, inherited from UI parent & unknown attributes). This is called by the UI when the embedding part needs to be committed.

        for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) {
            ui_attr.commit();
        }
        
        for (UiAttributeNode ui_attr : mUnknownUiAttributes) {
            ui_attr.commit();
        }
    
public booleancommitAttributeToXml(UiAttributeNode uiAttr, java.lang.String newValue)
Helper method to commit a single attribute value to XML.

This method updates the XML regardless of the current XML value. Callers should check first if an update is needed. If the new value is empty, the XML attribute will be actually removed.

Note that the caller MUST ensure that modifying the underlying XML model is safe and must take care of marking the model as dirty if necessary.

see
AndroidEditor#editXmlModel(Runnable)
param
uiAttr The attribute node to commit. Must be a child of this UiElementNode.
param
newValue The new value to set.
return
True if the XML attribute was modified or removed, false if nothing changed.

        // Get (or create) the underlying XML element node that contains the attributes.
        Node element = prepareCommit();
        if (element != null && uiAttr != null) {
            String attrLocalName = uiAttr.getDescriptor().getXmlLocalName();
            String attrNsUri = uiAttr.getDescriptor().getNamespaceUri();
            
            NamedNodeMap attrMap = element.getAttributes();
            if (newValue == null || newValue.length() == 0) {
                // Remove attribute if it's empty
                if (attrMap.getNamedItemNS(attrNsUri, attrLocalName) != null) {
                    attrMap.removeNamedItemNS(attrNsUri, attrLocalName);
                    return true;
                }
            } else {
                // Add or replace an attribute 
                Document doc = element.getOwnerDocument();
                if (doc != null) {
                    Attr attr = doc.createAttributeNS(attrNsUri, attrLocalName);
                    attr.setValue(newValue);
                    attr.setPrefix(lookupNamespacePrefix(element, attrNsUri));
                    attrMap.setNamedItemNS(attr);
                    return true;
                }
            }
        }
        return false;
    
public booleancommitDirtyAttributesToXml()
Helper method to commit all dirty attributes values to XML.

This method is useful if {@link #setAttributeValue(String, String, boolean)} has been called more than once and all the attributes marked as dirty must be commited to the XML. It calls {@link #commitAttributeToXml(UiAttributeNode, String)} on each dirty attribute.

Note that the caller MUST ensure that modifying the underlying XML model is safe and must take care of marking the model as dirty if necessary.

see
AndroidEditor#editXmlModel(Runnable)
return
True if one or more values were actually modified or removed, false if nothing changed.

        boolean result = false;
        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        
        for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
            UiAttributeNode ui_attr = entry.getValue();
            if (ui_attr.isDirty()) {
                result |= commitAttributeToXml(ui_attr, ui_attr.getCurrentValue());
                ui_attr.setDirty(false);
            }
        }
        return result;
    
public org.w3c.dom.NodecreateXmlNode()
Creates the underlying XML element node for this UI node if it doesn't already exists.

return
The new value of getXmlNode() (can be null if creation failed)

        if (mXmlNode != null) {
            return null;
        }
        Node parentXmlNode = null;
        if (mUiParent != null) {
            parentXmlNode = mUiParent.prepareCommit();
            if (parentXmlNode == null) {
                // The parent failed to create its own backing XML node. Abort.
                // No need to throw an exception, the parent will most likely
                // have done so itself.
                return null;
            }
        }

        String element_name = getDescriptor().getXmlName();
        Document doc = getXmlDocument();

        // We *must* have a root node. If not, we need to abort.
        if (doc == null) {
            throw new RuntimeException(
                    String.format("Missing XML document for %1$s XML node.", element_name));
        }

        // If we get here and parent_xml_node is null, the node is to be created
        // as the root node of the document (which can't be null, cf check above).
        if (parentXmlNode == null) {
            parentXmlNode = doc;
        }

        mXmlNode = doc.createElement(element_name);
        
        Node xmlNextSibling = null;

        UiElementNode uiNextSibling = getUiNextSibling();
        if (uiNextSibling != null) {
            xmlNextSibling = uiNextSibling.getXmlNode();
        }

        parentXmlNode.insertBefore(mXmlNode, xmlNextSibling);

        // Insert a separator after the tag, to make it easier to read
        Text sep = doc.createTextNode("\n");
        parentXmlNode.appendChild(sep);

        // Set all initial attributes in the XML node if they are not empty. 
        // Iterate on the descriptor list to get the desired order and then use the
        // internal values, if any.
        for (AttributeDescriptor attr_desc : getAttributeDescriptors()) {
            if (attr_desc instanceof XmlnsAttributeDescriptor) {
                XmlnsAttributeDescriptor desc = (XmlnsAttributeDescriptor) attr_desc;
                Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI,
                        desc.getXmlNsName());
                attr.setValue(desc.getValue());
                attr.setPrefix(desc.getXmlNsPrefix());
                mXmlNode.getAttributes().setNamedItemNS(attr);
            } else {
                UiAttributeNode ui_attr = getInternalUiAttributes().get(attr_desc);
                commitAttributeToXml(ui_attr, ui_attr.getCurrentValue());
            }
        }
        
        invokeUiUpdateListeners(UiUpdateState.CREATED);
        return mXmlNode;
    
public org.w3c.dom.NodedeleteXmlNode()
Removes the XML node corresponding to this UI node if it exists and also removes all mirrored information in this UI node (i.e. children, attributes)

return
The removed node or null if it didn't exist in the firtst place.

        if (mXmlNode == null) {
            return null;
        }

        // First clear the internals of the node and *then* actually deletes the XML
        // node (because doing so will generate an update even and this node may be
        // revisited via loadFromXmlNode).
        Node old_xml_node = mXmlNode;
        clearContent();
        
        Node xml_parent = old_xml_node.getParentNode();
        if (xml_parent == null) {
            xml_parent = getXmlDocument();
        }
        old_xml_node = xml_parent.removeChild(old_xml_node);

        invokeUiUpdateListeners(UiUpdateState.DELETED);
        return old_xml_node;
    
public UiAttributeNodefindUiAttribute(com.android.ide.eclipse.editors.descriptors.AttributeDescriptor attr_desc)
Returns the {@link UiAttributeNode} matching this attribute descriptor or null if not found.

param
attr_desc The {@link AttributeDescriptor} to match.
return
the {@link UiAttributeNode} matching this attribute descriptor or null if not found.

        return getInternalUiAttributes().get(attr_desc);
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodefindUiChildNode(java.lang.String path)
Finds a child node relative to this node using a path-like expression. F.ex. "node1/node2" would find a child "node1" that contains a child "node2" and returns the latter. If there are multiple nodes with the same name at the same level, always uses the first one found.

param
path The path like expression to select a child node.
return
The ui node found or null.

        String[] items = path.split("/");  //$NON-NLS-1$
        UiElementNode ui_node = this;
        for (String item : items) {
            boolean next_segment = false;
            for (UiElementNode c : ui_node.mUiChildren) {
                if (c.getDescriptor().getXmlName().equals(item)) {
                    ui_node = c;
                    next_segment = true;
                    break;
                }
            }
            if (!next_segment) {
                return null;
            }
        }
        return ui_node;
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodefindXmlNode(org.w3c.dom.Node xmlNode)
Finds an {@link UiElementNode} which contains the give XML {@link Node}. Looks recursively in all children UI nodes.

param
xmlNode The XML node to look for.
return
The {@link UiElementNode} that contains xmlNode or null if not found,

        if (xmlNode == null) {
            return null;
        }
        if (getXmlNode() == xmlNode) {
            return this;
        }
        
        for (UiElementNode uiChild : mUiChildren) {
            UiElementNode found = uiChild.findXmlNode(xmlNode);
            if (found != null) {
                return found;
            }
        }
        
        return null;
    
public com.android.ide.eclipse.adt.sdk.AndroidTargetDatagetAndroidTarget()
Returns the Android target data for the file being edited.

        return getEditor().getTargetData();
    
public com.android.ide.eclipse.editors.descriptors.AttributeDescriptor[]getAttributeDescriptors()
Returns the {@link AttributeDescriptor} array for the descriptor of this node.

Use this instead of getDescriptor().getAttributes() -- derived classes can override this to manipulate the attribute descriptor list depending on the current UI node.

        return mDescriptor.getAttributes();
    
public java.lang.StringgetAttributeValue(java.lang.String attrXmlName)
Utility method to retrieve the internal value of an attribute.

Note that this retrieves the *field* value if the attribute has some UI, and not the actual XML value. They may differ if the attribute is dirty.

param
attrXmlName The XML name of the attribute to modify
return
The current internal value for the attribute or null in case of error.

        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        
        for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
            AttributeDescriptor ui_desc = entry.getKey();
            if (ui_desc.getXmlLocalName().equals(attrXmlName)) {
                UiAttributeNode ui_attr = entry.getValue();
                return ui_attr.getCurrentValue();
            }
        }
        return null;
    
public java.lang.StringgetBreadcrumbTrailDescription(boolean include_root)
Computes a "breadcrumb trail" description for this node. It will look something like "Manifest > Application > .myactivity (Activity) > Intent-Filter"

param
include_root Whether to include the root (e.g. "Manifest") or not. Has no effect when called on the root node itself.
return
The "breadcrumb trail" description for this node.

        StringBuilder sb = new StringBuilder(getShortDescription());

        for (UiElementNode ui_node = getUiParent();
                ui_node != null;
                ui_node = ui_node.getUiParent()) {
            if (!include_root && ui_node.getUiParent() == null) {
                break;
            }
            sb.insert(0, String.format("%1$s > ", ui_node.getShortDescription())); //$NON-NLS-1$
        }
        
        return sb.toString();
    
public com.android.ide.eclipse.editors.descriptors.ElementDescriptorgetDescriptor()
Returns the {@link ElementDescriptor} for this node. This is never null.

Do not use this to call getDescriptor().getAttributes(), instead call getAttributeDescriptors() which can be overriden by derived classes.

        return mDescriptor;
    
public java.lang.ObjectgetEditData()
Returns the temporary data used by the editors for this object.

return
the data, or null if none has been set.

        return mEditData;
    
public java.lang.ObjectgetEditableValue()

        return null;
    
public com.android.ide.eclipse.editors.AndroidEditorgetEditor()
Returns the {@link AndroidEditor} that embeds this {@link UiElementNode}.

The value is initially null until the node is attached to its parent -- the value of the root node is then propagated.

return
The embedding {@link AndroidEditor} or null.

        return mUiParent == null ? mEditor : mUiParent.getEditor();
    
private java.util.MapgetHiddenAttributeDescriptors()
Returns the hidden {@link AttributeDescriptor} array for the descriptor of this node. This is a subset of the getAttributeDescriptors() list.

Use this instead of getDescriptor().getHiddenAttributes() -- potentially derived classes could override this to manipulate the attribute descriptor list depending on the current UI node. There's no need for it right now so keep it private.

        if (mCachedHiddenAttributes == null) {
            mCachedHiddenAttributes = new HashMap<String, AttributeDescriptor>();
            for (AttributeDescriptor attr_desc : getAttributeDescriptors()) {
                if (attr_desc instanceof XmlnsAttributeDescriptor) {
                    mCachedHiddenAttributes.put(
                            ((XmlnsAttributeDescriptor) attr_desc).getXmlNsName(), 
                            attr_desc);
                }
            }
        }
        return mCachedHiddenAttributes;
    
private java.util.HashMapgetInternalUiAttributes()
Gets or creates the internal UiAttributes list.

When the descriptor derives from ViewElementDescriptor, this list depends on the current UiParent node.

return
A new set of {@link UiAttributeNode} that matches the expected attributes for this node.

        if (mUiAttributes == null) {
            AttributeDescriptor[] attr_list = getAttributeDescriptors();
            mUiAttributes = new HashMap<AttributeDescriptor, UiAttributeNode>(attr_list.length);
            for (AttributeDescriptor desc : attr_list) {
                UiAttributeNode ui_node = desc.createUiNode(this);
                if (ui_node != null) {  // Some AttributeDescriptors do not have UI associated
                    mUiAttributes.put(desc, ui_node);
                }
            }
        }
        return mUiAttributes;
    
public org.eclipse.ui.views.properties.IPropertyDescriptor[]getPropertyDescriptors()

        List<IPropertyDescriptor> propDescs = new ArrayList<IPropertyDescriptor>();

        // get the standard descriptors
        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        Set<AttributeDescriptor> keys = attributeMap.keySet();
        
        
        // we only want the descriptor that do implement the IPropertyDescriptor interface.
        for (AttributeDescriptor key : keys) {
            if (key instanceof IPropertyDescriptor) {
                propDescs.add((IPropertyDescriptor)key);
            }
        }
        
        // now get the descriptor from the unknown attributes
        for (UiAttributeNode unknownNode : mUnknownUiAttributes) {
            if (unknownNode.getDescriptor() instanceof IPropertyDescriptor) {
                propDescs.add((IPropertyDescriptor)unknownNode.getDescriptor());
            }
        }
        
        // TODO cache this maybe, as it's not going to change (except for unknown descriptors)
        return propDescs.toArray(new IPropertyDescriptor[propDescs.size()]);
    
public java.lang.ObjectgetPropertyValue(java.lang.Object id)

        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        
        UiAttributeNode attribute = attributeMap.get(id);

        if (attribute == null) {
            // look for the id in the unknown attributes.
            for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
                if (id == unknownAttr.getDescriptor()) {
                    return unknownAttr;
                }
            }
        }
        
        return attribute;
    
public java.lang.StringgetShortDescription()
Computes a short string describing the UI node suitable for tree views. Uses the element's attribute "android:name" if present, or the "android:label" one followed by the element's name.

return
A short string describing the UI node suitable for tree views.

        if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) {

            // Application and Manifest nodes have a special treatment: they are unique nodes
            // so we don't bother trying to differentiate their strings and we fall back to
            // just using the UI name below.
            Element elem = (Element) mXmlNode;
            
            String attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
                                              AndroidManifestDescriptors.ANDROID_NAME_ATTR);
            if (attr == null || attr.length() == 0) {
                attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
                                           AndroidManifestDescriptors.ANDROID_LABEL_ATTR);
            }
            if (attr == null || attr.length() == 0) {
                attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
                                           XmlDescriptors.PREF_KEY_ATTR);
            }
            if (attr == null || attr.length() == 0) {
                attr = elem.getAttribute(ResourcesDescriptors.NAME_ATTR);
            }
            if (attr == null || attr.length() == 0) {
                attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
                                           LayoutDescriptors.ID_ATTR);

                if (attr != null && attr.length() > 0) {
                    for (String prefix : ID_PREFIXES) {
                        if (attr.startsWith(prefix)) {
                            attr = attr.substring(prefix.length());
                            break;
                        }
                    }
                }
            }
            if (attr != null && attr.length() > 0) {
                return String.format("%1$s (%2$s)", attr, mDescriptor.getUiName());
            }
        }

        return String.format("%1$s", mDescriptor.getUiName());
    
public java.util.CollectiongetUiAttributes()

return
A read-only version of the attributes collection.

        if (mReadOnlyUiAttributes == null) {
            mReadOnlyUiAttributes = Collections.unmodifiableCollection(
                    getInternalUiAttributes().values());
        }
        return mReadOnlyUiAttributes;
    
public java.util.ListgetUiChildren()

return
A read-only version of the children collection.

        if (mReadOnlyUiChildren == null) {
            mReadOnlyUiChildren = Collections.unmodifiableList(mUiChildren);
        }
        return mReadOnlyUiChildren;
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodegetUiNextSibling()
Returns the next UI sibling of this UI node. If the node does not have a next sibling, returns null.

        if (mUiParent != null) {
            List<UiElementNode> childlist = mUiParent.getUiChildren();
            if (childlist != null) {
                int size = childlist.size();
                if (size > 1 && childlist.get(size - 1) != this) {
                    int index = childlist.indexOf(this);
                    return index >= 0 && index < size - 1 ? childlist.get(index + 1) : null;
                }
            }
        }
        return null;
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodegetUiParent()

return
The parent {@link UiElementNode} or null if this is the root node.

        return mUiParent;
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodegetUiPreviousSibling()
Returns the previous UI sibling of this UI node. If the node does not have a previous sibling, returns null.

        if (mUiParent != null) {
            List<UiElementNode> childlist = mUiParent.getUiChildren();
            if (childlist != null && childlist.size() > 1 && childlist.get(0) != this) {
                int index = childlist.indexOf(this);
                return index > 0 ? childlist.get(index - 1) : null;
            }
        }
        return null;
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodegetUiRoot()
Returns The root {@link UiElementNode}.

        UiElementNode root = this;
        while (root.mUiParent != null) {
            root = root.mUiParent;
        }
        
        return root;
    
public java.util.CollectiongetUnknownUiAttributes()

return
A read-only version of the unknown attributes collection.

        return Collections.unmodifiableCollection(mUnknownUiAttributes);
    
public org.w3c.dom.DocumentgetXmlDocument()
Returns the XML {@link Document}.

The value is initially null until the UI node is attached to its UI parent -- the value of the document is then propagated.

return
the XML {@link Document} or the parent's XML {@link Document} or null.

        if (mXmlDocument != null) {
            return mXmlDocument;
        } else if (mUiParent != null) {
            return mUiParent.getXmlDocument();
        }
        return null;
    
public org.w3c.dom.NodegetXmlNode()
Returns the XML node associated with this UI node.

Some {@link ElementDescriptor} are declared as being "mandatory". This means the corresponding UI node will exist even if there is no corresponding XML node. Such structure is created and enforced by the parent of the tree, not the element themselves. However such nodes will likely not have an XML node associated, so getXmlNode() can return null.

return
The associated XML node. Can be null for mandatory nodes.

        return mXmlNode;
    
public final booleanhasError()
Returns whether this node, its attributes, or one of the children nodes (and attributes) has errors.

        if (mHasError) {
            return true;
        }

        // get the error value from the attributes.
        Collection<UiAttributeNode> attributes = getInternalUiAttributes().values();
        for (UiAttributeNode attribute : attributes) {
            if (attribute.hasError()) {
                return true;
            }
        }

        // and now from the children.
        for (UiElementNode child : mUiChildren) {
            if (child.hasError()) {
                return true;
            }
        }

        return false;
    
public com.android.ide.eclipse.editors.uimodel.UiElementNodeinsertNewUiChild(int index, com.android.ide.eclipse.editors.descriptors.ElementDescriptor descriptor)
Creates a new {@link UiElementNode} from the given {@link ElementDescriptor} and inserts it in the element children list at the specified position.

param
index The position where to insert in the element children list.
param
descriptor The {@link ElementDescriptor} that knows how to create the UI node.
return
The new UI node.

        UiElementNode ui_node;
        ui_node = descriptor.createUiNode();
        mUiChildren.add(index, ui_node);
        ui_node.setUiParent(this);
        ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED);
        return ui_node;
    
protected voidinvokeUiUpdateListeners(com.android.ide.eclipse.editors.uimodel.IUiUpdateListener.UiUpdateState state)
Invoke all registered {@link IUiUpdateListener} listening on this UI updates for this node.

        if (mUiUpdateListeners != null) {
            for (IUiUpdateListener listener : mUiUpdateListeners) {
                try {
                    listener.uiElementNodeUpdated(this, state);
                } catch (Exception e) {
                    // prevent a crashing listener from crashing the whole invocation chain
                    AdtPlugin.log(e, "UIElement Listener failed: %s, state=%s",  //$NON-NLS-1$
                            getBreadcrumbTrailDescription(true),
                            state.toString());
                }
            }
        }
    
public booleanisDirty()
Returns true if the part has been modified with respect to the data loaded from the model.

        for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) {
            if (ui_attr.isDirty()) {
                return true;
            }
        }
        
        for (UiAttributeNode ui_attr : mUnknownUiAttributes) {
            if (ui_attr.isDirty()) {
                return true;
            }
        }

        return false;
    
public booleanisPropertySet(java.lang.Object id)

        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        
        UiAttributeNode attribute = attributeMap.get(id);

        if (attribute != null) {
            return attribute.getCurrentValue().length() > 0;
        }
        
        // look for the id in the unknown attributes.
        for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
            if (id == unknownAttr.getDescriptor()) {
                return unknownAttr.getCurrentValue().length() > 0;
            }
        }
        
        return false;
    
public booleanloadFromXmlNode(org.w3c.dom.Node xml_node)
Populate this element node with all values from the given XML node. This fails if the given XML node has a different element name -- it won't change the type of this ui node. This method can be both used for populating values the first time and updating values after the XML model changed.

param
xml_node The XML node to mirror
return
Returns true if the XML structure has changed (nodes added, removed or replaced)

        boolean structure_changed = (mXmlNode != xml_node);
        mXmlNode = xml_node;
        if (xml_node != null) {
            updateAttributeList(xml_node);
            structure_changed |= updateElementList(xml_node);
            invokeUiUpdateListeners(structure_changed ? UiUpdateState.CHILDREN_CHANGED
                                                      : UiUpdateState.ATTR_UPDATED);
        }
        return structure_changed;
    
private java.lang.StringlookupNamespacePrefix(org.w3c.dom.Node node, java.lang.String nsUri)
Returns the namespace prefix matching the requested namespace URI. If no such declaration is found, returns the default "android" prefix.

param
node The current node. Must not be null.
param
nsUri The namespace URI of which the prefix is to be found, e.g. SdkConstants.NS_RESOURCES
return
The first prefix declared or the default "android" prefix.

        // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
        // The following code emulates this simple call:
        //   String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES);

        // if the requested URI is null, it denotes an attribute with no namespace.
        if (nsUri == null) {
            return null;
        }
        
        // per XML specification, the "xmlns" URI is reserved
        if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) {
            return "xmlns"; //$NON-NLS-1$
        }
        
        HashSet<String> visited = new HashSet<String>();
        Document doc = node == null ? null : node.getOwnerDocument();
        
        for (; node != null && node.getNodeType() == Node.ELEMENT_NODE;
               node = node.getParentNode()) {
            NamedNodeMap attrs = node.getAttributes();
            for (int n = attrs.getLength() - 1; n >= 0; --n) {
                Node attr = attrs.item(n);
                if ("xmlns".equals(attr.getPrefix())) {  //$NON-NLS-1$
                    String uri = attr.getNodeValue();
                    String nsPrefix = attr.getLocalName();
                    // Is this the URI we are looking for? If yes, we found its prefix.
                    if (nsUri.equals(uri)) {
                        return nsPrefix;
                    }
                    visited.add(nsPrefix);
                }
            }
        }
        
        // Use a sensible default prefix if we can't find one.
        // We need to make sure the prefix is not one that was declared in the scope
        // visited above. Use a default namespace prefix "android" for the Android resource
        // NS and use "ns" for all other custom namespaces.
        String prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
        String base = prefix;
        for (int i = 1; visited.contains(prefix); i++) {
            prefix = base + Integer.toString(i);
        }
        
        // Also create & define this prefix/URI in the XML document as an attribute in the
        // first element of the document.
        if (doc != null) {
            node = doc.getFirstChild();
            while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
                node = node.getNextSibling();
            }
            if (node != null) {
                Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, prefix);
                attr.setValue(nsUri);
                attr.setPrefix("xmlns"); //$NON-NLS-1$
                node.getAttributes().setNamedItemNS(attr);
            }
        }
        
        return prefix;
    
public org.w3c.dom.NodeprepareCommit()
Called by attributes when they want to commit their value to an XML node.

For mandatory nodes, this makes sure the underlying XML element node exists in the model. If not, it is created and assigned as the underlying XML node.
For non-mandatory nodes, simply return the underlying XML node, which must always exists.

return
The XML node matching this {@link UiElementNode} or null.

        if (getDescriptor().isMandatory()) {
            createXmlNode();
            // The new XML node has been created.
            // We don't need to refresh using loadFromXmlNode() since there are
            // no attributes or elements that need to be loading into this node.
        }
        return getXmlNode();
    
public voidrefreshUi()

        invokeUiUpdateListeners(UiUpdateState.ATTR_UPDATED);
    
public voidreloadFromXmlNode(org.w3c.dom.Node xml_node)
Clears the UI node and reload it from the given XML node.

This works by clearing all references to any previous XML or UI nodes and then reloads the XML document from scratch. The editor reference is kept.

This is used in the special case where the ElementDescriptor structure has changed. Rather than try to diff inflated UI nodes (as loadFromXmlNode does), we don't bother and reload everything. This is not subtle and should be used very rarely.

param
xml_node The XML node or document to reload. Can be null.

        // The editor needs to be preserved, it is not affected by an XML change.
        AndroidEditor editor = getEditor();
        clearContent();
        setEditor(editor);
        if (xml_node != null) {
            setXmlDocument(xml_node.getOwnerDocument());
        }
        // This will reload all the XML and recreate the UI structure from scratch.
        loadFromXmlNode(xml_node);
    
private booleanremoveUiChildAtIndex(int ui_index)
Internal helper to remove an UI child node given by its index in the internal child list. Also invokes the update listener on the node to be deleted.

param
ui_index The index of the UI child to remove, range 0 .. mUiChildren.size()-1
return
True if the structure has changed
throws
IndexOutOfBoundsException if index is out of mUiChildren's bounds. Of course you know that could never happen unless the computer is on fire or something.

        UiElementNode ui_node = mUiChildren.get(ui_index);
        invokeUiUpdateListeners(UiUpdateState.DELETED);
        if (ui_node.getDescriptor().isMandatory()) {
            // We can't remove a mandatory node, we just clear its content.

            // A mandatory node with no XML means it doesn't really exist, so it can't be
            // deleted.
            boolean xml_exists = (ui_node.getXmlNode() != null); 

            ui_node.clearContent();
            return xml_exists;
        } else {
            mUiChildren.remove(ui_index);
            return true;
        }
    
public voidremoveUpdateListener(IUiUpdateListener listener)
Removes an existing {@link IUiUpdateListener} from the internal update listener list. Does nothing if the list is empty or the listener is not registered.

       if (mUiUpdateListeners != null) {
           mUiUpdateListeners.remove(listener);
       }
    
public voidresetPropertyValue(java.lang.Object id)

        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        
        UiAttributeNode attribute = attributeMap.get(id);
        if (attribute != null) {
            // TODO: reset the value of the attribute
            
            return;
        }
        
        // look for the id in the unknown attributes.
        for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
            if (id == unknownAttr.getDescriptor()) {
                // TODO: reset the value of the attribute
                
                return;
            }
        }
    
public UiAttributeNodesetAttributeValue(java.lang.String attrXmlName, java.lang.String value, boolean override)
Utility method to internally set the value of a text attribute for the current UiElementNode.

This method is a helper. It silently ignores the errors such as the requested attribute not being present in the element or attribute not being settable. It accepts inherited attributes (such as layout).

This does not commit to the XML model. It does mark the attribute node as dirty. This is up to the caller.

see
#commitAttributeToXml(UiAttributeNode, String)
see
#commitDirtyAttributesToXml()
param
attrXmlName The XML name of the attribute to modify
param
value The new value for the attribute. If set to null, the attribute is removed.
param
override True if the value must be set even if one already exists.
return
The {@link UiAttributeNode} that has been modified or null.

        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        
        if (value == null) {
            value = ""; //$NON-NLS-1$ -- this removes an attribute
        }
        
        for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
            AttributeDescriptor ui_desc = entry.getKey();
            if (ui_desc.getXmlLocalName().equals(attrXmlName)) {
                UiAttributeNode ui_attr = entry.getValue();
                // Not all attributes are editable, ignore those which are not
                if (ui_attr instanceof IUiSettableAttributeNode) {
                    String current = ui_attr.getCurrentValue();
                    // Only update (and mark as dirty) if the attribute did not have any
                    // value or if the value was different.
                    if (override || current == null || !current.equals(value)) {
                        ((IUiSettableAttributeNode) ui_attr).setCurrentValue(value);
                        // mark the attribute as dirty since their internal content
                        // as been modified, but not the underlying XML model
                        ui_attr.setDirty(true);
                        return ui_attr;
                    }
                }
                break;
            }
        }
        return null;
    
public voidsetEditData(java.lang.Object data)
Sets the temporary data used by the editors.

param
data the data.

        mEditData = data;
    
public voidsetEditor(com.android.ide.eclipse.editors.AndroidEditor editor)
Sets the {@link AndroidEditor} handling this {@link UiElementNode} hierarchy.

The editor must always be set on the root node. This method takes care of that.

        if (mUiParent == null) {
            mEditor = editor;
        } else {
            mUiParent.setEditor(editor);
        }
    
public final voidsetHasError(boolean errorFlag)
Sets the error flag value.

param
errorFlag the error flag

        mHasError = errorFlag;
    
public voidsetPropertyValue(java.lang.Object id, java.lang.Object value)

        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
        
        UiAttributeNode attribute = attributeMap.get(id);
        
        if (attribute == null) {
            // look for the id in the unknown attributes.
            for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
                if (id == unknownAttr.getDescriptor()) {
                    attribute = unknownAttr;
                    break;
                }
            }
        }

        if (attribute != null) {

            // get the current value and compare it to the new value
            String oldValue = attribute.getCurrentValue();
            final String newValue = (String)value;
            
            if (oldValue.equals(newValue)) {
                return;
            }

            final UiAttributeNode fAttribute = attribute;
            AndroidEditor editor = getEditor();
            editor.editXmlModel(new Runnable() {
                public void run() {
                    commitAttributeToXml(fAttribute, newValue);
                }
            });
        }
    
protected voidsetUiParent(com.android.ide.eclipse.editors.uimodel.UiElementNode parent)
Sets the parent of this UiElementNode.

The root node has no parent.

        mUiParent = parent;
        // Invalidate the internal UiAttributes list, as it may depend on the actual UiParent.
        clearAttributes();
    
public voidsetXmlDocument(org.w3c.dom.Document xml_doc)
Sets the XML {@link Document}.

The XML {@link Document} is initially null. The XML {@link Document} must be set only on the UI root element node (this method takes care of that.)

        if (mUiParent == null) {
            mXmlDocument = xml_doc;
        } else {
            mUiParent.setXmlDocument(xml_doc);
        }
    
protected voidsetXmlNode(org.w3c.dom.Node xml_node)

        mXmlNode = xml_node;
    
protected voidupdateAttributeList(org.w3c.dom.Node xmlNode)
Updates the {@link UiAttributeNode} list for this {@link UiElementNode}.

For a given {@link UiElementNode}, the attribute list always exists in full and is totally independent of whether the XML model actually has the corresponding attributes.

For each attribute declared in this {@link UiElementNode}, get the corresponding XML attribute. It may not exist, in which case the value will be null. We don't really know if a value has changed, so the updateValue() is called on the UI sattribute in all cases.

param
xmlNode The XML node to mirror

        NamedNodeMap xmlAttrMap = xmlNode.getAttributes();
        HashSet<Node> visited = new HashSet<Node>();
        
        // For all known (i.e. expected) UI attributes, find an existing XML attribute of
        // same (uri, local name) and update the internal Ui attribute value.
        for (UiAttributeNode uiAttr : getInternalUiAttributes().values()) {
            AttributeDescriptor desc = uiAttr.getDescriptor();
            if (!(desc instanceof SeparatorAttributeDescriptor)) {
                Node xmlAttr = xmlAttrMap == null ? null :
                    xmlAttrMap.getNamedItemNS(desc.getNamespaceUri(), desc.getXmlLocalName());
                uiAttr.updateValue(xmlAttr);
                visited.add(xmlAttr);
            }
        }

        // Clone the current list of unknown attributes. We'll then remove from this list when
        // we still attributes which are still unknown. What will be left are the old unknown
        // attributes that have been deleted in the current XML attribute list.
        @SuppressWarnings("unchecked") //$NON-NLS-1$
        HashSet<UiAttributeNode> deleted = (HashSet<UiAttributeNode>) mUnknownUiAttributes.clone();

        // We need to ignore hidden attributes.
        Map<String, AttributeDescriptor> hiddenAttrDesc = getHiddenAttributeDescriptors();
        
        // Traverse the actual XML attribute list to find unknown attributes
        if (xmlAttrMap != null) {
            for (int i = 0; i < xmlAttrMap.getLength(); i++) {
                Node xmlAttr = xmlAttrMap.item(i);
                // Ignore attributes which have actual descriptors 
                if (visited.contains(xmlAttr)) {
                    continue;
                }
                
                String xmlFullName = xmlAttr.getNodeName();

                // Ignore attributes which are hidden (based on the prefix:localName key)
                if (hiddenAttrDesc.containsKey(xmlFullName)) {
                    continue;
                }
                
                String xmlAttrLocalName = xmlAttr.getLocalName();
                String xmlNsUri = xmlAttr.getNamespaceURI();

                UiAttributeNode uiAttr = null;
                for (UiAttributeNode a : mUnknownUiAttributes) {
                    String aLocalName = a.getDescriptor().getXmlLocalName();
                    String aNsUri = a.getDescriptor().getNamespaceUri();
                    if (aLocalName.equals(xmlAttrLocalName) &&
                            (aNsUri == xmlNsUri || (aNsUri != null && aNsUri.equals(xmlNsUri)))) {
                        // This attribute is still present in the unknown list
                        uiAttr = a;
                        // It has not been deleted
                        deleted.remove(a);
                        break;
                    }
                }
                if (uiAttr == null) {
                    // Create a new unknown attribute
                    TextAttributeDescriptor desc = new TextAttributeDescriptor(
                            xmlAttrLocalName, // xml name
                            xmlFullName, // ui name
                            xmlNsUri, // NS uri
                            "Unknown XML attribute"); // tooltip, translatable
                    uiAttr = desc.createUiNode(this);
                    mUnknownUiAttributes.add(uiAttr);
                }
                
                uiAttr.updateValue(xmlAttr);
            }
            
            // Remove from the internal list unknown attributes that have been deleted from the xml
            for (UiAttributeNode a : deleted) {
                mUnknownUiAttributes.remove(a);
            }
        }
    
protected booleanupdateElementList(org.w3c.dom.Node xml_node)
Updates the element list for this UiElementNode. At the end, the list of children UiElementNode here will match the one from the provided XML {@link Node}:
  • Walk both the current ui children list and the xml children list at the same time.
  • If we have a new xml child but already reached the end of the ui child list, add the new xml node.
  • Otherwise, check if the xml node is referenced later in the ui child list and if so, move it here. It means the XML child list has been reordered.
  • Otherwise, this is a new XML node that we add in the middle of the ui child list.
  • At the end, we may have finished walking the xml child list but still have remaining ui children, simply delete them as they matching trailing xml nodes that have been removed unless they are mandatory ui nodes.
Note that only the first case is used when populating the ui list the first time.

param
xml_node The XML node to mirror
return
True when the XML structure has changed.

        boolean structure_changed = false;
        int ui_index = 0;
        Node xml_child = xml_node.getFirstChild();
        while (xml_child != null) {
            if (xml_child.getNodeType() == Node.ELEMENT_NODE) {
                String element_name = xml_child.getNodeName();
                UiElementNode ui_node = null;
                if (mUiChildren.size() <= ui_index) {
                    // A new node is being added at the end of the list
                    ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name,
                            false /* recursive */);
                    if (desc == null) {
                        // Unknown node. Create a temporary descriptor for it.
                        // most important we want to auto-add unknown attributes to it.
                        AndroidEditor editor = getEditor();
                        IEditorInput editorInput = editor.getEditorInput();
                        if (editorInput instanceof IFileEditorInput) {
                            IFileEditorInput fileInput = (IFileEditorInput)editorInput;
                            desc = CustomViewDescriptorService.getInstance().getDescriptor(
                                    fileInput.getFile().getProject(), element_name);
                            if (desc == null) {
                                desc = new ElementDescriptor(element_name);
                            }
                        } else {
                            desc = new ElementDescriptor(element_name);
                            // TODO associate a new "?" icon to this descriptor.
                        }
                    }
                    structure_changed = true;
                    ui_node = appendNewUiChild(desc);
                    ui_index++;
                } else {
                    // A new node is being inserted or moved.
                    // Note: mandatory nodes can be created without an XML node in which case
                    // getXmlNode() is null.
                    UiElementNode ui_child;
                    int n = mUiChildren.size();
                    for (int j = ui_index; j < n; j++) {
                        ui_child = mUiChildren.get(j);
                        if (ui_child.getXmlNode() != null && ui_child.getXmlNode() == xml_child) {
                            if (j > ui_index) {
                                // Found the same XML node at some later index, now move it here.
                                mUiChildren.remove(j);
                                mUiChildren.add(ui_index, ui_child);
                                structure_changed = true;
                            }
                            ui_node = ui_child;
                            ui_index++;
                            break;
                        }
                    }

                    if (ui_node == null) {
                        // Look for an unused mandatory node with no XML node attached
                        // referencing the same XML element name
                        for (int j = ui_index; j < n; j++) {
                            ui_child = mUiChildren.get(j);
                            if (ui_child.getXmlNode() == null &&
                                    ui_child.getDescriptor().isMandatory() &&
                                    ui_child.getDescriptor().getXmlName().equals(element_name)) {
                                if (j > ui_index) {
                                    // Found it, now move it here
                                    mUiChildren.remove(j);
                                    mUiChildren.add(ui_index, ui_child);
                                }
                                // assign the XML node to this empty mandatory element.
                                ui_child.mXmlNode = xml_child;
                                structure_changed = true;
                                ui_node = ui_child;
                                ui_index++;
                            }
                        }
                    }

                    if (ui_node == null) {
                        // Inserting new node
                        ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name,
                                false /* recursive */);
                        if (desc == null) {
                            // Unknown element. Simply ignore it.
                            AdtPlugin.log(IStatus.WARNING,
                                    "AndroidManifest: Ignoring unknown '%s' XML element", //$NON-NLS-1$
                                    element_name);
                        } else {
                            structure_changed = true;
                            ui_node = insertNewUiChild(ui_index, desc);
                            ui_index++;
                        }
                    }
                }
                if (ui_node != null) {
                    // If we touched an UI Node, even an existing one, refresh its content.
                    // For new nodes, this will populate them recursively.
                    structure_changed |= ui_node.loadFromXmlNode(xml_child);
                }
            }
            xml_child = xml_child.getNextSibling();
        }

        // There might be extra UI nodes at the end if the XML node list got shorter.
        for (int index = mUiChildren.size() - 1; index >= ui_index; --index) {
             structure_changed |= removeUiChildAtIndex(index);
        }

        return structure_changed;