package processing.mode.java.pdex; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; import processing.app.Messages; public class ASTUtils { public static ASTNode getASTNodeAt(ASTNode root, int startJavaOffset, int stopJavaOffset) { Messages.log("* getASTNodeAt"); int length = stopJavaOffset - startJavaOffset; NodeFinder f = new NodeFinder(root, startJavaOffset, length); ASTNode node = f.getCoveredNode(); if (node == null) { node = f.getCoveringNode(); } if (node == null) { Messages.log("no node found"); } else { Messages.log("found " + node.getClass().getSimpleName()); } return node; } public static SimpleName getSimpleNameAt(ASTNode root, int startJavaOffset, int stopJavaOffset) { Messages.log("* getSimpleNameAt"); // Find node at offset ASTNode node = getASTNodeAt(root, startJavaOffset, stopJavaOffset); SimpleName result = null; if (node == null) { result = null; } else if (node.getNodeType() == ASTNode.SIMPLE_NAME) { result = (SimpleName) node; } else { // Return SimpleName with highest coverage List<SimpleName> simpleNames = getSimpleNameChildren(node); if (!simpleNames.isEmpty()) { // Compute coverage <selection x node> int[] coverages = simpleNames.stream() .mapToInt(name -> { int start = name.getStartPosition(); int stop = start + name.getLength(); return Math.min(stop, stopJavaOffset) - Math.max(startJavaOffset, start); }) .toArray(); // Select node with highest coverage int maxIndex = IntStream.range(0, simpleNames.size()) .filter(i -> coverages[i] >= 0) .reduce((i, j) -> coverages[i] > coverages[j] ? i : j) .orElse(-1); if (maxIndex == -1) return null; result = simpleNames.get(maxIndex); } } if (result == null) { Messages.log("no simple name found"); } else { Messages.log("found " + node.toString()); } return result; } public static List<SimpleName> getSimpleNameChildren(ASTNode node) { List<SimpleName> simpleNames = new ArrayList<>(); node.accept(new ASTVisitor() { @Override public boolean visit(SimpleName simpleName) { simpleNames.add(simpleName); return super.visit(simpleName); } }); return simpleNames; } public static IBinding resolveBinding(SimpleName node) { IBinding binding = node.resolveBinding(); if (binding == null) return null; // Fix constructor call/declaration being resolved as type if (binding.getKind() == IBinding.TYPE) { ASTNode context = node; // Go up until we find non Name or Type node // stop if context is type argument (parent is also Name/Type, but unrelated) while (isNameOrType(context) && !context.getLocationInParent().getId().equals("typeArguments")) { context = context.getParent(); } switch (context.getNodeType()) { case ASTNode.METHOD_DECLARATION: MethodDeclaration decl = (MethodDeclaration) context; if (decl.isConstructor()) { binding = decl.resolveBinding(); } break; case ASTNode.CLASS_INSTANCE_CREATION: ClassInstanceCreation cic = (ClassInstanceCreation) context; binding = cic.resolveConstructorBinding(); break; } } if (binding == null) return null; // Normalize parametrized and raw bindings into generic bindings switch (binding.getKind()) { case IBinding.TYPE: ITypeBinding type = (ITypeBinding) binding; if (type.isParameterizedType() || type.isRawType()) { binding = type.getErasure(); } break; case IBinding.METHOD: IMethodBinding method = (IMethodBinding) binding; ITypeBinding declaringClass = method.getDeclaringClass(); if (declaringClass.isParameterizedType() || declaringClass.isRawType()) { IMethodBinding[] methods = declaringClass.getErasure().getDeclaredMethods(); IMethodBinding generic = Arrays.stream(methods) .filter(method::overrides) .findAny().orElse(null); if (generic != null) method = generic; } if (method.isParameterizedMethod() || method.isRawMethod()) { method = method.getMethodDeclaration(); } binding = method; break; } return binding; } public static boolean isNameOrType(ASTNode node) { return node instanceof Name || node instanceof Type; } protected static List<SimpleName> findAllOccurrences(ASTNode root, String bindingKey) { List<SimpleName> occurences = new ArrayList<>(); root.getRoot().accept(new ASTVisitor() { @Override public boolean visit(SimpleName name) { IBinding binding = resolveBinding(name); if (binding != null && bindingKey.equals(binding.getKey())) { occurences.add(name); } return super.visit(name); } }); return occurences; } }