package org.eclipse.jdt.internal.core.jdom; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import java.util.Enumeration; | |
import org.eclipse.jdt.core.jdom.DOMException; | |
import org.eclipse.jdt.core.jdom.DOMFactory; | |
import org.eclipse.jdt.core.jdom.IDOMCompilationUnit; | |
import org.eclipse.jdt.core.jdom.IDOMFactory; | |
import org.eclipse.jdt.core.jdom.IDOMMethod; | |
import org.eclipse.jdt.core.jdom.IDOMNode; | |
import org.eclipse.jdt.internal.core.Util; | |
import org.eclipse.jdt.internal.core.util.CharArrayBuffer; | |
/** | |
* 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 (i.e. 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 | |
*/ | |
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; | |
/** | |
* 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(Util.bind("dom.unableAddChild"/*nonNLS*/)); | |
} | |
if (child == null) { | |
throw new IllegalArgumentException(Util.bind("dom.addNullChild"/*nonNLS*/)); | |
} | |
if (!isAllowableChild(child)) { | |
throw new DOMException(Util.bind("dom.addIncompatibleChild"/*nonNLS*/)); | |
} | |
if (child.getParent() != null) { | |
throw new DOMException(Util.bind("dom.addChildWithParent"/*nonNLS*/)); | |
} | |
/* 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(Util.bind("dom.addAncestorAsChild"/*nonNLS*/)); | |
} | |
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(Util.bind("dom.cannotDetail"/*nonNLS*/)); | |
} | |
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) { | |
int length = 0; | |
int offset = fSourceRange[0]; | |
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 (n != null && n.equals(name)) { | |
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 getEndPosition(); | |
} | |
/** | |
* 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(Util.bind("dom.addNullSibling"/*nonNLS*/)); | |
} | |
if (fParent == null) { | |
throw new DOMException(Util.bind("dom.addSiblingBeforeRoot"/*nonNLS*/)); | |
} | |
if (!fParent.isAllowableChild(sibling)) { | |
throw new DOMException(Util.bind("dom.addIncompatibleSibling"/*nonNLS*/)); | |
} | |
if (sibling.getParent() != null) { | |
throw new DOMException(Util.bind("dom.addSiblingWithParent"/*nonNLS*/)); | |
} | |
/* 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(Util.bind("dom.addAncestorAsSibling"/*nonNLS*/)); | |
} | |
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 | |
setSourceRangeEnd(((DOMType)parent).getCloseBodyPosition() - 1); | |
} | |
} else { | |
// this node's end position is just before the start of the next node | |
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(); | |
} |