blob: c47566bfa298264c0db8bea015fc495ec8213ccc [file] [log] [blame]
package org.checkerframework.javacutil;
import static com.sun.tools.javac.code.Kinds.VAR;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacScope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.DeferredAttr;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.comp.Resolve;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
/**
* A Utility class to find symbols corresponding to string references.
*/
public class Resolver {
private final Resolve resolve;
private final Names names;
private final Trees trees;
private final Log log;
private final Method FIND_METHOD;
private final Method FIND_IDENT_IN_TYPE;
private final Method FIND_IDENT_IN_PACKAGE;
private final Method FIND_TYPE;
public Resolver(ProcessingEnvironment env) {
Context context = ((JavacProcessingEnvironment) env).getContext();
this.resolve = Resolve.instance(context);
this.names = Names.instance(context);
this.trees = Trees.instance(env);
this.log = Log.instance(context);
try {
FIND_METHOD = Resolve.class.getDeclaredMethod("findMethod",
Env.class, Type.class, Name.class, List.class, List.class,
boolean.class, boolean.class, boolean.class);
FIND_METHOD.setAccessible(true);
FIND_IDENT_IN_TYPE = Resolve.class.getDeclaredMethod(
"findIdentInType", Env.class, Type.class, Name.class,
int.class);
FIND_IDENT_IN_TYPE.setAccessible(true);
FIND_IDENT_IN_PACKAGE = Resolve.class.getDeclaredMethod(
"findIdentInPackage", Env.class, TypeSymbol.class, Name.class,
int.class);
FIND_IDENT_IN_PACKAGE.setAccessible(true);
FIND_TYPE = Resolve.class.getDeclaredMethod(
"findType", Env.class, Name.class);
FIND_TYPE.setAccessible(true);
} catch (Exception e) {
Error err = new AssertionError(
"Compiler 'Resolve' class doesn't contain required 'find' method");
err.initCause(e);
throw err;
}
}
/**
* Finds the field with name {@code name} in a given type.
*
* <p>
* The method adheres to all the rules of Java's scoping (while also
* considering the imports) for name resolution.
*
* @param name
* The name of the field.
* @param type
* The type of the receiver (i.e., the type in which to look for
* the field).
* @param path
* The tree path to the local scope.
* @return The element for the field.
*/
public VariableElement findField(String name, TypeMirror type, TreePath path) {
Log.DiagnosticHandler discardDiagnosticHandler =
new Log.DiscardDiagnosticHandler(log);
try {
JavacScope scope = (JavacScope) trees.getScope(path);
Env<AttrContext> env = scope.getEnv();
Element res = wrapInvocation(FIND_IDENT_IN_TYPE, env, type,
names.fromString(name), VAR);
if (res.getKind() == ElementKind.FIELD) {
return (VariableElement) res;
} else {
// Most likely didn't find the field and the Element is a SymbolNotFoundError
return null;
}
} finally {
log.popDiagnosticHandler(discardDiagnosticHandler);
}
}
/**
* Finds the class literal with name {@code name}.
*
* <p>
* The method adheres to all the rules of Java's scoping (while also
* considering the imports) for name resolution.
*
* @param name
* The name of the class.
* @param path
* The tree path to the local scope.
* @return The element for the class.
*/
public Element findClass(String name, TreePath path) {
Log.DiagnosticHandler discardDiagnosticHandler =
new Log.DiscardDiagnosticHandler(log);
try {
JavacScope scope = (JavacScope) trees.getScope(path);
Env<AttrContext> env = scope.getEnv();
return wrapInvocation(FIND_TYPE, env, names.fromString(name));
} finally {
log.popDiagnosticHandler(discardDiagnosticHandler);
}
}
/**
* Finds the method element for a given name and list of expected parameter
* types.
*
* <p>
* The method adheres to all the rules of Java's scoping (while also
* considering the imports) for name resolution.
*
* @param methodName
* Name of the method to find.
* @param receiverType
* Type of the receiver of the method
* @param path
* Tree path.
* @return The method element (if found).
*/
public Element findMethod(String methodName, TypeMirror receiverType,
TreePath path, java.util.List<TypeMirror> argumentTypes) {
Log.DiagnosticHandler discardDiagnosticHandler =
new Log.DiscardDiagnosticHandler(log);
try {
JavacScope scope = (JavacScope) trees.getScope(path);
Env<AttrContext> env = scope.getEnv();
Type site = (Type) receiverType;
Name name = names.fromString(methodName);
List<Type> argtypes = List.nil();
for (TypeMirror a : argumentTypes) {
argtypes = argtypes.append((Type) a);
}
List<Type> typeargtypes = List.nil();
boolean allowBoxing = true;
boolean useVarargs = false;
boolean operator = true;
try {
// For some reason we have to set our own method context, which is rather ugly.
// TODO: find a nicer way to do this.
Object methodContext = buildMethodContext();
Object oldContext = getField(resolve, "currentResolutionContext");
setField(resolve, "currentResolutionContext", methodContext);
Element result = wrapInvocation(FIND_METHOD, env, site, name, argtypes,
typeargtypes, allowBoxing, useVarargs, operator);
setField(resolve, "currentResolutionContext", oldContext);
return result;
} catch (Throwable t) {
Error err = new AssertionError("Unexpected Reflection error");
err.initCause(t);
throw err;
}
} finally {
log.popDiagnosticHandler(discardDiagnosticHandler);
}
}
/**
* Build an instance of {@code Resolve$MethodResolutionContext}.
*/
protected Object buildMethodContext() throws ClassNotFoundException,
InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchFieldException {
// Class is not accessible, instantiate reflectively.
Class<?> methCtxClss = Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext");
Constructor<?> constructor = methCtxClss.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Object methodContext = constructor.newInstance(resolve);
// we need to also initialize the fields attrMode and step
setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK);
@SuppressWarnings("rawtypes")
List<?> phases = (List) getField(resolve, "methodResolutionSteps");
setField(methodContext, "step", phases.get(1));
return methodContext;
}
/** Reflectively set a field. */
private void setField(Object receiver, String fieldName,
Object value) throws NoSuchFieldException,
IllegalAccessException {
Field f = receiver.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(receiver, value);
}
/** Reflectively get the value of a field. */
private Object getField(Object receiver, String fieldName) throws NoSuchFieldException,
IllegalAccessException {
Field f = receiver.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(receiver);
}
private Symbol wrapInvocation(Method method, Object... args) {
try {
return (Symbol) method.invoke(resolve, args);
} catch (IllegalAccessException e) {
Error err = new AssertionError("Unexpected Reflection error");
err.initCause(e);
throw err;
} catch (IllegalArgumentException e) {
Error err = new AssertionError("Unexpected Reflection error");
err.initCause(e);
throw err;
} catch (InvocationTargetException e) {
Error err = new AssertionError("Unexpected Reflection error");
err.initCause(e);
throw err;
}
}
}