package edu.ucsd.arcum.interpreter.ast;
import static edu.ucsd.arcum.ArcumPlugin.DEBUG;
import java.util.*;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.core.dom.PrimitiveType.Code;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import edu.ucsd.arcum.exceptions.ArcumError;
import edu.ucsd.arcum.exceptions.Unreachable;
import edu.ucsd.arcum.interpreter.parser.FragmentParser;
import edu.ucsd.arcum.interpreter.query.Entity;
import edu.ucsd.arcum.interpreter.transformation.Conversion;
import edu.ucsd.arcum.util.StringUtil;
public class ASTUtil
{
// EXAMPLE: Eliminate this class and use the simple Google Function<String,T> class
// in the interfaces instead
public interface NameAccessor<T>
{
String getName(T element);
}
public static final NameAccessor<String> IDENTITY_ACCESSOR = new NameAccessor<String>() {
public String getName(String element) {
return element;
}
};
public static final NameAccessor<FormalParameter> PARAMETER_NAME = new NameAccessor<FormalParameter>() {
public String getName(FormalParameter param) {
return param.getIdentifier();
}
};
public static <T> List<T> flatten(Collection<List<T>> collection) {
List<T> result = new ArrayList<T>();
for (List<T> list : collection) {
result.addAll(list);
}
return result;
}
public static <T> void checkNames(List<T> list, NameAccessor<T> accessor) {
checkNames(list, accessor, "The name %s cannot be used"
+ " multiple times in the same context");
}
// EXAMPLE: Soon -- Handling cases like this where really every syntactic element
// should have a location, so that calls to fatalError would become fatalUserError
// Many alternative implementations are conceivable: 1) No locations, so dialogs;
// 2) locations stored externally; 3) locations stored internally
public static <T> void checkNames(List<T> list, NameAccessor<T> accessor,
String formattedMessage)
{
Set<String> names = new HashSet<String>();
for (T element : list) {
String name = accessor.getName(element);
if (names.contains(name)) {
ArcumError.fatalError(String.format(formattedMessage, name));
}
names.add(name);
}
}
public static <T> void extractNames(Collection<String> dest, Collection<T> list,
NameAccessor<T> accessor)
{
for (T element : list) {
dest.add(accessor.getName(element));
}
}
public static <T> T find(String name, Collection<T> list, NameAccessor<T> accessor) {
for (T element : list) {
if (accessor.getName(element).equals(name)) {
return element;
}
}
return null;
}
// Given a set of nodes, returns a subset of the nodes that do not have as
// parents (grand-parents, etc...) any of the other nodes.
public static List<ASTNode> findTrees(Collection<ASTNode> nodes) {
List<ASTNode> candidates = Lists.newArrayList(nodes);
Iterator<ASTNode> it = candidates.iterator();
nextElement: while (it.hasNext()) {
ASTNode curElement = it.next();
ASTNode expecting = curElement;
while (expecting != null) {
expecting = expecting.getParent();
if (nodes.contains(expecting)) {
// it can't be us, because another node is our parent
it.remove();
continue nextElement;
}
}
}
return candidates;
}
public static Type buildTypeNode(AST ast, ITypeBinding binding) {
if (binding.isPrimitive()) {
Code code = PrimitiveType.toCode(binding.getName());
return ast.newPrimitiveType(code);
}
else if (binding.isArray()) {
ITypeBinding elementType = binding.getElementType();
int dimensions = binding.getDimensions();
return ast.newArrayType(buildTypeNode(ast, elementType), dimensions);
}
else if (binding.isGenericType()) {
String qualifiedName = binding.getQualifiedName();
Name name = ast.newName(qualifiedName);
return ast.newSimpleType(name);
}
else if (binding.isParameterizedType() || binding.isRawType()) {
ITypeBinding[] typeArguments = binding.getTypeArguments();
ITypeBinding typeDeclaration = binding.getTypeDeclaration();
ParameterizedType type;
type = ast.newParameterizedType(buildTypeNode(ast, typeDeclaration));
for (ITypeBinding typeArgument : typeArguments) {
ASTNode typeArgumentNode = buildTypeNode(ast, typeArgument);
typeArgumentNode = Conversion.cleanseASTNode(ast, typeArgumentNode);
type.typeArguments().add(typeArgumentNode);
}
return type;
}
else {
return FragmentParser.getType(binding.getQualifiedName());
}
}
private static final Map<ASTNode, ASTNode> sugarTable;
private static final Map<ASTNode, ITypeBinding> parentTable;
static {
sugarTable = Maps.newIdentityHashMap();
parentTable = Maps.newIdentityHashMap();
}
public static <T extends ASTNode> void recordUpdatedNode(T original, T replacement) {
ASTNode root = replacement.getRoot();
sugarTable.put(root, original);
}
public static ASTNode queryUpdatedNode(ASTNode node) {
ASTNode result = sugarTable.get(node);
if (result != null) {
return result;
}
else {
return node;
}
}
public static void recordNewParent(ASTNode member, ITypeBinding parent) {
if (DEBUG) {
System.out.printf("Recording %s is a member of %s%n", member, parent);
}
parentTable.put(member, parent);
}
public static ITypeBinding queryNewParentOf(ASTNode member) {
ITypeBinding result = parentTable.get(member);
return result;
}
// Returns null when "node" has been generated and was not found in the original
// AST
public static CompilationUnit findCompilationUnit(ASTNode node) {
if (node == null) {
return null;
}
ASTNode root = node.getRoot();
if (root instanceof CompilationUnit) {
return (CompilationUnit)root;
}
else {
if (DEBUG) {
System.out.printf("For %s the sugarTable is used!%n", root);
}
root = root.getRoot();
return findCompilationUnit(sugarTable.get(root));
}
}
public static Object getDebugString(Object entity) {
if (entity instanceof ASTNode) {
ASTNode node = (ASTNode)entity;
ASTNode root = node.getRoot();
return String.format("%s ([a %s], startPos=%d, root=%s)", Entity
.getDisplayString(node), node.getClass().getSimpleName(), node
.getStartPosition(), root.getClass().getSimpleName());
}
else if (entity == null) {
return "{{null}}";
}
else {
return String.format("{{%s}}", StringUtil.debugDisplay(entity));
}
}
// Returns the method that this expression is defined in, or null if the
// expression is not contained in a method.
// MACNEIL: Note that inner-classes can have field initializations that might or
// might not be considered actually called by the method returned.
public static IMethodBinding getDefiningMethod(Expression expr) {
ASTNode parent = expr.getParent();
while (!(parent instanceof MethodDeclaration)
&& !(parent instanceof CompilationUnit))
{
// MACNEIL: A null pointer exception here means the sugar table should
// be used instead
parent = parent.getParent();
}
if (parent instanceof CompilationUnit) {
return null;
}
else {
MethodDeclaration decl = (MethodDeclaration)parent;
return decl.resolveBinding();
}
}
public static List<Annotation> getAnnotations(ASTNode node) {
List modifiersAndAnnotations = getExtendedModifiers(node);
List<Annotation> result = Lists.newArrayList();
for (Object modOrAnnot : modifiersAndAnnotations) {
if (modOrAnnot instanceof Annotation) {
result.add((Annotation)modOrAnnot);
}
}
return result;
}
public static List<IExtendedModifier> getExtendedModifiers(ASTNode node) {
List result;
if (node instanceof Type) {
Type type = (Type)node;
if (Entity.isReturnType(type)) {
ASTNode method = type.getParent();
List<IExtendedModifier> extendedModifiers = getExtendedModifiers(method);
result = Lists.newArrayList();
// All of the modifiers apply to the method, not the return type, so
// we only pick out annotations whose targets are types
// VERSION2: We need to find out how to get that target
for (IExtendedModifier modifier : extendedModifiers) {
if (modifier.isAnnotation()) {
Annotation annotation = (Annotation)modifier;
result.add(annotation);
}
}
return result;
}
}
if (node instanceof SingleVariableDeclaration) {
SingleVariableDeclaration svd = (SingleVariableDeclaration)node;
result = svd.modifiers();
}
else if (node instanceof VariableDeclarationFragment) {
VariableDeclarationFragment vdf = (VariableDeclarationFragment)node;
ASTNode parent = vdf.getParent();
if (parent instanceof VariableDeclarationExpression) {
VariableDeclarationExpression vde = (VariableDeclarationExpression)parent;
result = vde.modifiers();
}
else {
ArcumError.fatalError("Handle case where parent is: %s", //->
ASTUtil.getDebugString(parent));
return null;
}
}
else if (node instanceof VariableDeclarationStatement) {
VariableDeclarationStatement vds = (VariableDeclarationStatement)node;
result = vds.modifiers();
}
else if (node instanceof FieldDeclaration) {
FieldDeclaration fieldDecl = (FieldDeclaration)node;
result = fieldDecl.modifiers();
}
else if (node instanceof MethodDeclaration) {
MethodDeclaration methodDecl = (MethodDeclaration)node;
result = methodDecl.modifiers();
}
else {
ArcumError.fatalError("Handle case where node is: %s", //->
ASTUtil.getDebugString(node));
throw new Unreachable();
}
return result;
}
// Returns true when the given name (assumed to be found in a proper,
// fully-parented AST) is a label or Javadoc comment element (as opposed to a
// variable).
public static boolean isLabel(Name name) {
StructuralPropertyDescriptor spd = name.getLocationInParent();
if (spd == LabeledStatement.LABEL_PROPERTY //->
|| spd == ContinueStatement.LABEL_PROPERTY //->
|| spd == BreakStatement.LABEL_PROPERTY //->
|| isJavadocCommentPart(spd, name))
{
return true;
}
else {
return false;
}
}
private static boolean isJavadocCommentPart(StructuralPropertyDescriptor spd, Name name) {
if (spd == MethodRef.NAME_PROPERTY //->
|| spd == MethodRef.QUALIFIER_PROPERTY) {
return true;
}
ASTNode expecting = name.getParent();
while (expecting != null) {
if (expecting instanceof Javadoc) {
return true;
}
expecting = expecting.getParent();
}
return false;
}
public static ASTPath getPathToRoot(ASTNode node, ASTNode root) {
ASTPath result = new ASTPath();
while (node != root) {
result.addEdge(node);
node = node.getParent();
}
return result;
}
public static class ASTPath {
final List<PathEdge> path;
private ASTPath() {
this.path = Lists.newArrayList();
}
private void addEdge(ASTNode node) {
StructuralPropertyDescriptor spd = node.getLocationInParent();
int index = 0;
if (spd instanceof ChildListPropertyDescriptor) {
ASTNode parent = node.getParent();
List<?> list = (List<?>)parent.getStructuralProperty(spd);
index = list.indexOf(node);
}
PathEdge edge = new PathEdge(spd, index);
path.add(edge);
}
public ASTNode getASTNodeFrom(ASTNode node) {
for (int i=path.size() - 1; i >=0; --i) {
PathEdge edge = path.get(i);
Object child = node.getStructuralProperty(edge.spd);
if (child instanceof List) {
List<?> list = (List<?>)child;
node = (ASTNode)list.get(edge.index);
}
else {
node = (ASTNode)child;
}
}
return node;
}
private static class PathEdge {
private StructuralPropertyDescriptor spd;
private int index;
private PathEdge(StructuralPropertyDescriptor spd, int index) {
this.spd = spd;
this.index = index;
}
}
}
// Removes this ASTNode from it's parent
public static void removeNodeFromParent(ASTNode node) {
StructuralPropertyDescriptor spd = node.getLocationInParent();
ASTNode parent = node.getParent();
if (spd instanceof ChildPropertyDescriptor) {
parent.setStructuralProperty(spd, null);
}
else if (spd instanceof ChildListPropertyDescriptor) {
List<?> list = (List<?>)parent.getStructuralProperty(spd);
int index = list.indexOf(node);
list.remove(index);
}
else {
parent.setStructuralProperty(spd, null);
}
}
}