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