| /******************************************************************************* |
| * Copyright (c) 2000, 2004 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.core.jdom; |
| |
| import java.util.Enumeration; |
| |
| import org.eclipse.jdt.core.jdom.*; |
| import org.eclipse.jdt.internal.core.util.CharArrayBuffer; |
| import org.eclipse.jdt.internal.core.util.Messages; |
| |
| /** |
| * DOMNode provides an implementation for <code>IDOMNode</code>. |
| * |
| * <p>A node represents a document fragment. When a node is created, its |
| * contents are located in a contiguous range of a shared document. A shared |
| * document is a char array, and is shared in the sense that the contents of other |
| * document fragments may also be contained in the array. |
| * |
| * <p>A node maintains indicies of relevant portions of its contents |
| * in the shared document. Thus the original document and indicies create a |
| * form from which to generate the contents of the document fragment. As attributes |
| * of a node are changed, the node attempts to maintain the original formatting |
| * by only replacing relevant portions of the shared document with the value |
| * of new attributes (that is, filling in the form with replacement values). |
| * |
| * <p>When a node is first created, it is considered unfragmented. When any |
| * attribute of the node is altered, the node is then considered fragmented |
| * from that point on. A node is also considered fragmented if any of its |
| * descendants are fragmented. When a node is unfragmented, the contents of the |
| * node can be efficiently generated from the original shared document. When |
| * a node is fragmented, the contents of the node must be created using the |
| * original document and indicies as a form, filling in replacement values |
| * as required. |
| * |
| * <p>Generally, a node's contents consists of complete lines in a shared document. |
| * The contents of the node are normalized on creation to include any whitespace |
| * preceding the node on the line where the node begins, and to include and trailing |
| * whitespace up to the line where the next node begins. Any trailing // comments |
| * that begin on the line where the current node ends, are considered part of that |
| * node. |
| * |
| * @see IDOMNode |
| * @deprecated The JDOM was made obsolete by the addition in 2.0 of the more |
| * powerful, fine-grained DOM/AST API found in the |
| * org.eclipse.jdt.core.dom package. |
| */ |
| public abstract class DOMNode implements IDOMNode { |
| |
| /** |
| * The first child of this node - <code>null</code> |
| * when this node has no children. (Children of a node |
| * are implemented as a doubly linked list). |
| */ |
| protected DOMNode fFirstChild= null; |
| |
| /** |
| * The last child of this node - <code>null</code> |
| * when this node has no children. Used for efficient |
| * access to the last child when adding new children |
| * at the end of the linked list of children. |
| */ |
| protected DOMNode fLastChild= null; |
| |
| /** |
| * The sibling node following this node - <code>null</code> |
| * for the last node in the sibling list. |
| */ |
| protected DOMNode fNextNode= null; |
| |
| /** |
| * The parent of this node. A <code>null</code> |
| * parent indicates that this node is a root |
| * node of a document fragment. |
| */ |
| protected DOMNode fParent= null; |
| |
| /** |
| * The sibling node preceding this node - <code>null</code> |
| * for the first node in the sibling list. |
| */ |
| protected DOMNode fPreviousNode= null; |
| |
| /** |
| * True when this node has attributes that have |
| * been altered from their original state in the |
| * shared document, or when the attributes of a |
| * descendant have been altered. False when the |
| * contents of this node and all descendants are |
| * consistent with the content of the shared |
| * document. |
| */ |
| protected boolean fIsFragmented= false; |
| |
| /** |
| * The name of this node. For efficiency, the |
| * name of a node is duplicated in this variable |
| * on creation, rather than always having to fetch |
| * the name from the shared document. |
| */ |
| protected String fName= null; |
| |
| /** |
| * The original inclusive indicies of this node's name in |
| * the shared document. Values of -1 indiciate the name |
| * does not exist in the document. |
| */ |
| protected int[] fNameRange; |
| |
| /** |
| * The shared document that the contents for this node |
| * are contained in. Attribute indicies are positions |
| * in this character array. |
| */ |
| protected char[] fDocument= null; |
| |
| /** |
| * The original entire inclusive range of this node's contents |
| * within its document. Values of -1 indicate the contents |
| * of this node do not exist in the document. |
| */ |
| protected int[] fSourceRange; |
| |
| /** |
| * The current state of bit masks defined by this node. |
| * Initially all bit flags are turned off. All bit masks |
| * are defined by this class to avoid overlap, although |
| * bit masks are node type specific. |
| * |
| * @see #setMask |
| * @see #getMask |
| */ |
| protected int fStateMask= 0; |
| |
| /** |
| * This position is the position of the end of the last line separator before the closing brace starting |
| * position of the receiver. |
| */ |
| protected int fInsertionPosition; |
| |
| /** |
| * A bit mask indicating this field has an initializer |
| * expression |
| */ |
| protected static final int MASK_FIELD_HAS_INITIALIZER= 0x00000001; |
| |
| /** |
| * A bit mask indicating this field is a secondary variable |
| * declarator for a previous field declaration. |
| */ |
| protected static final int MASK_FIELD_IS_VARIABLE_DECLARATOR= 0x00000002; |
| |
| /** |
| * A bit mask indicating this field's type has been |
| * altered from its original contents in the document. |
| */ |
| protected static final int MASK_FIELD_TYPE_ALTERED= 0x00000004; |
| |
| /** |
| * A bit mask indicating this node's name has been |
| * altered from its original contents in the document. |
| */ |
| protected static final int MASK_NAME_ALTERED= 0x00000008; |
| |
| /** |
| * A bit mask indicating this node currently has a |
| * body. |
| */ |
| protected static final int MASK_HAS_BODY= 0x00000010; |
| |
| /** |
| * A bit mask indicating this node currently has a |
| * preceding comment. |
| */ |
| protected static final int MASK_HAS_COMMENT= 0x00000020; |
| |
| /** |
| * A bit mask indicating this method is a constructor. |
| */ |
| protected static final int MASK_IS_CONSTRUCTOR= 0x00000040; |
| |
| /** |
| * A bit mask indicating this type is a class. |
| */ |
| protected static final int MASK_TYPE_IS_CLASS= 0x00000080; |
| |
| /** |
| * A bit mask indicating this type has a superclass |
| * (requires or has an 'extends' clause). |
| */ |
| protected static final int MASK_TYPE_HAS_SUPERCLASS= 0x00000100; |
| |
| /** |
| * A bit mask indicating this type implements |
| * or extends some interfaces |
| */ |
| protected static final int MASK_TYPE_HAS_INTERFACES= 0x00000200; |
| |
| /** |
| * A bit mask indicating this return type of this method has |
| * been altered from the original contents. |
| */ |
| protected static final int MASK_RETURN_TYPE_ALTERED= 0x00000400; |
| |
| /** |
| * A bit mask indicating this node has detailed source indexes |
| */ |
| protected static final int MASK_DETAILED_SOURCE_INDEXES = 0x00000800; |
| |
| /** |
| * Creates a new empty document fragment. |
| */ |
| DOMNode() { |
| fName= null; |
| fDocument= null; |
| fSourceRange= new int[]{-1, -1}; |
| fNameRange= new int[]{-1, -1}; |
| fragment(); |
| } |
| /** |
| * Creates a new document fragment on the given range of the document. |
| * |
| * @param document - the document containing this node's original contents |
| * @param sourceRange - a two element array of integers describing the |
| * entire inclusive source range of this node within its document. |
| * Contents start on and include the character at the first position. |
| * Contents end on and include the character at the last position. |
| * An array of -1's indicates this node's contents do not exist |
| * in the document. |
| * @param name - the identifier portion of the name of this node, or |
| * <code>null</code> if this node does not have a name |
| * @param nameRange - a two element array of integers describing the |
| * entire inclusive source range of this node's name within its document, |
| * including any array qualifiers that might immediately follow the name |
| * or -1's if this node does not have a name. |
| */ |
| DOMNode(char[] document, int[] sourceRange, String name, int[] nameRange) { |
| super(); |
| fDocument= document; |
| fSourceRange= sourceRange; |
| fName= name; |
| fNameRange= nameRange; |
| |
| } |
| /** |
| * Adds the given un-parented node (document fragment) as the last child of |
| * this node. |
| * |
| * <p>When a child is added, this node must be considered fragmented such that |
| * the contents of this node are properly generated. |
| * |
| * @see IDOMNode#addChild(IDOMNode) |
| */ |
| public void addChild(IDOMNode child) throws IllegalArgumentException, DOMException { |
| basicAddChild(child); |
| |
| // if the node is a constructor, it must also be fragmented to update the constructor's name |
| if (child.getNodeType() == IDOMNode.METHOD && ((IDOMMethod)child).isConstructor()) { |
| ((DOMNode)child).fragment(); |
| } else { |
| fragment(); |
| } |
| } |
| /** |
| * Appends the current contents of this document fragment |
| * to the given <code>CharArrayBuffer</code>. |
| * |
| * <p>If this node is fragmented, contents must be generated by |
| * using the original document and indicies as a form for the current |
| * attribute values of this node. If this node not fragmented, the |
| * contents can be obtained from the document. |
| * |
| */ |
| protected void appendContents(CharArrayBuffer buffer) { |
| if (isFragmented()) { |
| appendFragmentedContents(buffer); |
| } else { |
| buffer.append(fDocument, fSourceRange[0], fSourceRange[1] + 1 - fSourceRange[0]); |
| } |
| } |
| /** |
| * Appends the contents of all children of this node to the |
| * given <code>CharArrayBuffer</code>. |
| * |
| * <p>This algorithm used minimizes String generation by merging |
| * adjacent unfragmented children into one substring operation. |
| * |
| */ |
| protected void appendContentsOfChildren(CharArrayBuffer buffer) { |
| DOMNode child= fFirstChild; |
| DOMNode sibling; |
| |
| int start= 0, end= 0; |
| if (child != null) { |
| start= child.getStartPosition(); |
| end= child.getEndPosition(); |
| } |
| while (child != null) { |
| sibling= child.fNextNode; |
| if (sibling != null) { |
| if (sibling.isContentMergableWith(child)) { |
| end= sibling.getEndPosition(); |
| } else { |
| if (child.isFragmented()) { |
| child.appendContents(buffer); |
| } else { |
| buffer.append(child.getDocument(), start, end + 1 - start); |
| } |
| start= sibling.getStartPosition(); |
| end= sibling.getEndPosition(); |
| } |
| } else { |
| if (child.isFragmented()) { |
| child.appendContents(buffer); |
| } else { |
| buffer.append(child.getDocument(), start, end + 1 - start); |
| } |
| } |
| child= sibling; |
| } |
| } |
| /** |
| * Appends the contents of this node to the given <code>CharArrayBufer</code>, using |
| * the original document and indicies as a form for the current attribute |
| * values of this node. |
| */ |
| protected abstract void appendFragmentedContents(CharArrayBuffer buffer); |
| /** |
| * Adds the given un-parented node (document fragment) as the last child of |
| * this node without setting this node's 'fragmented' flag. This |
| * method is only used by the <code>DOMBuilder</code> when creating a new DOM such |
| * that a new DOM is unfragmented. |
| */ |
| void basicAddChild(IDOMNode child) throws IllegalArgumentException, DOMException { |
| // verify child may be added |
| if (!canHaveChildren()) { |
| throw new DOMException(Messages.dom_unableAddChild); |
| } |
| if (child == null) { |
| throw new IllegalArgumentException(Messages.dom_addNullChild); |
| } |
| if (!isAllowableChild(child)) { |
| throw new DOMException(Messages.dom_addIncompatibleChild); |
| } |
| if (child.getParent() != null) { |
| throw new DOMException(Messages.dom_addChildWithParent); |
| } |
| /* NOTE: To test if the child is an ancestor of this node, we |
| * need only test if the root of this node is the child (the child |
| * is already a root since we have just guarenteed it has no parent). |
| */ |
| if (child == getRoot()) { |
| throw new DOMException(Messages.dom_addAncestorAsChild); |
| } |
| |
| DOMNode node= (DOMNode)child; |
| |
| // if the child is not already part of this document, localize its contents |
| // before adding it to the tree |
| if (node.getDocument() != getDocument()) { |
| node.localizeContents(); |
| } |
| |
| // add the child last |
| if (fFirstChild == null) { |
| // this is the first and only child |
| fFirstChild= node; |
| } else { |
| fLastChild.fNextNode= node; |
| node.fPreviousNode= fLastChild; |
| } |
| fLastChild= node; |
| node.fParent= this; |
| } |
| /** |
| * Generates detailed source indexes for this node if possible. |
| * |
| * @exception DOMException if unable to generate detailed source indexes |
| * for this node |
| */ |
| protected void becomeDetailed() throws DOMException { |
| if (!isDetailed()) { |
| DOMNode detailed= getDetailedNode(); |
| if (detailed == null) { |
| throw new DOMException(Messages.dom_cannotDetail); |
| } |
| if (detailed != this) { |
| shareContents(detailed); |
| } |
| } |
| } |
| /** |
| * Returns true if this node is allowed to have children, otherwise false. |
| * |
| * <p>Default implementation of <code>IDOMNode</code> interface method returns false; this |
| * method must be overridden by subclasses that implement nodes that allow |
| * children. |
| * |
| * @see IDOMNode#canHaveChildren() |
| */ |
| public boolean canHaveChildren() { |
| return false; |
| } |
| /** |
| * @see IDOMNode#clone() |
| */ |
| public Object clone() { |
| |
| // create a new buffer with all my contents and children contents |
| int length= 0; |
| char[] buffer= null; |
| int offset= fSourceRange[0]; |
| |
| if (offset >= 0) { |
| length= fSourceRange[1] - offset + 1; |
| buffer= new char[length]; |
| System.arraycopy(fDocument, offset, buffer, 0, length); |
| } |
| DOMNode clone= newDOMNode(); |
| clone.shareContents(this); |
| clone.fDocument = buffer; |
| |
| if (offset > 0) { |
| clone.offset(0 - offset); |
| } |
| |
| // clone my children |
| if (canHaveChildren()) { |
| Enumeration children= getChildren(); |
| while (children.hasMoreElements()) { |
| DOMNode child= (DOMNode)children.nextElement(); |
| if (child.fDocument == fDocument) { |
| DOMNode childClone= child.cloneSharingDocument(buffer, offset); |
| clone.basicAddChild(childClone); |
| } else { |
| DOMNode childClone= (DOMNode)child.clone(); |
| clone.addChild(childClone); |
| } |
| |
| } |
| } |
| |
| return clone; |
| } |
| private DOMNode cloneSharingDocument(char[] document, int rootOffset) { |
| |
| DOMNode clone = newDOMNode(); |
| clone.shareContents(this); |
| clone.fDocument = document; |
| if (rootOffset > 0) { |
| clone.offset(0 - rootOffset); |
| } |
| |
| if (canHaveChildren()) { |
| Enumeration children = getChildren(); |
| while (children.hasMoreElements()) { |
| DOMNode child = (DOMNode) children.nextElement(); |
| if (child.fDocument == fDocument) { |
| DOMNode childClone= child.cloneSharingDocument(document, rootOffset); |
| clone.basicAddChild(childClone); |
| } else { |
| DOMNode childClone= (DOMNode)child.clone(); |
| clone.addChild(childClone); |
| } |
| } |
| } |
| return clone; |
| } |
| /** |
| * Sets this node's fragmented flag and all ancestor fragmented flags |
| * to <code>true<code>. This happens when an attribute of this node or a descendant |
| * node has been altered. When a node is fragmented, its contents must |
| * be generated from its attributes and original "form" rather than |
| * from the original contents in the document. |
| */ |
| protected void fragment() { |
| if (!isFragmented()) { |
| fIsFragmented= true; |
| if (fParent != null) { |
| fParent.fragment(); |
| } |
| } |
| } |
| /** |
| * @see IDOMNode#getCharacters() |
| */ |
| public char[] getCharacters() { |
| CharArrayBuffer buffer= new CharArrayBuffer(); |
| appendContents(buffer); |
| return buffer.getContents(); |
| } |
| /** |
| * @see IDOMNode#getChild(String) |
| */ |
| public IDOMNode getChild(String name) { |
| DOMNode child = fFirstChild; |
| while (child != null) { |
| String n = child.getName(); |
| if (name == null) { |
| if (n == null) { |
| return child; |
| } |
| } else { |
| if (name.equals(n)) { |
| return child; |
| } |
| } |
| child = child.fNextNode; |
| } |
| return null; |
| } |
| /** |
| * @see IDOMNode#getChildren() |
| */ |
| public Enumeration getChildren() { |
| return new SiblingEnumeration(fFirstChild); |
| } |
| /** |
| * Returns the current contents of this document fragment, |
| * or <code>null</code> if this node has no contents. |
| * |
| * <p>If this node is fragmented, contents must be generated by |
| * using the original document and indicies as a form for the current |
| * attribute values of this node. If this node not fragmented, the |
| * contents can be obtained from the document. |
| * |
| * @see IDOMNode#getContents() |
| */ |
| public String getContents() { |
| CharArrayBuffer buffer= new CharArrayBuffer(); |
| appendContents(buffer); |
| return buffer.toString(); |
| } |
| /** |
| * Returns a new document fragment representing this node with |
| * detailed source indexes. Subclasses that provide a detailed |
| * implementation must override this method. |
| */ |
| protected DOMNode getDetailedNode() { |
| return this; |
| } |
| /** |
| * Returns the document containing this node's original contents. |
| * The document may be shared by other nodes. |
| */ |
| protected char[] getDocument() { |
| return fDocument; |
| } |
| /** |
| * Returns the original position of the last character of this |
| * node's contents in its document. |
| */ |
| public int getEndPosition() { |
| return fSourceRange[1]; |
| } |
| /** |
| * Returns a factory with which to create new document fragments. |
| */ |
| protected IDOMFactory getFactory() { |
| return new DOMFactory(); |
| } |
| /** |
| * @see IDOMNode#getFirstChild() |
| */ |
| public IDOMNode getFirstChild() { |
| return fFirstChild; |
| } |
| /** |
| * Returns the position at which the first child of this node should be inserted. |
| */ |
| public int getInsertionPosition() { |
| return fInsertionPosition; |
| } |
| /** |
| * Returns <code>true</code> if the given mask of this node's state flag |
| * is turned on, otherwise <code>false</code>. |
| */ |
| protected boolean getMask(int mask) { |
| return (fStateMask & mask) > 0; |
| } |
| /** |
| * @see IDOMNode#getName() |
| */ |
| public String getName() { |
| return fName; |
| } |
| /** |
| * Returns the source code to be used for this node's name. |
| */ |
| protected char[] getNameContents() { |
| if (isNameAltered()) { |
| return fName.toCharArray(); |
| } else { |
| if (fName == null || fNameRange[0] < 0) { |
| return null; |
| } else { |
| int length = fNameRange[1] + 1 - fNameRange[0]; |
| char[] result = new char[length]; |
| System.arraycopy(fDocument, fNameRange[0], result, 0, length); |
| return result; |
| } |
| } |
| } |
| /** |
| * @see IDOMNode#getNextNode() |
| */ |
| public IDOMNode getNextNode() { |
| return fNextNode; |
| } |
| /** |
| * @see IDOMNode#getParent() |
| */ |
| public IDOMNode getParent() { |
| return fParent; |
| } |
| /** |
| * Answers a source position which corresponds to the end of the parent |
| * element's declaration. |
| */ |
| protected int getParentEndDeclaration() { |
| IDOMNode parent = getParent(); |
| if (parent == null) { |
| return 0; |
| } else { |
| if (parent instanceof IDOMCompilationUnit) { |
| return 0; |
| } else { |
| return ((DOMType)parent).getOpenBodyEnd(); |
| } |
| } |
| } |
| /** |
| * @see IDOMNode#getPreviousNode() |
| */ |
| public IDOMNode getPreviousNode() { |
| return fPreviousNode; |
| } |
| /** |
| * Returns the root node of this document fragment. |
| */ |
| protected IDOMNode getRoot() { |
| if (fParent == null) { |
| return this; |
| } else { |
| return fParent.getRoot(); |
| } |
| } |
| /** |
| * Returns the original position of the first character of this |
| * node's contents in its document. |
| */ |
| public int getStartPosition() { |
| return fSourceRange[0]; |
| } |
| /** |
| * @see IDOMNode#insertSibling(IDOMNode) |
| */ |
| public void insertSibling(IDOMNode sibling) throws IllegalArgumentException, DOMException { |
| // verify sibling may be added |
| if (sibling == null) { |
| throw new IllegalArgumentException(Messages.dom_addNullSibling); |
| } |
| if (fParent == null) { |
| throw new DOMException(Messages.dom_addSiblingBeforeRoot); |
| } |
| if (!fParent.isAllowableChild(sibling)) { |
| throw new DOMException(Messages.dom_addIncompatibleSibling); |
| } |
| if (sibling.getParent() != null) { |
| throw new DOMException(Messages.dom_addSiblingWithParent); |
| } |
| /* NOTE: To test if the sibling is an ancestor of this node, we |
| * need only test if the root of this node is the child (the sibling |
| * is already a root since we have just guaranteed it has no parent). |
| */ |
| if (sibling == getRoot()) { |
| throw new DOMException(Messages.dom_addAncestorAsSibling); |
| } |
| |
| DOMNode node= (DOMNode)sibling; |
| |
| // if the sibling is not already part of this document, localize its contents |
| // before inserting it into the tree |
| if (node.getDocument() != getDocument()) { |
| node.localizeContents(); |
| } |
| |
| // insert the node |
| if (fPreviousNode == null) { |
| fParent.fFirstChild= node; |
| } else { |
| fPreviousNode.fNextNode= node; |
| } |
| node.fParent= fParent; |
| node.fPreviousNode= fPreviousNode; |
| node.fNextNode= this; |
| fPreviousNode= node; |
| |
| // if the node is a constructor, it must also be fragmented to update the constructor's name |
| if (node.getNodeType() == IDOMNode.METHOD && ((IDOMMethod)node).isConstructor()) { |
| node.fragment(); |
| } else { |
| fParent.fragment(); |
| } |
| } |
| /** |
| * @see IDOMNode |
| */ |
| public boolean isAllowableChild(IDOMNode node) { |
| return false; |
| } |
| /** |
| * Returns <code>true</code> if the contents of this node are from the same document as |
| * the given node, the contents of this node immediately follow the contents |
| * of the given node, and neither this node or the given node are fragmented - |
| * otherwise <code>false</code>. |
| */ |
| protected boolean isContentMergableWith(DOMNode node) { |
| return !node.isFragmented() && !isFragmented() && node.getDocument() == getDocument() && |
| node.getEndPosition() + 1 == getStartPosition(); |
| } |
| /** |
| * Returns <code>true</code> if this node has detailed source index information, |
| * or <code>false</code> if this node has limited source index information. To |
| * perform some manipulations, detailed indexes are required. |
| */ |
| protected boolean isDetailed() { |
| return getMask(MASK_DETAILED_SOURCE_INDEXES); |
| } |
| /** |
| * Returns <code>true</code> if this node's or a descendant node's contents |
| * have been altered since this node was created. This indicates |
| * that the contents of this node are no longer consistent with |
| * the contents of this node's document. |
| */ |
| protected boolean isFragmented() { |
| return fIsFragmented; |
| } |
| /** |
| * Returns <code>true</code> if this noed's name has been altered |
| * from the original document contents. |
| */ |
| protected boolean isNameAltered() { |
| return getMask(MASK_NAME_ALTERED); |
| } |
| /** |
| * @see IDOMNode#isSignatureEqual(IDOMNode) |
| * |
| * <p>By default, the signatures of two nodes are equal if their |
| * type and names are equal. Node types that have other requirements |
| * for equality must override this method. |
| */ |
| public boolean isSignatureEqual(IDOMNode node) { |
| return getNodeType() == node.getNodeType() && getName().equals(node.getName()); |
| } |
| /** |
| * Localizes the contents of this node and all descendant nodes, |
| * such that this node is no longer dependent on its original |
| * document in order to generate its contents. This node and all |
| * descendant nodes become unfragmented and share a new |
| * document. |
| */ |
| protected void localizeContents() { |
| |
| DOMNode clone= (DOMNode)clone(); |
| shareContents(clone); |
| |
| } |
| /** |
| * Returns a new empty <code>DOMNode</code> for this instance. |
| */ |
| protected abstract DOMNode newDOMNode(); |
| /** |
| * Normalizes this <code>DOMNode</code>'s source positions to include whitespace preceeding |
| * the node on the line on which the node starts, and all whitespace after the node up to |
| * the next node's start |
| */ |
| void normalize(ILineStartFinder finder) { |
| if (getPreviousNode() == null) |
| normalizeStartPosition(getParentEndDeclaration(), finder); |
| |
| // Set the children's position |
| if (canHaveChildren()) { |
| Enumeration children = getChildren(); |
| while(children.hasMoreElements()) |
| ((DOMNode)children.nextElement()).normalize(finder); |
| } |
| |
| normalizeEndPosition(finder, (DOMNode)getNextNode()); |
| } |
| /** |
| * Normalizes this <code>DOMNode</code>'s end position. |
| */ |
| void normalizeEndPosition(ILineStartFinder finder, DOMNode next) { |
| if (next == null) { |
| // this node's end position includes all of the characters up |
| // to the end of the enclosing node |
| DOMNode parent = (DOMNode) getParent(); |
| if (parent == null || parent instanceof DOMCompilationUnit) { |
| setSourceRangeEnd(fDocument.length - 1); |
| } else { |
| // parent is a type |
| int temp = ((DOMType)parent).getCloseBodyPosition() - 1; |
| setSourceRangeEnd(temp); |
| fInsertionPosition = Math.max(finder.getLineStart(temp + 1), getEndPosition()); |
| } |
| } else { |
| // this node's end position is just before the start of the next node |
| int temp = next.getStartPosition() - 1; |
| fInsertionPosition = Math.max(finder.getLineStart(temp + 1), getEndPosition()); |
| next.normalizeStartPosition(getEndPosition(), finder); |
| setSourceRangeEnd(next.getStartPosition() - 1); |
| } |
| } |
| /** |
| * Normalizes this <code>DOMNode</code>'s start position. |
| */ |
| void normalizeStartPosition(int previousEnd, ILineStartFinder finder) { |
| int nodeStart = getStartPosition(); |
| int lineStart = finder.getLineStart(nodeStart); |
| if (nodeStart > lineStart && (lineStart > previousEnd || (previousEnd == 0 && lineStart == 0))) |
| setStartPosition(lineStart); |
| } |
| /** |
| * Offsets all the source indexes in this node by the given amount. |
| */ |
| protected void offset(int offset) { |
| offsetRange(fNameRange, offset); |
| offsetRange(fSourceRange, offset); |
| } |
| /** |
| * Offsets the source range by the given amount |
| */ |
| protected void offsetRange(int[] range, int offset) { |
| for (int i= 0; i < range.length; i++) { |
| range[i]+=offset; |
| if (range[i] < 0) { |
| range[i]= -1; |
| } |
| } |
| } |
| /** |
| * Returns a copy of the given range. |
| */ |
| protected int[] rangeCopy(int[] range) { |
| int[] copy= new int[range.length]; |
| for (int i= 0; i < range.length; i++) { |
| copy[i]= range[i]; |
| } |
| return copy; |
| } |
| /** |
| * Separates this node from its parent and siblings, maintaining any ties that |
| * this node has to the underlying document fragment. |
| * |
| * <p>When a child is removed, its parent is fragmented such that it properly |
| * generates its contents. |
| * |
| * @see IDOMNode#remove() |
| */ |
| public void remove() { |
| |
| if (fParent != null) { |
| fParent.fragment(); |
| } |
| |
| // link siblings |
| if (fNextNode != null) { |
| fNextNode.fPreviousNode= fPreviousNode; |
| } |
| if (fPreviousNode != null) { |
| fPreviousNode.fNextNode= fNextNode; |
| } |
| // fix parent's pointers |
| if (fParent != null) { |
| if (fParent.fFirstChild == this) { |
| fParent.fFirstChild= fNextNode; |
| } |
| if (fParent.fLastChild == this) { |
| fParent.fLastChild= fPreviousNode; |
| } |
| } |
| // remove myself |
| fParent= null; |
| fNextNode= null; |
| fPreviousNode= null; |
| } |
| /** |
| * Sets the specified mask of this node's state mask on or off |
| * based on the boolean value - true -> on, false -> off. |
| */ |
| protected void setMask(int mask, boolean on) { |
| if (on) { |
| fStateMask |= mask; |
| } else { |
| fStateMask &= ~mask; |
| } |
| } |
| /** |
| * @see IDOMNode#setName |
| */ |
| public void setName(String name) { |
| fName= name; |
| setNameAltered(true); |
| fragment(); |
| } |
| /** |
| * Sets the state of this node as having |
| * its name attribute altered from the original |
| * document contents. |
| */ |
| protected void setNameAltered(boolean altered) { |
| setMask(MASK_NAME_ALTERED, altered); |
| } |
| /** |
| * Sets the original position of the last character of this node's contents |
| * in its document. This method is only used during DOM creation while |
| * normalizing the source range of each node. |
| */ |
| protected void setSourceRangeEnd(int end) { |
| fSourceRange[1]= end; |
| } |
| /** |
| * Sets the original position of the first character of this node's contents |
| * in its document. This method is only used during DOM creation while |
| * normalizing the source range of each node. |
| */ |
| protected void setStartPosition(int start) { |
| fSourceRange[0]= start; |
| } |
| /** |
| * Sets the contents of this node and descendant nodes to be the |
| * (identical) contents of the given node and its descendants. This |
| * does not effect this node's parent and sibling configuration, |
| * only the contents of this node. This is used only to localize |
| * the contents of this node. |
| */ |
| protected void shareContents(DOMNode node) { |
| fDocument= node.fDocument; |
| fIsFragmented= node.fIsFragmented; |
| fName= node.fName; |
| fNameRange= rangeCopy(node.fNameRange); |
| fSourceRange= rangeCopy(node.fSourceRange); |
| fStateMask= node.fStateMask; |
| |
| |
| if (canHaveChildren()) { |
| Enumeration myChildren= getChildren(); |
| Enumeration otherChildren= node.getChildren(); |
| DOMNode myChild, otherChild; |
| while (myChildren.hasMoreElements()) { |
| myChild= (DOMNode)myChildren.nextElement(); |
| otherChild= (DOMNode)otherChildren.nextElement(); |
| myChild.shareContents(otherChild); |
| } |
| } |
| } |
| /** |
| * Returns a <code>String</code> representing this node - for Debug purposes only. |
| */ |
| public abstract String toString(); |
| } |