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         invokeUiUpdateListeners(UiUpdateState.CREATED);
   1088         return mXmlNode;
   1089     }
   1090 
   1091     /**
   1092      * Removes the XML node corresponding to this UI node if it exists
   1093      * and also removes all mirrored information in this UI node (i.e. children, attributes)
   1094      *
   1095      * @return The removed node or null if it didn't exist in the first place.
   1096      */
   1097     public Node deleteXmlNode() {
   1098         if (mXmlNode == null) {
   1099             return null;
   1100         }
   1101 
   1102         // First clear the internals of the node and *then* actually deletes the XML
   1103         // node (because doing so will generate an update even and this node may be
   1104         // revisited via loadFromXmlNode).
   1105         Node oldXmlNode = mXmlNode;
   1106         clearContent();
   1107 
   1108         Node xmlParent = oldXmlNode.getParentNode();
   1109         if (xmlParent == null) {
   1110             xmlParent = getXmlDocument();
   1111         }
   1112         Node previousSibling = oldXmlNode.getPreviousSibling();
   1113         oldXmlNode = xmlParent.removeChild(oldXmlNode);
   1114 
   1115         // We need to remove the text node BEFORE the removed element, since THAT's the
   1116         // indentation node for the removed element.
   1117         if (previousSibling != null && previousSibling.getNodeType() == Node.TEXT_NODE
   1118                 && previousSibling.getNodeValue().trim().length() == 0) {
   1119             xmlParent.removeChild(previousSibling);
   1120         }
   1121 
   1122         invokeUiUpdateListeners(UiUpdateState.DELETED);
   1123         return oldXmlNode;
   1124     }
   1125 
   1126     /**
   1127      * Updates the element list for this UiElementNode.
   1128      * At the end, the list of children UiElementNode here will match the one from the
   1129      * provided XML {@link Node}:
   1130      * <ul>
   1131      * <li> Walk both the current ui children list and the xml children list at the same time.
   1132      * <li> If we have a new xml child but already reached the end of the ui child list, add the
   1133      *      new xml node.
   1134      * <li> Otherwise, check if the xml node is referenced later in the ui child list and if so,
   1135      *      move it here. It means the XML child list has been reordered.
   1136      * <li> Otherwise, this is a new XML node that we add in the middle of the ui child list.
   1137      * <li> At the end, we may have finished walking the xml child list but still have remaining
   1138      *      ui children, simply delete them as they matching trailing xml nodes that have been
   1139      *      removed unless they are mandatory ui nodes.
   1140      * </ul>
   1141      * Note that only the first case is used when populating the ui list the first time.
   1142      *
   1143      * @param xmlNode The XML node to mirror
   1144      * @return True when the XML structure has changed.
   1145      */
   1146     protected boolean updateElementList(Node xmlNode) {
   1147         boolean structureChanged = false;
   1148         boolean hasMandatoryLast = false;
   1149         int uiIndex = 0;
   1150         Node xmlChild = xmlNode.getFirstChild();
   1151         while (xmlChild != null) {
   1152             if (xmlChild.getNodeType() == Node.ELEMENT_NODE) {
   1153                 String elementName = xmlChild.getNodeName();
   1154                 UiElementNode uiNode = null;
   1155                 if (mUiChildren.size() <= uiIndex) {
   1156                     // A new node is being added at the end of the list
   1157                     ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
   1158                             false /* recursive */);
   1159                     if (desc == null) {
   1160                         // Unknown node. Create a temporary descriptor for it.
   1161                         // We'll add unknown attributes to it later.
   1162                         IUnknownDescriptorProvider p = getUnknownDescriptorProvider();
   1163                         desc = p.getDescriptor(elementName);
   1164                     }
   1165                     structureChanged = true;
   1166                     uiNode = appendNewUiChild(desc);
   1167                     uiIndex++;
   1168                 } else {
   1169                     // A new node is being inserted or moved.
   1170                     // Note: mandatory nodes can be created without an XML node in which case
   1171                     // getXmlNode() is null.
   1172                     UiElementNode uiChild;
   1173                     int n = mUiChildren.size();
   1174                     for (int j = uiIndex; j < n; j++) {
   1175                         uiChild = mUiChildren.get(j);
   1176                         if (uiChild.getXmlNode() != null && uiChild.getXmlNode() == xmlChild) {
   1177                             if (j > uiIndex) {
   1178                                 // Found the same XML node at some later index, now move it here.
   1179                                 mUiChildren.remove(j);
   1180                                 mUiChildren.add(uiIndex, uiChild);
   1181                                 structureChanged = true;
   1182                             }
   1183                             uiNode = uiChild;
   1184                             uiIndex++;
   1185                             break;
   1186                         }
   1187                     }
   1188 
   1189                     if (uiNode == null) {
   1190                         // Look for an unused mandatory node with no XML node attached
   1191                         // referencing the same XML element name
   1192                         for (int j = uiIndex; j < n; j++) {
   1193                             uiChild = mUiChildren.get(j);
   1194                             if (uiChild.getXmlNode() == null &&
   1195                                     uiChild.getDescriptor().getMandatory() !=
   1196                                                                 Mandatory.NOT_MANDATORY &&
   1197                                     uiChild.getDescriptor().getXmlName().equals(elementName)) {
   1198 
   1199                                 if (j > uiIndex) {
   1200                                     // Found it, now move it here
   1201                                     mUiChildren.remove(j);
   1202                                     mUiChildren.add(uiIndex, uiChild);
   1203                                 }
   1204                                 // Assign the XML node to this empty mandatory element.
   1205                                 uiChild.mXmlNode = xmlChild;
   1206                                 structureChanged = true;
   1207                                 uiNode = uiChild;
   1208                                 uiIndex++;
   1209                             }
   1210                         }
   1211                     }
   1212 
   1213                     if (uiNode == null) {
   1214                         // Inserting new node
   1215                         ElementDescriptor desc = mDescriptor.findChildrenDescriptor(elementName,
   1216                                 false /* recursive */);
   1217                         if (desc == null) {
   1218                             // Unknown element. Simply ignore it.
   1219                             AdtPlugin.log(IStatus.WARNING,
   1220                                     "AndroidManifest: Ignoring unknown '%s' XML element", //$NON-NLS-1$
   1221                                     elementName);
   1222                         } else {
   1223                             structureChanged = true;
   1224                             uiNode = insertNewUiChild(uiIndex, desc);
   1225                             uiIndex++;
   1226                         }
   1227                     }
   1228                 }
   1229                 if (uiNode != null) {
   1230                     // If we touched an UI Node, even an existing one, refresh its content.
   1231                     // For new nodes, this will populate them recursively.
   1232                     structureChanged |= uiNode.loadFromXmlNode(xmlChild);
   1233 
   1234                     // Remember if there are any mandatory-last nodes to reorder.
   1235                     hasMandatoryLast |=
   1236                         uiNode.getDescriptor().getMandatory() == Mandatory.MANDATORY_LAST;
   1237                 }
   1238             }
   1239             xmlChild = xmlChild.getNextSibling();
   1240         }
   1241 
   1242         // There might be extra UI nodes at the end if the XML node list got shorter.
   1243         for (int index = mUiChildren.size() - 1; index >= uiIndex; --index) {
   1244              structureChanged |= removeUiChildAtIndex(index);
   1245         }
   1246 
   1247         if (hasMandatoryLast) {
   1248             // At least one mandatory-last uiNode was moved. Let's see if we can
   1249             // move them back to the last position. That's possible if the only
   1250             // thing between these and the end are other mandatory empty uiNodes
   1251             // (mandatory uiNodes with no XML attached are pure "virtual" reserved
   1252             // slots and it's ok to reorganize them but other can't.)
   1253             int n = mUiChildren.size() - 1;
   1254             for (int index = n; index >= 0; index--) {
   1255                 UiElementNode uiChild = mUiChildren.get(index);
   1256                 Mandatory mand = uiChild.getDescriptor().getMandatory();
   1257                 if (mand == Mandatory.MANDATORY_LAST && index < n) {
   1258                     // Remove it from index and move it back at the end of the list.
   1259                     mUiChildren.remove(index);
   1260                     mUiChildren.add(uiChild);
   1261                 } else if (mand == Mandatory.NOT_MANDATORY || uiChild.getXmlNode() != null) {
   1262                     // We found at least one non-mandatory or a mandatory node with an actual
   1263                     // XML attached, so there's nothing we can reorganize past this point.
   1264                     break;
   1265                 }
   1266             }
   1267         }
   1268 
   1269         return structureChanged;
   1270     }
   1271 
   1272     /**
   1273      * Internal helper to remove an UI child node given by its index in the
   1274      * internal child list.
   1275      *
   1276      * Also invokes the update listener on the node to be deleted *after* the node has
   1277      * been removed.
   1278      *
   1279      * @param uiIndex The index of the UI child to remove, range 0 .. mUiChildren.size()-1
   1280      * @return True if the structure has changed
   1281      * @throws IndexOutOfBoundsException if index is out of mUiChildren's bounds. Of course you
   1282      *         know that could never happen unless the computer is on fire or something.
   1283      */
   1284     private boolean removeUiChildAtIndex(int uiIndex) {
   1285         UiElementNode uiNode = mUiChildren.get(uiIndex);
   1286         ElementDescriptor desc = uiNode.getDescriptor();
   1287 
   1288         try {
   1289             if (uiNode.getDescriptor().getMandatory() != Mandatory.NOT_MANDATORY) {
   1290                 // This is a mandatory node. Such a node must exist in the UiNode hierarchy
   1291                 // even if there's no XML counterpart. However we only need to keep one.
   1292 
   1293                 // Check if the parent (e.g. this node) has another similar ui child node.
   1294                 boolean keepNode = true;
   1295                 for (UiElementNode child : mUiChildren) {
   1296                     if (child != uiNode && child.getDescriptor() == desc) {
   1297                         // We found another child with the same descriptor that is not
   1298                         // the node we want to remove. This means we have one mandatory
   1299                         // node so we can safely remove uiNode.
   1300                         keepNode = false;
   1301                         break;
   1302                     }
   1303                 }
   1304 
   1305                 if (keepNode) {
   1306                     // We can't remove a mandatory node as we need to keep at least one
   1307                     // mandatory node in the parent. Instead we just clear its content
   1308                     // (including its XML Node reference).
   1309 
   1310                     // A mandatory node with no XML means it doesn't really exist, so it can't be
   1311                     // deleted. So the structure will change only if the ui node is actually
   1312                     // associated to an XML node.
   1313                     boolean xmlExists = (uiNode.getXmlNode() != null);
   1314 
   1315                     uiNode.clearContent();
   1316                     return xmlExists;
   1317                 }
   1318             }
   1319 
   1320             mUiChildren.remove(uiIndex);
   1321 
   1322             return true;
   1323         } finally {
   1324             // Tell listeners that a node has been removed.
   1325             // The model has already been modified.
   1326             invokeUiUpdateListeners(UiUpdateState.DELETED);
   1327         }
   1328     }
   1329 
   1330     /**
   1331      * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
   1332      * and appends it to the end of the element children list.
   1333      *
   1334      * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
   1335      * @return The new UI node that has been appended
   1336      */
   1337     public UiElementNode appendNewUiChild(ElementDescriptor descriptor) {
   1338         UiElementNode uiNode;
   1339         uiNode = descriptor.createUiNode();
   1340         mUiChildren.add(uiNode);
   1341         uiNode.setUiParent(this);
   1342         uiNode.invokeUiUpdateListeners(UiUpdateState.CREATED);
   1343         return uiNode;
   1344     }
   1345 
   1346     /**
   1347      * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
   1348      * and inserts it in the element children list at the specified position.
   1349      *
   1350      * @param index The position where to insert in the element children list.
   1351      *              Shifts the element currently at that position (if any) and any
   1352      *              subsequent elements to the right (adds one to their indices).
   1353      *              Index must >= 0 and <= getUiChildren.size().
   1354      *              Using size() means to append to the end of the list.
   1355      * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
   1356      * @return The new UI node.
   1357      */
   1358     public UiElementNode insertNewUiChild(int index, ElementDescriptor descriptor) {
   1359         UiElementNode uiNode;
   1360         uiNode = descriptor.createUiNode();
   1361         mUiChildren.add(index, uiNode);
   1362         uiNode.setUiParent(this);
   1363         uiNode.invokeUiUpdateListeners(UiUpdateState.CREATED);
   1364         return uiNode;
   1365     }
   1366 
   1367     /**
   1368      * Updates the {@link UiAttributeNode} list for this {@link UiElementNode}
   1369      * using the values from the XML element.
   1370      * <p/>
   1371      * For a given {@link UiElementNode}, the attribute list always exists in
   1372      * full and is totally independent of whether the XML model actually
   1373      * has the corresponding attributes.
   1374      * <p/>
   1375      * For each attribute declared in this {@link UiElementNode}, get
   1376      * the corresponding XML attribute. It may not exist, in which case the
   1377      * value will be null. We don't really know if a value has changed, so
   1378      * the updateValue() is called on the UI attribute in all cases.
   1379      *
   1380      * @param xmlNode The XML node to mirror
   1381      */
   1382     protected void updateAttributeList(Node xmlNode) {
   1383         NamedNodeMap xmlAttrMap = xmlNode.getAttributes();
   1384         HashSet<Node> visited = new HashSet<Node>();
   1385 
   1386         // For all known (i.e. expected) UI attributes, find an existing XML attribute of
   1387         // same (uri, local name) and update the internal Ui attribute value.
   1388         for (UiAttributeNode uiAttr : getInternalUiAttributes().values()) {
   1389             AttributeDescriptor desc = uiAttr.getDescriptor();
   1390             if (!(desc instanceof SeparatorAttributeDescriptor)) {
   1391                 Node xmlAttr = xmlAttrMap == null ? null :
   1392                     xmlAttrMap.getNamedItemNS(desc.getNamespaceUri(), desc.getXmlLocalName());
   1393                 uiAttr.updateValue(xmlAttr);
   1394                 visited.add(xmlAttr);
   1395             }
   1396         }
   1397 
   1398         // Clone the current list of unknown attributes. We'll then remove from this list when
   1399         // we find attributes which are still unknown. What will be left are the old unknown
   1400         // attributes that have been deleted in the current XML attribute list.
   1401         @SuppressWarnings("unchecked")
   1402         HashSet<UiAttributeNode> deleted = (HashSet<UiAttributeNode>) mUnknownUiAttributes.clone();
   1403 
   1404         // We need to ignore hidden attributes.
   1405         Map<String, AttributeDescriptor> hiddenAttrDesc = getHiddenAttributeDescriptors();
   1406 
   1407         // Traverse the actual XML attribute list to find unknown attributes
   1408         if (xmlAttrMap != null) {
   1409             for (int i = 0; i < xmlAttrMap.getLength(); i++) {
   1410                 Node xmlAttr = xmlAttrMap.item(i);
   1411                 // Ignore attributes which have actual descriptors
   1412                 if (visited.contains(xmlAttr)) {
   1413                     continue;
   1414                 }
   1415 
   1416                 String xmlFullName = xmlAttr.getNodeName();
   1417 
   1418                 // Ignore attributes which are hidden (based on the prefix:localName key)
   1419                 if (hiddenAttrDesc.containsKey(xmlFullName)) {
   1420                     continue;
   1421                 }
   1422 
   1423                 String xmlAttrLocalName = xmlAttr.getLocalName();
   1424                 String xmlNsUri = xmlAttr.getNamespaceURI();
   1425 
   1426                 UiAttributeNode uiAttr = null;
   1427                 for (UiAttributeNode a : mUnknownUiAttributes) {
   1428                     String aLocalName = a.getDescriptor().getXmlLocalName();
   1429                     String aNsUri = a.getDescriptor().getNamespaceUri();
   1430                     if (aLocalName.equals(xmlAttrLocalName) &&
   1431                             (aNsUri == xmlNsUri || (aNsUri != null && aNsUri.equals(xmlNsUri)))) {
   1432                         // This attribute is still present in the unknown list
   1433                         uiAttr = a;
   1434                         // It has not been deleted
   1435                         deleted.remove(a);
   1436                         break;
   1437                     }
   1438                 }
   1439                 if (uiAttr == null) {
   1440                     uiAttr = addUnknownAttribute(xmlFullName, xmlAttrLocalName, xmlNsUri);
   1441                 }
   1442 
   1443                 uiAttr.updateValue(xmlAttr);
   1444             }
   1445 
   1446             // Remove from the internal list unknown attributes that have been deleted from the xml
   1447             for (UiAttributeNode a : deleted) {
   1448                 mUnknownUiAttributes.remove(a);
   1449                 mCachedAllUiAttributes = null;
   1450             }
   1451         }
   1452     }
   1453 
   1454     /**
   1455      * Create a new temporary text attribute descriptor for the unknown attribute
   1456      * and returns a new {@link UiAttributeNode} associated to this descriptor.
   1457      * <p/>
   1458      * The attribute is not marked as dirty, doing so is up to the caller.
   1459      */
   1460     private UiAttributeNode addUnknownAttribute(String xmlFullName,
   1461             String xmlAttrLocalName, String xmlNsUri) {
   1462         // Create a new unknown attribute of format string
   1463         TextAttributeDescriptor desc = new TextAttributeDescriptor(
   1464                 xmlAttrLocalName,           // xml name
   1465                 xmlFullName,                // ui name
   1466                 xmlNsUri,                   // NS uri
   1467                 "Unknown XML attribute",    // tooltip, translatable
   1468                 new AttributeInfo(xmlAttrLocalName, new Format[] { Format.STRING } )
   1469                 );
   1470         UiAttributeNode uiAttr = desc.createUiNode(this);
   1471         mUnknownUiAttributes.add(uiAttr);
   1472         mCachedAllUiAttributes = null;
   1473         return uiAttr;
   1474     }
   1475 
   1476     /**
   1477      * Invoke all registered {@link IUiUpdateListener} listening on this UI update for this node.
   1478      */
   1479     protected void invokeUiUpdateListeners(UiUpdateState state) {
   1480         if (mUiUpdateListeners != null) {
   1481             for (IUiUpdateListener listener : mUiUpdateListeners) {
   1482                 try {
   1483                     listener.uiElementNodeUpdated(this, state);
   1484                 } catch (Exception e) {
   1485                     // prevent a crashing listener from crashing the whole invocation chain
   1486                     AdtPlugin.log(e, "UIElement Listener failed: %s, state=%s",  //$NON-NLS-1$
   1487                             getBreadcrumbTrailDescription(true),
   1488                             state.toString());
   1489                 }
   1490             }
   1491         }
   1492     }
   1493 
   1494     // --- for derived implementations only ---
   1495 
   1496     @VisibleForTesting
   1497     public void setXmlNode(Node xmlNode) {
   1498         mXmlNode = xmlNode;
   1499     }
   1500 
   1501     public void refreshUi() {
   1502         invokeUiUpdateListeners(UiUpdateState.ATTR_UPDATED);
   1503     }
   1504 
   1505 
   1506     // ------------- Helpers
   1507 
   1508     /**
   1509      * Helper method to commit a single attribute value to XML.
   1510      * <p/>
   1511      * This method updates the XML regardless of the current XML value.
   1512      * Callers should check first if an update is needed.
   1513      * If the new value is empty, the XML attribute will be actually removed.
   1514      * <p/>
   1515      * Note that the caller MUST ensure that modifying the underlying XML model is
   1516      * safe and must take care of marking the model as dirty if necessary.
   1517      *
   1518      * @see AndroidXmlEditor#wrapEditXmlModel(Runnable)
   1519      *
   1520      * @param uiAttr The attribute node to commit. Must be a child of this UiElementNode.
   1521      * @param newValue The new value to set.
   1522      * @return True if the XML attribute was modified or removed, false if nothing changed.
   1523      */
   1524     public boolean commitAttributeToXml(UiAttributeNode uiAttr, String newValue) {
   1525         // Get (or create) the underlying XML element node that contains the attributes.
   1526         Node element = prepareCommit();
   1527         if (element != null && uiAttr != null) {
   1528             String attrLocalName = uiAttr.getDescriptor().getXmlLocalName();
   1529             String attrNsUri = uiAttr.getDescriptor().getNamespaceUri();
   1530 
   1531             NamedNodeMap attrMap = element.getAttributes();
   1532             if (newValue == null || newValue.length() == 0) {
   1533                 // Remove attribute if it's empty
   1534                 if (attrMap.getNamedItemNS(attrNsUri, attrLocalName) != null) {
   1535                     attrMap.removeNamedItemNS(attrNsUri, attrLocalName);
   1536                     return true;
   1537                 }
   1538             } else {
   1539                 // Add or replace an attribute
   1540                 Document doc = element.getOwnerDocument();
   1541                 if (doc != null) {
   1542                     Attr attr;
   1543                     if (attrNsUri != null && attrNsUri.length() > 0) {
   1544                         attr = (Attr) attrMap.getNamedItemNS(attrNsUri, attrLocalName);
   1545                         if (attr == null) {
   1546                             attr = doc.createAttributeNS(attrNsUri, attrLocalName);
   1547                             attr.setPrefix(lookupNamespacePrefix(element, attrNsUri));
   1548                             attrMap.setNamedItemNS(attr);
   1549                         }
   1550                     } else {
   1551                         attr = (Attr) attrMap.getNamedItem(attrLocalName);
   1552                         if (attr == null) {
   1553                             attr = doc.createAttribute(attrLocalName);
   1554                             attrMap.setNamedItem(attr);
   1555                         }
   1556                     }
   1557                     attr.setValue(newValue);
   1558                     return true;
   1559                 }
   1560             }
   1561         }
   1562         return false;
   1563     }
   1564 
   1565     /**
   1566      * Helper method to commit all dirty attributes values to XML.
   1567      * <p/>
   1568      * This method is useful if {@link #setAttributeValue(String, String, String, boolean)} has
   1569      * been called more than once and all the attributes marked as dirty must be committed to
   1570      * the XML. It calls {@link #commitAttributeToXml(UiAttributeNode, String)} on each dirty
   1571      * attribute.
   1572      * <p/>
   1573      * Note that the caller MUST ensure that modifying the underlying XML model is
   1574      * safe and must take care of marking the model as dirty if necessary.
   1575      *
   1576      * @see AndroidXmlEditor#wrapEditXmlModel(Runnable)
   1577      *
   1578      * @return True if one or more values were actually modified or removed,
   1579      *         false if nothing changed.
   1580      */
   1581     @SuppressWarnings("null") // Eclipse is confused by the logic and gets it wrong
   1582     public boolean commitDirtyAttributesToXml() {
   1583         boolean result = false;
   1584         List<UiAttributeNode> dirtyAttributes = new ArrayList<UiAttributeNode>();
   1585         for (UiAttributeNode uiAttr : getAllUiAttributes()) {
   1586             if (uiAttr.isDirty()) {
   1587                 String value = uiAttr.getCurrentValue();
   1588                 if (value != null && value.length() > 0) {
   1589                     // Defer the new attributes: set these last and in order
   1590                     dirtyAttributes.add(uiAttr);
   1591                 } else {
   1592                     result |= commitAttributeToXml(uiAttr, value);
   1593                     uiAttr.setDirty(false);
   1594                 }
   1595             }
   1596         }
   1597         if (dirtyAttributes.size() > 0) {
   1598             result = true;
   1599 
   1600             Collections.sort(dirtyAttributes);
   1601 
   1602             // The Eclipse XML model will *always* append new attributes.
   1603             // Therefore, if any of the dirty attributes are new, they will appear
   1604             // after any existing, clean attributes on the element. To fix this,
   1605             // we need to first remove any of these attributes, then insert them
   1606             // back in the right order.
   1607             Node element = prepareCommit();
   1608             if (element == null) {
   1609                 return result;
   1610             }
   1611 
   1612             if (AdtPrefs.getPrefs().getFormatGuiXml() && getEditor().supportsFormatOnGuiEdit()) {
   1613                 // If auto formatting, don't bother with attribute sorting here since the
   1614                 // order will be corrected as soon as the edit is committed anyway
   1615                 for (UiAttributeNode uiAttribute : dirtyAttributes) {
   1616                     commitAttributeToXml(uiAttribute, uiAttribute.getCurrentValue());
   1617                     uiAttribute.setDirty(false);
   1618                 }
   1619 
   1620                 return result;
   1621             }
   1622 
   1623             String firstName = dirtyAttributes.get(0).getDescriptor().getXmlLocalName();
   1624             NamedNodeMap attributes = ((Element) element).getAttributes();
   1625             List<Attr> move = new ArrayList<Attr>();
   1626             for (int i = 0, n = attributes.getLength(); i < n; i++) {
   1627                 Attr attribute = (Attr) attributes.item(i);
   1628                 if (UiAttributeNode.compareAttributes(attribute.getLocalName(), firstName) > 0) {
   1629                     move.add(attribute);
   1630                 }
   1631             }
   1632 
   1633             for (Attr attribute : move) {
   1634                 if (attribute.getNamespaceURI() != null) {
   1635                     attributes.removeNamedItemNS(attribute.getNamespaceURI(),
   1636                             attribute.getLocalName());
   1637                 } else {
   1638                     attributes.removeNamedItem(attribute.getName());
   1639                 }
   1640             }
   1641 
   1642             // Merge back the removed DOM attribute nodes and the new UI attribute nodes.
   1643             // In cases where the attribute DOM name and the UI attribute names equal,
   1644             // skip the DOM nodes and just apply the UI attributes.
   1645             int domAttributeIndex = 0;
   1646             int domAttributeIndexMax = move.size();
   1647             int uiAttributeIndex = 0;
   1648             int uiAttributeIndexMax = dirtyAttributes.size();
   1649 
   1650             while (true) {
   1651                 Attr domAttribute;
   1652                 UiAttributeNode uiAttribute;
   1653 
   1654                 int compare;
   1655                 if (uiAttributeIndex < uiAttributeIndexMax) {
   1656                     if (domAttributeIndex < domAttributeIndexMax) {
   1657                         domAttribute = move.get(domAttributeIndex);
   1658                         uiAttribute = dirtyAttributes.get(uiAttributeIndex);
   1659 
   1660                         String domAttributeName = domAttribute.getLocalName();
   1661                         String uiAttributeName = uiAttribute.getDescriptor().getXmlLocalName();
   1662                         compare = UiAttributeNode.compareAttributes(domAttributeName,
   1663                                 uiAttributeName);
   1664                     } else {
   1665                         compare = 1;
   1666                         uiAttribute = dirtyAttributes.get(uiAttributeIndex);
   1667                         domAttribute = null;
   1668                     }
   1669                 } else if (domAttributeIndex < domAttributeIndexMax) {
   1670                     compare = -1;
   1671                     domAttribute = move.get(domAttributeIndex);
   1672                     uiAttribute = null;
   1673                 } else {
   1674                     break;
   1675                 }
   1676 
   1677                 if (compare < 0) {
   1678                     if (domAttribute.getNamespaceURI() != null) {
   1679                         attributes.setNamedItemNS(domAttribute);
   1680                     } else {
   1681                         attributes.setNamedItem(domAttribute);
   1682                     }
   1683                     domAttributeIndex++;
   1684                 } else {
   1685                     assert compare >= 0;
   1686                     if (compare == 0) {
   1687                         domAttributeIndex++;
   1688                     }
   1689                     commitAttributeToXml(uiAttribute, uiAttribute.getCurrentValue());
   1690                     uiAttribute.setDirty(false);
   1691                     uiAttributeIndex++;
   1692                 }
   1693             }
   1694         }
   1695 
   1696         return result;
   1697     }
   1698 
   1699     /**
   1700      * Returns the namespace prefix matching the requested namespace URI.
   1701      * If no such declaration is found, returns the default "android" prefix.
   1702      *
   1703      * @param node The current node. Must not be null.
   1704      * @param nsUri The namespace URI of which the prefix is to be found,
   1705      *              e.g. SdkConstants.NS_RESOURCES
   1706      * @return The first prefix declared or the default "android" prefix.
   1707      */
   1708     public static String lookupNamespacePrefix(Node node, String nsUri) {
   1709         // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
   1710         // The following code emulates this simple call:
   1711         //   String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES);
   1712 
   1713         // if the requested URI is null, it denotes an attribute with no namespace.
   1714         if (nsUri == null) {
   1715             return null;
   1716         }
   1717 
   1718         // per XML specification, the "xmlns" URI is reserved
   1719         if (XMLNS_URI.equals(nsUri)) {
   1720             return XMLNS;
   1721         }
   1722 
   1723         HashSet<String> visited = new HashSet<String>();
   1724         Document doc = node == null ? null : node.getOwnerDocument();
   1725 
   1726         // Ask the document about it. This method may not be implemented by the Document.
   1727         String nsPrefix = null;
   1728         try {
   1729             nsPrefix = doc != null ? doc.lookupPrefix(nsUri) : null;
   1730             if (nsPrefix != null) {
   1731                 return nsPrefix;
   1732             }
   1733         } catch (Throwable t) {
   1734             // ignore
   1735         }
   1736 
   1737         // If that failed, try to look it up manually.
   1738         // This also gathers prefixed in use in the case we want to generate a new one below.
   1739         for (; node != null && node.getNodeType() == Node.ELEMENT_NODE;
   1740                node = node.getParentNode()) {
   1741             NamedNodeMap attrs = node.getAttributes();
   1742             for (int n = attrs.getLength() - 1; n >= 0; --n) {
   1743                 Node attr = attrs.item(n);
   1744                 if (XMLNS.equals(attr.getPrefix())) {
   1745                     String uri = attr.getNodeValue();
   1746                     nsPrefix = attr.getLocalName();
   1747                     // Is this the URI we are looking for? If yes, we found its prefix.
   1748                     if (nsUri.equals(uri)) {
   1749                         return nsPrefix;
   1750                     }
   1751                     visited.add(nsPrefix);
   1752                 }
   1753             }
   1754         }
   1755 
   1756         // Failed the find a prefix. Generate a new sensible default prefix.
   1757         //
   1758         // We need to make sure the prefix is not one that was declared in the scope
   1759         // visited above. Use a default namespace prefix "android" for the Android resource
   1760         // NS and use "ns" for all other custom namespaces.
   1761         String prefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_NAME : "ns"; //$NON-NLS-1$
   1762         String base = prefix;
   1763         for (int i = 1; visited.contains(prefix); i++) {
   1764             prefix = base + Integer.toString(i);
   1765         }
   1766         // Also create & define this prefix/URI in the XML document as an attribute in the
   1767         // first element of the document.
   1768         if (doc != null) {
   1769             node = doc.getFirstChild();
   1770             while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
   1771                 node = node.getNextSibling();
   1772             }
   1773             if (node != null) {
   1774                 Attr attr = doc.createAttributeNS(XMLNS_URI, prefix);
   1775                 attr.setValue(nsUri);
   1776                 attr.setPrefix(XMLNS);
   1777                 node.getAttributes().setNamedItemNS(attr);
   1778             }
   1779         }
   1780 
   1781         return prefix;
   1782     }
   1783 
   1784     /**
   1785      * Utility method to internally set the value of a text attribute for the current
   1786      * UiElementNode.
   1787      * <p/>
   1788      * This method is a helper. It silently ignores the errors such as the requested
   1789      * attribute not being present in the element or attribute not being settable.
   1790      * It accepts inherited attributes (such as layout).
   1791      * <p/>
   1792      * This does not commit to the XML model. It does mark the attribute node as dirty.
   1793      * This is up to the caller.
   1794      *
   1795      * @see #commitAttributeToXml(UiAttributeNode, String)
   1796      * @see #commitDirtyAttributesToXml()
   1797      *
   1798      * @param attrXmlName The XML <em>local</em> name of the attribute to modify
   1799      * @param attrNsUri The namespace URI of the attribute.
   1800      *                  Can be null if the attribute uses the global namespace.
   1801      * @param value The new value for the attribute. If set to null, the attribute is removed.
   1802      * @param override True if the value must be set even if one already exists.
   1803      * @return The {@link UiAttributeNode} that has been modified or null.
   1804      */
   1805     public UiAttributeNode setAttributeValue(
   1806             String attrXmlName,
   1807             String attrNsUri,
   1808             String value,
   1809             boolean override) {
   1810         if (value == null) {
   1811             value = ""; //$NON-NLS-1$ -- this removes an attribute
   1812         }
   1813 
   1814         getEditor().scheduleNodeReformat(this, true);
   1815 
   1816         // Try with all internal attributes
   1817         UiAttributeNode uiAttr = setInternalAttrValue(
   1818                 getAllUiAttributes(), attrXmlName, attrNsUri, value, override);
   1819         if (uiAttr != null) {
   1820             return uiAttr;
   1821         }
   1822 
   1823         if (uiAttr == null) {
   1824             // Failed to find the attribute. For non-android attributes that is mostly expected,
   1825             // in which case we just create a new custom one. As a side effect, we'll find the
   1826             // attribute descriptor via getAllUiAttributes().
   1827             addUnknownAttribute(attrXmlName, attrXmlName, attrNsUri);
   1828 
   1829             // We've created the attribute, but not actually set the value on it, so let's do it.
   1830             // Try with the updated internal attributes.
   1831             // Implementation detail: we could just do a setCurrentValue + setDirty on the
   1832             // uiAttr returned by addUnknownAttribute(); however going through setInternalAttrValue
   1833             // means we won't duplicate the logic, at the expense of doing one more lookup.
   1834             uiAttr = setInternalAttrValue(
   1835                     getAllUiAttributes(), attrXmlName, attrNsUri, value, override);
   1836         }
   1837 
   1838         return uiAttr;
   1839     }
   1840 
   1841     private UiAttributeNode setInternalAttrValue(
   1842             Collection<UiAttributeNode> attributes,
   1843             String attrXmlName,
   1844             String attrNsUri,
   1845             String value,
   1846             boolean override) {
   1847 
   1848         // For namespace less attributes (like the "layout" attribute of an <include> tag
   1849         // we may be passed "" as the namespace (during an attribute copy), and it
   1850         // should really be null instead.
   1851         if (attrNsUri != null && attrNsUri.length() == 0) {
   1852             attrNsUri = null;
   1853         }
   1854 
   1855         for (UiAttributeNode uiAttr : attributes) {
   1856             AttributeDescriptor uiDesc = uiAttr.getDescriptor();
   1857 
   1858             if (uiDesc.getXmlLocalName().equals(attrXmlName)) {
   1859                 // Both NS URI must be either null or equal.
   1860                 if ((attrNsUri == null && uiDesc.getNamespaceUri() == null) ||
   1861                         (attrNsUri != null && attrNsUri.equals(uiDesc.getNamespaceUri()))) {
   1862 
   1863                     // Not all attributes are editable, ignore those which are not.
   1864                     if (uiAttr instanceof IUiSettableAttributeNode) {
   1865                         String current = uiAttr.getCurrentValue();
   1866                         // Only update (and mark as dirty) if the attribute did not have any
   1867                         // value or if the value was different.
   1868                         if (override || current == null || !current.equals(value)) {
   1869                             ((IUiSettableAttributeNode) uiAttr).setCurrentValue(value);
   1870                             // mark the attribute as dirty since their internal content
   1871                             // as been modified, but not the underlying XML model
   1872                             uiAttr.setDirty(true);
   1873                             return uiAttr;
   1874                         }
   1875                     }
   1876 
   1877                     // We found the attribute but it's not settable. Since attributes are
   1878                     // not duplicated, just abandon here.
   1879                     break;
   1880                 }
   1881             }
   1882         }
   1883 
   1884         return null;
   1885     }
   1886 
   1887     /**
   1888      * Utility method to retrieve the internal value of an attribute.
   1889      * <p/>
   1890      * Note that this retrieves the *field* value if the attribute has some UI, and
   1891      * not the actual XML value. They may differ if the attribute is dirty.
   1892      *
   1893      * @param attrXmlName The XML name of the attribute to modify
   1894      * @return The current internal value for the attribute or null in case of error.
   1895      */
   1896     public String getAttributeValue(String attrXmlName) {
   1897         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   1898 
   1899         for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
   1900             AttributeDescriptor uiDesc = entry.getKey();
   1901             if (uiDesc.getXmlLocalName().equals(attrXmlName)) {
   1902                 UiAttributeNode uiAttr = entry.getValue();
   1903                 return uiAttr.getCurrentValue();
   1904             }
   1905         }
   1906         return null;
   1907     }
   1908 
   1909     // ------ IPropertySource methods
   1910 
   1911     public Object getEditableValue() {
   1912         return null;
   1913     }
   1914 
   1915     /*
   1916      * (non-Javadoc)
   1917      * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors()
   1918      *
   1919      * Returns the property descriptor for this node. Since the descriptors are not linked to the
   1920      * data, the AttributeDescriptor are used directly.
   1921      */
   1922     public IPropertyDescriptor[] getPropertyDescriptors() {
   1923         List<IPropertyDescriptor> propDescs = new ArrayList<IPropertyDescriptor>();
   1924 
   1925         // get the standard descriptors
   1926         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   1927         Set<AttributeDescriptor> keys = attributeMap.keySet();
   1928 
   1929 
   1930         // we only want the descriptor that do implement the IPropertyDescriptor interface.
   1931         for (AttributeDescriptor key : keys) {
   1932             if (key instanceof IPropertyDescriptor) {
   1933                 propDescs.add((IPropertyDescriptor)key);
   1934             }
   1935         }
   1936 
   1937         // now get the descriptor from the unknown attributes
   1938         for (UiAttributeNode unknownNode : mUnknownUiAttributes) {
   1939             if (unknownNode.getDescriptor() instanceof IPropertyDescriptor) {
   1940                 propDescs.add((IPropertyDescriptor)unknownNode.getDescriptor());
   1941             }
   1942         }
   1943 
   1944         // TODO cache this maybe, as it's not going to change (except for unknown descriptors)
   1945         return propDescs.toArray(new IPropertyDescriptor[propDescs.size()]);
   1946     }
   1947 
   1948     /*
   1949      * (non-Javadoc)
   1950      * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java.lang.Object)
   1951      *
   1952      * Returns the value of a given property. The id is the result of IPropertyDescriptor.getId(),
   1953      * which return the AttributeDescriptor itself.
   1954      */
   1955     public Object getPropertyValue(Object id) {
   1956         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   1957 
   1958         UiAttributeNode attribute = attributeMap.get(id);
   1959 
   1960         if (attribute == null) {
   1961             // look for the id in the unknown attributes.
   1962             for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   1963                 if (id == unknownAttr.getDescriptor()) {
   1964                     return unknownAttr;
   1965                 }
   1966             }
   1967         }
   1968 
   1969         return attribute;
   1970     }
   1971 
   1972     /*
   1973      * (non-Javadoc)
   1974      * @see org.eclipse.ui.views.properties.IPropertySource#isPropertySet(java.lang.Object)
   1975      *
   1976      * Returns whether the property is set. In our case this is if the string is non empty.
   1977      */
   1978     public boolean isPropertySet(Object id) {
   1979         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   1980 
   1981         UiAttributeNode attribute = attributeMap.get(id);
   1982 
   1983         if (attribute != null) {
   1984             return attribute.getCurrentValue().length() > 0;
   1985         }
   1986 
   1987         // look for the id in the unknown attributes.
   1988         for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   1989             if (id == unknownAttr.getDescriptor()) {
   1990                 return unknownAttr.getCurrentValue().length() > 0;
   1991             }
   1992         }
   1993 
   1994         return false;
   1995     }
   1996 
   1997     /*
   1998      * (non-Javadoc)
   1999      * @see org.eclipse.ui.views.properties.IPropertySource#resetPropertyValue(java.lang.Object)
   2000      *
   2001      * Reset the property to its default value. For now we simply empty it.
   2002      */
   2003     public void resetPropertyValue(Object id) {
   2004         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   2005 
   2006         UiAttributeNode attribute = attributeMap.get(id);
   2007         if (attribute != null) {
   2008             // TODO: reset the value of the attribute
   2009 
   2010             return;
   2011         }
   2012 
   2013         // look for the id in the unknown attributes.
   2014         for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   2015             if (id == unknownAttr.getDescriptor()) {
   2016                 // TODO: reset the value of the attribute
   2017 
   2018                 return;
   2019             }
   2020         }
   2021     }
   2022 
   2023     /*
   2024      * (non-Javadoc)
   2025      * @see org.eclipse.ui.views.properties.IPropertySource#setPropertyValue(java.lang.Object, java.lang.Object)
   2026      *
   2027      * Set the property value. id is the result of IPropertyDescriptor.getId(), which is the
   2028      * AttributeDescriptor itself. Value should be a String.
   2029      */
   2030     public void setPropertyValue(Object id, Object value) {
   2031         HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
   2032 
   2033         UiAttributeNode attribute = attributeMap.get(id);
   2034 
   2035         if (attribute == null) {
   2036             // look for the id in the unknown attributes.
   2037             for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
   2038                 if (id == unknownAttr.getDescriptor()) {
   2039                     attribute = unknownAttr;
   2040                     break;
   2041                 }
   2042             }
   2043         }
   2044 
   2045         if (attribute != null) {
   2046 
   2047             // get the current value and compare it to the new value
   2048             String oldValue = attribute.getCurrentValue();
   2049             final String newValue = (String)value;
   2050 
   2051             if (oldValue.equals(newValue)) {
   2052                 return;
   2053             }
   2054 
   2055             final UiAttributeNode fAttribute = attribute;
   2056             AndroidXmlEditor editor = getEditor();
   2057             editor.wrapEditXmlModel(new Runnable() {
   2058                 public void run() {
   2059                     commitAttributeToXml(fAttribute, newValue);
   2060                 }
   2061             });
   2062         }
   2063     }
   2064 
   2065     /**
   2066      * Returns true if this node is an ancestor (parent, grandparent, and so on)
   2067      * of the given node. Note that a node is not considered an ancestor of
   2068      * itself.
   2069      *
   2070      * @param node the node to test
   2071      * @return true if this node is an ancestor of the given node
   2072      */
   2073     public boolean isAncestorOf(UiElementNode node) {
   2074         node = node.getUiParent();
   2075         while (node != null) {
   2076             if (node == this) {
   2077                 return true;
   2078             }
   2079             node = node.getUiParent();
   2080         }
   2081         return false;
   2082     }
   2083 
   2084     /**
   2085      * Finds the nearest common parent of the two given nodes (which could be one of the
   2086      * two nodes as well)
   2087      *
   2088      * @param node1 the first node to test
   2089      * @param node2 the second node to test
   2090      * @return the nearest common parent of the two given nodes
   2091      */
   2092     public static UiElementNode getCommonAncestor(UiElementNode node1, UiElementNode node2) {
   2093         while (node2 != null) {
   2094             UiElementNode current = node1;
   2095             while (current != null && current != node2) {
   2096                 current = current.getUiParent();
   2097             }
   2098             if (current == node2) {
   2099                 return current;
   2100             }
   2101             node2 = node2.getUiParent();
   2102         }
   2103 
   2104         return null;
   2105     }
   2106 }
   2107