UiElementNode.java revision e13151727c63786342cddc3ea355425582bd4e7a
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
17package com.android.ide.eclipse.adt.internal.editors.uimodel;
18
19import com.android.ide.eclipse.adt.AdtPlugin;
20import com.android.ide.eclipse.adt.internal.editors.AndroidEditor;
21import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
22import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
23import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
24import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
25import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
26import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
27import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
28import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
29import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors;
30import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState;
31import com.android.ide.eclipse.adt.internal.editors.xml.descriptors.XmlDescriptors;
32import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
33import com.android.sdklib.SdkConstants;
34
35import org.eclipse.core.runtime.IStatus;
36import org.eclipse.ui.IEditorInput;
37import org.eclipse.ui.IFileEditorInput;
38import org.eclipse.ui.views.properties.IPropertyDescriptor;
39import org.eclipse.ui.views.properties.IPropertySource;
40import org.w3c.dom.Attr;
41import org.w3c.dom.Document;
42import org.w3c.dom.Element;
43import org.w3c.dom.NamedNodeMap;
44import org.w3c.dom.Node;
45import org.w3c.dom.Text;
46
47import java.util.ArrayList;
48import java.util.Collection;
49import java.util.Collections;
50import java.util.HashMap;
51import java.util.HashSet;
52import java.util.List;
53import java.util.Map;
54import java.util.Set;
55import java.util.Map.Entry;
56
57/**
58 * Represents an XML node that can be modified by the user interface in the XML editor.
59 * <p/>
60 * Each tree viewer used in the application page's parts needs to keep a model representing
61 * each underlying node in the tree. This interface represents the base type for such a node.
62 * <p/>
63 * Each node acts as an intermediary model between the actual XML model (the real data support)
64 * and the tree viewers or the corresponding page parts.
65 * <p/>
66 * Element nodes don't contain data per se. Their data is contained in their attributes
67 * as well as their children's attributes, see {@link UiAttributeNode}.
68 * <p/>
69 * The structure of a given {@link UiElementNode} is declared by a corresponding
70 * {@link ElementDescriptor}.
71 * <p/>
72 * The class implements {@link IPropertySource}, in order to fill the Eclipse property tab when
73 * an element is selected. The {@link AttributeDescriptor} are used property descriptors.
74 */
75public class UiElementNode implements IPropertySource {
76
77    /** List of prefixes removed from android:id strings when creating short descriptions. */
78    private static String[] ID_PREFIXES = {
79        "@android:id/", //$NON-NLS-1$
80        "@+id/", "@id/", "@+", "@" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
81
82    /** The element descriptor for the node. Always present, never null. */
83    private ElementDescriptor mDescriptor;
84    /** The parent element node in the UI model. It is null for a root element or until
85     *  the node is attached to its parent. */
86    private UiElementNode mUiParent;
87    /** The {@link AndroidEditor} handling the UI hierarchy. This is defined only for the
88     *  root node. All children have the value set to null and query their parent. */
89    private AndroidEditor mEditor;
90    /** The XML {@link Document} model that is being mirror by the UI model. This is defined
91     *  only for the root node. All children have the value set to null and query their parent. */
92    private Document mXmlDocument;
93    /** The XML {@link Node} mirror by this UI node. This can be null for mandatory UI node which
94     *  have no corresponding XML node or for new UI nodes before their XML node is set. */
95    private Node mXmlNode;
96    /** The list of all UI children nodes. Can be empty but never null. There's one UI children
97     *  node per existing XML children node. */
98    private ArrayList<UiElementNode> mUiChildren;
99    /** The list of <em>all</em> UI attributes, as declared in the {@link ElementDescriptor}.
100     *  The list is always defined and never null. Unlike the UiElementNode children list, this
101     *  is always defined, even for attributes that do not exist in the XML model -- that's because
102     *  "missing" attributes in the XML model simply mean a default value is used. Also note that
103     *  the underlying collection is a map, so order is not respected. To get the desired attribute
104     *  order, iterate through the {@link ElementDescriptor}'s attribute list. */
105    private HashMap<AttributeDescriptor, UiAttributeNode> mUiAttributes;
106    private HashSet<UiAttributeNode> mUnknownUiAttributes;
107    /** A read-only view of the UI children node collection. */
108    private List<UiElementNode> mReadOnlyUiChildren;
109    /** A read-only view of the UI attributes collection. */
110    private Collection<UiAttributeNode> mReadOnlyUiAttributes;
111    /** A map of hidden attribute descriptors. Key is the XML name. */
112    private Map<String, AttributeDescriptor> mCachedHiddenAttributes;
113    /** An optional list of {@link IUiUpdateListener}. Most element nodes will not have any
114     *  listeners attached, so the list is only created on demand and can be null. */
115    private ArrayList<IUiUpdateListener> mUiUpdateListeners;
116    /** Error Flag */
117    private boolean mHasError;
118    /** Temporary data used by the editors. This data is not sync'ed with the XML */
119    private Object mEditData;
120
121    /**
122     * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}.
123     *
124     * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null.
125     */
126    public UiElementNode(ElementDescriptor elementDescriptor) {
127        mDescriptor = elementDescriptor;
128        clearContent();
129    }
130
131    /**
132     * Clears the {@link UiElementNode} by resetting the children list and
133     * the {@link UiAttributeNode}s list.
134     * Also resets the attached XML node, document, editor if any.
135     * <p/>
136     * The parent {@link UiElementNode} node is not reset so that it's position
137     * in the hierarchy be left intact, if any.
138     */
139    /* package */ void clearContent() {
140        mXmlNode = null;
141        mXmlDocument = null;
142        mEditor = null;
143        clearAttributes();
144        mReadOnlyUiChildren = null;
145        if (mUiChildren == null) {
146            mUiChildren = new ArrayList<UiElementNode>();
147        } else {
148            // We can't remove mandatory nodes, we just clear them.
149            for (int i = mUiChildren.size() - 1; i >= 0; --i) {
150                removeUiChildAtIndex(i);
151            }
152        }
153    }
154
155    /**
156     * Clears the internal list of attributes, the read-only cached version of it
157     * and the read-only cached hidden attribute list.
158     */
159    private void clearAttributes() {
160        mUiAttributes = null;
161        mReadOnlyUiAttributes = null;
162        mCachedHiddenAttributes = null;
163        mUnknownUiAttributes = new HashSet<UiAttributeNode>();
164    }
165
166    /**
167     * Gets or creates the internal UiAttributes list.
168     * <p/>
169     * When the descriptor derives from ViewElementDescriptor, this list depends on the
170     * current UiParent node.
171     *
172     * @return A new set of {@link UiAttributeNode} that matches the expected
173     *         attributes for this node.
174     */
175    private HashMap<AttributeDescriptor, UiAttributeNode> getInternalUiAttributes() {
176        if (mUiAttributes == null) {
177            AttributeDescriptor[] attr_list = getAttributeDescriptors();
178            mUiAttributes = new HashMap<AttributeDescriptor, UiAttributeNode>(attr_list.length);
179            for (AttributeDescriptor desc : attr_list) {
180                UiAttributeNode ui_node = desc.createUiNode(this);
181                if (ui_node != null) {  // Some AttributeDescriptors do not have UI associated
182                    mUiAttributes.put(desc, ui_node);
183                }
184            }
185        }
186        return mUiAttributes;
187    }
188
189    /**
190     * Computes a short string describing the UI node suitable for tree views.
191     * Uses the element's attribute "android:name" if present, or the "android:label" one
192     * followed by the element's name.
193     *
194     * @return A short string describing the UI node suitable for tree views.
195     */
196    public String getShortDescription() {
197        if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) {
198
199            // Application and Manifest nodes have a special treatment: they are unique nodes
200            // so we don't bother trying to differentiate their strings and we fall back to
201            // just using the UI name below.
202            Element elem = (Element) mXmlNode;
203
204            String attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
205                                              AndroidManifestDescriptors.ANDROID_NAME_ATTR);
206            if (attr == null || attr.length() == 0) {
207                attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
208                                           AndroidManifestDescriptors.ANDROID_LABEL_ATTR);
209            }
210            if (attr == null || attr.length() == 0) {
211                attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
212                                           XmlDescriptors.PREF_KEY_ATTR);
213            }
214            if (attr == null || attr.length() == 0) {
215                attr = elem.getAttribute(ResourcesDescriptors.NAME_ATTR);
216            }
217            if (attr == null || attr.length() == 0) {
218                attr = elem.getAttributeNS(SdkConstants.NS_RESOURCES,
219                                           LayoutDescriptors.ID_ATTR);
220
221                if (attr != null && attr.length() > 0) {
222                    for (String prefix : ID_PREFIXES) {
223                        if (attr.startsWith(prefix)) {
224                            attr = attr.substring(prefix.length());
225                            break;
226                        }
227                    }
228                }
229            }
230            if (attr != null && attr.length() > 0) {
231                return String.format("%1$s (%2$s)", attr, mDescriptor.getUiName());
232            }
233        }
234
235        return String.format("%1$s", mDescriptor.getUiName());
236    }
237
238    /**
239     * Computes a "breadcrumb trail" description for this node.
240     * It will look something like "Manifest > Application > .myactivity (Activity) > Intent-Filter"
241     *
242     * @param include_root Whether to include the root (e.g. "Manifest") or not. Has no effect
243     *                     when called on the root node itself.
244     * @return The "breadcrumb trail" description for this node.
245     */
246    public String getBreadcrumbTrailDescription(boolean include_root) {
247        StringBuilder sb = new StringBuilder(getShortDescription());
248
249        for (UiElementNode ui_node = getUiParent();
250                ui_node != null;
251                ui_node = ui_node.getUiParent()) {
252            if (!include_root && ui_node.getUiParent() == null) {
253                break;
254            }
255            sb.insert(0, String.format("%1$s > ", ui_node.getShortDescription())); //$NON-NLS-1$
256        }
257
258        return sb.toString();
259    }
260
261    /**
262     * Sets the XML {@link Document}.
263     * <p/>
264     * The XML {@link Document} is initially null. The XML {@link Document} must be set only on the
265     * UI root element node (this method takes care of that.)
266     */
267    public void setXmlDocument(Document xml_doc) {
268        if (mUiParent == null) {
269            mXmlDocument = xml_doc;
270        } else {
271            mUiParent.setXmlDocument(xml_doc);
272        }
273    }
274
275    /**
276     * Returns the XML {@link Document}.
277     * <p/>
278     * The value is initially null until the UI node is attached to its UI parent -- the value
279     * of the document is then propagated.
280     *
281     * @return the XML {@link Document} or the parent's XML {@link Document} or null.
282     */
283    public Document getXmlDocument() {
284        if (mXmlDocument != null) {
285            return mXmlDocument;
286        } else if (mUiParent != null) {
287            return mUiParent.getXmlDocument();
288        }
289        return null;
290    }
291
292    /**
293     * Returns the XML node associated with this UI node.
294     * <p/>
295     * Some {@link ElementDescriptor} are declared as being "mandatory". This means the
296     * corresponding UI node will exist even if there is no corresponding XML node. Such structure
297     * is created and enforced by the parent of the tree, not the element themselves. However
298     * such nodes will likely not have an XML node associated, so getXmlNode() can return null.
299     *
300     * @return The associated XML node. Can be null for mandatory nodes.
301     */
302    public Node getXmlNode() {
303        return mXmlNode;
304    }
305
306    /**
307     * Returns the {@link ElementDescriptor} for this node. This is never null.
308     * <p/>
309     * Do not use this to call getDescriptor().getAttributes(), instead call
310     * getAttributeDescriptors() which can be overriden by derived classes.
311     */
312    public ElementDescriptor getDescriptor() {
313        return mDescriptor;
314    }
315
316    /**
317     * Returns the {@link AttributeDescriptor} array for the descriptor of this node.
318     * <p/>
319     * Use this instead of getDescriptor().getAttributes() -- derived classes can override
320     * this to manipulate the attribute descriptor list depending on the current UI node.
321     */
322    public AttributeDescriptor[] getAttributeDescriptors() {
323        return mDescriptor.getAttributes();
324    }
325
326    /**
327     * Returns the hidden {@link AttributeDescriptor} array for the descriptor of this node.
328     * This is a subset of the getAttributeDescriptors() list.
329     * <p/>
330     * Use this instead of getDescriptor().getHiddenAttributes() -- potentially derived classes
331     * could override this to manipulate the attribute descriptor list depending on the current
332     * UI node. There's no need for it right now so keep it private.
333     */
334    private Map<String, AttributeDescriptor> getHiddenAttributeDescriptors() {
335        if (mCachedHiddenAttributes == null) {
336            mCachedHiddenAttributes = new HashMap<String, AttributeDescriptor>();
337            for (AttributeDescriptor attr_desc : getAttributeDescriptors()) {
338                if (attr_desc instanceof XmlnsAttributeDescriptor) {
339                    mCachedHiddenAttributes.put(
340                            ((XmlnsAttributeDescriptor) attr_desc).getXmlNsName(),
341                            attr_desc);
342                }
343            }
344        }
345        return mCachedHiddenAttributes;
346    }
347
348    /**
349     * Sets the parent of this UiElementNode.
350     * <p/>
351     * The root node has no parent.
352     */
353    protected void setUiParent(UiElementNode parent) {
354        mUiParent = parent;
355        // Invalidate the internal UiAttributes list, as it may depend on the actual UiParent.
356        clearAttributes();
357    }
358
359    /**
360     * @return The parent {@link UiElementNode} or null if this is the root node.
361     */
362    public UiElementNode getUiParent() {
363        return mUiParent;
364    }
365
366    /**
367     * Returns The root {@link UiElementNode}.
368     */
369    public UiElementNode getUiRoot() {
370        UiElementNode root = this;
371        while (root.mUiParent != null) {
372            root = root.mUiParent;
373        }
374
375        return root;
376    }
377
378    /**
379     * Returns the previous UI sibling of this UI node.
380     * If the node does not have a previous sibling, returns null.
381     */
382    public UiElementNode getUiPreviousSibling() {
383        if (mUiParent != null) {
384            List<UiElementNode> childlist = mUiParent.getUiChildren();
385            if (childlist != null && childlist.size() > 1 && childlist.get(0) != this) {
386                int index = childlist.indexOf(this);
387                return index > 0 ? childlist.get(index - 1) : null;
388            }
389        }
390        return null;
391    }
392
393    /**
394     * Returns the next UI sibling of this UI node.
395     * If the node does not have a next sibling, returns null.
396     */
397    public UiElementNode getUiNextSibling() {
398        if (mUiParent != null) {
399            List<UiElementNode> childlist = mUiParent.getUiChildren();
400            if (childlist != null) {
401                int size = childlist.size();
402                if (size > 1 && childlist.get(size - 1) != this) {
403                    int index = childlist.indexOf(this);
404                    return index >= 0 && index < size - 1 ? childlist.get(index + 1) : null;
405                }
406            }
407        }
408        return null;
409    }
410
411    /**
412     * Sets the {@link AndroidEditor} handling this {@link UiElementNode} hierarchy.
413     * <p/>
414     * The editor must always be set on the root node. This method takes care of that.
415     */
416    public void setEditor(AndroidEditor editor) {
417        if (mUiParent == null) {
418            mEditor = editor;
419        } else {
420            mUiParent.setEditor(editor);
421        }
422    }
423
424    /**
425     * Returns the {@link AndroidEditor} that embeds this {@link UiElementNode}.
426     * <p/>
427     * The value is initially null until the node is attached to its parent -- the value
428     * of the root node is then propagated.
429     *
430     * @return The embedding {@link AndroidEditor} or null.
431     */
432    public AndroidEditor getEditor() {
433        return mUiParent == null ? mEditor : mUiParent.getEditor();
434    }
435
436    /**
437     * Returns the Android target data for the file being edited.
438     */
439    public AndroidTargetData getAndroidTarget() {
440        return getEditor().getTargetData();
441    }
442
443    /**
444     * @return A read-only version of the children collection.
445     */
446    public List<UiElementNode> getUiChildren() {
447        if (mReadOnlyUiChildren == null) {
448            mReadOnlyUiChildren = Collections.unmodifiableList(mUiChildren);
449        }
450        return mReadOnlyUiChildren;
451    }
452
453    /**
454     * @return A read-only version of the attributes collection.
455     */
456    public Collection<UiAttributeNode> getUiAttributes() {
457        if (mReadOnlyUiAttributes == null) {
458            mReadOnlyUiAttributes = Collections.unmodifiableCollection(
459                    getInternalUiAttributes().values());
460        }
461        return mReadOnlyUiAttributes;
462    }
463
464    /**
465     * @return A read-only version of the unknown attributes collection.
466     */
467    public Collection<UiAttributeNode> getUnknownUiAttributes() {
468        return Collections.unmodifiableCollection(mUnknownUiAttributes);
469    }
470
471    /**
472     * Sets the error flag value.
473     * @param errorFlag the error flag
474     */
475    public final void setHasError(boolean errorFlag) {
476        mHasError = errorFlag;
477    }
478
479    /**
480     * Returns whether this node, its attributes, or one of the children nodes (and attributes)
481     * has errors.
482     */
483    public final boolean hasError() {
484        if (mHasError) {
485            return true;
486        }
487
488        // get the error value from the attributes.
489        Collection<UiAttributeNode> attributes = getInternalUiAttributes().values();
490        for (UiAttributeNode attribute : attributes) {
491            if (attribute.hasError()) {
492                return true;
493            }
494        }
495
496        // and now from the children.
497        for (UiElementNode child : mUiChildren) {
498            if (child.hasError()) {
499                return true;
500            }
501        }
502
503        return false;
504    }
505
506    /**
507     * Adds a new {@link IUiUpdateListener} to the internal update listener list.
508     */
509    public void addUpdateListener(IUiUpdateListener listener) {
510       if (mUiUpdateListeners == null) {
511           mUiUpdateListeners = new ArrayList<IUiUpdateListener>();
512       }
513       if (!mUiUpdateListeners.contains(listener)) {
514           mUiUpdateListeners.add(listener);
515       }
516    }
517
518    /**
519     * Removes an existing {@link IUiUpdateListener} from the internal update listener list.
520     * Does nothing if the list is empty or the listener is not registered.
521     */
522    public void removeUpdateListener(IUiUpdateListener listener) {
523       if (mUiUpdateListeners != null) {
524           mUiUpdateListeners.remove(listener);
525       }
526    }
527
528    /**
529     * Finds a child node relative to this node using a path-like expression.
530     * F.ex. "node1/node2" would find a child "node1" that contains a child "node2" and
531     * returns the latter. If there are multiple nodes with the same name at the same
532     * level, always uses the first one found.
533     *
534     * @param path The path like expression to select a child node.
535     * @return The ui node found or null.
536     */
537    public UiElementNode findUiChildNode(String path) {
538        String[] items = path.split("/");  //$NON-NLS-1$
539        UiElementNode ui_node = this;
540        for (String item : items) {
541            boolean next_segment = false;
542            for (UiElementNode c : ui_node.mUiChildren) {
543                if (c.getDescriptor().getXmlName().equals(item)) {
544                    ui_node = c;
545                    next_segment = true;
546                    break;
547                }
548            }
549            if (!next_segment) {
550                return null;
551            }
552        }
553        return ui_node;
554    }
555
556    /**
557     * Finds an {@link UiElementNode} which contains the give XML {@link Node}.
558     * Looks recursively in all children UI nodes.
559     *
560     * @param xmlNode The XML node to look for.
561     * @return The {@link UiElementNode} that contains xmlNode or null if not found,
562     */
563    public UiElementNode findXmlNode(Node xmlNode) {
564        if (xmlNode == null) {
565            return null;
566        }
567        if (getXmlNode() == xmlNode) {
568            return this;
569        }
570
571        for (UiElementNode uiChild : mUiChildren) {
572            UiElementNode found = uiChild.findXmlNode(xmlNode);
573            if (found != null) {
574                return found;
575            }
576        }
577
578        return null;
579    }
580
581    /**
582     * Returns the {@link UiAttributeNode} matching this attribute descriptor or
583     * null if not found.
584     *
585     * @param attr_desc The {@link AttributeDescriptor} to match.
586     * @return the {@link UiAttributeNode} matching this attribute descriptor or null
587     *         if not found.
588     */
589    public UiAttributeNode findUiAttribute(AttributeDescriptor attr_desc) {
590        return getInternalUiAttributes().get(attr_desc);
591    }
592
593    /**
594     * Populate this element node with all values from the given XML node.
595     *
596     * This fails if the given XML node has a different element name -- it won't change the
597     * type of this ui node.
598     *
599     * This method can be both used for populating values the first time and updating values
600     * after the XML model changed.
601     *
602     * @param xml_node The XML node to mirror
603     * @return Returns true if the XML structure has changed (nodes added, removed or replaced)
604     */
605    public boolean loadFromXmlNode(Node xml_node) {
606        boolean structure_changed = (mXmlNode != xml_node);
607        mXmlNode = xml_node;
608        if (xml_node != null) {
609            updateAttributeList(xml_node);
610            structure_changed |= updateElementList(xml_node);
611            invokeUiUpdateListeners(structure_changed ? UiUpdateState.CHILDREN_CHANGED
612                                                      : UiUpdateState.ATTR_UPDATED);
613        }
614        return structure_changed;
615    }
616
617    /**
618     * Clears the UI node and reload it from the given XML node.
619     * <p/>
620     * This works by clearing all references to any previous XML or UI nodes and
621     * then reloads the XML document from scratch. The editor reference is kept.
622     * <p/>
623     * This is used in the special case where the ElementDescriptor structure has changed.
624     * Rather than try to diff inflated UI nodes (as loadFromXmlNode does), we don't bother
625     * and reload everything. This is not subtle and should be used very rarely.
626     *
627     * @param xml_node The XML node or document to reload. Can be null.
628     */
629    public void reloadFromXmlNode(Node xml_node) {
630        // The editor needs to be preserved, it is not affected by an XML change.
631        AndroidEditor editor = getEditor();
632        clearContent();
633        setEditor(editor);
634        if (xml_node != null) {
635            setXmlDocument(xml_node.getOwnerDocument());
636        }
637        // This will reload all the XML and recreate the UI structure from scratch.
638        loadFromXmlNode(xml_node);
639    }
640
641    /**
642     * Called by attributes when they want to commit their value
643     * to an XML node.
644     * <p/>
645     * For mandatory nodes, this makes sure the underlying XML element node
646     * exists in the model. If not, it is created and assigned as the underlying
647     * XML node.
648     * </br>
649     * For non-mandatory nodes, simply return the underlying XML node, which
650     * must always exists.
651     *
652     * @return The XML node matching this {@link UiElementNode} or null.
653     */
654    public Node prepareCommit() {
655        if (getDescriptor().isMandatory()) {
656            createXmlNode();
657            // The new XML node has been created.
658            // We don't need to refresh using loadFromXmlNode() since there are
659            // no attributes or elements that need to be loading into this node.
660        }
661        return getXmlNode();
662    }
663
664    /**
665     * Commits the attributes (all internal, inherited from UI parent & unknown attributes).
666     * This is called by the UI when the embedding part needs to be committed.
667     */
668    public void commit() {
669        for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) {
670            ui_attr.commit();
671        }
672
673        for (UiAttributeNode ui_attr : mUnknownUiAttributes) {
674            ui_attr.commit();
675        }
676    }
677
678    /**
679     * Returns true if the part has been modified with respect to the data
680     * loaded from the model.
681     */
682    public boolean isDirty() {
683        for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) {
684            if (ui_attr.isDirty()) {
685                return true;
686            }
687        }
688
689        for (UiAttributeNode ui_attr : mUnknownUiAttributes) {
690            if (ui_attr.isDirty()) {
691                return true;
692            }
693        }
694
695        return false;
696    }
697
698    /**
699     * Creates the underlying XML element node for this UI node if it doesn't already
700     * exists.
701     *
702     * @return The new value of getXmlNode() (can be null if creation failed)
703     */
704    public Node createXmlNode() {
705        if (mXmlNode != null) {
706            return null;
707        }
708        Node parentXmlNode = null;
709        if (mUiParent != null) {
710            parentXmlNode = mUiParent.prepareCommit();
711            if (parentXmlNode == null) {
712                // The parent failed to create its own backing XML node. Abort.
713                // No need to throw an exception, the parent will most likely
714                // have done so itself.
715                return null;
716            }
717        }
718
719        String element_name = getDescriptor().getXmlName();
720        Document doc = getXmlDocument();
721
722        // We *must* have a root node. If not, we need to abort.
723        if (doc == null) {
724            throw new RuntimeException(
725                    String.format("Missing XML document for %1$s XML node.", element_name));
726        }
727
728        // If we get here and parent_xml_node is null, the node is to be created
729        // as the root node of the document (which can't be null, cf check above).
730        if (parentXmlNode == null) {
731            parentXmlNode = doc;
732        }
733
734        mXmlNode = doc.createElement(element_name);
735
736        Node xmlNextSibling = null;
737
738        UiElementNode uiNextSibling = getUiNextSibling();
739        if (uiNextSibling != null) {
740            xmlNextSibling = uiNextSibling.getXmlNode();
741        }
742
743        parentXmlNode.insertBefore(mXmlNode, xmlNextSibling);
744
745        // Insert a separator after the tag, to make it easier to read
746        Text sep = doc.createTextNode("\n");
747        parentXmlNode.appendChild(sep);
748
749        // Set all initial attributes in the XML node if they are not empty.
750        // Iterate on the descriptor list to get the desired order and then use the
751        // internal values, if any.
752        for (AttributeDescriptor attr_desc : getAttributeDescriptors()) {
753            if (attr_desc instanceof XmlnsAttributeDescriptor) {
754                XmlnsAttributeDescriptor desc = (XmlnsAttributeDescriptor) attr_desc;
755                Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI,
756                        desc.getXmlNsName());
757                attr.setValue(desc.getValue());
758                attr.setPrefix(desc.getXmlNsPrefix());
759                mXmlNode.getAttributes().setNamedItemNS(attr);
760            } else {
761                UiAttributeNode ui_attr = getInternalUiAttributes().get(attr_desc);
762                commitAttributeToXml(ui_attr, ui_attr.getCurrentValue());
763            }
764        }
765
766        invokeUiUpdateListeners(UiUpdateState.CREATED);
767        return mXmlNode;
768    }
769
770    /**
771     * Removes the XML node corresponding to this UI node if it exists
772     * and also removes all mirrored information in this UI node (i.e. children, attributes)
773     *
774     * @return The removed node or null if it didn't exist in the firtst place.
775     */
776    public Node deleteXmlNode() {
777        if (mXmlNode == null) {
778            return null;
779        }
780
781        // First clear the internals of the node and *then* actually deletes the XML
782        // node (because doing so will generate an update even and this node may be
783        // revisited via loadFromXmlNode).
784        Node old_xml_node = mXmlNode;
785        clearContent();
786
787        Node xml_parent = old_xml_node.getParentNode();
788        if (xml_parent == null) {
789            xml_parent = getXmlDocument();
790        }
791        old_xml_node = xml_parent.removeChild(old_xml_node);
792
793        invokeUiUpdateListeners(UiUpdateState.DELETED);
794        return old_xml_node;
795    }
796
797    /**
798     * Updates the element list for this UiElementNode.
799     * At the end, the list of children UiElementNode here will match the one from the
800     * provided XML {@link Node}:
801     * <ul>
802     * <li> Walk both the current ui children list and the xml children list at the same time.
803     * <li> If we have a new xml child but already reached the end of the ui child list, add the
804     *      new xml node.
805     * <li> Otherwise, check if the xml node is referenced later in the ui child list and if so,
806     *      move it here. It means the XML child list has been reordered.
807     * <li> Otherwise, this is a new XML node that we add in the middle of the ui child list.
808     * <li> At the end, we may have finished walking the xml child list but still have remaining
809     *      ui children, simply delete them as they matching trailing xml nodes that have been
810     *      removed unless they are mandatory ui nodes.
811     * </ul>
812     * Note that only the first case is used when populating the ui list the first time.
813     *
814     * @param xml_node The XML node to mirror
815     * @return True when the XML structure has changed.
816     */
817    protected boolean updateElementList(Node xml_node) {
818        boolean structure_changed = false;
819        int ui_index = 0;
820        Node xml_child = xml_node.getFirstChild();
821        while (xml_child != null) {
822            if (xml_child.getNodeType() == Node.ELEMENT_NODE) {
823                String element_name = xml_child.getNodeName();
824                UiElementNode ui_node = null;
825                if (mUiChildren.size() <= ui_index) {
826                    // A new node is being added at the end of the list
827                    ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name,
828                            false /* recursive */);
829                    if (desc == null) {
830                        // Unknown node. Create a temporary descriptor for it.
831                        // most important we want to auto-add unknown attributes to it.
832                        AndroidEditor editor = getEditor();
833                        IEditorInput editorInput = editor.getEditorInput();
834                        if (editorInput instanceof IFileEditorInput) {
835                            IFileEditorInput fileInput = (IFileEditorInput)editorInput;
836                            desc = CustomViewDescriptorService.getInstance().getDescriptor(
837                                    fileInput.getFile().getProject(), element_name);
838                            if (desc == null) {
839                                desc = new ElementDescriptor(element_name);
840                            }
841                        } else {
842                            desc = new ElementDescriptor(element_name);
843                            // TODO associate a new "?" icon to this descriptor.
844                        }
845                    }
846                    structure_changed = true;
847                    ui_node = appendNewUiChild(desc);
848                    ui_index++;
849                } else {
850                    // A new node is being inserted or moved.
851                    // Note: mandatory nodes can be created without an XML node in which case
852                    // getXmlNode() is null.
853                    UiElementNode ui_child;
854                    int n = mUiChildren.size();
855                    for (int j = ui_index; j < n; j++) {
856                        ui_child = mUiChildren.get(j);
857                        if (ui_child.getXmlNode() != null && ui_child.getXmlNode() == xml_child) {
858                            if (j > ui_index) {
859                                // Found the same XML node at some later index, now move it here.
860                                mUiChildren.remove(j);
861                                mUiChildren.add(ui_index, ui_child);
862                                structure_changed = true;
863                            }
864                            ui_node = ui_child;
865                            ui_index++;
866                            break;
867                        }
868                    }
869
870                    if (ui_node == null) {
871                        // Look for an unused mandatory node with no XML node attached
872                        // referencing the same XML element name
873                        for (int j = ui_index; j < n; j++) {
874                            ui_child = mUiChildren.get(j);
875                            if (ui_child.getXmlNode() == null &&
876                                    ui_child.getDescriptor().isMandatory() &&
877                                    ui_child.getDescriptor().getXmlName().equals(element_name)) {
878                                if (j > ui_index) {
879                                    // Found it, now move it here
880                                    mUiChildren.remove(j);
881                                    mUiChildren.add(ui_index, ui_child);
882                                }
883                                // assign the XML node to this empty mandatory element.
884                                ui_child.mXmlNode = xml_child;
885                                structure_changed = true;
886                                ui_node = ui_child;
887                                ui_index++;
888                            }
889                        }
890                    }
891
892                    if (ui_node == null) {
893                        // Inserting new node
894                        ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name,
895                                false /* recursive */);
896                        if (desc == null) {
897                            // Unknown element. Simply ignore it.
898                            AdtPlugin.log(IStatus.WARNING,
899                                    "AndroidManifest: Ignoring unknown '%s' XML element", //$NON-NLS-1$
900                                    element_name);
901                        } else {
902                            structure_changed = true;
903                            ui_node = insertNewUiChild(ui_index, desc);
904                            ui_index++;
905                        }
906                    }
907                }
908                if (ui_node != null) {
909                    // If we touched an UI Node, even an existing one, refresh its content.
910                    // For new nodes, this will populate them recursively.
911                    structure_changed |= ui_node.loadFromXmlNode(xml_child);
912                }
913            }
914            xml_child = xml_child.getNextSibling();
915        }
916
917        // There might be extra UI nodes at the end if the XML node list got shorter.
918        for (int index = mUiChildren.size() - 1; index >= ui_index; --index) {
919             structure_changed |= removeUiChildAtIndex(index);
920        }
921
922        return structure_changed;
923    }
924
925    /**
926     * Internal helper to remove an UI child node given by its index in the
927     * internal child list.
928     *
929     * Also invokes the update listener on the node to be deleted *after* the node has
930     * been removed.
931     *
932     * @param ui_index The index of the UI child to remove, range 0 .. mUiChildren.size()-1
933     * @return True if the structure has changed
934     * @throws IndexOutOfBoundsException if index is out of mUiChildren's bounds. Of course you
935     *         know that could never happen unless the computer is on fire or something.
936     */
937    private boolean removeUiChildAtIndex(int ui_index) {
938        UiElementNode ui_node = mUiChildren.get(ui_index);
939        ElementDescriptor desc = ui_node.getDescriptor();
940
941        try {
942            if (ui_node.getDescriptor().isMandatory()) {
943                // This is a mandatory node. Such a node must exist in the UiNode hierarchy
944                // even if there's no XML counterpart. However we only need to keep one.
945
946                // Check if the parent (e.g. this node) has another similar ui child node.
947                boolean keepNode = true;
948                for (UiElementNode child : mUiChildren) {
949                    if (child != ui_node && child.getDescriptor() == desc) {
950                        // We found another child with the same descriptor that is not
951                        // the node we want to remove. This means we have one mandatory
952                        // node so we can safely remove ui_node.
953                        keepNode = false;
954                        break;
955                    }
956                }
957
958                if (keepNode) {
959                    // We can't remove a mandatory node as we need to keep at least one
960                    // mandatory node in the parent. Instead we just clear its content
961                    // (including its XML Node reference).
962
963                    // A mandatory node with no XML means it doesn't really exist, so it can't be
964                    // deleted. So the structure will change only if the ui node is actually
965                    // associated to an XML node.
966                    boolean xml_exists = (ui_node.getXmlNode() != null);
967
968                    ui_node.clearContent();
969                    return xml_exists;
970                }
971            }
972
973            mUiChildren.remove(ui_index);
974            return true;
975        } finally {
976            // Tell listeners that a node has been removed.
977            // The model has already been modified.
978            invokeUiUpdateListeners(UiUpdateState.DELETED);
979        }
980    }
981
982    /**
983     * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
984     * and appends it to the end of the element children list.
985     *
986     * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
987     * @return The new UI node that has been appended
988     */
989    public UiElementNode appendNewUiChild(ElementDescriptor descriptor) {
990        UiElementNode ui_node;
991        ui_node = descriptor.createUiNode();
992        mUiChildren.add(ui_node);
993        ui_node.setUiParent(this);
994        ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED);
995        return ui_node;
996    }
997
998    /**
999     * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor}
1000     * and inserts it in the element children list at the specified position.
1001     *
1002     * @param index The position where to insert in the element children list.
1003     * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node.
1004     * @return The new UI node.
1005     */
1006    public UiElementNode insertNewUiChild(int index, ElementDescriptor descriptor) {
1007        UiElementNode ui_node;
1008        ui_node = descriptor.createUiNode();
1009        mUiChildren.add(index, ui_node);
1010        ui_node.setUiParent(this);
1011        ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED);
1012        return ui_node;
1013    }
1014
1015    /**
1016     * Updates the {@link UiAttributeNode} list for this {@link UiElementNode}.
1017     * <p/>
1018     * For a given {@link UiElementNode}, the attribute list always exists in
1019     * full and is totally independent of whether the XML model actually
1020     * has the corresponding attributes.
1021     * <p/>
1022     * For each attribute declared in this {@link UiElementNode}, get
1023     * the corresponding XML attribute. It may not exist, in which case the
1024     * value will be null. We don't really know if a value has changed, so
1025     * the updateValue() is called on the UI sattribute in all cases.
1026     *
1027     * @param xmlNode The XML node to mirror
1028     */
1029    protected void updateAttributeList(Node xmlNode) {
1030        NamedNodeMap xmlAttrMap = xmlNode.getAttributes();
1031        HashSet<Node> visited = new HashSet<Node>();
1032
1033        // For all known (i.e. expected) UI attributes, find an existing XML attribute of
1034        // same (uri, local name) and update the internal Ui attribute value.
1035        for (UiAttributeNode uiAttr : getInternalUiAttributes().values()) {
1036            AttributeDescriptor desc = uiAttr.getDescriptor();
1037            if (!(desc instanceof SeparatorAttributeDescriptor)) {
1038                Node xmlAttr = xmlAttrMap == null ? null :
1039                    xmlAttrMap.getNamedItemNS(desc.getNamespaceUri(), desc.getXmlLocalName());
1040                uiAttr.updateValue(xmlAttr);
1041                visited.add(xmlAttr);
1042            }
1043        }
1044
1045        // Clone the current list of unknown attributes. We'll then remove from this list when
1046        // we still attributes which are still unknown. What will be left are the old unknown
1047        // attributes that have been deleted in the current XML attribute list.
1048        @SuppressWarnings("unchecked") //$NON-NLS-1$
1049        HashSet<UiAttributeNode> deleted = (HashSet<UiAttributeNode>) mUnknownUiAttributes.clone();
1050
1051        // We need to ignore hidden attributes.
1052        Map<String, AttributeDescriptor> hiddenAttrDesc = getHiddenAttributeDescriptors();
1053
1054        // Traverse the actual XML attribute list to find unknown attributes
1055        if (xmlAttrMap != null) {
1056            for (int i = 0; i < xmlAttrMap.getLength(); i++) {
1057                Node xmlAttr = xmlAttrMap.item(i);
1058                // Ignore attributes which have actual descriptors
1059                if (visited.contains(xmlAttr)) {
1060                    continue;
1061                }
1062
1063                String xmlFullName = xmlAttr.getNodeName();
1064
1065                // Ignore attributes which are hidden (based on the prefix:localName key)
1066                if (hiddenAttrDesc.containsKey(xmlFullName)) {
1067                    continue;
1068                }
1069
1070                String xmlAttrLocalName = xmlAttr.getLocalName();
1071                String xmlNsUri = xmlAttr.getNamespaceURI();
1072
1073                UiAttributeNode uiAttr = null;
1074                for (UiAttributeNode a : mUnknownUiAttributes) {
1075                    String aLocalName = a.getDescriptor().getXmlLocalName();
1076                    String aNsUri = a.getDescriptor().getNamespaceUri();
1077                    if (aLocalName.equals(xmlAttrLocalName) &&
1078                            (aNsUri == xmlNsUri || (aNsUri != null && aNsUri.equals(xmlNsUri)))) {
1079                        // This attribute is still present in the unknown list
1080                        uiAttr = a;
1081                        // It has not been deleted
1082                        deleted.remove(a);
1083                        break;
1084                    }
1085                }
1086                if (uiAttr == null) {
1087                    // Create a new unknown attribute
1088                    TextAttributeDescriptor desc = new TextAttributeDescriptor(
1089                            xmlAttrLocalName, // xml name
1090                            xmlFullName, // ui name
1091                            xmlNsUri, // NS uri
1092                            "Unknown XML attribute"); // tooltip, translatable
1093                    uiAttr = desc.createUiNode(this);
1094                    mUnknownUiAttributes.add(uiAttr);
1095                }
1096
1097                uiAttr.updateValue(xmlAttr);
1098            }
1099
1100            // Remove from the internal list unknown attributes that have been deleted from the xml
1101            for (UiAttributeNode a : deleted) {
1102                mUnknownUiAttributes.remove(a);
1103            }
1104        }
1105    }
1106
1107    /**
1108     * Invoke all registered {@link IUiUpdateListener} listening on this UI update for this node.
1109     */
1110    protected void invokeUiUpdateListeners(UiUpdateState state) {
1111        if (mUiUpdateListeners != null) {
1112            for (IUiUpdateListener listener : mUiUpdateListeners) {
1113                try {
1114                    listener.uiElementNodeUpdated(this, state);
1115                } catch (Exception e) {
1116                    // prevent a crashing listener from crashing the whole invocation chain
1117                    AdtPlugin.log(e, "UIElement Listener failed: %s, state=%s",  //$NON-NLS-1$
1118                            getBreadcrumbTrailDescription(true),
1119                            state.toString());
1120                }
1121            }
1122        }
1123    }
1124
1125    // --- for derived implementations only ---
1126
1127    // TODO doc
1128    protected void setXmlNode(Node xml_node) {
1129        mXmlNode = xml_node;
1130    }
1131
1132    /**
1133     * Sets the temporary data used by the editors.
1134     * @param data the data.
1135     *
1136     * @since GLE1
1137     * @deprecated Used by GLE1. Should be deprecated for GLE2.
1138     */
1139    public void setEditData(Object data) {
1140        mEditData = data;
1141    }
1142
1143    /**
1144     * Returns the temporary data used by the editors for this object.
1145     * @return the data, or <code>null</code> if none has been set.
1146     */
1147    public Object getEditData() {
1148        return mEditData;
1149    }
1150
1151    public void refreshUi() {
1152        invokeUiUpdateListeners(UiUpdateState.ATTR_UPDATED);
1153    }
1154
1155
1156    // ------------- Helpers
1157
1158    /**
1159     * Helper method to commit a single attribute value to XML.
1160     * <p/>
1161     * This method updates the XML regardless of the current XML value.
1162     * Callers should check first if an update is needed.
1163     * If the new value is empty, the XML attribute will be actually removed.
1164     * <p/>
1165     * Note that the caller MUST ensure that modifying the underlying XML model is
1166     * safe and must take care of marking the model as dirty if necessary.
1167     *
1168     * @see AndroidEditor#editXmlModel(Runnable)
1169     *
1170     * @param uiAttr The attribute node to commit. Must be a child of this UiElementNode.
1171     * @param newValue The new value to set.
1172     * @return True if the XML attribute was modified or removed, false if nothing changed.
1173     */
1174    public boolean commitAttributeToXml(UiAttributeNode uiAttr, String newValue) {
1175        // Get (or create) the underlying XML element node that contains the attributes.
1176        Node element = prepareCommit();
1177        if (element != null && uiAttr != null) {
1178            String attrLocalName = uiAttr.getDescriptor().getXmlLocalName();
1179            String attrNsUri = uiAttr.getDescriptor().getNamespaceUri();
1180
1181            NamedNodeMap attrMap = element.getAttributes();
1182            if (newValue == null || newValue.length() == 0) {
1183                // Remove attribute if it's empty
1184                if (attrMap.getNamedItemNS(attrNsUri, attrLocalName) != null) {
1185                    attrMap.removeNamedItemNS(attrNsUri, attrLocalName);
1186                    return true;
1187                }
1188            } else {
1189                // Add or replace an attribute
1190                Document doc = element.getOwnerDocument();
1191                if (doc != null) {
1192                    Attr attr = doc.createAttributeNS(attrNsUri, attrLocalName);
1193                    attr.setValue(newValue);
1194                    attr.setPrefix(lookupNamespacePrefix(element, attrNsUri));
1195                    attrMap.setNamedItemNS(attr);
1196                    return true;
1197                }
1198            }
1199        }
1200        return false;
1201    }
1202
1203    /**
1204     * Helper method to commit all dirty attributes values to XML.
1205     * <p/>
1206     * This method is useful if {@link #setAttributeValue(String, String, boolean)} has been
1207     * called more than once and all the attributes marked as dirty must be commited to the
1208     * XML. It calls {@link #commitAttributeToXml(UiAttributeNode, String)} on each dirty
1209     * attribute.
1210     * <p/>
1211     * Note that the caller MUST ensure that modifying the underlying XML model is
1212     * safe and must take care of marking the model as dirty if necessary.
1213     *
1214     * @see AndroidEditor#editXmlModel(Runnable)
1215     *
1216     * @return True if one or more values were actually modified or removed,
1217     *         false if nothing changed.
1218     */
1219    public boolean commitDirtyAttributesToXml() {
1220        boolean result = false;
1221        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1222
1223        for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
1224            UiAttributeNode ui_attr = entry.getValue();
1225            if (ui_attr.isDirty()) {
1226                result |= commitAttributeToXml(ui_attr, ui_attr.getCurrentValue());
1227                ui_attr.setDirty(false);
1228            }
1229        }
1230        return result;
1231    }
1232
1233    /**
1234     * Returns the namespace prefix matching the requested namespace URI.
1235     * If no such declaration is found, returns the default "android" prefix.
1236     *
1237     * @param node The current node. Must not be null.
1238     * @param nsUri The namespace URI of which the prefix is to be found,
1239     *              e.g. SdkConstants.NS_RESOURCES
1240     * @return The first prefix declared or the default "android" prefix.
1241     */
1242    private String lookupNamespacePrefix(Node node, String nsUri) {
1243        // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
1244        // The following code emulates this simple call:
1245        //   String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES);
1246
1247        // if the requested URI is null, it denotes an attribute with no namespace.
1248        if (nsUri == null) {
1249            return null;
1250        }
1251
1252        // per XML specification, the "xmlns" URI is reserved
1253        if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) {
1254            return "xmlns"; //$NON-NLS-1$
1255        }
1256
1257        HashSet<String> visited = new HashSet<String>();
1258        Document doc = node == null ? null : node.getOwnerDocument();
1259
1260        for (; node != null && node.getNodeType() == Node.ELEMENT_NODE;
1261               node = node.getParentNode()) {
1262            NamedNodeMap attrs = node.getAttributes();
1263            for (int n = attrs.getLength() - 1; n >= 0; --n) {
1264                Node attr = attrs.item(n);
1265                if ("xmlns".equals(attr.getPrefix())) {  //$NON-NLS-1$
1266                    String uri = attr.getNodeValue();
1267                    String nsPrefix = attr.getLocalName();
1268                    // Is this the URI we are looking for? If yes, we found its prefix.
1269                    if (nsUri.equals(uri)) {
1270                        return nsPrefix;
1271                    }
1272                    visited.add(nsPrefix);
1273                }
1274            }
1275        }
1276
1277        // Use a sensible default prefix if we can't find one.
1278        // We need to make sure the prefix is not one that was declared in the scope
1279        // visited above. Use a default namespace prefix "android" for the Android resource
1280        // NS and use "ns" for all other custom namespaces.
1281        String prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
1282        String base = prefix;
1283        for (int i = 1; visited.contains(prefix); i++) {
1284            prefix = base + Integer.toString(i);
1285        }
1286
1287        // Also create & define this prefix/URI in the XML document as an attribute in the
1288        // first element of the document.
1289        if (doc != null) {
1290            node = doc.getFirstChild();
1291            while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
1292                node = node.getNextSibling();
1293            }
1294            if (node != null) {
1295                Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, prefix);
1296                attr.setValue(nsUri);
1297                attr.setPrefix("xmlns"); //$NON-NLS-1$
1298                node.getAttributes().setNamedItemNS(attr);
1299            }
1300        }
1301
1302        return prefix;
1303    }
1304
1305    /**
1306     * Utility method to internally set the value of a text attribute for the current
1307     * UiElementNode.
1308     * <p/>
1309     * This method is a helper. It silently ignores the errors such as the requested
1310     * attribute not being present in the element or attribute not being settable.
1311     * It accepts inherited attributes (such as layout).
1312     * <p/>
1313     * This does not commit to the XML model. It does mark the attribute node as dirty.
1314     * This is up to the caller.
1315     *
1316     * @see #commitAttributeToXml(UiAttributeNode, String)
1317     * @see #commitDirtyAttributesToXml()
1318     *
1319     * @param attrXmlName The XML name of the attribute to modify
1320     * @param value The new value for the attribute. If set to null, the attribute is removed.
1321     * @param override True if the value must be set even if one already exists.
1322     * @return The {@link UiAttributeNode} that has been modified or null.
1323     */
1324    public UiAttributeNode setAttributeValue(String attrXmlName, String value, boolean override) {
1325        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1326
1327        if (value == null) {
1328            value = ""; //$NON-NLS-1$ -- this removes an attribute
1329        }
1330
1331        for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
1332            AttributeDescriptor ui_desc = entry.getKey();
1333            if (ui_desc.getXmlLocalName().equals(attrXmlName)) {
1334                UiAttributeNode ui_attr = entry.getValue();
1335                // Not all attributes are editable, ignore those which are not
1336                if (ui_attr instanceof IUiSettableAttributeNode) {
1337                    String current = ui_attr.getCurrentValue();
1338                    // Only update (and mark as dirty) if the attribute did not have any
1339                    // value or if the value was different.
1340                    if (override || current == null || !current.equals(value)) {
1341                        ((IUiSettableAttributeNode) ui_attr).setCurrentValue(value);
1342                        // mark the attribute as dirty since their internal content
1343                        // as been modified, but not the underlying XML model
1344                        ui_attr.setDirty(true);
1345                        return ui_attr;
1346                    }
1347                }
1348                break;
1349            }
1350        }
1351        return null;
1352    }
1353
1354    /**
1355     * Utility method to retrieve the internal value of an attribute.
1356     * <p/>
1357     * Note that this retrieves the *field* value if the attribute has some UI, and
1358     * not the actual XML value. They may differ if the attribute is dirty.
1359     *
1360     * @param attrXmlName The XML name of the attribute to modify
1361     * @return The current internal value for the attribute or null in case of error.
1362     */
1363    public String getAttributeValue(String attrXmlName) {
1364        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1365
1366        for (Entry<AttributeDescriptor, UiAttributeNode> entry : attributeMap.entrySet()) {
1367            AttributeDescriptor ui_desc = entry.getKey();
1368            if (ui_desc.getXmlLocalName().equals(attrXmlName)) {
1369                UiAttributeNode ui_attr = entry.getValue();
1370                return ui_attr.getCurrentValue();
1371            }
1372        }
1373        return null;
1374    }
1375
1376    // ------ IPropertySource methods
1377
1378    public Object getEditableValue() {
1379        return null;
1380    }
1381
1382    /*
1383     * (non-Javadoc)
1384     * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors()
1385     *
1386     * Returns the property descriptor for this node. Since the descriptors are not linked to the
1387     * data, the AttributeDescriptor are used directly.
1388     */
1389    public IPropertyDescriptor[] getPropertyDescriptors() {
1390        List<IPropertyDescriptor> propDescs = new ArrayList<IPropertyDescriptor>();
1391
1392        // get the standard descriptors
1393        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1394        Set<AttributeDescriptor> keys = attributeMap.keySet();
1395
1396
1397        // we only want the descriptor that do implement the IPropertyDescriptor interface.
1398        for (AttributeDescriptor key : keys) {
1399            if (key instanceof IPropertyDescriptor) {
1400                propDescs.add((IPropertyDescriptor)key);
1401            }
1402        }
1403
1404        // now get the descriptor from the unknown attributes
1405        for (UiAttributeNode unknownNode : mUnknownUiAttributes) {
1406            if (unknownNode.getDescriptor() instanceof IPropertyDescriptor) {
1407                propDescs.add((IPropertyDescriptor)unknownNode.getDescriptor());
1408            }
1409        }
1410
1411        // TODO cache this maybe, as it's not going to change (except for unknown descriptors)
1412        return propDescs.toArray(new IPropertyDescriptor[propDescs.size()]);
1413    }
1414
1415    /*
1416     * (non-Javadoc)
1417     * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java.lang.Object)
1418     *
1419     * Returns the value of a given property. The id is the result of IPropertyDescriptor.getId(),
1420     * which return the AttributeDescriptor itself.
1421     */
1422    public Object getPropertyValue(Object id) {
1423        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1424
1425        UiAttributeNode attribute = attributeMap.get(id);
1426
1427        if (attribute == null) {
1428            // look for the id in the unknown attributes.
1429            for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
1430                if (id == unknownAttr.getDescriptor()) {
1431                    return unknownAttr;
1432                }
1433            }
1434        }
1435
1436        return attribute;
1437    }
1438
1439    /*
1440     * (non-Javadoc)
1441     * @see org.eclipse.ui.views.properties.IPropertySource#isPropertySet(java.lang.Object)
1442     *
1443     * Returns whether the property is set. In our case this is if the string is non empty.
1444     */
1445    public boolean isPropertySet(Object id) {
1446        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1447
1448        UiAttributeNode attribute = attributeMap.get(id);
1449
1450        if (attribute != null) {
1451            return attribute.getCurrentValue().length() > 0;
1452        }
1453
1454        // look for the id in the unknown attributes.
1455        for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
1456            if (id == unknownAttr.getDescriptor()) {
1457                return unknownAttr.getCurrentValue().length() > 0;
1458            }
1459        }
1460
1461        return false;
1462    }
1463
1464    /*
1465     * (non-Javadoc)
1466     * @see org.eclipse.ui.views.properties.IPropertySource#resetPropertyValue(java.lang.Object)
1467     *
1468     * Reset the property to its default value. For now we simply empty it.
1469     */
1470    public void resetPropertyValue(Object id) {
1471        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1472
1473        UiAttributeNode attribute = attributeMap.get(id);
1474        if (attribute != null) {
1475            // TODO: reset the value of the attribute
1476
1477            return;
1478        }
1479
1480        // look for the id in the unknown attributes.
1481        for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
1482            if (id == unknownAttr.getDescriptor()) {
1483                // TODO: reset the value of the attribute
1484
1485                return;
1486            }
1487        }
1488    }
1489
1490    /*
1491     * (non-Javadoc)
1492     * @see org.eclipse.ui.views.properties.IPropertySource#setPropertyValue(java.lang.Object, java.lang.Object)
1493     *
1494     * Set the property value. id is the result of IPropertyDescriptor.getId(), which is the
1495     * AttributeDescriptor itself. Value should be a String.
1496     */
1497    public void setPropertyValue(Object id, Object value) {
1498        HashMap<AttributeDescriptor, UiAttributeNode> attributeMap = getInternalUiAttributes();
1499
1500        UiAttributeNode attribute = attributeMap.get(id);
1501
1502        if (attribute == null) {
1503            // look for the id in the unknown attributes.
1504            for (UiAttributeNode unknownAttr : mUnknownUiAttributes) {
1505                if (id == unknownAttr.getDescriptor()) {
1506                    attribute = unknownAttr;
1507                    break;
1508                }
1509            }
1510        }
1511
1512        if (attribute != null) {
1513
1514            // get the current value and compare it to the new value
1515            String oldValue = attribute.getCurrentValue();
1516            final String newValue = (String)value;
1517
1518            if (oldValue.equals(newValue)) {
1519                return;
1520            }
1521
1522            final UiAttributeNode fAttribute = attribute;
1523            AndroidEditor editor = getEditor();
1524            editor.editXmlModel(new Runnable() {
1525                public void run() {
1526                    commitAttributeToXml(fAttribute, newValue);
1527                }
1528            });
1529        }
1530    }
1531}
1532