Home | History | Annotate | Download | only in uimodel
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.uimodel;
     18 
     19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME;
     20 import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
     21 import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
     22 import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
     23 import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS;
     24 import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_URI;
     25 import static com.android.sdklib.SdkConstants.NS_RESOURCES;
     26 import static com.android.tools.lint.detector.api.LintConstants.XMLNS_PREFIX;
     27 
     28 import com.android.annotations.Nullable;
     29 import com.android.annotations.VisibleForTesting;
     30 import com.android.ide.common.api.IAttributeInfo.Format;
     31 import com.android.ide.common.resources.platform.AttributeInfo;
     32 import com.android.ide.eclipse.adt.AdtPlugin;
     33 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     34 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
     35 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     36 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory;
     37 import com.android.ide.eclipse.adt.internal.editors.descriptors.IUnknownDescriptorProvider;
     38 import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
     39 import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
     40 import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
     41 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
     42 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
     43 import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
     44 import com.android.ide.eclipse.adt.internal.editors.otherxml.descriptors.OtherXmlDescriptors;
     45 import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
     46 import com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors;
     47 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     48 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     49 import com.android.sdklib.SdkConstants;
     50 
     51 import org.eclipse.core.resources.IProject;
     52 import org.eclipse.jface.viewers.StyledString;
     53 import org.eclipse.ui.views.properties.IPropertyDescriptor;
     54 import org.eclipse.ui.views.properties.IPropertySource;
     55 import org.eclipse.wst.xml.core.internal.document.ElementImpl;
     56 import org.w3c.dom.Attr;
     57 import org.w3c.dom.Document;
     58 import org.w3c.dom.Element;
     59 import org.w3c.dom.NamedNodeMap;
     60 import org.w3c.dom.Node;
     61 import org.w3c.dom.Text;
     62 
     63 import java.util.ArrayList;
     64 import java.util.Collection;
     65 import java.util.Collections;
     66 import java.util.HashMap;
     67 import java.util.HashSet;
     68 import java.util.List;
     69 import java.util.Locale;
     70 import java.util.Map;
     71 import java.util.Map.Entry;
     72 import java.util.Set;
     73 
     74 /**
     75  * Represents an XML node that can be modified by the user interface in the XML editor.
     76  * <p/>
     77  * Each tree viewer used in the application page's parts needs to keep a model representing
     78  * each underlying node in the tree. This interface represents the base type for such a node.
     79  * <p/>
     80  * Each node acts as an intermediary model between the actual XML model (the real data support)
     81  * and the tree viewers or the corresponding page parts.
     82  * <p/>
     83  * Element nodes don't contain data per se. Their data is contained in their attributes
     84  * as well as their children's attributes, see {@link UiAttributeNode}.
     85  * <p/>
     86  * The structure of a given {@link UiElementNode} is declared by a corresponding
     87  * {@link ElementDescriptor}.
     88  * <p/>
     89  * The class implements {@link IPropertySource}, in order to fill the Eclipse property tab when
     90  * an element is selected. The {@link AttributeDescriptor} are used property descriptors.
     91  */
     92 @SuppressWarnings("restriction") // XML model
     93 public class UiElementNode implements IPropertySource {
     94 
     95     /** List of prefixes removed from android:id strings when creating short descriptions. */
     96     private static String[] ID_PREFIXES = {
     97         "@android:id/", //$NON-NLS-1$
     98         NEW_ID_PREFIX, ID_PREFIX, "@+", "@" }; //$NON-NLS-1$ //$NON-NLS-2$
     99 
    100     /** The element descriptor for the node. Always present, never null. */
    101     private ElementDescriptor mDescriptor;
    102     /** The parent element node in the UI model. It is null for a root element or until
    103      *  the node is attached to its parent. */
    104     private UiElementNode mUiParent;
    105     /** The {@link AndroidXmlEditor} handling the UI hierarchy. This is defined only for the
    106      *  root node. All children have the value set to null and query their parent. */
    107     private AndroidXmlEditor mEditor;
    108     /** The XML {@link Document} model that is being mirror by the UI model. This is defined
    109      *  only for the root node. All children have the value set to null and query their parent. */
    110     private Document mXmlDocument;
    111     /** The XML {@link Node} mirror by this UI node. This can be null for mandatory UI node which
    112      *  have no corresponding XML node or for new UI nodes before their XML node is set. */
    113     private Node mXmlNode;
    114     /** The list of all UI children nodes. Can be empty but never null. There's one UI children
    115      *  node per existing XML children node. */
    116     private ArrayList<UiElementNode> mUiChildren;
    117     /** The list of <em>all</em> UI attributes, as declared in the {@link ElementDescriptor}.
    118      *  The list is always defined and never null. Unlike the UiElementNode children list, this
    119      *  is always defined, even for attributes that do not exist in the XML model - that's because
    120      *  "missing" attributes in the XML model simply mean a default value is used. Also note that
    121      *  the underlying collection is a map, so order is not respected. To get the desired attribute
    122      *  order, iterate through the {@link ElementDescriptor}'s attribute list. */
    123     private HashMap<AttributeDescriptor, UiAttributeNode> mUiAttributes;
    124     private HashSet<UiAttributeNode> mUnknownUiAttributes;
    125     /** A read-only view of the UI children node collection. */
    126     private List<UiElementNode> mReadOnlyUiChildren;
    127     /** A read-only view of the UI attributes collection. */
    128     private Collection<UiAttributeNode> mCachedAllUiAttributes;
    129     /** A map of hidden attribute descriptors. Key is the XML name. */
    130     private Map<String, AttributeDescriptor> mCachedHiddenAttributes;
    131     /** An optional list of {@link IUiUpdateListener}. Most element nodes will not have any
    132      *  listeners attached, so the list is only created on demand and can be null. */
    133     private List<IUiUpdateListener> mUiUpdateListeners;
    134     /** A provider that knows how to create {@link ElementDescriptor} from unmapped XML names.
    135      *  The default is to have one that creates new {@link ElementDescriptor}. */
    136     private IUnknownDescriptorProvider mUnknownDescProvider;
    137     /** Error Flag */
    138     private boolean mHasError;
    139 
    140     /**
    141      * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}.
    142      *
    143      * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null.
    144      */
    145     public UiElementNode(ElementDescriptor elementDescriptor) {
    146         mDescriptor = elementDescriptor;
    147         clearContent();
    148     }
    149 
    150     @Override
    151     public String toString() {
    152       return String.format("%s [desc: %s, parent: %s, children: %d]",         //$NON-NLS-1$
    153               this.getClass().getSimpleName(),
    154               mDescriptor,
    155               mUiParent != null ? mUiParent.toString() : "none",              //$NON-NLS-1$
    156                       mUiChildren != null ? mUiChildren.size() : 0
    157       );
    158     }
    159 
    160     /**
    161      * Clears the {@link UiElementNode} by resetting the children list and
    162      * the {@link UiAttributeNode}s list.
    163      * Also resets the attached XML node, document, editor if any.
    164      * <p/>
    165      * The parent {@link UiElementNode} node is not reset so that it's position
    166      * in the hierarchy be left intact, if any.
    167      */
    168     /* package */ void clearContent() {
    169         mXmlNode = null;
    170         mXmlDocument = null;
    171         mEditor = null;
    172         clearAttributes();
    173         mReadOnlyUiChildren = null;
    174         if (mUiChildren == null) {
    175             mUiChildren = new ArrayList<UiElementNode>();
    176         } else {
    177             // We can't remove mandatory nodes, we just clear them.
    178             for (int i = mUiChildren.size() - 1; i >= 0; --i) {
    179                 removeUiChildAtIndex(i);
    180             }
    181         }
    182     }
    183 
    184     /**
    185      * Clears the internal list of attributes, the read-only cached version of it
    186      * and the read-only cached hidden attribute list.
    187      */
    188     private void clearAttributes() {
    189         mUiAttributes = null;
    190         mCachedAllUiAttributes = null;
    191         mCachedHiddenAttributes = null;
    192         mUnknownUiAttributes = new HashSet<UiAttributeNode>();
    193     }
    194 
    195     /**
    196      * Gets or creates the internal UiAttributes list.
    197      * <p/>
    198      * When the descriptor derives from ViewElementDescriptor, this list depends on the
    199      * current UiParent node.
    200      *
    201      * @return A new set of {@link UiAttributeNode} that matches the expected
    202      *         attributes for this node.
    203      */
    204     private HashMap<AttributeDescriptor, UiAttributeNode> getInternalUiAttributes() {
    205         if (mUiAttributes == null) {
    206             AttributeDescriptor[] attrList = getAttributeDescriptors();
    207             mUiAttributes = new HashMap<AttributeDescriptor, UiAttributeNode>(attrList.length);
    208             for (AttributeDescriptor desc : attrList) {
    209                 UiAttributeNode uiNode = desc.createUiNode(this);
    210                 if (uiNode != null) {  // Some AttributeDescriptors do not have UI associated
    211                     mUiAttributes.put(desc, uiNode);
    212                 }
    213             }
    214         }
    215         return mUiAttributes;
    216     }
    217 
    218     /**
    219      * Computes a short string describing the UI node suitable for tree views.
    220      * Uses the element's attribute "android:name" if present, or the "android:label" one
    221      * followed by the element's name if not repeated.
    222      *
    223      * @return A short string describing the UI node suitable for tree views.
    224      */
    225     public String getShortDescription() {
    226         String name = mDescriptor.getUiName();
    227         String attr = getDescAttribute();
    228         if (attr != null) {
    229             // If the ui name is repeated in the attribute value, don't use it.
    230             // Typical case is to avoid ".pkg.MyActivity (Activity)".
    231             if (attr.contains(name)) {
    232                 return attr;
    233             } else {
    234                 return String.format("%1$s (%2$s)", attr, name);
    235             }
    236         }
    237 
    238         return name;
    239     }
    240 
    241     /** Returns the key attribute that can be used to describe this node, or null */
    242     private String getDescAttribute() {
    243         if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) {
    244             // Application and Manifest nodes have a special treatment: they are unique nodes
    245             // so we don't bother trying to differentiate their strings and we fall back to
    246             // just using the UI name below.
    247             Element elem = (Element) mXmlNode;
    248 
    249             String attr = _Element_getAttributeNS(elem,
    250                                 SdkConstants.NS_RESOURCES,
    251                                 AndroidManifestDescriptors.ANDROID_NAME_ATTR);
    252             if (attr == null || attr.length() == 0) {
    253                 attr = _Element_getAttributeNS(elem,
    254                                 SdkConstants.NS_RESOURCES,
    255                                 AndroidManifestDescriptors.ANDROID_LABEL_ATTR);
    256             } else if (mXmlNode.getNodeName().equals(LayoutDescriptors.VIEW_FRAGMENT)) {
    257                 attr = attr.substring(attr.lastIndexOf('.') + 1);
    258             }
    259             if (attr == null || attr.length() == 0) {
    260                 attr = _Element_getAttributeNS(elem,
    261                                 SdkConstants.NS_RESOURCES,
    262                                 OtherXmlDescriptors.PREF_KEY_ATTR);
    263             }
    264             if (attr == null || attr.length() == 0) {
    265                 attr = _Element_getAttributeNS(elem,
    266                                 null, // no namespace
    267                                 ValuesDescriptors.NAME_ATTR);
    268             }
    269             if (attr == null || attr.length() == 0) {
    270                 attr = _Element_getAttributeNS(elem,
    271                                 SdkConstants.NS_RESOURCES,
    272                                 LayoutDescriptors.ID_ATTR);
    273 
    274                 if (attr != null && attr.length() > 0) {
    275                     for (String prefix : ID_PREFIXES) {
    276                         if (attr.startsWith(prefix)) {
    277                             attr = attr.substring(prefix.length());
    278                             break;
    279                         }
    280                     }
    281                 }
    282             }
    283             if (attr != null && attr.length() > 0) {
    284                 return attr;
    285             }
    286         }
    287 
    288         return null;
    289     }
    290 
    291     /**
    292      * Computes a styled string describing the UI node suitable for tree views.
    293      * Similar to {@link #getShortDescription()} but styles the Strings.
    294      *
    295      * @return A styled string describing the UI node suitable for tree views.
    296      */
    297     public StyledString getStyledDescription() {
    298         String uiName = mDescriptor.getUiName();
    299 
    300         // Special case: for <view>, show the class attribute value instead.
    301         // This is done here rather than in the descriptor since this depends on
    302         // node instance data.
    303         if (LayoutDescriptors.VIEW_VIEWTAG.equals(uiName) && mXmlNode instanceof Element) {
    304             Element element = (Element) mXmlNode;
    305             String cls = element.getAttribute(ATTR_CLASS);
    306             if (cls != null) {
    307                 uiName = cls.substring(cls.lastIndexOf('.') + 1);
    308             }
    309         }
    310 
    311         StyledString styledString = new StyledString();
    312         String attr = getDescAttribute();
    313         if (attr != null) {
    314             // Don't append the two when it's a repeat, e.g. Button01 (Button),
    315             // only when the ui name is not part of the attribute
    316             if (attr.toLowerCase(Locale.US).indexOf(uiName.toLowerCase(Locale.US)) == -1) {
    317                 styledString.append(attr);
    318                 styledString.append(String.format(" (%1$s)", uiName),
    319                         StyledString.DECORATIONS_STYLER);
    320             } else {
    321                 styledString.append(attr);
    322             }
    323         }
    324 
    325         if (styledString.length() == 0) {
    326             styledString.append(uiName);
    327         }
    328 
    329         return styledString;
    330     }
    331 
    332     /**
    333      * Retrieves an attribute value by local name and namespace URI.
    334      * <br>Per [<a href='http://www.w3.org/TR/1999/REC-xml-names-19990114/'>XML Namespaces</a>]
    335      * , applications must use the value <code>null</code> as the
    336      * <code>namespaceURI</code> parameter for methods if they wish to have
    337      * no namespace.
    338      * <p/>
    339      * Note: This is a wrapper around {@link Element#getAttributeNS(String, String)}.
    340      * In some versions of webtools, the getAttributeNS implementation crashes with an NPE.
    341      * This wrapper will return an empty string instead.
    342      *
    343      * @see Element#getAttributeNS(String, String)
    344      * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=318108">https://bugs.eclipse.org/bugs/show_bug.cgi?id=318108</a>
    345      * @return The result from {@link Element#getAttributeNS(String, String)} or an empty string.
    346      */
    347     private String _Element_getAttributeNS(Element element,
    348             String namespaceURI,
    349             String localName) {
    350         try {
    351             return element.getAttributeNS(namespaceURI, localName);
    352         } catch (Exception ignore) {
    353             return "";
    354         }
    355     }
    356 
    357     /**
    358      * Computes a "breadcrumb trail" description for this node.
    359      * It will look something like "Manifest > Application > .myactivity (Activity) > Intent-Filter"
    360      *
    361      * @param includeRoot Whether to include the root (e.g. "Manifest") or not. Has no effect
    362      *                     when called on the root node itself.
    363      * @return The "breadcrumb trail" description for this node.
    364      */
    365     public String getBreadcrumbTrailDescription(boolean includeRoot) {
    366         StringBuilder sb = new StringBuilder(getShortDescription());
    367 
    368         for (UiElementNode uiNode = getUiParent();
    369                 uiNode != null;
    370                 uiNode = uiNode.getUiParent()) {
    371             if (!includeRoot && uiNode.getUiParent() == null) {
    372                 break;
    373             }
    374             sb.insert(0, String.format("%1$s > ", uiNode.getShortDescription())); //$NON-NLS-1$
    375         }
    376 
    377         return sb.toString();
    378     }
    379 
    380     /**
    381      * Sets the XML {@link Document}.
    382      * <p/>
    383      * The XML {@link Document} is initially null. The XML {@link Document} must be set only on the
    384      * UI root element node (this method takes care of that.)
    385      * @param xmlDoc The new XML document to associate this node with.
    386      */
    387     public void setXmlDocument(Document xmlDoc) {
    388         if (mUiParent == null) {
    389             mXmlDocument = xmlDoc;
    390         } else {
    391             mUiParent.setXmlDocument(xmlDoc);
    392         }
    393     }
    394 
    395     /**
    396      * Returns the XML {@link Document}.
    397      * <p/>
    398      * The value is initially null until the UI node is attached to its UI parent -- the value
    399      * of the document is then propagated.
    400      *
    401      * @return the XML {@link Document} or the parent's XML {@link Document} or null.
    402      */
    403     public Document getXmlDocument() {
    404         if (mXmlDocument != null) {
    405             return mXmlDocument;
    406         } else if (mUiParent != null) {
    407             return mUiParent.getXmlDocument();
    408         }
    409         return null;
    410     }
    411 
    412     /**
    413      * Returns the XML node associated with this UI node.
    414      * <p/>
    415      * Some {@link ElementDescriptor} are declared as being "mandatory". This means the
    416      * corresponding UI node will exist even if there is no corresponding XML node. Such structure
    417      * is created and enforced by the parent of the tree, not the element themselves. However
    418      * such nodes will likely not have an XML node associated, so getXmlNode() can return null.
    419      *
    420      * @return The associated XML node. Can be null for mandatory nodes.
    421      */
    422     public Node getXmlNode() {
    423         return mXmlNode;
    424     }
    425 
    426     /**
    427      * Returns the {@link ElementDescriptor} for this node. This is never null.
    428      * <p/>
    429      * Do not use this to call getDescriptor().getAttributes(), instead call
    430      * getAttributeDescriptors() which can be overridden by derived classes.
    431      * @return The {@link ElementDescriptor} for this node. This is never null.
    432      */
    433     public ElementDescriptor getDescriptor() {
    434         return mDescriptor;
    435     }
    436 
    437     /**
    438      * Returns the {@link AttributeDescriptor} array for the descriptor of this node.
    439      * <p/>
    440      * Use this instead of getDescriptor().getAttributes() -- derived classes can override
    441      * this to manipulate the attribute descriptor list depending on the current UI node.
    442      * @return The {@link AttributeDescriptor} array for the descriptor of this node.
    443      */
    444     public AttributeDescriptor[] getAttributeDescriptors() {
    445         return mDescriptor.getAttributes();
    446     }
    447 
    448     /**
    449      * Returns the hidden {@link AttributeDescriptor} array for the descriptor of this node.
    450      * This is a subset of the getAttributeDescriptors() list.
    451      * <p/>
    452      * Use this instead of getDescriptor().getHiddenAttributes() -- potentially derived classes
    453      * could override this to manipulate the attribute descriptor list depending on the current
    454      * UI node. There's no need for it right now so keep it private.
    455      */
    456     private Map<String, AttributeDescriptor> getHiddenAttributeDescriptors() {
    457         if (mCachedHiddenAttributes == null) {
    458             mCachedHiddenAttributes = new HashMap<String, AttributeDescriptor>();
    459             for (AttributeDescriptor attrDesc : getAttributeDescriptors()) {
    460                 if (attrDesc instanceof XmlnsAttributeDescriptor) {
    461                     mCachedHiddenAttributes.put(
    462                             ((XmlnsAttributeDescriptor) attrDesc).getXmlNsName(),
    463                             attrDesc);
    464                 }
    465             }
    466         }
    467         return mCachedHiddenAttributes;
    468     }
    469 
    470     /**
    471      * Sets the parent of this UiElementNode.
    472      * <p/>
    473      * The root node has no parent.
    474      */
    475     protected void setUiParent(UiElementNode parent) {
    476         mUiParent = parent;
    477         // Invalidate the internal UiAttributes list, as it may depend on the actual UiParent.
    478         clearAttributes();
    479     }
    480 
    481     /**
    482      * @return The parent {@link UiElementNode} or null if this is the root node.
    483      */
    484     public UiElementNode getUiParent() {
    485         return mUiParent;
    486     }
    487 
    488     /**
    489      * Returns the root {@link UiElementNode}.
    490      *
    491      * @return The root {@link UiElementNode}.
    492      */
    493     public UiElementNode getUiRoot() {
    494         UiElementNode root = this;
    495         while (root.mUiParent != null) {
    496             root = root.mUiParent;
    497         }
    498 
    499         return root;
    500     }
    501 
    502     /**
    503      * Returns the index of this sibling (where the first child has index 0, the second child
    504      * has index 1, and so on.)
    505      *
    506      * @return The sibling index of this node
    507      */
    508     public int getUiSiblingIndex() {
    509         if (mUiParent != null) {
    510             int index = 0;
    511             for (UiElementNode node : mUiParent.getUiChildren()) {
    512                 if (node == this) {
    513                     break;
    514                 }
    515                 index++;
    516             }
    517             return index;
    518         }
    519 
    520         return 0;
    521     }
    522 
    523     /**
    524      * Returns the previous UI sibling of this UI node. If the node does not have a previous
    525      * sibling, returns null.
    526      *
    527      * @return The previous UI sibling of this UI node, or null if not applicable.
    528      */
    529     public UiElementNode getUiPreviousSibling() {
    530         if (mUiParent != null) {
    531             List<UiElementNode> childlist = mUiParent.getUiChildren();
    532             if (childlist != null && childlist.size() > 1 && childlist.get(0) != this) {
    533                 int index = childlist.indexOf(this);
    534                 return index > 0 ? childlist.get(index - 1) : null;
    535             }
    536         }
    537         return null;
    538     }
    539 
    540     /**
    541      * Returns the next UI sibling of this UI node.
    542      * If the node does not have a next sibling, returns null.
    543      *
    544      * @return The next UI sibling of this UI node, or null.
    545      */
    546     public UiElementNode getUiNextSibling() {
    547         if (mUiParent != null) {
    548             List<UiElementNode> childlist = mUiParent.getUiChildren();
    549             if (childlist != null) {
    550                 int size = childlist.size();
    551                 if (size > 1 && childlist.get(size - 1) != this) {
    552                     int index = childlist.indexOf(this);
    553                     return index >= 0 && index < size - 1 ? childlist.get(index + 1) : null;
    554                 }
    555             }
    556         }
    557         return null;
    558     }
    559 
    560     /**
    561      * Sets the {@link AndroidXmlEditor} handling this {@link UiElementNode} hierarchy.
    562      * <p/>
    563      * The editor must always be set on the root node. This method takes care of that.
    564      *
    565      * @param editor The editor to associate this node with.
    566      */
    567     public void setEditor(AndroidXmlEditor editor) {
    568         if (mUiParent == null) {
    569             mEditor = editor;
    570         } else {
    571             mUiParent.setEditor(editor);
    572         }
    573     }
    574 
    575     /**
    576      * Returns the {@link AndroidXmlEditor} that embeds this {@link UiElementNode}.
    577      * <p/>
    578      * The value is initially null until the node is attached to its parent -- the value
    579      * of the root node is then propagated.
    580      *
    581      * @return The embedding {@link AndroidXmlEditor} or null.
    582      */
    583     public AndroidXmlEditor getEditor() {
    584         return mUiParent == null ? mEditor : mUiParent.getEditor();
    585     }
    586 
    587     /**
    588      * Returns the Android target data for the file being edited.
    589      *
    590      * @return The Android target data for the file being edited.
    591      */
    592     public AndroidTargetData getAndroidTarget() {
    593         return getEditor().getTargetData();
    594     }
    595 
    596     /**
    597      * @return A read-only version of the children collection.
    598      */
    599     public List<UiElementNode> getUiChildren() {
    600         if (mReadOnlyUiChildren == null) {
    601             mReadOnlyUiChildren = Collections.unmodifiableList(mUiChildren);
    602         }
    603         return mReadOnlyUiChildren;
    604     }
    605 
    606     /**
    607      * Returns a collection containing all the known attributes as well as
    608      * all the unknown ui attributes.
    609      *
    610      * @return A read-only version of the attributes collection.
    611      */
    612     public Collection<UiAttributeNode> getAllUiAttributes() {
    613         if (mCachedAllUiAttributes == null) {
    614 
    615             List<UiAttributeNode> allValues =
    616                 new ArrayList<UiAttributeNode>(getInternalUiAttributes().values());
    617             allValues.addAll(mUnknownUiAttributes);
    618 
    619             mCachedAllUiAttributes = Collections.unmodifiableCollection(allValues);
    620         }
    621         return mCachedAllUiAttributes;
    622     }
    623 
    624     /**
    625      * Returns all the unknown ui attributes, that is those we found defined in the
    626      * actual XML but that we don't have descriptors for.
    627      *
    628      * @return A read-only version of the unknown attributes collection.
    629      */
    630     public Collection<UiAttributeNode> getUnknownUiAttributes() {
    631         return Collections.unmodifiableCollection(mUnknownUiAttributes);
    632     }
    633 
    634     /**
    635      * Sets the error flag value.
    636      *
    637      * @param errorFlag the error flag
    638      */
    639     public final void setHasError(boolean errorFlag) {
    640         mHasError = errorFlag;
    641     }
    642 
    643     /**
    644      * Returns whether this node, its attributes, or one of the children nodes (and attributes)
    645      * has errors.
    646      *
    647      * @return True if this node, its attributes, or one of the children nodes (and attributes)
    648      * has errors.
    649      */
    650     public final boolean hasError() {
    651         if (mHasError) {
    652             return true;
    653         }
    654 
    655         // get the error value from the attributes.
    656         for (UiAttributeNode attribute : getAllUiAttributes()) {
    657             if (attribute.hasError()) {
    658                 return true;
    659             }
    660         }
    661 
    662         // and now from the children.
    663         for (UiElementNode child : mUiChildren) {
    664             if (child.hasError()) {
    665                 return true;
    666             }
    667         }
    668 
    669         return false;
    670     }
    671 
    672     /**
    673      * Returns the provider that knows how to create {@link ElementDescriptor} from unmapped
    674      * XML names.
    675      * <p/>
    676      * The default is to have one that creates new {@link ElementDescriptor}.
    677      * <p/>
    678      * There is only one such provider in any UI model tree, attached to the root node.
    679      *
    680      * @return An instance of {@link IUnknownDescriptorProvider}. Can never be null.
    681      */
    682     public IUnknownDescriptorProvider getUnknownDescriptorProvider() {
    683         if (mUiParent != null) {
    684             return mUiParent.getUnknownDescriptorProvider();
    685         }
    686         if (mUnknownDescProvider == null) {
    687             // Create the default one on demand.
    688             mUnknownDescProvider = new IUnknownDescriptorProvider() {
    689 
    690                 private final HashMap<String, ElementDescriptor> mMap =
    691                     new HashMap<String, ElementDescriptor>();
    692 
    693                 /**
    694                  * The default is to create a new ElementDescriptor wrapping
    695                  * the unknown XML local name and reuse previously created descriptors.
    696                  */
    697                 @Override
    698                 public ElementDescriptor getDescriptor(String xmlLocalName) {
    699 
    700                     ElementDescriptor desc = mMap.get(xmlLocalName);
    701 
    702                     if (desc == null) {
    703                         desc = new ElementDescriptor(xmlLocalName);
    704                         mMap.put(xmlLocalName, desc);
    705                     }
    706 
    707                     return desc;
    708                 }
    709             };
    710         }
    711         return mUnknownDescProvider;
    712     }
    713 
    714     /**
    715      * Sets the provider that knows how to create {@link ElementDescriptor} from unmapped
    716      * XML names.
    717      * <p/>
    718      * The default is to have one that creates new {@link ElementDescriptor}.
    719      * <p/>
    720      * There is only one such provider in any UI model tree, attached to the root node.
    721      *
    722      * @param unknownDescProvider The new provider to use. Must not be null.
    723      */
    724     public void setUnknownDescriptorProvider(IUnknownDescriptorProvider unknownDescProvider) {
    725         if (mUiParent == null) {
    726             mUnknownDescProvider = unknownDescProvider;
    727         } else {
    728             mUiParent.setUnknownDescriptorProvider(unknownDescProvider);
    729         }
    730     }
    731 
    732     /**
    733      * Adds a new {@link IUiUpdateListener} to the internal update listener list.
    734      *
    735      * @param listener The listener to add.
    736      */
    737     public void addUpdateListener(IUiUpdateListener listener) {
    738        if (mUiUpdateListeners == null) {
    739            mUiUpdateListeners = new ArrayList<IUiUpdateListener>();
    740        }
    741        if (!mUiUpdateListeners.contains(listener)) {
    742            mUiUpdateListeners.add(listener);
    743        }
    744     }
    745 
    746     /**
    747      * Removes an existing {@link IUiUpdateListener} from the internal update listener list.
    748      * Does nothing if the list is empty or the listener is not registered.
    749      *
    750      * @param listener The listener to remove.
    751      */
    752     public void removeUpdateListener(IUiUpdateListener listener) {
    753        if (mUiUpdateListeners != null) {
    754            mUiUpdateListeners.remove(listener);
    755        }
    756     }
    757 
    758     /**
    759      * Finds a child node relative to this node using a path-like expression.
    760      * F.ex. "node1/node2" would find a child "node1" that contains a child "node2" and
    761      * returns the latter. If there are multiple nodes with the same name at the same
    762      * level, always uses the first one found.
    763      *
    764      * @param path The path like expression to select a child node.
    765      * @return The ui node found or null.
    766      */
    767     public UiElementNode findUiChildNode(String path) {
    768         String[] items = path.split("/");  //$NON-NLS-1$
    769         UiElementNode uiNode = this;
    770         for (String item : items) {
    771             boolean nextSegment = false;
    772             for (UiElementNode c : uiNode.mUiChildren) {
    773                 if (c.getDescriptor().getXmlName().equals(item)) {
    774                     uiNode = c;
    775                     nextSegment = true;
    776                     break;
    777                 }
    778             }
    779             if (!nextSegment) {
    780                 return null;
    781             }
    782         }
    783         return uiNode;
    784     }
    785 
    786     /**
    787      * Finds an {@link UiElementNode} which contains the give XML {@link Node}.
    788      * Looks recursively in all children UI nodes.
    789      *
    790      * @param xmlNode The XML node to look for.
    791      * @return The {@link UiElementNode} that contains xmlNode or null if not found,
    792      */
    793     public UiElementNode findXmlNode(Node xmlNode) {
    794         if (xmlNode == null) {
    795             return null;
    796         }
    797         if (getXmlNode() == xmlNode) {
    798             return this;
    799         }
    800 
    801         for (UiElementNode uiChild : mUiChildren) {
    802             UiElementNode found = uiChild.findXmlNode(xmlNode);
    803             if (found != null) {
    804                 return found;
    805             }
    806         }
    807 
    808         return null;
    809     }
    810 
    811     /**
    812      * Returns the {@link UiAttributeNode} matching this attribute descriptor or
    813      * null if not found.
    814      *
    815      * @param attrDesc The {@link AttributeDescriptor} to match.
    816      * @return the {@link UiAttributeNode} matching this attribute descriptor or null
    817      *         if not found.
    818      */
    819     public UiAttributeNode findUiAttribute(AttributeDescriptor attrDesc) {
    820         return getInternalUiAttributes().get(attrDesc);
    821     }
    822 
    823     /**
    824      * Populate this element node with all values from the given XML node.
    825      *
    826      * This fails if the given XML node has a different element name -- it won't change the
    827      * type of this ui node.
    828      *
    829      * This method can be both used for populating values the first time and updating values
    830      * after the XML model changed.
    831      *
    832      * @param xmlNode The XML node to mirror
    833      * @return Returns true if the XML structure has changed (nodes added, removed or replaced)
    834      */
    835     public boolean loadFromXmlNode(Node xmlNode) {
    836         boolean structureChanged = (mXmlNode != xmlNode);
    837         mXmlNode = xmlNode;
    838         if (xmlNode != null) {
    839             updateAttributeList(xmlNode);
    840             structureChanged |= updateElementList(xmlNode);
    841             invokeUiUpdateListeners(structureChanged ? UiUpdateState.CHILDREN_CHANGED
    842                                                       : UiUpdateState.ATTR_UPDATED);
    843         }
    844         return structureChanged;
    845     }
    846 
    847     /**
    848      * Clears the UI node and reload it from the given XML node.
    849      * <p/>
    850      * This works by clearing all references to any previous XML or UI nodes and
    851      * then reloads the XML document from scratch. The editor reference is kept.
    852      * <p/>
    853      * This is used in the special case where the ElementDescriptor structure has changed.
    854      * Rather than try to diff inflated UI nodes (as loadFromXmlNode does), we don't bother
    855      * and reload everything. This is not subtle and should be used very rarely.
    856      *
    857      * @param xmlNode The XML node or document to reload. Can be null.
    858      */
    859     public void reloadFromXmlNode(Node xmlNode) {
    860         // The editor needs to be preserved, it is not affected by an XML change.
    861         AndroidXmlEditor editor = getEditor();
    862         clearContent();
    863         setEditor(editor);
    864         if (xmlNode != null) {
    865             setXmlDocument(xmlNode.getOwnerDocument());
    866         }
    867         // This will reload all the XML and recreate the UI structure from scratch.
    868         loadFromXmlNode(xmlNode);
    869     }
    870 
    871     /**
    872      * Called by attributes when they want to commit their value
    873      * to an XML node.
    874      * <p/>
    875      * For mandatory nodes, this makes sure the underlying XML element node
    876      * exists in the model. If not, it is created and assigned as the underlying
    877      * XML node.
    878      * </br>
    879      * For non-mandatory nodes, simply return the underlying XML node, which
    880      * must always exists.
    881      *
    882      * @return The XML node matching this {@link UiElementNode} or null.
    883      */
    884     public Node prepareCommit() {
    885         if (getDescriptor().getMandatory() != Mandatory.NOT_MANDATORY) {
    886             createXmlNode();
    887             // The new XML node has been created.
    888             // We don't need to refresh using loadFromXmlNode() since there are
    889             // no attributes or elements that need to be loading into this node.
    890         }
    891         return getXmlNode();
    892     }
    893 
    894     /**
    895      * Commits the attributes (all internal, inherited from UI parent & unknown attributes).
    896      * This is called by the UI when the embedding part needs to be committed.
    897      */
    898     public void commit() {
    899         for (UiAttributeNode uiAttr : getAllUiAttributes()) {
    900             uiAttr.commit();
    901         }
    902     }
    903 
    904     /**
    905      * Returns true if the part has been modified with respect to the data
    906      * loaded from the model.
    907      * @return True if the part has been modified with respect to the data
    908      * loaded from the model.
    909      */
    910     public boolean isDirty() {
    911         for (UiAttributeNode uiAttr : getAllUiAttributes()) {
    912             if (uiAttr.isDirty()) {
    913                 return true;
    914             }
    915         }
    916 
    917         return false;
    918     }
    919 
    920     /**
    921      * Creates the underlying XML element node for this UI node if it doesn't already
    922      * exists.
    923      *
    924      * @return The new value of getXmlNode() (can be null if creation failed)
    925      */
    926     public Node createXmlNode() {
    927         if (mXmlNode != null) {
    928             return null;
    929         }
    930         Node parentXmlNode = null;
    931         if (mUiParent != null) {
    932             parentXmlNode = mUiParent.prepareCommit();
    933             if (parentXmlNode == null) {
    934                 // The parent failed to create its own backing XML node. Abort.
    935                 // No need to throw an exception, the parent will most likely
    936                 // have done so itself.
    937                 return null;
    938             }
    939         }
    940 
    941         String elementName = getDescriptor().getXmlName();
    942         Document doc = getXmlDocument();
    943 
    944         // We *must* have a root node. If not, we need to abort.
    945         if (doc == null) {
    946             throw new RuntimeException(
    947                     String.format("Missing XML document for %1$s XML node.", elementName));
    948         }
    949 
    950         // If we get here and parentXmlNode is null, the node is to be created
    951         // as the root node of the document (which can't be null, cf. check above).
    952         if (parentXmlNode == null) {
    953             parentXmlNode = doc;
    954         }
    955 
    956         mXmlNode = doc.createElement(elementName);
    957 
    958         // If this element does not have children, mark it as an empty tag
    959         // such that the XML looks like <tag/> instead of <tag></tag>
    960         if (!mDescriptor.hasChildren()) {
    961             if (mXmlNode instanceof ElementImpl) {
    962                 ElementImpl element = (ElementImpl) mXmlNode;
    963                 element.setEmptyTag(true);
    964             }
    965         }
    966 
    967         Node xmlNextSibling = null;
    968 
    969         UiElementNode uiNextSibling = getUiNextSibling();
    970         if (uiNextSibling != null) {
    971             xmlNextSibling = uiNextSibling.getXmlNode();
    972         }
    973 
    974         Node previousTextNode = null;
    975         if (xmlNextSibling != null) {
    976             Node previousNode = xmlNextSibling.getPreviousSibling();
    977             if (previousNode != null && previousNode.getNodeType() == Node.TEXT_NODE) {
    978                 previousTextNode = previousNode;
    979             }
    980         } else {
    981             Node lastChild = parentXmlNode.getLastChild();
    982             if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) {
    983                 previousTextNode = lastChild;
    984             }
    985         }
    986 
    987         String insertAfter = null;
    988 
    989         // Try to figure out the indentation node to insert. Even in auto-formatting
    990         // we need to do this, because it turns out the XML editor's formatter does
    991         // not do a very good job with completely botched up XML; it does a much better
    992         // job if the new XML is already mostly well formatted. Thus, the main purpose
    993         // of applying the real XML formatter after our own indentation attempts here is
    994         // to make it apply its own tab-versus-spaces indentation properties, have it
    995         // insert line breaks before attributes (if the user has configured that), etc.
    996 
    997         // First figure out the indentation level of the newly inserted element;
    998         // this is either the same as the previous sibling, or if there is no sibling,
    999         // it's the indentation of the parent plus one indentation level.
   1000         boolean isFirstChild = getUiPreviousSibling() == null
   1001                 || parentXmlNode.getFirstChild() == null;
   1002         AndroidXmlEditor editor = getEditor();
   1003         String indent;
   1004         String parentIndent = ""; //$NON-NLS-1$
   1005         if (isFirstChild) {
   1006             indent = parentIndent = editor.getIndent(parentXmlNode);
   1007             // We need to add one level of indentation. Are we using tabs?
   1008             // Can't get to formatting settings so let's just look at the
   1009             // parent indentation and see if we can guess
   1010             if (indent.length() > 0 && indent.charAt(indent.length()-1) == '\t') {
   1011                 indent = indent + '\t';
   1012             } else {
   1013                 // Not using tabs, or we can't figure it out (because parent had no
   1014                 // indentation). In that case, indent with 4 spaces, as seems to
   1015                 // be the Android default.
   1016                 indent = indent + "    "; //$NON-NLS-1$
   1017             }
   1018         } else {
   1019             // Find out the indent of the previous sibling
   1020             indent = editor.getIndent(getUiPreviousSibling().getXmlNode());
   1021         }
   1022 
   1023         // We want to insert the new element BEFORE the text node which precedes
   1024         // the next element, since that text node is the next element's indentation!
   1025         if (previousTextNode != null) {
   1026             xmlNextSibling = previousTextNode;
   1027         } else {
   1028             // If there's no previous text node, we are probably inside an
   1029             // empty element (<LinearLayout>|</LinearLayout>) and in that case we need
   1030             // to not only insert a newline and indentation before the new element, but
   1031             // after it as well.
   1032             insertAfter = parentIndent;
   1033         }
   1034 
   1035         // Insert indent text node before the new element
   1036         Text indentNode = doc.createTextNode("\n" + indent); //$NON-NLS-1$
   1037         parentXmlNode.insertBefore(indentNode, xmlNextSibling);
   1038 
   1039         // Insert the element itself
   1040         parentXmlNode.insertBefore(mXmlNode, xmlNextSibling);
   1041 
   1042         // Insert a separator after the tag. We only do this when we've inserted
   1043         // a tag into an area where there was no whitespace before
   1044         // (e.g. a new child of <LinearLayout></LinearLayout>).
   1045         if (insertAfter != null) {
   1046             Text sep = doc.createTextNode("\n" + insertAfter); //$NON-NLS-1$
   1047             parentXmlNode.insertBefore(sep, xmlNextSibling);
   1048         }
   1049 
   1050         // Set all initial attributes in the XML node if they are not empty.
   1051         // Iterate on the descriptor list to get the desired order and then use the
   1052         // internal values, if any.
   1053         List<UiAttributeNode> addAttributes = new ArrayList<UiAttributeNode>();
   1054 
   1055         for (AttributeDescriptor attrDesc : getAttributeDescriptors()) {
   1056             if (attrDesc instanceof XmlnsAttributeDescriptor) {
   1057                 XmlnsAttributeDescriptor desc = (XmlnsAttributeDescriptor) attrDesc;
   1058                 Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI,
   1059                         desc.getXmlNsName());
   1060                 attr.setValue(desc.getValue());
   1061                 attr.setPrefix(desc.getXmlNsPrefix());
   1062                 mXmlNode.getAttributes().setNamedItemNS(attr);
   1063             } else {
   1064                 UiAttributeNode uiAttr = getInternalUiAttributes().get(attrDesc);
   1065 
   1066                 // Don't apply the attribute immediately, instead record this attribute
   1067                 // such that we can gather all attributes and sort them first.
   1068                 // This is necessary because the XML model will *append* all attributes
   1069                 // so we want to add them in a particular order.
   1070                 // (Note that we only have to worry about UiAttributeNodes with non null
   1071                 // values, since this is a new node and we therefore don't need to attempt
   1072                 // to remove existing attributes)
   1073                 String value = uiAttr.getCurrentValue();
   1074                 if (value != null && value.length() > 0) {
   1075                     addAttributes.add(uiAttr);
   1076                 }
   1077             }
   1078         }
   1079 
   1080         // Sort and apply the attributes in order, because the Eclipse XML model will always
   1081         // append the XML attributes, so by inserting them in our desired order they will
   1082         // appear that way in the XML
   1083         Collections.sort(addAttributes);
   1084 
   1085         for (UiAttributeNode node : addAttributes) {
   1086             commitAttributeToXml(node, node.getCurrentValue());
   1087             node.setDirty(false);
   1088         }
   1089 
   1090         getEditor().scheduleNodeReformat(this, false);
   1091 
   1092         // Notify per-node listeners
   1093         invokeUiUpdateListeners(UiUpdateState.CREATED);
   1094         // Notify global listeners
   1095         fireNodeCreated(this, getUiSiblingIndex());
   1096 
   1097         return mXmlNode;
   1098     }
   1099 
   1100     /**
   1101      * Removes the XML node corresponding to this UI node if it exists
   1102      * and also removes all mirrored information in this UI node (i.e. children, attributes)
   1103      *
   1104      * @return The removed node or null if it didn't exist in the first place.
   1105      */
   1106     public Node deleteXmlNode() {
   1107         if (mXmlNode == null) {
   1108             return null;
   1109         }
   1110 
   1111         int previousIndex = getUiSiblingIndex();
   1112 
   1113         // First clear the internals of the node and *then* actually deletes the XML
   1114         // node (because doing so will generate an update even and this node may be
   1115         // revisited via loadFromXmlNode).
   1116         Node oldXmlNode = mXmlNode;
   1117         clearContent();
   1118 
   1119         Node xmlParent = oldXmlNode.getParentNode();
   1120         if (xmlParent == null) {
   1121             xmlParent = getXmlDocument();
   1122         }
   1123         Node previousSibling = oldXmlNode.getPreviousSibling();
   1124         oldXmlNode = xmlParent.removeChild(oldXmlNode);
   1125 
   1126         // We need to remove the text node BEFORE the removed element, since THAT's the
   1127         // indentation node for the removed element.
   1128         if (previousSibling != null && previousSibling.getNodeType() == Node.TEXT_NODE
   1129                 && previousSibling.getNodeValue().trim().length() == 0) {
   1130             xmlParent.removeChild(previousSibling);
   1131         }
   1132 
   1133         invokeUiUpdateListeners(UiUpdateState.DELETED);
   1134         fireNodeDeleted(this, previousIndex);
   1135 
   1136         return oldXmlNode;
   1137     }
   1138 
   1139     /**
   1140      * Updates the element list for this UiElementNode.
   1141      * At the end, the list of children UiElementNode here will match the one from the
   1142      * provided XML {@link Node}:
   1143      * <ul>
   1144      * <li> Walk both the current ui children list and the xml children list at the same time.
   1145      * <li> If we have a new xml child but already reached the end of the ui child list, add the
   1146      *      new xml node.
   1147      * <li> Otherwise, check if the xml node is referenced later in the ui child list and if so,
   1148      *      move it here. It means the XML child list has been reordered.
   1149      * <li> Otherwise, this is a new XML node that we add in the middle of the ui child list.
   1150      * <li> At the end, we may have finished walking the xml child list but still have remaining
   1151      *      ui children, simply delete them as they matching trailing xml nodes that have been
   1152      *      removed unless they are mandatory ui nodes.
   1153      * </ul>
   1154      * Note that only the first case is used when populating the ui list the first time.
   1155      *
   1156      * @param xmlNode The XML node to mirror
   1157      * @return True when the XML structure has changed.
   1158      */
   1159     protected boolean updateElementList(Node xmlNode) {
   1160         boolean structureChanged = false;
   1161         boolean hasMandatoryLast = false;
   1162         int uiIndex = 0;
   1163         Node xmlChild = xmlNode.getFirstChild();
   1164         while (xmlChild != null) {
   1165             if (xmlChild.getNodeType() == Node.ELEMENT_NODE) {
   1166                 String elementName = xmlChild.getNodeName();
   1167                 UiElementNode uiNode = null;
   1168                 if (mUiChildren.size() <= uiIndex) {
   1169                     // A new node is being added at the end of the list
   1170                     ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
   1171                             false /* recursive */);
   1172                     if (desc == null) {
   1173                         // Unknown node. Create a temporary descriptor for it.
   1174                         // We'll add unknown attributes to it later.
   1175                         IUnknownDescriptorProvider p = getUnknownDescriptorProvider();
   1176                         desc = p.getDescriptor(elementName);
   1177                     }
   1178                     structureChanged = true;
   1179                     uiNode = appendNewUiChild(desc);
   1180                     uiIndex++;
   1181                 } else {
   1182                     // A new node is being inserted or moved.
   1183                     // Note: mandatory nodes can be created without an XML node in which case
   1184                     // getXmlNode() is null.
   1185                     UiElementNode uiChild;
   1186                     int n = mUiChildren.size();
   1187                     for (int j = uiIndex; j < n; j++) {
   1188                         uiChild = mUiChildren.get(j);
   1189                         if (uiChild.getXmlNode() != null && uiChild.getXmlNode() == xmlChild) {
   1190                             if (j > uiIndex) {
   1191                                 // Found the same XML node at some later index, now move it here.
   1192                                 mUiChildren.remove(j);
   1193                                 mUiChildren.add(uiIndex, uiChild);
   1194                                 structureChanged = true;
   1195                             }
   1196                             uiNode = uiChild;
   1197                             uiIndex++;
   1198                             break;
   1199                         }
   1200                     }
   1201 
   1202                     if (uiNode == null) {
   1203                         // Look for an unused mandatory node with no XML node attached
   1204                         // referencing the same XML element name
   1205                         for (int j = uiIndex; j < n; j++) {
   1206                             uiChild = mUiChildren.get(j);
   1207                             if (uiChild.getXmlNode() == null &&
   1208                                     uiChild.getDescriptor().getMandatory() !=
   1209                                                                 Mandatory.NOT_MANDATORY &&
   1210                                     uiChild.getDescriptor().getXmlName().equals(elementName)) {
   1211 
   1212                                 if (j > uiIndex) {
   1213                                     // Found it, now move it here
   1214                                     mUiChildren.remove(j);
   1215                                     mUiChildren.add(uiIndex, uiChild);
   1216                                 }
   1217                                 // Assign the XML node to this empty mandatory element.
   1218                                 uiChild.mXmlNode = xmlChild;
   1219                                 structureChanged = true;
   1220                                 uiNode = uiChild;
   1221                                 uiIndex++;
   1222                             }
   1223                         }
   1224                     }
   1225 
   1226                     if (uiNode == null) {
   1227                         // Inserting new node
   1228                         ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
   1229                                 false /* recursive */);
   1230                         if (desc == null && elementName.indexOf('.') != -1) {
   1231                             IProject project = getEditor().getProject();
   1232                             if (project != null) {
   1233                                 desc = CustomViewDescriptorService.getInstance().getDescriptor(
   1234                                         project, elementName);
   1235                             }
   1236                         }
   1237                         if (desc == null) {
   1238                             // Unknown node. Create a temporary descriptor for it.
   1239                             // We'll add unknown attributes to it later.
   1240                             IUnknownDescriptorProvider p = getUnknownDescriptorProvider();
   1241                             desc = p.getDescriptor(elementName);
   1242                         } else {
   1243                             structureChanged = true;
   1244                             uiNode = insertNewUiChild(uiIndex, desc);
   1245                             uiIndex++;
   1246                         }
   1247                     }
   1248                 }
   1249                 if (uiNode != null) {
   1250                     // If we touched an UI Node, even an existing one, refresh its content.
   1251                     // For new nodes, this will populate them recursively.
   1252                     structureChanged |= uiNode.loadFromXmlNode(xmlChild);
   1253 
   1254                     // Remember if there are any mandatory-last nodes to reorder.
   1255                     hasMandatoryLast |=
   1256                         uiNode.getDescriptor().getMandatory() == Mandatory.MANDATORY_LAST;
   1257                 }
   1258             }
   1259             xmlChild = xmlChild.getNextSibling();
   1260         }
   1261 
   1262         // There might be extra UI nodes at the end if the XML node list got shorter.
   1263         for (int index = mUiChildren.size() - 1; index >= uiIndex; --index) {
   1264              structureChanged |= removeUiChildAtIndex(index);
   1265         }
   1266 
   1267         if (hasMandatoryLast) {
   1268             // At least one mandatory-last uiNode was moved. Let's see if we can
   1269             // move them back to the last position. That's possible if the only
   1270             // thing between these and the end are other mandatory empty uiNodes
   1271             // (mandatory uiNodes with no XML attached are pure "virtual" reserved
   1272             // slots and it's ok to reorganize them but other can't.)
   1273             int n = mUiChildren.size() - 1;
   1274             for (int index = n; index >= 0; index--) {
   1275                 UiElementNode uiChild = mUiChildren.get(index);
   1276                 Mandatory mand = uiChild.getDescriptor().getMandatory();
   1277                 if (mand == Mandatory.MANDATORY_LAST && index < n) {
   1278                     // Remove it from index and move it back at the end of the list.
   1279                     mUiChildren.remove(index);
   1280                     mUiChildren.add(uiChild);
   1281                 } else if (mand == Mandatory.NOT_MANDATORY || uiChild.getXmlNode() != null) {
   1282                     // We found at least one non-mandatory or a mandatory node with an actual
   1283                     // XML attached, so there's nothing we can reorganize past this point.
   1284                     break;
   1285                 }
   1286             }
   1287         }
   1288 
   1289         return structureChanged;
   1290     }
   1291 
   1292     /**
   1293      * Internal helper to remove an UI child node given by its index in the
   1294      * internal child list.
   1295      *
   1296      * Also invokes the update listener on the node to be deleted *after* the node has
   1297      * been removed.
   1298      *
   1299      * @param uiIndex The index of the UI child to remove, range 0 .. mUiChildren.size()-1
   1300      * @return True if the structure has changed
   1301      * @throws IndexOutOfBoundsException if index is out of mUiChildren's bounds. Of course you
   1302      *         know that could never happen unless the computer is on fire or something.
   1303      */
   1304     private boolean removeUiChildAtIndex(int uiIndex) {
   1305         UiElementNode uiNode = mUiChildren.get(uiIndex);
   1306         ElementDescriptor desc = uiNode.getDescriptor();
   1307 
   1308         try {
   1309             if (uiNode.getDescriptor().getMandatory() != Mandatory.NOT_MANDATORY) {
   1310                 // This is a mandatory node. Such a node must exist in the UiNode hierarchy
   1311                 // even if there's no XML counterpart. However we only need to keep one.
   1312 
   1313                 // Check if the parent (e.g. this node) has another similar ui child node.
   1314                 boolean keepNode = true;
   1315                 for (UiElementNode child : mUiChildren) {
   1316                     if (child != uiNode && child.getDescriptor() == desc) {
   1317                         // We found another child with the same descriptor that is not
   1318                         // the node we want to remove. This means we have one mandatory
   1319                         // node so we can safely remove uiNode.
   1320                         keepNode = false;
   1321                         break;
   1322                     }
   1323                 }
   1324 
   1325                 if (keepNode) {
   1326                     // We can't remove a mandatory node as we need to keep at least one
   1327                     // mandatory node in the parent. Instead we just clear its content
   1328                     // (including its XML Node reference).
   1329 
   1330                     // A mandatory node with no XML means it doesn't really exist, so it can't be
   1331                     // deleted. So the structure will change only if the ui node is actually
   1332                     // associated to an XML node.
   1333                     boolean xmlExists = (uiNode.getXmlNode() != null);
   1334 
   1335                     uiNode.clearContent();
   1336                     return xmlExists;
   1337                 }
   1338             }
   1339 
   1340             mUiChildren.remove(uiIndex);
   1341 
   1342             return true;
   1343         } finally {
   1344             // Tell listeners that a node has been removed.
   1345             // The model has already been modified.
   1346             invokeUiUpdateListeners(UiUpdateState.DELETED);
   1347         }
   1348     }
   1349 
   1350     /**
   1351      * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
   1352      * and appends it to the end of the element children list.
   1353      *
   1354      * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
   1355      * @return The new UI node that has been appended
   1356      */
   1357     public UiElementNode appendNewUiChild(ElementDescriptor descriptor) {
   1358         UiElementNode uiNode;
   1359         uiNode = descriptor.createUiNode();
   1360         mUiChildren.add(uiNode);
   1361         uiNode.setUiParent(this);
   1362         uiNode.invokeUiUpdateListeners(UiUpdateState.CREATED);
   1363         return uiNode;
   1364     }
   1365 
   1366     /**
   1367      * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
   1368      * and inserts it in the element children list at the specified position.
   1369      *
   1370      * @param index The position where to insert in the element children list.
   1371      *              Shifts the element currently at that position (if any) and any
   1372      *              subsequent elements to the right (adds one to their indices).
   1373      *              Index must >= 0 and <= getUiChildren.size().
   1374      *              Using size() means to append to the end of the list.
   1375      * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
   1376      * @return The new UI node.
   1377      */
   1378     public UiElementNode insertNewUiChild(int index, ElementDescriptor descriptor) {
   1379         UiElementNode uiNode;
   1380         uiNode = descriptor.createUiNode();
   1381         mUiChildren.add(index, uiNode);
   1382         uiNode.setUiParent(this);
   1383         uiNode.invokeUiUpdateListeners(UiUpdateState.CREATED);
   1384         return uiNode;
   1385     }
   1386 
   1387     /**
   1388      * Updates the {@link UiAttributeNode} list for this {@link UiElementNode}
   1389      * using the values from the XML element.
   1390      * <p/>
   1391      * For a given {@link UiElementNode}, the attribute list always exists in
   1392      * full and is totally independent of whether the XML model actually
   1393      * has the corresponding attributes.
   1394      * <p/>
   1395      * For each attribute declared in this {@link UiElementNode}, get
   1396      * the corresponding XML attribute. It may not exist, in which case the
   1397      * value will be null. We don't really know if a value has changed, so
   1398      * the updateValue() is called on the UI attribute in all cases.
   1399      *
   1400      * @param xmlNode The XML node to mirror
   1401      */
   1402     protected void updateAttributeList(Node xmlNode) {
   1403         NamedNodeMap xmlAttrMap = xmlNode.getAttributes();
   1404         HashSet<Node> visited = new HashSet<Node>();
   1405 
   1406         // For all known (i.e. expected) UI attributes, find an existing XML attribute of
   1407         // same (uri, local name) and update the internal Ui attribute value.
   1408         for (UiAttributeNode uiAttr : getInternalUiAttributes().values()) {
   1409             AttributeDescriptor desc = uiAttr.getDescriptor();
   1410             if (!(desc instanceof SeparatorAttributeDescriptor)) {
   1411                 Node xmlAttr = xmlAttrMap == null ? null :
   1412                     xmlAttrMap.getNamedItemNS(desc.getNamespaceUri(), desc.getXmlLocalName());
   1413                 uiAttr.updateValue(xmlAttr);
   1414                 visited.add(xmlAttr);
   1415             }
   1416         }
   1417 
   1418         // Clone the current list of unknown attributes. We'll then remove from this list when
   1419         // we find attributes which are still unknown. What will be left are the old unknown
   1420         // attributes that have been deleted in the current XML attribute list.
   1421         @SuppressWarnings("unchecked")
   1422         HashSet<UiAttributeNode> deleted = (HashSet<UiAttributeNode>) mUnknownUiAttributes.clone();
   1423 
   1424         // We need to ignore hidden attributes.
   1425         Map<String, AttributeDescriptor> hiddenAttrDesc = getHiddenAttributeDescriptors();
   1426 
   1427         // Traverse the actual XML attribute list to find unknown attributes
   1428         if (xmlAttrMap != null) {
   1429             for (int i = 0; i < xmlAttrMap.getLength(); i++) {
   1430                 Node xmlAttr = xmlAttrMap.item(i);
   1431                 // Ignore attributes which have actual descriptors
   1432                 if (visited.contains(xmlAttr)) {
   1433                     continue;
   1434                 }
   1435 
   1436                 String xmlFullName = xmlAttr.getNodeName();
   1437 
   1438                 // Ignore attributes which are hidden (based on the prefix:localName key)
   1439                 if (hiddenAttrDesc.containsKey(xmlFullName)) {
   1440                     continue;
   1441                 }
   1442 
   1443                 String xmlAttrLocalName = xmlAttr.getLocalName();
   1444                 String xmlNsUri = xmlAttr.getNamespaceURI();
   1445 
   1446                 UiAttributeNode uiAttr = null;
   1447                 for (UiAttributeNode a : mUnknownUiAttributes) {
   1448                     String aLocalName = a.getDescriptor().getXmlLocalName();
   1449                     String aNsUri = a.getDescriptor().getNamespaceUri();
   1450                     if (aLocalName.equals(xmlAttrLocalName) &&
   1451                             (aNsUri == xmlNsUri || (aNsUri != null && aNsUri.equals(xmlNsUri)))) {
   1452                         // This attribute is still present in the unknown list
   1453                         uiAttr = a;
   1454                         // It has not been deleted
   1455                         deleted.remove(a);
   1456                         break;
   1457                     }
   1458                 }
   1459                 if (uiAttr == null) {
   1460                     uiAttr = addUnknownAttribute(xmlFullName, xmlAttrLocalName, xmlNsUri);
   1461                 }
   1462 
   1463                 uiAttr.updateValue(xmlAttr);
   1464             }
   1465 
   1466             // Remove from the internal list unknown attributes that have been deleted from the xml
   1467             for (UiAttributeNode a : deleted) {
   1468                 mUnknownUiAttributes.remove(a);
   1469                 mCachedAllUiAttributes = null;
   1470             }
   1471         }
   1472     }
   1473 
   1474     /**
   1475      * Create a new temporary text attribute descriptor for the unknown attribute
   1476      * and returns a new {@link UiAttributeNode} associated to this descriptor.
   1477      * <p/>
   1478      * The attribute is not marked as dirty, doing so is up to the caller.
   1479      */
   1480     private UiAttributeNode addUnknownAttribute(String xmlFullName,
   1481             String xmlAttrLocalName, String xmlNsUri) {
   1482         // Create a new unknown attribute of format string
   1483         TextAttributeDescriptor desc = new TextAttributeDescriptor(
   1484                 xmlAttrLocalName,           // xml name
   1485                 xmlNsUri,                // ui name
   1486                 new AttributeInfo(xmlAttrLocalName, Format.STRING_SET)
   1487                 );
   1488         UiAttributeNode uiAttr = desc.createUiNode(this);
   1489         mUnknownUiAttributes.add(uiAttr);
   1490         mCachedAllUiAttributes = null;
   1491         return uiAttr;
   1492     }
   1493 
   1494     /**
   1495      * Invoke all registered {@link IUiUpdateListener} listening on this UI update for this node.
   1496      */
   1497     protected void invokeUiUpdateListeners(UiUpdateState state) {
   1498         if (mUiUpdateListeners != null) {
   1499             for (IUiUpdateListener listener : mUiUpdateListeners) {
   1500                 try {
   1501                     listener.uiElementNodeUpdated(this, state);
   1502                 } catch (Exception e) {
   1503                     // prevent a crashing listener from crashing the whole invocation chain
   1504                     AdtPlugin.log(e, "UIElement Listener failed: %s, state=%s",  //$NON-NLS-1$
   1505                             getBreadcrumbTrailDescription(true),
   1506                             state.toString());
   1507                 }
   1508             }
   1509         }
   1510     }
   1511 
   1512     // --- for derived implementations only ---
   1513 
   1514     @VisibleForTesting
   1515     public void setXmlNode(Node xmlNode) {
   1516         mXmlNode = xmlNode;
   1517     }
   1518 
   1519     public void refreshUi() {
   1520         invokeUiUpdateListeners(UiUpdateState.ATTR_UPDATED);
   1521     }
   1522 
   1523 
   1524     // ------------- Helpers
   1525 
   1526     /**
   1527      * Helper method to commit a single attribute value to XML.
   1528      * <p/>
   1529      * This method updates the XML regardless of the current XML value.
   1530      * Callers should check first if an update is needed.
   1531      * If the new value is empty, the XML attribute will be actually removed.
   1532      * <p/>
   1533      * Note that the caller MUST ensure that modifying the underlying XML model is
   1534      * safe and must take care of marking the model as dirty if necessary.
   1535      *
   1536      * @see AndroidXmlEditor#wrapEditXmlModel(Runnable)
   1537      *
   1538      * @param uiAttr The attribute node to commit. Must be a child of this UiElementNode.
   1539      * @param newValue The new value to set.
   1540      * @return True if the XML attribute was modified or removed, false if nothing changed.
   1541      */
   1542     public boolean commitAttributeToXml(UiAttributeNode uiAttr, String newValue) {
   1543         // Get (or create) the underlying XML element node that contains the attributes.
   1544         Node element = prepareCommit();
   1545         if (element != null && uiAttr != null) {
   1546             String attrLocalName = uiAttr.getDescriptor().getXmlLocalName();
   1547             String attrNsUri = uiAttr.getDescriptor().getNamespaceUri();
   1548 
   1549             NamedNodeMap attrMap = element.getAttributes();
   1550             if (newValue == null || newValue.length() == 0) {
   1551                 // Remove attribute if it's empty
   1552                 if (attrMap.getNamedItemNS(attrNsUri, attrLocalName) != null) {
   1553                     attrMap.removeNamedItemNS(attrNsUri, attrLocalName);
   1554                     return true;
   1555                 }
   1556             } else {
   1557                 // Add or replace an attribute
   1558                 Document doc = element.getOwnerDocument();
   1559                 if (doc != null) {
   1560                     Attr attr;
   1561                     if (attrNsUri != null && attrNsUri.length() > 0) {
   1562                         attr = (Attr) attrMap.getNamedItemNS(attrNsUri, attrLocalName);
   1563                         if (attr == null) {
   1564                             attr = doc.createAttributeNS(attrNsUri, attrLocalName);
   1565                             attr.setPrefix(lookupNamespacePrefix(element, attrNsUri));
   1566                             attrMap.setNamedItemNS(attr);
   1567                         }
   1568                     } else {
   1569                         attr = (Attr) attrMap.getNamedItem(attrLocalName);
   1570                         if (attr == null) {
   1571                             attr = doc.createAttribute(attrLocalName);
   1572                             attrMap.setNamedItem(attr);
   1573                         }
   1574                     }
   1575                     attr.setValue(newValue);
   1576                     return true;
   1577                 }
   1578             }
   1579         }
   1580         return false;
   1581     }
   1582 
   1583     /**
   1584      * Helper method to commit all dirty attributes values to XML.
   1585      * <p/>
   1586      * This method is useful if {@link #setAttributeValue(String, String, String, boolean)} has
   1587      * been called more than once and all the attributes marked as dirty must be committed to
   1588      * the XML. It calls {@link #commitAttributeToXml(UiAttributeNode, String)} on each dirty
   1589      * attribute.
   1590      * <p/>
   1591      * Note that the caller MUST ensure that modifying the underlying XML model is
   1592      * safe and must take care of marking the model as dirty if necessary.
   1593      *
   1594      * @see AndroidXmlEditor#wrapEditXmlModel(Runnable)
   1595      *
   1596      * @return True if one or more values were actually modified or removed,
   1597      *         false if nothing changed.
   1598      */
   1599     @SuppressWarnings("null") // Eclipse is confused by the logic and gets it wrong
   1600     public boolean commitDirtyAttributesToXml() {
   1601         boolean result = false;
   1602         List<UiAttributeNode> dirtyAttributes = new ArrayList<UiAttributeNode>();
   1603         for (UiAttributeNode uiAttr : getAllUiAttributes()) {
   1604             if (uiAttr.isDirty()) {
   1605                 String value = uiAttr.getCurrentValue();
   1606                 if (value != null && value.length() > 0) {
   1607                     // Defer the new attributes: set these last and in order
   1608                     dirtyAttributes.add(uiAttr);
   1609                 } else {
   1610                     result |= commitAttributeToXml(uiAttr, value);
   1611                     uiAttr.setDirty(false);
   1612                 }
   1613             }
   1614         }
   1615         if (dirtyAttributes.size() > 0) {
   1616             result = true;
   1617 
   1618             Collections.sort(dirtyAttributes);
   1619 
   1620             // The Eclipse XML model will *always* append new attributes.
   1621             // Therefore, if any of the dirty attributes are new, they will appear
   1622             // after any existing, clean attributes on the element. To fix this,
   1623             // we need to first remove any of these attributes, then insert them
   1624             // back in the right order.
   1625             Node element = prepareCommit();
   1626             if (element == null) {
   1627                 return result;
   1628             }
   1629 
   1630             if (AdtPrefs.getPrefs().getFormatGuiXml() && getEditor().supportsFormatOnGuiEdit()) {
   1631                 // If auto formatting, don't bother with attribute sorting here since the
   1632                 // order will be corrected as soon as the edit is committed anyway
   1633                 for (UiAttributeNode uiAttribute : dirtyAttributes) {
   1634                     commitAttributeToXml(uiAttribute, uiAttribute.getCurrentValue());
   1635                     uiAttribute.setDirty(false);
   1636                 }
   1637 
   1638                 return result;
   1639             }
   1640 
   1641             AttributeDescriptor descriptor = dirtyAttributes.get(0).getDescriptor();
   1642             String firstName = descriptor.getXmlLocalName();
   1643             String firstNamePrefix = null;
   1644             if (descriptor.getNamespaceUri() != null) {
   1645                 firstNamePrefix = lookupNamespacePrefix(element, descriptor.getNamespaceUri());
   1646             }
   1647             NamedNodeMap attributes = ((Element) element).getAttributes();
   1648             List<Attr> move = new ArrayList<Attr>();
   1649             for (int i = 0, n = attributes.getLength(); i < n; i++) {
   1650                 Attr attribute = (Attr) attributes.item(i);
   1651                 if (UiAttributeNode.compareAttributes(
   1652                         attribute.getPrefix(), attribute.getLocalName(),
   1653                         firstNamePrefix, firstName) > 0) {
   1654                     move.add(attribute);
   1655                 }
   1656             }
   1657 
   1658             for (Attr attribute : move) {
   1659                 if (attribute.getNamespaceURI() != null) {
   1660                     attributes.removeNamedItemNS(attribute.getNamespaceURI(),
   1661                             attribute.getLocalName());
   1662                 } else {
   1663                     attributes.removeNamedItem(attribute.getName());
   1664                 }
   1665             }
   1666 
   1667             // Merge back the removed DOM attribute nodes and the new UI attribute nodes.
   1668             // In cases where the attribute DOM name and the UI attribute names equal,
   1669             // skip the DOM nodes and just apply the UI attributes.
   1670             int domAttributeIndex = 0;
   1671             int domAttributeIndexMax = move.size();
   1672             int uiAttributeIndex = 0;
   1673             int uiAttributeIndexMax = dirtyAttributes.size();
   1674 
   1675             while (true) {
   1676                 Attr domAttribute;
   1677                 UiAttributeNode uiAttribute;
   1678 
   1679                 int compare;
   1680                 if (uiAttributeIndex < uiAttributeIndexMax) {
   1681                     if (domAttributeIndex < domAttributeIndexMax) {
   1682                         domAttribute = move.get(domAttributeIndex);
   1683                         uiAttribute = dirtyAttributes.get(uiAttributeIndex);
   1684 
   1685                         String domAttributeName = domAttribute.getLocalName();
   1686                         String uiAttributeName = uiAttribute.getDescriptor().getXmlLocalName();
   1687                         compare = UiAttributeNode.compareAttributes(domAttributeName,
   1688                                 uiAttributeName);
   1689                     } else {
   1690                         compare = 1;
   1691                         uiAttribute = dirtyAttributes.get(uiAttributeIndex);
   1692                         domAttribute = null;
   1693                     }
   1694                 } else if (domAttributeIndex < domAttributeIndexMax) {
   1695                     compare = -1;
   1696                     domAttribute = move.get(domAttributeIndex);
   1697                     uiAttribute = null;
   1698                 } else {
   1699                     break;
   1700                 }
   1701 
   1702                 if (compare < 0) {
   1703                     if (domAttribute.getNamespaceURI() != null) {
   1704                         attributes.setNamedItemNS(domAttribute);
   1705                     } else {
   1706                         attributes.setNamedItem(domAttribute);
   1707                     }
   1708                     domAttributeIndex++;
   1709                 } else {
   1710                     assert compare >= 0;
   1711                     if (compare == 0) {
   1712                         domAttributeIndex++;
   1713                     }
   1714                     commitAttributeToXml(uiAttribute, uiAttribute.getCurrentValue());
   1715                     uiAttribute.setDirty(false);
   1716                     uiAttributeIndex++;
   1717                 }
   1718             }
   1719         }
   1720 
   1721         return result;
   1722     }
   1723 
   1724     /**
   1725      * Returns the namespace prefix matching the requested namespace URI.
   1726      * If no such declaration is found, returns the default "android" prefix for
   1727      * the Android URI, and "app" for other URI's.
   1728      *
   1729      * @param node The current node. Must not be null.
   1730      * @param nsUri The namespace URI of which the prefix is to be found,
   1731      *              e.g. SdkConstants.NS_RESOURCES
   1732      * @return The first prefix declared or the default "android" prefix
   1733      *              (or "app" for non-Android URIs)
   1734      */
   1735     public static String lookupNamespacePrefix(Node node, String nsUri) {
   1736         String defaultPrefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_NAME : "app"; //$NON-NLS-1$
   1737         return lookupNamespacePrefix(node, nsUri, defaultPrefix);
   1738     }
   1739 
   1740     /**
   1741      * Returns the namespace prefix matching the requested namespace URI.
   1742      * If no such declaration is found, returns the default "android" prefix.
   1743      *
   1744      * @param node The current node. Must not be null.
   1745      * @param nsUri The namespace URI of which the prefix is to be found,
   1746      *              e.g. SdkConstants.NS_RESOURCES
   1747      * @param defaultPrefix The default prefix (root) to use if the namespace
   1748      *              is not found. If null, do not create a new namespace
   1749      *              if this URI is not defined for the document.
   1750      * @return The first prefix declared or the provided prefix (possibly with
   1751      *              a number appended to avoid conflicts with existing prefixes.
   1752      */
   1753     public static String lookupNamespacePrefix(
   1754             @Nullable Node node, @Nullable String nsUri, @Nullable String defaultPrefix) {
   1755         // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
   1756         // The following code emulates this simple call:
   1757         //   String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES);
   1758 
   1759         // if the requested URI is null, it denotes an attribute with no namespace.
   1760         if (nsUri == null) {
   1761             return null;
   1762         }
   1763 
   1764         // per XML specification, the "xmlns" URI is reserved
   1765         if (XMLNS_URI.equals(nsUri)) {
   1766             return XMLNS;
   1767         }
   1768 
   1769         HashSet<String> visited = new HashSet<String>();
   1770         Document doc = node == null ? null : node.getOwnerDocument();
   1771 
   1772         // Ask the document about it. This method may not be implemented by the Document.
   1773         String nsPrefix = null;
   1774         try {
   1775             nsPrefix = doc != null ? doc.lookupPrefix(nsUri) : null;
   1776             if (nsPrefix != null) {
   1777                 return nsPrefix;
   1778             }
   1779         } catch (Throwable t) {
   1780             // ignore
   1781         }
   1782 
   1783         // If that failed, try to look it up manually.
   1784         // This also gathers prefixed in use in the case we want to generate a new one below.
   1785         for (; node != null && node.getNodeType() == Node.ELEMENT_NODE;
   1786                node = node.getParentNode()) {
   1787             NamedNodeMap attrs = node.getAttributes();
   1788             for (int n = attrs.getLength() - 1; n >= 0; --n) {
   1789                 Node attr = attrs.item(n);
   1790                 if (XMLNS.equals(attr.getPrefix())) {
   1791                     String uri = attr.getNodeValue();
   1792                     nsPrefix = attr.getLocalName();
   1793                     // Is this the URI we are looking for? If yes, we found its prefix.
   1794                     if (nsUri.equals(uri)) {
   1795                         return nsPrefix;
   1796                     }
   1797                     visited.add(nsPrefix);
   1798                 }
   1799             }
   1800         }
   1801 
   1802         // Failed the find a prefix. Generate a new sensible default prefix, unless
   1803         // defaultPrefix was null in which case the caller does not want the document
   1804         // modified.
   1805         if (defaultPrefix == null) {
   1806             return null;
   1807         }
   1808 
   1809         //
   1810         // We need to make sure the prefix is not one that was declared in the scope
   1811         // visited above. Pick a unique prefix from the provided default prefix.
   1812         String prefix = defaultPrefix;
   1813         String base = prefix;
   1814         for (int i = 1; visited.contains(prefix); i++) {
   1815             prefix = base + Integer.toString(i);
   1816         }
   1817         // Also create & define this prefix/URI in the XML document as an attribute in the
   1818         // first element of the document.
   1819         if (doc != null) {
   1820             node = doc.getFirstChild();
   1821             while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
   1822                 node = node.getNextSibling();
   1823             }
   1824             if (node != null) {
   1825                 // This doesn't work:
   1826                 //Attr attr = doc.createAttributeNS(XMLNS_URI, prefix);
   1827                 //attr.setPrefix(XMLNS);
   1828                 //
   1829                 // Xerces throws
   1830                 //org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or
   1831                 // change an object in a way which is incorrect with regard to namespaces.
   1832                 //
   1833                 // Instead pass in the concatenated prefix. (This is covered by
   1834                 // the UiElementNodeTest#testCreateNameSpace() test.)
   1835                 Attr attr = doc.createAttributeNS(XMLNS_URI, XMLNS_PREFIX + prefix);
   1836                 attr.setValue(nsUri);
   1837                 node.getAttributes().setNamedItemNS(attr);
   1838             }
   1839         }
   1840 
   1841         return prefix;
   1842     }
   1843 
   1844     /**
   1845      * Utility method to internally set the value of a text attribute for the current
   1846      * UiElementNode.
   1847      * <p/>
   1848      * This method is a helper. It silently ignores the errors such as the requested
   1849      * attribute not being present in the element or attribute not being settable.
   1850      * It accepts inherited attributes (such as layout).
   1851      * <p/>
   1852      * This does not commit to the XML model. It does mark the attribute node as dirty.
   1853      * This is up to the caller.
   1854      *
   1855      * @see #commitAttributeToXml(UiAttributeNode, String)
   1856      * @see #commitDirtyAttributesToXml()
   1857      *
   1858      * @param attrXmlName The XML <em>local</em> name of the attribute to modify
   1859      * @param attrNsUri The namespace URI of the attribute.
   1860      *                  Can be null if the attribute uses the global namespace.
   1861      * @param value The new value for the attribute. If set to null, the attribute is removed.
   1862      * @param override True if the value must be set even if one already exists.
   1863      * @return The {@link UiAttributeNode} that has been modified or null.
   1864      */
   1865     public UiAttributeNode setAttributeValue(
   1866             String attrXmlName,
   1867             String attrNsUri,
   1868             String value,
   1869             boolean override) {
   1870         if (value == null) {
   1871             value = ""; //$NON-NLS-1$ -- this removes an attribute
   1872         }
   1873 
   1874         getEditor().scheduleNodeReformat(this, true);
   1875 
   1876         // Try with all internal attributes
   1877         UiAttributeNode uiAttr = setInternalAttrValue(
   1878                 getAllUiAttributes(), attrXmlName, attrNsUri, value, override);
   1879         if (uiAttr != null) {
   1880             return uiAttr;
   1881         }
   1882 
   1883         if (uiAttr == null) {
   1884             // Failed to find the attribute. For non-android attributes that is mostly expected,
   1885             // in which case we just create a new custom one. As a side effect, we'll find the
   1886             // attribute descriptor via getAllUiAttributes().
   1887             addUnknownAttribute(attrXmlName, attrXmlName, attrNsUri);
   1888 
   1889             // We've created the attribute, but not actually set the value on it, so let's do it.
   1890             // Try with the updated internal attributes.
   1891             // Implementation detail: we could just do a setCurrentValue + setDirty on the
   1892             // uiAttr returned by addUnknownAttribute(); however going through setInternalAttrValue
   1893             // means we won't duplicate the logic, at the expense of doing one more lookup.
   1894             uiAttr = setInternalAttrValue(
   1895                     getAllUiAttributes(), attrXmlName, attrNsUri, value, override);
   1896         }
   1897 
   1898         return uiAttr;
   1899     }
   1900 
   1901     private UiAttributeNode setInternalAttrValue(
   1902             Collection<UiAttributeNode> attributes,
   1903             String attrXmlName,
   1904             String attrNsUri,
   1905             String value,
   1906             boolean override) {
   1907 
   1908         // For namespace less attributes (like the "layout" attribute of an <include> tag
   1909         // we may be passed "" as the namespace (during an attribute copy), and it
   1910         // should really be null instead.
   1911         if (attrNsUri != null && attrNsUri.length() == 0) {
   1912             attrNsUri = null;
   1913         }
   1914 
   1915         for (UiAttributeNode uiAttr : attributes) {
   1916             AttributeDescriptor uiDesc = uiAttr.getDescriptor();
   1917 
   1918             if (uiDesc.getXmlLocalName().equals(attrXmlName)) {
   1919                 // Both NS URI must be either null or equal.
   1920                 if ((attrNsUri == null && uiDesc.getNamespaceUri() == null) ||
   1921                         (attrNsUri != null && attrNsUri.equals(uiDesc.getNamespaceUri()))) {
   1922 
   1923                     // Not all attributes are editable, ignore those which are not.
   1924                     if (uiAttr instanceof IUiSettableAttributeNode) {
   1925                         String current = uiAttr.getCurrentValue();
   1926                         // Only update (and mark as dirty) if the attribute did not have any
   1927                         // value or if the value was different.
   1928                         if (override || current == null || !current.equals(value)) {
   1929                             ((IUiSettableAttributeNode) uiAttr).setCurrentValue(value);
   1930                             // mark the attribute as dirty since their internal content
   1931                             // as been modified, but not the underlying XML model
   1932                             uiAttr.setDirty(true);
   1933                             return uiAttr;
   1934                         }
   1935                     }
   1936 
   1937                     // We found the attribute but it's not settable. Since attributes are
   1938                     // not duplicated, just abandon here.
   1939                     break;
   1940                 }
   1941             }
   1942         }
   1943 
   1944         return null;
   1945     }
   1946 
   1947     /**
   1948      * Utility method to retrieve the internal value of an attribute.
   1949      * <p/>
   1950      * Note that this retrieves the *field* value if the attribute has some UI, and
   1951      * not the actual XML value. They may differ if the attribute is dirty.
   1952      *
   1953      * @param attrXmlName The XML name of the attribute to modify
   1954      * @return The current internal value for the attribute or null in case of error.
   1955      */
   1956     public String getAttributeValue(String attrXmlName) {
   1957         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   1958 
   1959         for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
   1960             AttributeDescriptor uiDesc = entry.getKey();
   1961             if (uiDesc.getXmlLocalName().equals(attrXmlName)) {
   1962                 UiAttributeNode uiAttr = entry.getValue();
   1963                 return uiAttr.getCurrentValue();
   1964             }
   1965         }
   1966         return null;
   1967     }
   1968 
   1969     // ------ IPropertySource methods
   1970 
   1971     @Override
   1972     public Object getEditableValue() {
   1973         return null;
   1974     }
   1975 
   1976     /*
   1977      * (non-Javadoc)
   1978      * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors()
   1979      *
   1980      * Returns the property descriptor for this node. Since the descriptors are not linked to the
   1981      * data, the AttributeDescriptor are used directly.
   1982      */
   1983     @Override
   1984     public IPropertyDescriptor[] getPropertyDescriptors() {
   1985         List<IPropertyDescriptor> propDescs = new ArrayList<IPropertyDescriptor>();
   1986 
   1987         // get the standard descriptors
   1988         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   1989         Set<AttributeDescriptor> keys = attributeMap.keySet();
   1990 
   1991 
   1992         // we only want the descriptor that do implement the IPropertyDescriptor interface.
   1993         for (AttributeDescriptor key : keys) {
   1994             if (key instanceof IPropertyDescriptor) {
   1995                 propDescs.add((IPropertyDescriptor)key);
   1996             }
   1997         }
   1998 
   1999         // now get the descriptor from the unknown attributes
   2000         for (UiAttributeNode unknownNode : mUnknownUiAttributes) {
   2001             if (unknownNode.getDescriptor() instanceof IPropertyDescriptor) {
   2002                 propDescs.add((IPropertyDescriptor)unknownNode.getDescriptor());
   2003             }
   2004         }
   2005 
   2006         // TODO cache this maybe, as it's not going to change (except for unknown descriptors)
   2007         return propDescs.toArray(new IPropertyDescriptor[propDescs.size()]);
   2008     }
   2009 
   2010     /*
   2011      * (non-Javadoc)
   2012      * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java.lang.Object)
   2013      *
   2014      * Returns the value of a given property. The id is the result of IPropertyDescriptor.getId(),
   2015      * which return the AttributeDescriptor itself.
   2016      */
   2017     @Override
   2018     public Object getPropertyValue(Object id) {
   2019         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   2020 
   2021         UiAttributeNode attribute = attributeMap.get(id);
   2022 
   2023         if (attribute == null) {
   2024             // look for the id in the unknown attributes.
   2025             for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   2026                 if (id == unknownAttr.getDescriptor()) {
   2027                     return unknownAttr;
   2028                 }
   2029             }
   2030         }
   2031 
   2032         return attribute;
   2033     }
   2034 
   2035     /*
   2036      * (non-Javadoc)
   2037      * @see org.eclipse.ui.views.properties.IPropertySource#isPropertySet(java.lang.Object)
   2038      *
   2039      * Returns whether the property is set. In our case this is if the string is non empty.
   2040      */
   2041     @Override
   2042     public boolean isPropertySet(Object id) {
   2043         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   2044 
   2045         UiAttributeNode attribute = attributeMap.get(id);
   2046 
   2047         if (attribute != null) {
   2048             return attribute.getCurrentValue().length() > 0;
   2049         }
   2050 
   2051         // look for the id in the unknown attributes.
   2052         for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   2053             if (id == unknownAttr.getDescriptor()) {
   2054                 return unknownAttr.getCurrentValue().length() > 0;
   2055             }
   2056         }
   2057 
   2058         return false;
   2059     }
   2060 
   2061     /*
   2062      * (non-Javadoc)
   2063      * @see org.eclipse.ui.views.properties.IPropertySource#resetPropertyValue(java.lang.Object)
   2064      *
   2065      * Reset the property to its default value. For now we simply empty it.
   2066      */
   2067     @Override
   2068     public void resetPropertyValue(Object id) {
   2069         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   2070 
   2071         UiAttributeNode attribute = attributeMap.get(id);
   2072         if (attribute != null) {
   2073             // TODO: reset the value of the attribute
   2074 
   2075             return;
   2076         }
   2077 
   2078         // look for the id in the unknown attributes.
   2079         for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   2080             if (id == unknownAttr.getDescriptor()) {
   2081                 // TODO: reset the value of the attribute
   2082 
   2083                 return;
   2084             }
   2085         }
   2086     }
   2087 
   2088     /*
   2089      * (non-Javadoc)
   2090      * @see org.eclipse.ui.views.properties.IPropertySource#setPropertyValue(java.lang.Object, java.lang.Object)
   2091      *
   2092      * Set the property value. id is the result of IPropertyDescriptor.getId(), which is the
   2093      * AttributeDescriptor itself. Value should be a String.
   2094      */
   2095     @Override
   2096     public void setPropertyValue(Object id, Object value) {
   2097         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   2098 
   2099         UiAttributeNode attribute = attributeMap.get(id);
   2100 
   2101         if (attribute == null) {
   2102             // look for the id in the unknown attributes.
   2103             for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   2104                 if (id == unknownAttr.getDescriptor()) {
   2105                     attribute = unknownAttr;
   2106                     break;
   2107                 }
   2108             }
   2109         }
   2110 
   2111         if (attribute != null) {
   2112 
   2113             // get the current value and compare it to the new value
   2114             String oldValue = attribute.getCurrentValue();
   2115             final String newValue = (String)value;
   2116 
   2117             if (oldValue.equals(newValue)) {
   2118                 return;
   2119             }
   2120 
   2121             final UiAttributeNode fAttribute = attribute;
   2122             AndroidXmlEditor editor = getEditor();
   2123             editor.wrapEditXmlModel(new Runnable() {
   2124                 @Override
   2125                 public void run() {
   2126                     commitAttributeToXml(fAttribute, newValue);
   2127                 }
   2128             });
   2129         }
   2130     }
   2131 
   2132     /**
   2133      * Returns true if this node is an ancestor (parent, grandparent, and so on)
   2134      * of the given node. Note that a node is not considered an ancestor of
   2135      * itself.
   2136      *
   2137      * @param node the node to test
   2138      * @return true if this node is an ancestor of the given node
   2139      */
   2140     public boolean isAncestorOf(UiElementNode node) {
   2141         node = node.getUiParent();
   2142         while (node != null) {
   2143             if (node == this) {
   2144                 return true;
   2145             }
   2146             node = node.getUiParent();
   2147         }
   2148         return false;
   2149     }
   2150 
   2151     /**
   2152      * Finds the nearest common parent of the two given nodes (which could be one of the
   2153      * two nodes as well)
   2154      *
   2155      * @param node1 the first node to test
   2156      * @param node2 the second node to test
   2157      * @return the nearest common parent of the two given nodes
   2158      */
   2159     public static UiElementNode getCommonAncestor(UiElementNode node1, UiElementNode node2) {
   2160         while (node2 != null) {
   2161             UiElementNode current = node1;
   2162             while (current != null && current != node2) {
   2163                 current = current.getUiParent();
   2164             }
   2165             if (current == node2) {
   2166                 return current;
   2167             }
   2168             node2 = node2.getUiParent();
   2169         }
   2170 
   2171         return null;
   2172     }
   2173 
   2174     // ---- Global node create/delete Listeners ----
   2175 
   2176     /** List of listeners to be notified of newly created nodes, or null */
   2177     private static List<NodeCreationListener> sListeners;
   2178 
   2179     /** Notify listeners that a new node has been created */
   2180     private void fireNodeCreated(UiElementNode newChild, int index) {
   2181         // Nothing to do if there aren't any listeners. We don't need to worry about
   2182         // the case where one thread is firing node changes while another is adding a listener
   2183         // (in that case it's still okay for this node firing not to be heard) so perform
   2184         // the check outside of synchronization.
   2185         if (sListeners == null) {
   2186             return;
   2187         }
   2188         synchronized (UiElementNode.class) {
   2189             if (sListeners != null) {
   2190                 UiElementNode parent = newChild.getUiParent();
   2191                 for (NodeCreationListener listener : sListeners) {
   2192                     listener.nodeCreated(parent, newChild, index);
   2193                 }
   2194             }
   2195         }
   2196     }
   2197 
   2198     /** Notify listeners that a new node has been deleted */
   2199     private void fireNodeDeleted(UiElementNode oldChild, int index) {
   2200         if (sListeners == null) {
   2201             return;
   2202         }
   2203         synchronized (UiElementNode.class) {
   2204             if (sListeners != null) {
   2205                 UiElementNode parent = oldChild.getUiParent();
   2206                 for (NodeCreationListener listener : sListeners) {
   2207                     listener.nodeDeleted(parent, oldChild, index);
   2208                 }
   2209             }
   2210         }
   2211     }
   2212 
   2213     /**
   2214      * Adds a {@link NodeCreationListener} to be notified when new nodes are created
   2215      *
   2216      * @param listener the listener to be notified
   2217      */
   2218     public static void addNodeCreationListener(NodeCreationListener listener) {
   2219         synchronized (UiElementNode.class) {
   2220             if (sListeners == null) {
   2221                 sListeners = new ArrayList<NodeCreationListener>(1);
   2222             }
   2223             sListeners.add(listener);
   2224         }
   2225     }
   2226 
   2227     /**
   2228      * Removes a {@link NodeCreationListener} from the set of listeners such that it is
   2229      * no longer notified when nodes are created.
   2230      *
   2231      * @param listener the listener to be removed from the notification list
   2232      */
   2233     public static void removeNodeCreationListener(NodeCreationListener listener) {
   2234         synchronized (UiElementNode.class) {
   2235             sListeners.remove(listener);
   2236             if (sListeners.size() == 0) {
   2237                 sListeners = null;
   2238             }
   2239         }
   2240     }
   2241 
   2242     /** Interface implemented by listeners to be notified of newly created nodes */
   2243     public interface NodeCreationListener {
   2244         /**
   2245          * Called when a new child node is created and added to the given parent
   2246          *
   2247          * @param parent the parent of the created node
   2248          * @param child the newly node
   2249          * @param index the index among the siblings of the child <b>after</b>
   2250          *            insertion
   2251          */
   2252         void nodeCreated(UiElementNode parent, UiElementNode child, int index);
   2253 
   2254         /**
   2255          * Called when a child node is removed from the given parent
   2256          *
   2257          * @param parent the parent of the removed node
   2258          * @param child the removed node
   2259          * @param previousIndex the index among the siblings of the child
   2260          *            <b>before</b> removal
   2261          */
   2262         void nodeDeleted(UiElementNode parent, UiElementNode child, int previousIndex);
   2263     }
   2264 }
   2265