/*******************************************************************************
* Copyright (c) 2009 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
* Zend Technologies
*******************************************************************************/
package org2.eclipse.php.internal.core.ast.nodes;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org2.eclipse.php.internal.core.PHPVersion;
import org2.eclipse.php.internal.core.ast.match.ASTMatcher;
import org2.eclipse.php.internal.core.ast.visitor.Visitor;
import com.aptana.editor.php.core.typebinding.IBinding;
/**
* Abstract superclass of all Abstract Syntax Tree (AST) node types.
* <p>
* An AST node represents a PHP source code construct, such as a name, type,
* expression, statement, or declaration.
* <p>
* ASTs do not contain cycles.
* <p>
*
* @see Visitable
* @author Modhe S., Roy G. ,2007
* </p>
*/
public abstract class ASTNode implements Visitable {
/**
* ASTNode Types
*/
public static final int ARRAY_ACCESS = 0;
public static final int ARRAY_CREATION = 1;
public static final int ARRAY_ELEMENT = 2;
public static final int ASSIGNMENT = 3;
public static final int AST_ERROR = 4;
public static final int BACK_TICK_EXPRESSION = 5;
public static final int BLOCK = 6;
public static final int BREAK_STATEMENT = 7;
public static final int CAST_EXPRESSION = 8;
public static final int CATCH_CLAUSE = 9;
public static final int STATIC_CONSTANT_ACCESS = 10;
public static final int CONSTANT_DECLARATION = 11;
public static final int CLASS_DECLARATION = 12;
public static final int CLASS_INSTANCE_CREATION = 13;
public static final int CLASS_NAME = 14;
public static final int CLONE_EXPRESSION = 15;
public static final int COMMENT = 16;
public static final int CONDITIONAL_EXPRESSION = 17;
public static final int CONTINUE_STATEMENT = 18;
public static final int DECLARE_STATEMENT = 19;
public static final int DO_STATEMENT = 20;
public static final int ECHO_STATEMENT = 21;
public static final int EMPTY_STATEMENT = 22;
public static final int EXPRESSION_STATEMENT = 23;
public static final int FIELD_ACCESS = 24;
public static final int FIELD_DECLARATION = 25;
public static final int FOR_EACH_STATEMENT = 26;
public static final int FORMAL_PARAMETER = 27;
public static final int FOR_STATEMENT = 28;
public static final int FUNCTION_DECLARATION = 29;
public static final int FUNCTION_INVOCATION = 30;
public static final int FUNCTION_NAME = 31;
public static final int GLOBAL_STATEMENT = 32;
public static final int IDENTIFIER = 33;
public static final int IF_STATEMENT = 34;
public static final int IGNORE_ERROR = 35;
public static final int INCLUDE = 36;
public static final int INFIX_EXPRESSION = 37;
public static final int IN_LINE_HTML = 38;
public static final int INSTANCE_OF_EXPRESSION = 39;
public static final int INTERFACE_DECLARATION = 40;
public static final int LIST_VARIABLE = 41;
public static final int METHOD_DECLARATION = 42;
public static final int METHOD_INVOCATION = 43;
public static final int POSTFIX_EXPRESSION = 44;
public static final int PREFIX_EXPRESSION = 45;
public static final int PROGRAM = 46;
public static final int QUOTE = 47;
public static final int REFERENCE = 48;
public static final int REFLECTION_VARIABLE = 49;
public static final int RETURN_STATEMENT = 50;
public static final int SCALAR = 51;
public static final int STATIC_FIELD_ACCESS = 52;
public static final int STATIC_METHOD_INVOCATION = 53;
public static final int STATIC_STATEMENT = 54;
public static final int SWITCH_CASE = 55;
public static final int SWITCH_STATEMENT = 56;
public static final int THROW_STATEMENT = 57;
public static final int TRY_STATEMENT = 58;
public static final int UNARY_OPERATION = 59;
public static final int VARIABLE = 60;
public static final int WHILE_STATEMENT = 61;
public static final int PARENTHESIS_EXPRESSION = 62;
public static final int SINGLE_FIELD_DECLARATION = 63;
public static final int NAMESPACE = 64;
public static final int NAMESPACE_NAME = 65;
public static final int USE_STATEMENT_PART = 66;
public static final int USE_STATEMENT = 67;
public static final int GOTO_LABEL = 68;
public static final int GOTO_STATEMENT = 69;
public static final int LAMBDA_FUNCTION_DECLARATION = 70;
public static final int TRAIT_USE_STATEMENT = 71;
public static final int TRAIT_DECLARATION = 72;
public static final int FULLY_QUALIFIED_TRAIT_METHOD_REFERENCE = 73;
/**
* Internal convenience constant indicating that there is definite risk of
* cycles.
*/
static final boolean CYCLE_RISK = true;
/**
* Internal convenience constant indicating that there is no risk of cycles.
*/
static final boolean NO_CYCLE_RISK = false;
/**
* Internal convenience constant indicating that a structural property is
* mandatory.
*/
static final boolean MANDATORY = true;
/**
* Internal convenience constant indicating that a structural property is
* optional.
*/
static final boolean OPTIONAL = false;
/**
* Flag constant (bit mask, value 1) indicating that there is something not
* quite right with this AST node.
* <p>
* The standard parser (<code>ASTParser</code>) sets this flag on a node to
* indicate a syntax error detected in the vicinity.
* </p>
*/
public static final char MALFORMED = 1;
/**
* Flag constant (bit mask, value 2) indicating that this is a node that was
* created by the parser (as opposed to one created by another party).
* <p>
* The standard parser (<code>ASTParser</code>) sets this flag on the nodes
* it creates.
* </p>
*/
public static final char ORIGINAL = 2;
/**
* Flag constant (bit mask, value 4) indicating that this node is
* unmodifiable. When a node is marked unmodifiable, the following
* operations result in a runtime exception:
* <ul>
* <li>Change a simple property of this node.</li>
* <li>Add or remove a child node from this node.</li>
* <li>Parent (or re-parent) this node.</li>
* </ul>
* <p>
* The standard parser (<code>ASTParser</code>) does not set this flag on
* the nodes it creates. However, clients may set this flag on a node to
* prevent further modification of the its structural properties.
* </p>
*/
public static final char PROTECT = 4;
/**
* Flag constant (bit mask, value 8) indicating that this node or a part of
* this node is recovered from source that contains a syntax error detected
* in the vicinity.
* <p>
* The standard parser (<code>ASTParser</code>) sets this flag on a node to
* indicate a recovered node.
* </p>
*/
public static final char RECOVERED = 8;
/**
* Source range
*/
private int start = -1;
private int length = 0;
/**
* character containing flags; none set by default.
* <p>
* N.B. This is a private field, but declared as package-visible for more
* efficient access from inner classes.
* </p>
*
* @see #MALFORMED, #PROTECT, #RECOVERED, #ORIGINAL
*/
int flags = 0;
/**
* Property of parent in which this node is a child, or <code>null</code> if
* this node is a root. Initially <code>null</code>.
*
* @see #getLocationInParent
*/
private StructuralPropertyDescriptor location = null;
/**
* Owning AST.
* <p>
* N.B. This is a private field, but declared as package-visible for more
* efficient access from inner classes.
* </p>
*/
final AST ast;
/**
* Parent AST node, or <code>null</code> if this node is a root. Initially
* <code>null</code>.
*/
private ASTNode parent = null;
/**
* An unmodifiable empty map (used to implement <code>properties()</code>).
*/
private static final Map UNMODIFIABLE_EMPTY_MAP = Collections
.unmodifiableMap(new HashMap(1));
/**
* Primary field used in representing node properties efficiently. If
* <code>null</code>, this node has no properties. If a <code>String</code>,
* this is the name of this node's sole property, and <code>property2</code>
* contains its value. If a <code>HashMap</code>, this is the table of
* property name-value mappings; <code>property2</code>, if non-null is its
* unmodifiable equivalent. Initially <code>null</code>.
*
* @see #property2
*/
private Object property1 = null;
/**
* Auxillary field used in representing node properties efficiently.
*
* @see #property1
*/
private Object property2 = null;
// Aptana addition - com.aptana.editor.php.core.typebinding.IBinding
private IBinding binding;
/**
* Construct an empty ASTNode and attach it with the given AST
*
* @param ast
*/
public ASTNode(AST ast) {
if (ast == null) {
throw new IllegalArgumentException();
}
this.ast = ast;
setFlags(ast.getDefaultNodeFlag());
}
/**
* Construct a ranged ASTNode and attach it with the given AST
*
* @param ast
*/
public ASTNode(int start, int end, AST ast) {
this(ast);
this.start = start;
this.length = end - start;
}
/**
* Accepts the given visitor on a visit of the current node.
*
* @param visitor
* the visitor object
* @exception IllegalArgumentException
* if the visitor is null
*/
public final void accept(Visitor visitor) {
if (visitor == null) {
throw new IllegalArgumentException();
}
// begin with the generic pre-visit
visitor.preVisit(this);
// dynamic dispatch to internal method for type-specific visit/endVisit
accept0(visitor);
// end with the generic post-visit
visitor.postVisit(this);
}
/**
* Accepts the given visitor on a type-specific visit of the current node.
* This method must be implemented in all concrete AST node types.
* <p>
* General template for implementation on each concrete ASTNode class:
*
* <pre>
* <code>
* boolean visitChildren = visitor.visit(this);
* if (visitChildren) {
* // visit children in normal left to right reading order
* ... acceptChild();
* }
* visitor.endVisit(this);
* </code>
* </pre>
*
* Note that the caller (<code>accept</code>) take cares of invoking
* <code>visitor.preVisit(this)</code> and
* <code>visitor.postVisit(this)</code>.
* </p>
*
* @param visitor
* the visitor object
*/
abstract void accept0(Visitor visitor);
/**
* Returns whether the subtree rooted at the given node matches the given
* other object as decided by the given matcher.
* <p>
* This internal method is implemented in each of the concrete node
* subclasses.
* </p>
*
* @param matcher
* the matcher
* @param other
* the other object, or <code>null</code>
* @return <code>true</code> if the subtree matches, or <code>false</code>
* if they do not match
*/
public abstract boolean subtreeMatch(ASTMatcher matcher, Object other);
/**
* Returns an integer value identifying the type of this concrete AST node.
* The values are small positive integers, suitable for use in switch
* statements.
* <p>
* For each concrete node type there is a unique node type constant (name
* and value).
* </p>
*
* @return one of the node type constants
*/
public abstract int getType();
/**
* Returns the location of this node within its parent, or <code>null</code>
* if this is a root node.
* <p>
*
* <pre>
* ASTNode node = ...;
* ASTNode parent = node.getParent();
* StructuralPropertyDescriptor location = node.getLocationInParent();
* assert (parent != null) == (location != null);
* if ((location != null) && location.isChildProperty())
* assert parent.getStructuralProperty(location) == node;
* if ((location != null) && location.isChildListProperty())
* assert ((List) parent.getStructuralProperty(location)).contains(node);
* </pre>
*
* </p>
* <p>
* Note that the relationship between an AST node and its parent node may
* change over the lifetime of a node.
* </p>
*
* @return the location of this node in its parent, or <code>null</code> if
* this node has no parent
*/
public final StructuralPropertyDescriptor getLocationInParent() {
return this.location;
}
/**
* Returns the value of the given structural property for this node. The
* value returned depends on the kind of property:
* <ul>
* <li>{@link SimplePropertyDescriptor} - the value of the given simple
* property, or <code>null</code> if none; primitive values are "boxed"</li>
* <li>{@link ChildPropertyDescriptor} - the child node (type
* <code>ASTNode</code>), or <code>null</code> if none</li>
* <li>{@link ChildListPropertyDescriptor} - the list (element type:
* {@link ASTNode})</li>
* </ul>
*
* @param property
* the property
* @return the value, or <code>null</code> if none
* @exception RuntimeException
* if this node does not have the given property
*/
public final Object getStructuralProperty(
StructuralPropertyDescriptor property) {
if (property instanceof SimplePropertyDescriptor) {
SimplePropertyDescriptor p = (SimplePropertyDescriptor) property;
if (p.getValueType() == Integer.class) {
int result = internalGetSetIntProperty(p, true, 0);
return new Integer(result);
} else if (p.getValueType() == Boolean.class) {
boolean result = internalGetSetBooleanProperty(p, true, false);
return Boolean.valueOf(result);
} else {
return internalGetSetObjectProperty(p, true, null);
}
}
if (property instanceof ChildPropertyDescriptor) {
return internalGetSetChildProperty(
(ChildPropertyDescriptor) property, true, null);
}
if (property instanceof ChildListPropertyDescriptor) {
return internalGetChildListProperty((ChildListPropertyDescriptor) property);
}
throw new IllegalArgumentException();
}
/**
* Sets the value of the given structural property for this node. The value
* passed depends on the kind of property:
* <ul>
* <li>{@link SimplePropertyDescriptor} - the new value of the given simple
* property, or <code>null</code> if none; primitive values are "boxed"</li>
* <li>{@link ChildPropertyDescriptor} - the new child node (type
* <code>ASTNode</code>), or <code>null</code> if none</li>
* <li>{@link ChildListPropertyDescriptor} - not allowed</li>
* </ul>
*
* @param property
* the property
* @param value
* the property value
* @exception RuntimeException
* if this node does not have the given property, or if the
* given property cannot be set
*/
public final void setStructuralProperty(
StructuralPropertyDescriptor property, Object value) {
if (property instanceof SimplePropertyDescriptor) {
SimplePropertyDescriptor p = (SimplePropertyDescriptor) property;
if (p.getValueType() == int.class) {
int arg = ((Integer) value).intValue();
internalGetSetIntProperty(p, false, arg);
return;
} else if (p.getValueType() == boolean.class) {
boolean arg = ((Boolean) value).booleanValue();
internalGetSetBooleanProperty(p, false, arg);
return;
} else {
if (value == null && p.isMandatory()) {
throw new IllegalArgumentException();
}
internalGetSetObjectProperty(p, false, value);
return;
}
}
if (property instanceof ChildPropertyDescriptor) {
ChildPropertyDescriptor p = (ChildPropertyDescriptor) property;
ASTNode child = (ASTNode) value;
if (child == null && p.isMandatory()) {
throw new IllegalArgumentException();
}
internalGetSetChildProperty(p, false, child);
return;
}
if (property instanceof ChildListPropertyDescriptor) {
throw new IllegalArgumentException(
"Cannot set the list of child list property"); //$NON-NLS-1$
}
}
/**
* Returns a list of structural property descriptors for nodes of the same
* type as this node. Clients must not modify the result.
* <p>
* Note that property descriptors are a meta-level mechanism for
* manipulating ASTNodes in a generic way. They are unrelated to
* <code>get/setProperty</code>.
* </p>
*
* @return a list of property descriptors (element type:
* {@link StructuralPropertyDescriptor})
*/
public final List<StructuralPropertyDescriptor> structuralPropertiesForType() {
return internalStructuralPropertiesForType(this.ast.apiLevel);
}
/**
* Returns a list of property descriptors for this node type. Clients must
* not modify the result. This abstract method must be implemented in each
* concrete AST node type.
* <p>
* N.B. This method is package-private, so that the implementations of this
* method in each of the concrete AST node types do not clutter up the API
* doc.
* </p>
*
* @param apiLevel
* the API level; one of the <code>AST.JLS*</code> constants
* @return a list of property descriptors (element type:
* {@link StructuralPropertyDescriptor})
*/
abstract List<StructuralPropertyDescriptor> internalStructuralPropertiesForType(
PHPVersion apiLevel);
/**
* @return the related AST
*/
public AST getAST() {
return this.ast;
}
/**
* Returns this node's parent node, or <code>null</code> if this is the root
* node.
* <p>
* Note that the relationship between an AST node and its parent node may
* change over the lifetime of a node.
* </p>
*
* @return the parent of this node, or <code>null</code> if none
*/
public ASTNode getParent() {
return parent;
}
/**
* Sets or clears this node's parent node and location.
* <p>
*
* @param parent
* the new parent of this node, or <code>null</code> if none
* @see #getParent
*/
public void setParent(ASTNode parent, StructuralPropertyDescriptor location) {
this.parent = parent;
this.location = location;
}
public final int getLength() {
return length;
}
public final int getStart() {
return start;
}
public final int getEnd() {
return start + length;
}
/**
* Returns the node class for the corresponding node type.
*
* @param nodeType
* AST node type
* @return the corresponding <code>ASTNode</code> subclass
* @exception IllegalArgumentException
* if <code>nodeType</code> is not a legal AST node type
* @see #getNodeType()
*/
public static Class<? extends ASTNode> nodeClassForType(int nodeType) {
switch (nodeType) {
case ARRAY_ACCESS:
return ArrayAccess.class;
case ARRAY_CREATION:
return ArrayCreation.class;
case ARRAY_ELEMENT:
return ArrayElement.class;
case ASSIGNMENT:
return Assignment.class;
case AST_ERROR:
return ASTError.class;
case BACK_TICK_EXPRESSION:
return BackTickExpression.class;
case BLOCK:
return Block.class;
case BREAK_STATEMENT:
return BreakStatement.class;
case CAST_EXPRESSION:
return CastExpression.class;
case CATCH_CLAUSE:
return CatchClause.class;
case STATIC_CONSTANT_ACCESS:
return StaticConstantAccess.class;
case CONSTANT_DECLARATION:
return ConstantDeclaration.class;
case CLASS_DECLARATION:
return ClassDeclaration.class;
case CLASS_INSTANCE_CREATION:
return ClassInstanceCreation.class;
case CLASS_NAME:
return ClassName.class;
case CLONE_EXPRESSION:
return CloneExpression.class;
case COMMENT:
return Comment.class;
case CONDITIONAL_EXPRESSION:
return ConditionalExpression.class;
case CONTINUE_STATEMENT:
return ContinueStatement.class;
case DECLARE_STATEMENT:
return DeclareStatement.class;
case DO_STATEMENT:
return DoStatement.class;
case ECHO_STATEMENT:
return EchoStatement.class;
case EMPTY_STATEMENT:
return EmptyStatement.class;
case EXPRESSION_STATEMENT:
return ExpressionStatement.class;
case FIELD_ACCESS:
return FieldAccess.class;
case FIELD_DECLARATION:
return FieldsDeclaration.class;
case FOR_EACH_STATEMENT:
return ForEachStatement.class;
case FORMAL_PARAMETER:
return FormalParameter.class;
case FOR_STATEMENT:
return ForStatement.class;
case FUNCTION_DECLARATION:
return FunctionDeclaration.class;
case FUNCTION_INVOCATION:
return FunctionInvocation.class;
case FUNCTION_NAME:
return FunctionName.class;
case GLOBAL_STATEMENT:
return GlobalStatement.class;
case GOTO_LABEL:
return GotoLabel.class;
case GOTO_STATEMENT:
return GotoStatement.class;
case IDENTIFIER:
return Identifier.class;
case IF_STATEMENT:
return IfStatement.class;
case IGNORE_ERROR:
return IgnoreError.class;
case INCLUDE:
return Include.class;
case INFIX_EXPRESSION:
return InfixExpression.class;
case IN_LINE_HTML:
return InLineHtml.class;
case INSTANCE_OF_EXPRESSION:
return InstanceOfExpression.class;
case INTERFACE_DECLARATION:
return InterfaceDeclaration.class;
case LAMBDA_FUNCTION_DECLARATION:
return LambdaFunctionDeclaration.class;
case LIST_VARIABLE:
return ListVariable.class;
case METHOD_DECLARATION:
return MethodDeclaration.class;
case METHOD_INVOCATION:
return MethodInvocation.class;
case NAMESPACE:
return NamespaceDeclaration.class;
case NAMESPACE_NAME:
return NamespaceName.class;
case POSTFIX_EXPRESSION:
return PostfixExpression.class;
case PREFIX_EXPRESSION:
return PrefixExpression.class;
case PROGRAM:
return Program.class;
case QUOTE:
return Quote.class;
case REFERENCE:
return Reference.class;
case REFLECTION_VARIABLE:
return ReflectionVariable.class;
case RETURN_STATEMENT:
return ReturnStatement.class;
case SCALAR:
return Scalar.class;
case STATIC_FIELD_ACCESS:
return StaticFieldAccess.class;
case STATIC_METHOD_INVOCATION:
return StaticMethodInvocation.class;
case STATIC_STATEMENT:
return StaticStatement.class;
case SWITCH_CASE:
return SwitchCase.class;
case SWITCH_STATEMENT:
return SwitchStatement.class;
case THROW_STATEMENT:
return ThrowStatement.class;
case TRY_STATEMENT:
return TryStatement.class;
case UNARY_OPERATION:
return UnaryOperation.class;
case USE_STATEMENT:
return UseStatement.class;
case USE_STATEMENT_PART:
return UseStatementPart.class;
case VARIABLE:
return Variable.class;
case WHILE_STATEMENT:
return WhileStatement.class;
case PARENTHESIS_EXPRESSION:
return ParenthesisExpression.class;
}
throw new IllegalArgumentException();
}
public String toString() {
final StringBuffer buffer = new StringBuffer();
toString(buffer, ""); //$NON-NLS-1$
return buffer.toString();
}
/**
* Appends the start, length parameters to the buffer
*/
protected void appendInterval(StringBuffer buffer) {
buffer.append(" start='").append(start).append("' length='").append(length).append("'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Formats a given string to an XML file
*
* @param input
* @return String the formatted string
*/
protected static String getXmlStringValue(String input) {
String escapedString = input;
escapedString = escapedString.replaceAll("&", "&"); //$NON-NLS-1$ //$NON-NLS-2$
escapedString = escapedString.replaceAll(">", ">"); //$NON-NLS-1$ //$NON-NLS-2$
escapedString = escapedString.replaceAll("<", "<"); //$NON-NLS-1$ //$NON-NLS-2$
escapedString = escapedString.replaceAll("'", "'"); //$NON-NLS-1$ //$NON-NLS-2$
return escapedString;
}
/**
* @return the Program's root for a given ASTNode
*/
public Program getProgramRoot() {
ASTNode node = this;
while (node != null) {
if (node.getType() == ASTNode.PROGRAM) {
return (Program) node;
}
node = node.getParent();
}
return null;
}
/**
* For a given node, returns the outer node that surrounds it
*
* @return the enclosing node for this node
*/
public ASTNode getEnclosingBodyNode() {
ASTNode node = this;
do {
switch (node.getType()) {
case ASTNode.FUNCTION_DECLARATION:
return node;
case ASTNode.FIELD_DECLARATION:
return null;
case ASTNode.PROGRAM:
return node;
}
node = node.getParent();
} while (node != null);
return null;
}
/**
* Returns the root node at or above this node; returns this node if it is a
* root.
*
* @return the root node at or above this node
*/
public final ASTNode getRoot() {
ASTNode candidate = this;
while (true) {
ASTNode p = candidate.getParent();
if (p == null) {
// candidate has no parent - that's the guy
return candidate;
}
candidate = p;
}
}
/**
* Sets the source range of the original source file where the source
* fragment corresponding to this node was found.
* <p>
* See {@link ASTParser#setKind(int)} for details on precisely where source
* ranges are supposed to begin and end.
* </p>
*
* @param startPosition
* a 0-based character index, or <code>-1</code> if no source
* position information is available for this node
* @param length
* a (possibly 0) length, or <code>0</code> if no source position
* information is recorded for this node
* @see #getStartPosition()
* @see #getLength()
* @see ASTParser
*/
public final void setSourceRange(int startPosition, int length) {
if (startPosition >= 0 && length < 0) {
throw new IllegalArgumentException();
}
if (startPosition < 0 && length != 0) {
throw new IllegalArgumentException();
}
this.start = startPosition;
this.length = length;
}
/**
* Removes this node from its parent. Has no effect if this node is
* unparented. If this node appears as an element of a child list property
* of its parent, then this node is removed from the list using
* <code>List.remove</code>. If this node appears as the value of a child
* property of its parent, then this node is detached from its parent by
* passing <code>null</code> to the appropriate setter method; this
* operation fails if this node is in a mandatory property.
*
*/
public final void delete() {
StructuralPropertyDescriptor p = getLocationInParent();
if (p == null) {
// node is unparented
return;
}
if (p.isChildProperty()) {
getParent().setStructuralProperty(this.location, null);
return;
}
if (p.isChildListProperty()) {
List l = (List) getParent().getStructuralProperty(this.location);
l.remove(this);
}
}
/**
* Prelude portion of the "3 step program" for replacing the old child of
* this node with another node. Here is the code pattern found in all AST
* node subclasses:
*
* <pre>
* ASTNode oldChild = this.foo;
* preReplaceChild(oldChild, newFoo, FOO_PROPERTY);
* this.foo = newFoo;
* postReplaceChild(oldChild, newFoo, FOO_PROPERTY);
* </pre>
*
* The first part (preReplaceChild) does all the precondition checks,
* reports pre-delete events, and changes parent links. The old child is
* delinked from its parent (making it a root node), and the new child node
* is linked to its parent. The new child node must be a root node in the
* same AST as its new parent, and must not be an ancestor of this node. All
* three nodes must be modifiable (not PROTECTED). The replace operation
* must fail atomically; so it is crucial that all precondition checks be
* done before any linking and delinking happens. The final part
* (postReplaceChild )reports post-add events.
* <p>
* This method calls <code>ast.modifying()</code> for the nodes affected.
* </p>
*
* @param oldChild
* the old child of this node, or <code>null</code> if there was
* no old child to replace
* @param newChild
* the new child of this node, or <code>null</code> if there is
* no replacement child
* @param property
* the property descriptor of this node describing the
* relationship between node and child
* @exception RuntimeException
* if:
* <ul>
* <li>the node belongs to a different AST</li>
* <li>the node already has a parent</li>
* <li>a cycle in would be created</li>
* <li>any of the nodes involved are unmodifiable</li>
* </ul>
*/
final void preReplaceChild(ASTNode oldChild, ASTNode newChild,
ChildPropertyDescriptor property) {
if ((this.flags & PROTECT) != 0) {
// this node is protected => cannot gain or lose children
throw new IllegalArgumentException("AST node cannot be modified"); //$NON-NLS-1$
}
if (newChild != null) {
checkNewChild(this, newChild, property.cycleRisk, null);
}
// delink old child from parent
if (oldChild != null) {
if ((oldChild.flags & PROTECT) != 0) {
// old child node is protected => cannot be unparented
throw new IllegalArgumentException(
"AST node cannot be modified"); //$NON-NLS-1$
}
if (newChild != null) {
this.ast.preReplaceChildEvent(this, oldChild, newChild,
property);
} else {
this.ast.preRemoveChildEvent(this, oldChild, property);
}
oldChild.setParent(null, null);
} else {
if (newChild != null) {
this.ast.preAddChildEvent(this, newChild, property);
}
}
// link new child to parent
if (newChild != null) {
newChild.setParent(this, property);
// cannot notify postAddChildEvent until parent is linked to child
// too
}
}
/**
* Postlude portion of the "3 step program" for replacing the old child of
* this node with another node. See
* {@link #preReplaceChild(ASTNode, ASTNode, ChildPropertyDescriptor)} for
* details.
*/
final void postReplaceChild(ASTNode oldChild, ASTNode newChild,
ChildPropertyDescriptor property) {
// link new child to parent
if (newChild != null) {
if (oldChild != null) {
this.ast.postReplaceChildEvent(this, oldChild, newChild,
property);
} else {
this.ast.postAddChildEvent(this, newChild, property);
}
} else {
this.ast.postRemoveChildEvent(this, oldChild, property);
}
}
/**
* Prelude portion of the "3 step program" for changing the value of a
* simple property of this node. Here is the code pattern found in all AST
* node subclasses:
*
* <pre>
* preValueChange(FOO_PROPERTY);
* this.foo = newFoo;
* postValueChange(FOO_PROPERTY);
* </pre>
*
* The first part (preValueChange) does the precondition check to make sure
* the node is modifiable (not PROTECTED). The change operation must fail
* atomically; so it is crucial that the precondition checks are done before
* the field is hammered. The final part (postValueChange)reports
* post-change events.
* <p>
* This method calls <code>ast.modifying()</code> for the node affected.
* </p>
*
* @param property
* the property descriptor of this node
* @exception RuntimeException
* if:
* <ul>
* <li>this node is unmodifiable</li>
* </ul>
*/
final void preValueChange(SimplePropertyDescriptor property) {
if ((this.flags & PROTECT) != 0) {
// this node is protected => cannot change valure of properties
throw new IllegalArgumentException("AST node cannot be modified"); //$NON-NLS-1$
}
this.ast.preValueChangeEvent(this, property);
this.ast.modifying();
}
/**
* Postlude portion of the "3 step program" for replacing the old child of
* this node with another node. See
* {@link #preValueChange(SimplePropertyDescriptor)} for details.
*/
final void postValueChange(SimplePropertyDescriptor property) {
this.ast.postValueChangeEvent(this, property);
}
/**
* Ensures that this node is modifiable (that is, not marked PROTECTED). If
* successful, calls ast.modifying().
*
* @exception RuntimeException
* is not modifiable
*/
final void checkModifiable() {
if ((this.flags & PROTECT) != 0) {
throw new IllegalArgumentException("AST node cannot be modified"); //$NON-NLS-1$
}
this.ast.modifying();
}
/**
* Begin lazy initialization of this node. Here is the code pattern found in
* all AST node subclasses:
*
* <pre>
* if (this.foo == null) {
* // lazy init must be thread-safe for readers
* synchronized (this) {
* if (this.foo == null) {
* preLazyInit();
* this.foo = ...; // code to create new node
* postLazyInit(this.foo, FOO_PROPERTY);
* }
* }
* }
* </pre>
*
* @since 3.0
*/
final void preLazyInit() {
// IMPORTANT: this method is called by readers
// ASTNode.this is locked at this point
this.ast.disableEvents();
// will turn events back on in postLasyInit
}
/**
* End lazy initialization of this node.
*
* @param newChild
* the new child of this node, or <code>null</code> if there is
* no replacement child
* @param property
* the property descriptor of this node describing the
* relationship between node and child
* @since 3.0
*/
final void postLazyInit(ASTNode newChild, ChildPropertyDescriptor property) {
// IMPORTANT: this method is called by readers
// ASTNode.this is locked at this point
// newChild is brand new (so no chance of concurrent access)
newChild.setParent(this, property);
// turn events back on (they were turned off in corresponding
// preLazyInit)
this.ast.reenableEvents();
}
/**
* Returns the named property of this node, or <code>null</code> if none.
*
* @param propertyName
* the property name
* @return the property value, or <code>null</code> if none
* @see #setProperty(String,Object)
*/
public final Object getProperty(String propertyName) {
if (propertyName == null) {
throw new IllegalArgumentException();
}
if (this.property1 == null) {
// node has no properties at all
return null;
}
if (this.property1 instanceof String) {
// node has only a single property
if (propertyName.equals(this.property1)) {
return this.property2;
} else {
return null;
}
}
// otherwise node has table of properties
Map m = (Map) this.property1;
return m.get(propertyName);
}
/**
* Sets the named property of this node to the given value, or to
* <code>null</code> to clear it.
* <p>
* Clients should employ property names that are sufficiently unique to
* avoid inadvertent conflicts with other clients that might also be setting
* properties on the same node.
* </p>
* <p>
* Note that modifying a property is not considered a modification to the
* AST itself. This is to allow clients to decorate existing nodes with
* their own properties without jeopardizing certain things (like the
* validity of bindings), which rely on the underlying tree remaining
* static.
* </p>
*
* @param propertyName
* the property name
* @param data
* the new property value, or <code>null</code> if none
* @see #getProperty(String)
*/
public final void setProperty(String propertyName, Object data) {
if (propertyName == null) {
throw new IllegalArgumentException();
}
// N.B. DO NOT CALL ast.modifying();
if (this.property1 == null) {
// node has no properties at all
if (data == null) {
// we already know this
return;
}
// node gets its fist property
this.property1 = propertyName;
this.property2 = data;
return;
}
if (this.property1 instanceof String) {
// node has only a single property
if (propertyName.equals(this.property1)) {
// we're in luck
this.property2 = data;
if (data == null) {
// just deleted last property
this.property1 = null;
this.property2 = null;
}
return;
}
if (data == null) {
// we already know this
return;
}
// node already has one property - getting its second
// convert to more flexible representation
HashMap m = new HashMap(2);
m.put(this.property1, this.property2);
m.put(propertyName, data);
this.property1 = m;
this.property2 = null;
return;
}
// node has two or more properties
HashMap m = (HashMap) this.property1;
if (data == null) {
m.remove(propertyName);
// check for just one property left
if (m.size() == 1) {
// convert to more efficient representation
Map.Entry[] entries = (Map.Entry[]) m.entrySet().toArray(
new Map.Entry[1]);
this.property1 = entries[0].getKey();
this.property2 = entries[0].getValue();
}
return;
} else {
m.put(propertyName, data);
// still has two or more properties
return;
}
}
/**
* Returns an unmodifiable table of the properties of this node with non-
* <code>null</code> values.
*
* @return the table of property values keyed by property name (key type:
* <code>String</code>; value type: <code>Object</code>)
*/
public final Map properties() {
if (this.property1 == null) {
// node has no properties at all
return UNMODIFIABLE_EMPTY_MAP;
}
if (this.property1 instanceof String) {
// node has a single property
return Collections.singletonMap(this.property1, this.property2);
}
// node has two or more properties
if (this.property2 == null) {
this.property2 = Collections.unmodifiableMap((Map) this.property1);
}
// property2 is unmodifiable wrapper for map in property1
return (Map) this.property2;
}
/**
* Returns the flags associated with this node.
* <p>
* No flags are associated with newly created nodes.
* </p>
* <p>
* The flags are the bitwise-or of individual flags. The following flags are
* currently defined:
* <ul>
* <li>{@link #MALFORMED} - indicates node is syntactically malformed</li>
* <li>{@link #ORIGINAL} - indicates original node created by ASTParser</li>
* <li>{@link #PROTECT} - indicates node is protected from further
* modification</li>
* <li>{@link #RECOVERED} - indicates node or a part of this node is
* recovered from source that contains a syntax error</li>
* </ul>
* Other bit positions are reserved for future use.
* </p>
*
* @return the bitwise-or of individual flags
* @see #setFlags(int)
*/
public final int getFlags() {
return this.flags & 0xFFFF;
}
/**
* Sets the flags associated with this node to the given value.
* <p>
* The flags are the bitwise-or of individual flags. The following flags are
* currently defined:
* <ul>
* <li>{@link #MALFORMED} - indicates node is syntactically malformed</li>
* <li>{@link #ORIGINAL} - indicates original node created by ASTParser</li>
* <li>{@link #PROTECT} - indicates node is protected from further
* modification</li>
* <li>{@link #RECOVERED} - indicates node or a part of this node is
* recovered from source that contains a syntax error</li>
* </ul>
* Other bit positions are reserved for future use.
* </p>
* <p>
* Note that the flags are <em>not</em> considered a structural property of
* the node, and can be changed even if the node is marked as protected.
* </p>
*
* @param flags
* the bitwise-or of individual flags
* @see #getFlags()
*/
public final void setFlags(int flags) {
this.ast.modifying();
this.flags |= flags;
}
/**
* Returns a deep copy of the subtree of AST nodes rooted at the given node.
* The resulting nodes are owned by the given AST, which may be different
* from the ASTs of the given node. Even if the given node has a parent, the
* result node will be unparented.
* <p>
* Source range information on the original nodes is automatically copied to
* the new nodes. Client properties (<code>properties</code>) are not
* carried over.
* </p>
* <p>
* The node's <code>AST</code> and the target <code>AST</code> must support
* the same API level.
* </p>
*
* @param target
* the AST that is to own the nodes in the result
* @param node
* the node to copy, or <code>null</code> if none
* @return the copied node, or <code>null</code> if <code>node</code> is
* <code>null</code>
*/
public static <T extends ASTNode> T copySubtree(AST target, T node) {
if (node == null) {
return null;
}
if (target == null) {
throw new IllegalArgumentException();
}
if (target.apiLevel() != node.getAST().apiLevel()) {
throw new UnsupportedOperationException();
}
T newNode = (T) node.clone(target);
return newNode;
}
/**
* Returns a deep copy of the subtrees of AST nodes rooted at the given list
* of nodes. The resulting nodes are owned by the given AST, which may be
* different from the ASTs of the nodes in the list. Even if the nodes in
* the list have parents, the nodes in the result will be unparented.
* <p>
* Source range information on the original nodes is automatically copied to
* the new nodes. Client properties (<code>properties</code>) are not
* carried over.
* </p>
*
* @param target
* the AST that is to own the nodes in the result
* @param nodes
* the list of nodes to copy (element type: <code>ASTNode</code>)
* @return the list of copied subtrees (element type: <code>ASTNode</code>)
*/
public static List copySubtrees(AST target, List nodes) {
List result = new ArrayList(nodes.size());
for (Iterator it = nodes.iterator(); it.hasNext();) {
ASTNode oldNode = (ASTNode) it.next();
ASTNode newNode = oldNode.clone(target);
result.add(newNode);
}
return result;
}
/**
* Returns a deep copy of the subtree of AST nodes rooted at this node. The
* resulting nodes are owned by the given AST, which may be different from
* the AST of this node. Even if this node has a parent, the result node
* will be unparented.
* <p>
* This method reports pre- and post-clone events, and dispatches to
* <code>clone0(AST)</code> which is reimplemented in node subclasses.
* </p>
*
* @param target
* the AST that is to own the nodes in the result
* @return the root node of the copies subtree
*/
final ASTNode clone(AST target) {
this.ast.preCloneNodeEvent(this);
ASTNode c = this.clone0(target);
this.ast.postCloneNodeEvent(this, c);
return c;
}
/**
* Returns a deep copy of the subtree of AST nodes rooted at this node. The
* resulting nodes are owned by the given AST, which may be different from
* the AST of this node. Even if this node has a parent, the result node
* will be unparented.
* <p>
* This method must be implemented in subclasses.
* </p>
* <p>
* This method does not report pre- and post-clone events. All callers
* should instead call <code>clone(AST)</code> to ensure that pre- and
* post-clone events are reported.
* </p>
* <p>
* N.B. This method is package-private, so that the implementations of this
* method in each of the concrete AST node types do not clutter up the API
* doc.
* </p>
*
* @param target
* the AST that is to own the nodes in the result
* @return the root node of the copies subtree
*/
abstract ASTNode clone0(AST target);
/**
* Checks whether the given new child node is a node in a different AST from
* its parent-to-be, whether it is already has a parent, whether adding it
* to its parent-to-be would create a cycle, and whether the child is of the
* right type. The parent-to-be is the enclosing instance.
*
* @param node
* the parent-to-be node
* @param newChild
* the new child of the parent
* @param cycleCheck
* <code>true</code> if cycles are possible and need to be
* checked, <code>false</code> if cycles are impossible and do
* not need to be checked
* @param nodeType
* a type constraint on child nodes, or <code>null</code> if no
* special check is required
* @exception IllegalArgumentException
* if:
* <ul>
* <li>the child is null</li>
* <li>the node belongs to a different AST</li>
* <li>the child has the incorrect node type</li>
* <li>the node already has a parent</li>
* <li>a cycle in would be created</li>
* </ul>
*/
static void checkNewChild(ASTNode node, ASTNode newChild,
boolean cycleCheck, Class nodeType) {
if (newChild.ast != node.ast) {
// new child is from a different AST
throw new IllegalArgumentException();
}
if (newChild.getParent() != null) {
// new child currently has a different parent
throw new IllegalArgumentException();
}
if (cycleCheck && newChild == node.getProgramRoot()) {
// inserting new child would create a cycle
throw new IllegalArgumentException();
}
Class childClass = newChild.getClass();
if (nodeType != null && !nodeType.isAssignableFrom(childClass)) {
// new child is not of the right type
throw new ClassCastException();
}
if ((newChild.flags & PROTECT) != 0) {
// new child node is protected => cannot be parented
throw new IllegalArgumentException("AST node cannot be modified"); //$NON-NLS-1$
}
}
/**
* Sets the value of the given int-valued property for this node. The
* default implementation of this method throws an exception explaining that
* this node does not have such a property. This method should be extended
* in subclasses that have at leasy one simple property whose value type is
* int.
*
* @param property
* the property
* @param get
* <code>true</code> for a get operation, and <code>false</code>
* for a set operation
* @param value
* the new property value; ignored for get operations
* @return the value; always returns <code>0</code> for set operations
* @exception RuntimeException
* if this node does not have the given property, or if the
* given value cannot be set as specified
*/
int internalGetSetIntProperty(SimplePropertyDescriptor property,
boolean get, int value) {
throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
}
/**
* Sets the value of the given boolean-valued property for this node. The
* default implementation of this method throws an exception explaining that
* this node does not have such a property. This method should be extended
* in subclasses that have at leasy one simple property whose value type is
* boolean.
*
* @param property
* the property
* @param get
* <code>true</code> for a get operation, and <code>false</code>
* for a set operation
* @param value
* the new property value; ignored for get operations
* @return the value; always returns <code>false</code> for set operations
* @exception RuntimeException
* if this node does not have the given property, or if the
* given value cannot be set as specified
*/
boolean internalGetSetBooleanProperty(SimplePropertyDescriptor property,
boolean get, boolean value) {
throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
}
/**
* Sets the value of the given property for this node. The default
* implementation of this method throws an exception explaining that this
* node does not have such a property. This method should be extended in
* subclasses that have at leasy one simple property whose value type is a
* reference type.
*
* @param property
* the property
* @param get
* <code>true</code> for a get operation, and <code>false</code>
* for a set operation
* @param value
* the new property value, or <code>null</code> if none; ignored
* for get operations
* @return the value, or <code>null</code> if none; always returns
* <code>null</code> for set operations
* @exception RuntimeException
* if this node does not have the given property, or if the
* given value cannot be set as specified
*/
Object internalGetSetObjectProperty(SimplePropertyDescriptor property,
boolean get, Object value) {
throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
}
/**
* Sets the child value of the given property for this node. The default
* implementation of this method throws an exception explaining that this
* node does not have such a property. This method should be extended in
* subclasses that have at leasy one child property.
*
* @param property
* the property
* @param get
* <code>true</code> for a get operation, and <code>false</code>
* for a set operation
* @param child
* the new child value, or <code>null</code> if none; always
* <code>null</code> for get operations
* @return the child, or <code>null</code> if none; always returns
* <code>null</code> for set operations
* @exception RuntimeException
* if this node does not have the given property, or if the
* given child cannot be set as specified
*/
ASTNode internalGetSetChildProperty(ChildPropertyDescriptor property,
boolean get, ASTNode child) {
throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
}
/**
* Returns the list value of the given property for this node. The default
* implementation of this method throws an exception explaining that this
* noed does not have such a property. This method should be extended in
* subclasses that have at leasy one child list property.
*
* @param property
* the property
* @return the list (element type: {@link ASTNode})
* @exception RuntimeException
* if the given node does not have the given property
*/
List<StructuralPropertyDescriptor> internalGetChildListProperty(
ChildListPropertyDescriptor property) {
throw new RuntimeException("Node does not have this property"); //$NON-NLS-1$
}
/**
* A specialized implementation of a list of ASTNodes. The implementation is
* based on an ArrayList.
*/
class NodeList<T extends ASTNode> extends AbstractList<T> {
/**
* The underlying list in which the nodes of this list are stored
* (element type: <code>ASTNode</code>).
* <p>
* Be stingy on storage - assume that list will be empty.
* </p>
* <p>
* This field declared default visibility (rather than private) so that
* accesses from <code>NodeList.Cursor</code> do not require a synthetic
* accessor method.
* </p>
*/
ArrayList<T> store = new ArrayList<T>(0);
/**
* The property descriptor for this list.
*/
ChildListPropertyDescriptor propertyDescriptor;
/**
* A cursor for iterating over the elements of the list. Does not lose
* its position if the list is changed during the iteration.
*/
class Cursor implements Iterator<T> {
/**
* The position of the cursor between elements. If the value is N,
* then the cursor sits between the element at positions N-1 and N.
* Initially just before the first element of the list.
*/
private int position = 0;
/*
* (non-Javadoc) Method declared on <code>Iterator</code>.
*/
public boolean hasNext() {
return this.position < NodeList.this.store.size();
}
/*
* (non-Javadoc) Method declared on <code>Iterator</code>.
*/
public T next() {
T result = NodeList.this.store.get(this.position);
this.position++;
return result;
}
/*
* (non-Javadoc) Method declared on <code>Iterator</code>.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Adjusts this cursor to accomodate an add/remove at the given
* index.
*
* @param index
* the position at which the element was added or removed
* @param delta
* +1 for add, and -1 for remove
*/
void update(int index, int delta) {
if (this.position > index) {
// the cursor has passed the added or removed element
this.position += delta;
}
}
}
/**
* A list of currently active cursors (element type: <code>Cursor</code>
* ), or <code>null</code> if there are no active cursors.
* <p>
* It is important for storage considerations to maintain the
* null-means-empty invariant; otherwise, every NodeList instance will
* waste a lot of space. A cursor is needed only for the duration of a
* visit to the child nodes. Under normal circumstances, only a single
* cursor is needed; multiple cursors are only required if there are
* multiple visits going on at the same time.
* </p>
*/
private List<? super Cursor> cursors = null;
/**
* Creates a new empty list of nodes owned by this node. This node will
* be the common parent of all nodes added to this list.
*
* @param property
* the property descriptor
* @since 3.0
*/
NodeList(ChildListPropertyDescriptor property) {
super();
this.propertyDescriptor = property;
}
/*
* (non-javadoc)
*
* @see java.util.AbstractCollection#size()
*/
public int size() {
return this.store.size();
}
/*
* (non-javadoc)
*
* @see AbstractList#get(int)
*/
public T get(int index) {
return this.store.get(index);
}
/*
* (non-javadoc)
*
* @see List#set(int, java.lang.Object)
*/
public T set(int index, T element) {
if (element == null) {
throw new IllegalArgumentException();
}
if ((ASTNode.this.flags & PROTECT) != 0) {
// this node is protected => cannot gain or lose children
throw new IllegalArgumentException(
"AST node cannot be modified"); //$NON-NLS-1$
}
// delink old child from parent, and link new child to parent
ASTNode newChild = (ASTNode) element;
ASTNode oldChild = (ASTNode) this.store.get(index);
if (oldChild == newChild) {
return (T) oldChild;
}
if ((oldChild.flags & PROTECT) != 0) {
// old child is protected => cannot be unparented
throw new IllegalArgumentException(
"AST node cannot be modified"); //$NON-NLS-1$
}
ASTNode.checkNewChild(ASTNode.this, newChild,
this.propertyDescriptor.cycleRisk,
this.propertyDescriptor.elementType);
ASTNode.this.ast.preReplaceChildEvent(ASTNode.this, oldChild,
newChild, this.propertyDescriptor);
T result = this.store.set(index, (T) newChild);
// n.b. setParent will call ast.modifying()
oldChild.setParent(null, null);
newChild.setParent(ASTNode.this, this.propertyDescriptor);
ASTNode.this.ast.postReplaceChildEvent(ASTNode.this, oldChild,
newChild, this.propertyDescriptor);
return result;
}
/*
* (non-javadoc)
*
* @see List#add(int, java.lang.Object)
*/
public void add(int index, T element) {
if (element == null) {
throw new IllegalArgumentException();
}
if ((ASTNode.this.flags & PROTECT) != 0) {
// this node is protected => cannot gain or lose children
throw new IllegalArgumentException(
"AST node cannot be modified"); //$NON-NLS-1$
}
// link new child to parent
ASTNode newChild = (ASTNode) element;
ASTNode.checkNewChild(ASTNode.this, newChild,
this.propertyDescriptor.cycleRisk,
this.propertyDescriptor.elementType);
ASTNode.this.ast.preAddChildEvent(ASTNode.this, newChild,
this.propertyDescriptor);
this.store.add(index, element);
updateCursors(index, +1);
// n.b. setParent will call ast.modifying()
newChild.setParent(ASTNode.this, this.propertyDescriptor);
ASTNode.this.ast.postAddChildEvent(ASTNode.this, newChild,
this.propertyDescriptor);
}
/*
* (non-javadoc)
*
* @see List#remove(int)
*/
public T remove(int index) {
if ((ASTNode.this.flags & PROTECT) != 0) {
// this node is protected => cannot gain or lose children
throw new IllegalArgumentException(
"AST node cannot be modified"); //$NON-NLS-1$
}
// delink old child from parent
ASTNode oldChild = (ASTNode) this.store.get(index);
if ((oldChild.flags & PROTECT) != 0) {
// old child is protected => cannot be unparented
throw new IllegalArgumentException(
"AST node cannot be modified"); //$NON-NLS-1$
}
ASTNode.this.ast.preRemoveChildEvent(ASTNode.this, oldChild,
this.propertyDescriptor);
// n.b. setParent will call ast.modifying()
oldChild.setParent(null, null);
T result = this.store.remove(index);
updateCursors(index, -1);
ASTNode.this.ast.postRemoveChildEvent(ASTNode.this, oldChild,
this.propertyDescriptor);
return result;
}
/**
* Allocate a cursor to use for a visit. The client must call
* <code>releaseCursor</code> when done.
* <p>
* This method is internally synchronized on this NodeList. It is
* thread-safe to create a cursor.
* </p>
*
* @return a new cursor positioned before the first element of the list
*/
Cursor newCursor() {
synchronized (this) {
// serialize cursor management on this NodeList
if (this.cursors == null) {
// convert null to empty list
this.cursors = new ArrayList(1);
}
Cursor result = new Cursor();
this.cursors.add(result);
return result;
}
}
/**
* Releases the given cursor at the end of a visit.
* <p>
* This method is internally synchronized on this NodeList. It is
* thread-safe to release a cursor.
* </p>
*
* @param cursor
* the cursor
*/
void releaseCursor(Cursor cursor) {
synchronized (this) {
// serialize cursor management on this NodeList
this.cursors.remove(cursor);
if (this.cursors.isEmpty()) {
// important: convert empty list back to null
// otherwise the node will hang on to needless junk
this.cursors = null;
}
}
}
/**
* Adjusts all cursors to accomodate an add/remove at the given index.
* <p>
* This method is only used when the list is being modified. The AST is
* not thread-safe if any of the clients are modifying it.
* </p>
*
* @param index
* the position at which the element was added or removed
* @param delta
* +1 for add, and -1 for remove
*/
private void updateCursors(int index, int delta) {
if (this.cursors == null) {
// there are no cursors to worry about
return;
}
for (Iterator it = this.cursors.iterator(); it.hasNext();) {
Cursor c = (Cursor) it.next();
c.update(index, delta);
}
}
}
// ---- Aptana Additions ----
public void setBinding(IBinding binding)
{
this.binding = binding;
}
public IBinding getBinding()
{
return this.binding;
}
}