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