/*
* Copyright 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.ast;
import com.google.dart.engine.ast.visitor.ToSourceVisitor;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.utilities.io.PrintStringWriter;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
/**
* The abstract class {@code AstNode} defines the behavior common to all nodes in the AST structure
* for a Dart program.
*
* @coverage dart.engine.ast
*/
public abstract class AstNode {
/**
* An empty array of ast nodes.
*/
public static final AstNode[] EMPTY_ARRAY = new AstNode[0];
/**
* The parent of the node, or {@code null} if the node is the root of an AST structure.
*/
private AstNode parent;
/**
* A table mapping the names of properties to their values, or {@code null} if this node does not
* have any properties associated with it.
*/
private Map<String, Object> propertyMap;
/**
* A comparator that can be used to sort AST nodes in lexical order. In other words,
* {@code compare} will return a negative value if the offset of the first node is less than the
* offset of the second node, zero (0) if the nodes have the same offset, and a positive value if
* if the offset of the first node is greater than the offset of the second node.
*/
public static final Comparator<AstNode> LEXICAL_ORDER = new Comparator<AstNode>() {
@Override
public int compare(AstNode first, AstNode second) {
return second.getOffset() - first.getOffset();
}
};
/**
* Use the given visitor to visit this node.
*
* @param visitor the visitor that will visit this node
* @return the value returned by the visitor as a result of visiting this node
*/
public abstract <R> R accept(AstVisitor<R> visitor);
/**
* Return the node of the given class that most immediately encloses this node, or {@code null} if
* there is no enclosing node of the given class.
*
* @param nodeClass the class of the node to be returned
* @return the node of the given type that encloses this node
*/
@SuppressWarnings("unchecked")
public <E extends AstNode> E getAncestor(Class<E> enclosingClass) {
AstNode node = this;
while (node != null && !enclosingClass.isInstance(node)) {
node = node.getParent();
}
return (E) node;
}
/**
* Return the first token included in this node's source range.
*
* @return the first token included in this node's source range
*/
public abstract Token getBeginToken();
/**
* Return the offset of the character immediately following the last character of this node's
* source range. This is equivalent to {@code node.getOffset() + node.getLength()}. For a
* compilation unit this will be equal to the length of the unit's source. For synthetic nodes
* this will be equivalent to the node's offset (because the length is zero (0) by definition).
*
* @return the offset of the character just past the node's source range
*/
public int getEnd() {
return getOffset() + getLength();
}
/**
* Return the last token included in this node's source range.
*
* @return the last token included in this node's source range
*/
public abstract Token getEndToken();
/**
* Return the number of characters in the node's source range.
*
* @return the number of characters in the node's source range
*/
public int getLength() {
Token beginToken = getBeginToken();
Token endToken = getEndToken();
if (beginToken == null || endToken == null) {
return -1;
}
return endToken.getOffset() + endToken.getLength() - beginToken.getOffset();
}
/**
* Return the offset from the beginning of the file to the first character in the node's source
* range.
*
* @return the offset from the beginning of the file to the first character in the node's source
* range
*/
public int getOffset() {
Token beginToken = getBeginToken();
if (beginToken == null) {
return -1;
}
return beginToken.getOffset();
}
/**
* Return this node's parent node, or {@code null} if this node is the root of an AST structure.
* <p>
* Note that the relationship between an AST node and its parent node may change over the lifetime
* of a node.
*
* @return the parent of this node, or {@code null} if none
*/
public AstNode getParent() {
return parent;
}
/**
* Return the value of the property with the given name, or {@code null} if this node does not
* have a property with the given name.
*
* @return the value of the property with the given name
*/
public Object getProperty(String propertyName) {
if (propertyMap == null) {
return null;
}
return propertyMap.get(propertyName);
}
/**
* Return the node at the root of this node's AST structure. Note that this method's performance
* is linear with respect to the depth of the node in the AST structure (O(depth)).
*
* @return the node at the root of this node's AST structure
*/
public final AstNode getRoot() {
AstNode root = this;
AstNode parent = getParent();
while (parent != null) {
root = parent;
parent = root.getParent();
}
return root;
}
/**
* Return {@code true} if this node is a synthetic node. A synthetic node is a node that was
* introduced by the parser in order to recover from an error in the code. Synthetic nodes always
* have a length of zero ({@code 0}).
*
* @return {@code true} if this node is a synthetic node
*/
public boolean isSynthetic() {
return false;
}
/**
* Set the value of the property with the given name to the given value. If the value is
* {@code null}, the property will effectively be removed.
*
* @param propertyName the name of the property whose value is to be set
* @param propertyValue the new value of the property
*/
public void setProperty(String propertyName, Object propertyValue) {
if (propertyValue == null) {
if (propertyMap != null) {
propertyMap.remove(propertyName);
if (propertyMap.isEmpty()) {
propertyMap = null;
}
}
} else {
if (propertyMap == null) {
propertyMap = new HashMap<String, Object>();
}
propertyMap.put(propertyName, propertyValue);
}
}
/**
* Return a textual description of this node in a form approximating valid source. The returned
* string will not be valid source primarily in the case where the node itself is not well-formed.
*
* @return the source code equivalent of this node
*/
public String toSource() {
PrintStringWriter writer = new PrintStringWriter();
accept(new ToSourceVisitor(writer));
return writer.toString();
}
@Override
public String toString() {
return toSource();
}
/**
* Use the given visitor to visit all of the children of this node. The children will be visited
* in source order.
*
* @param visitor the visitor that will be used to visit the children of this node
*/
public abstract void visitChildren(AstVisitor<?> visitor);
/**
* Make this node the parent of the given child node.
*
* @param child the node that will become a child of this node
* @return the node that was made a child of this node
*/
protected <T extends AstNode> T becomeParentOf(T child) {
if (child != null) {
AstNode node = child; // Java 7 access rules require a temp of a concrete type.
node.setParent(this);
}
return child;
}
/**
* If the given child is not {@code null}, use the given visitor to visit it.
*
* @param child the child to be visited
* @param visitor the visitor that will be used to visit the child
*/
protected void safelyVisitChild(AstNode child, AstVisitor<?> visitor) {
if (child != null) {
child.accept(visitor);
}
}
/**
* Set the parent of this node to the given node.
*
* @param newParent the node that is to be made the parent of this node
*/
private void setParent(AstNode newParent) {
parent = newParent;
}
}