com.google.gwt.reflect.rebind.generators.MemberGenerator Maven / Gradle / Ivy
package com.google.gwt.reflect.rebind.generators;
import static com.google.gwt.reflect.rebind.ReflectionUtilType.extractAnnotations;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.UnsafeNativeLong;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.HasAnnotations;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.UnifyAstView;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.reflect.client.strategy.GwtRetention;
import com.google.gwt.reflect.client.strategy.ReflectionStrategy;
import com.google.gwt.reflect.shared.GwtReflect;
import com.google.gwt.reflect.shared.JsMemberPool;
import com.google.gwt.thirdparty.xapi.dev.source.ClassBuffer;
import com.google.gwt.thirdparty.xapi.dev.source.MemberBuffer;
import com.google.gwt.thirdparty.xapi.dev.source.MethodBuffer;
import com.google.gwt.thirdparty.xapi.dev.source.PrintBuffer;
import com.google.gwt.thirdparty.xapi.source.read.JavaModel.IsQualified;
import java.lang.annotation.Annotation;
import java.util.HashMap;
/**
* The base class of all constructor, method and field generators,
* and also of almost all injectors used for reflection.
*
* This rather large class is used to encapsulate all of the low-level generator details
* used by all other generator / injector implementations.
*
* @author James X. Nelson ([email protected], @james)
*
*/
@ReflectionStrategy
public abstract class MemberGenerator {
private static class MemberPoolMethods {
/**
* A mapping to one of the various getter methods on the {@link JsMemberPool} class.
*
* These getters are used for runtime lookups of members, when reflection is performed
* on reference objects, rather than literals.
*/
private final HashMap getters = new HashMap();
/**
* A mapping of constructors that we may want to cache and reuse.
*/
private final HashMap constructors = new HashMap();
private JMethod doThrow;
/**
* The {@link JMethod} for {@link JsMemberPool#getMembers(Class)}, used so we can create invocations
* that will translate a {@link Class} into a {@link JsMemberPool}.
*/
private JMethod getMembers;
/**
* Whether or not we should fail-fast when trying to perform runtime reflection.
*
* If gwt.reflect.never.fail is set to false, then this boolean will be true,
* and we should fail the compile instead of generating references to runtime lookup methods.
*/
public Boolean shouldFail;
/**
* Finds the requested getter method from the {@link JsMemberPool} class.
*
* @param ast -> The {@link UnifyAstView} for looking up {@link JMethod}s
* @param memberGetter -> The name of the getter to find
* @return -> The {@link JMethod} needed to create a runtime invocation of the requested method.
*
* @throws UnableToCompleteException -> If any fatal error occurs
*/
public JMethod findGetterFor(final UnifyAstView ast, final String memberGetter)
throws UnableToCompleteException {
// First, check the cache
final JMethod getter = getters.get(memberGetter);
if (getter != null) {
return getter;
}
// Ensure we have a valid reference to the getMembers method.
findGetMembersMethod(ast);
// Use the method to grab the JClassType of JsMemberPool
final JDeclaredType type = ast.translate((JDeclaredType) getMembers.getOriginalReturnType());
// Search the type for the method that matches the requested name
for (final JMethod method : type.getMethods()) {
if (method.getName().equals(memberGetter)) {
// Save the method so we don't waste time looking it up again
getters.put(memberGetter, method);
return method;
}
}
// If the method was not found, abort
ast.error(type, "Type " + type.getName()
+ " does not have member getter method " + memberGetter);
throw new UnableToCompleteException();
}
/**
* Search for the method {@link JsMemberPool#getMembers(Class)}. This method is used to load
* the {@link JsMemberPool} for a given class, to perform runtime reflection lookups.
*
* @param ast -> The {@link UnifyAstView} used to find the {@link JMethod} we want.
* @return -> The JMethod used to invoke {@link JsMemberPool#getMembers(Class)}
*/
public JMethod findGetMembersMethod(final UnifyAstView ast) {
if (getMembers == null) {
// First, get the type
final JDeclaredType type = ast.searchForTypeBySource(JsMemberPool.class.getCanonicalName());
// Then, iterate through the methers to find .getMembers(). We do not overload this method,
// so we don't bother checking the signature.
for (final JMethod method : type.getMethods()) {
if (method.getName().equals("getMembers")) {
getMembers = ast.translate(method);
break;
}
}
}
return getMembers;
}
/**
* This method checks if we should fail fast when a reflection method is referenced without all-literal
* parameters, or if we should allow runtime lookups of reflection objects. Defaults to false.
*
* @param logger -> The {@link TreeLogger} for logging
* @param ast -> The {@link UnifyAstView} where we will retrieve our property oracle from.
* @return -> true if the compile should break if attempting to do reflection without using all
* compile-time literals.
*/
public boolean shouldFail(final TreeLogger logger, final UnifyAstView ast) {
// Use the existing cached values
if (shouldFail != null) {
return shouldFail;
}
// Grab our property from the oracle
final PropertyOracle properties = ast.getRebindPermutationOracle().getConfigurationPropertyOracle();
try {
final ConfigurationProperty config = properties
.getConfigurationProperty("gwt.reflect.never.fail");
if (config == null) {
// We may want to change the default fail level to true
shouldFail = false;
} else {
shouldFail = !"true".equals(config.getValues().get(0));
}
} catch (final BadPropertyValueException e) {
logger.log(Type.WARN, "Could not find configuration property gwt.reflect.never.fail; "
+ "did you remember to inherit com.google.gwt.reflect.Reflect?", e);
shouldFail = false;
}
return shouldFail;
}
public JConstructor findConstructor(final TreeLogger logger, final String sourceTypeName, final String signature, final UnifyAstView ast) throws UnableToCompleteException {
JConstructor constructor = constructors.get(signature);
if (constructor != null) {
return constructor;
}
final JDeclaredType clazz = ast.searchForTypeBySource(sourceTypeName);
if (clazz == null) {
logger.log(Type.ERROR, "Unable to find type "+sourceTypeName+" using source name lookup for constructor "+signature);
throw new UnableToCompleteException();
}
constructor = (JConstructor) clazz.findMethod(signature, false);
if (constructor == null) {
logger.log(Type.ERROR, "Unable to find constructor " + signature + " in "+sourceTypeName+"\n"+clazz.getMethods());
for (final JMethod method : clazz.getMethods()) {
logger.log(Type.ERROR, "Meow "+method.getSignature());
}
throw new UnableToCompleteException();
}
constructors.put(signature, constructor);
return constructor;
}
public JMethod getThrowMethod(final TreeLogger logger, final UnifyAstView ast) {
if (doThrow == null) {
final JDeclaredType gwtReflect = ast.searchForTypeBySource(GwtReflect.class.getName());
for (final JMethod method : gwtReflect.getMethods()) {
if (method.getName().equals("doThrow")) {
doThrow = method;
break;
}
}
}
return doThrow;
}
}
/**
* A default instance of {@link ReflectionStrategy}, to be used if a type does not specify an instance
* of its own to use.
*/
public static final ReflectionStrategy DEFAULT_STRATEGY =
MemberGenerator.class.getAnnotation(ReflectionStrategy.class);
/**
* A unique string to be placed between the canonical name of the type that sourced a method we want to
* generate a provider for, and the name of the method itself, followed by the parameter types.
*
* Example:
* com.foo.Type::myMethod(Class) will have a provider class:
* com.foo.Type_mthd_myMethod_java_lang_Class
*/
public static final String METHOD_SPACER = "_mthd_";
/**
* A unique string to be placed between the canonical name of the type that sourced a field we want to
* generate a provider for, and the name of the field itself.
*
* Example:
* com.foo.Type::myField will have a provider class:
* com.foo.Type_fld_myField
*/
public static final String FIELD_SPACER = "_fld_";
/**
* A unique string to be placed between the canonical name of the type that sourced a constructor we want to
* generate a provider for, and the types of the parameters of that constructor.
*
* Example:
* com.foo.Type::new(Class) will have a provider class:
* com.foo.Type_ctr_java_lang_Class
*/
public static final String CONSTRUCTOR_SPACER = "_ctr_";
/**
* A handy string for the fully qualified name of the {@link GwtReflect} class
*/
private static final String GWT_REFLECT = GwtReflect.class.getName();
/**
* A handy string for the fully qualified name of the {@link JavaScriptObject} class
*/
private static final String JSO = JavaScriptObject.class.getSimpleName();
/**
* A handy string for the fully qualified name of the {@link GwtReflect#nullCheck(Object)} method
*/
protected static final String NULL_CHECK = "@" + GWT_REFLECT + "::nullCheck(*)(o);";
/**
* The default logLevel, {@link Type#DEBUG}, for all subclasses.
*/
private static final Type logLevel = Type.DEBUG;
/**
* A ThreadLocal for safely storing an instance of {@link MemberPoolMethods} during a compile.
*/
private static final ThreadLocal memberPoolMethods = new ThreadLocal() {
@Override
protected MemberPoolMethods initialValue() {
return new MemberPoolMethods();
};
};
/**
* Cleans up our thread local which stores {@link JMethod} that will no longer be valid on the next recompile.
*/
public static void cleanup() {
memberPoolMethods.remove();
}
/**
* If the configuration property gwt.reflect.never.fail is true or missing, then
* this method will return an expression that checks the JsMemberPool at runtime
* for an enhanced member as defined by the {@link #memberGetter()} method name.
*
* If gwt.reflect.never.fail is set to the default of false, this will throw
* an {@link UnableToCompleteException}, thus, you are encouraged to log a warning
* at the logLevel of {@link #warnLevel(TreeLogger, UnifyAstView)} before invoking this method.
*/
protected JExpression maybeCheckConstPool(final TreeLogger logger, final UnifyAstView ast,
final JMethodCall callSite, final JExpression inst, final JExpression ... params) throws UnableToCompleteException {
if (shouldFailIfMissing(logger, ast)) {
throw new UnableToCompleteException();
}
return checkConstPool(ast, callSite, inst, params);
}
protected void appendAnnotationSupplier(final TreeLogger logger, final MemberBuffer out,
final HasAnnotations member, final GwtRetention retention, final ReflectionGeneratorContext ctx)
throws UnableToCompleteException {
final Annotation[] annos = extractAnnotations(retention.annotationRetention(), member);
// Print a call to new AnnotationSupplierX, which returns the constant annotation array for the supplied annos
final IsQualified supplier = ctx.getConstPool().annotationArraySupplier(logger,
ctx.getGeneratorContext(), out, annos);
final String supplierClass = out.addImport(supplier.getQualifiedName());
out.println("new "+supplierClass+"()");
}
protected void appendClassArray(final MethodBuffer out,
final JParameter[] params,
final ReflectionGeneratorContext ctx) {
final JType[] types = new JType[params.length];
for (int i = params.length; i --> 0;) {
types[i] = params[i].getType();
}
appendClassArray(out, types, ctx);
}
protected void appendClassArray(final MethodBuffer out,
final T[] types,
final ReflectionGeneratorContext ctx) {
int i = types.length;
final String[] names = new String[i];
for (; i-- > 0;) {
names[i] = types[i].getErasedType().getQualifiedSourceName();
}
final ConstPoolGenerator constPool = ctx.getConstPool();
constPool.arrayOfClasses(ctx.getLogger(), out, names);
}
protected JMethodCall checkConstPool(final UnifyAstView ast,
final JMethodCall callSite, final JExpression classRef,
final JExpression... args) throws UnableToCompleteException {
final JMethod initPool = getMemberPoolInit(ast);
final JMethodCall getMemberPool = new JMethodCall(initPool.getSourceInfo(),
null, initPool);
getMemberPool.addArg(classRef);
final MemberPoolMethods map = memberPoolMethods.get();
final JMethod getter = map.findGetterFor(ast,
memberGetter());
final JMethodCall checkPool = new JMethodCall(initPool.getSourceInfo(),
getMemberPool, getter);
for (final JExpression arg : args) {
checkPool.addArg(arg);
}
return checkPool;
}
protected void createInvokerMethod(final ClassBuffer cb,
final JClassType type, final JType returnType,
final String methodName, final JParameter[] params, final boolean isStatic, final boolean isNotPrivate) {
boolean hasLong = returnType.getJNISignature().equals("J");
final StringBuilder functionSig = new StringBuilder();
StringBuilder jsniSig = new StringBuilder();
StringBuilder arguments = new StringBuilder();
// Fill in parameter data
final boolean isNotCtor = !"new".equals(methodName);
assert isStatic || isNotCtor : "Constructors must be static!";
for (int i = 0, m = params.length; i < m; i++) {
JType param = params[i].getType();
// if (param.isParameterized() != null) {
// param = param.isParameterized().getRawType();
// }
final boolean isArray = param.isArray() != null;
while (param.isArray() != null) {
jsniSig.append("[");
param = param.isArray().getComponentType();
}
jsniSig.append(param.getJNISignature());
final char varName = Character
.toUpperCase(Character.forDigit(10 + i, 36));
if (isNotCtor || i > 0) {
functionSig.append(", ");
}
functionSig.append(varName);
if (i > 0) {
arguments.append(", ");
}
if (!isArray) {
maybeStartUnboxing(arguments, param);
}
arguments.append(varName);
if (!isArray) {
maybeFinishUnboxing(arguments, param);
}
hasLong |= "J".equals(param.getJNISignature());
}
final MethodBuffer invoker = cb.addImports(JavaScriptObject.class)
.createMethod("public static " + JSO + " " + "invoker()")
.setUseJsni(true)
.print("return function(");
if (isNotCtor) {
invoker.print("o");
}
if (hasLong) {
invoker.addAnnotation(UnsafeNativeLong.class);
}
invoker.println(functionSig + ") {");
if (!isStatic) {
invoker.indentln(NULL_CHECK);
}
// Build the structure of the method invoker javascript function
String typeName = type.getQualifiedSourceName();
String invokeName = methodName;
final boolean returns = isReturnable(returnType);
final boolean useStaticDispatch = !isStatic && isNotPrivate;
final boolean isConstructor = "new".equals(methodName);
if (returns) {
invoker.print("return ");
}
maybeStartBoxing(invoker, returnType);
if (!isStatic) {
// Due to a bug with accessing instance methods on Strings and JSOs, we add an
// extra layer of "staticifying" to public instance methods.
// This is because these methods are not translated correctly from JSNI,
// so we instead make a static method that we can safely reference in JSNI.
if (useStaticDispatch) {
jsniSig = new StringBuilder(type.getJNISignature()).append(jsniSig);
final String args = arguments.toString();
arguments = new StringBuilder();
if (!isConstructor) {
arguments.append("o").append(args.length() == 0 ? "" : ",");
}
arguments.append(args);
invokeName = methodName+"$$$";
final String returnName = cb.addImport(returnType.getErasedType().getQualifiedSourceName());
final MethodBuffer staticMethod = cb.createMethod("private static "+returnName+" "+invokeName+"()");
if (!isConstructor) {
staticMethod.addParameter(typeName, "_");
}
if (returns) {
staticMethod.print("return ");
}
if (isConstructor) {
staticMethod.print("new "+returnName);
} else {
staticMethod.print("_.");
}
staticMethod.print(methodName).print("(");
staticMethod.addExceptions(Throwable.class);// Let exceptions bubble
for (int i = 0, m = params.length; i < m; i++) {
final JParameter param = params[i];
final String paramName = toParamName(i);
staticMethod.addParameter(param.getType().getErasedType().getQualifiedSourceName(), paramName);
if (i > 0) {
staticMethod.print(",");
}
staticMethod.print(paramName);
}
staticMethod.println(");");
typeName = cb.getQualifiedName();
} else {
invoker.print("o.");
}
}
invoker
.indent()
.print("@").print(typeName)
.print("::")
.print(invokeName)
.print("(")
.print(jsniSig.toString())
.print(")")
.print("(")
.print(arguments.toString())
.print(")");
maybeFinishBoxing(invoker, returnType);
invoker
.println(";")
.outdent()
.println("};");
}
protected JMethod getMemberPoolInit(
final UnifyAstView ast) throws UnableToCompleteException {
final MemberPoolMethods map = memberPoolMethods.get();
return map.findGetMembersMethod(ast);
}
protected boolean isDebug(final JClassType type, final int memberType) {
ReflectionStrategy strategy = type.getAnnotation(ReflectionStrategy.class);
if (strategy == null) {
strategy = type.getPackage().getAnnotation(ReflectionStrategy.class);
}
if (strategy == null) {
return false;
}
return (strategy.debug() & memberType) > 0;
}
protected boolean isReturnable(final JType returnType) {
return !"V".equals(returnType.getJNISignature());
}
protected Type logLevel() {
return logLevel;
}
protected abstract String memberGetter();
protected boolean shouldFailIfMissing(final TreeLogger logger, final UnifyAstView ast) {
return memberPoolMethods.get().shouldFail(logger, ast);
}
protected String toString(final JExpression inst) {
return inst == null ? "null" : inst.getClass().getName()+": "+inst;
}
protected Type warnLevel(final TreeLogger logger, final UnifyAstView ast) {
if (shouldFailIfMissing(logger, ast)) {
return Type.ERROR;
}
return Type.WARN;
}
private void maybeFinishBoxing(final PrintBuffer invoke,
final JType returnType) {
final JPrimitiveType prim = returnType.isPrimitive();
if (prim != null) {
switch (prim) {
case BOOLEAN:
invoke.print(" ? @java.lang.Boolean::TRUE : @java.lang.Boolean::FALSE");
case VOID:
return;
default:
invoke.print(")");
}
}
}
private void maybeFinishUnboxing(final StringBuilder b, final JType returnType) {
final JPrimitiveType type = returnType.isPrimitive();
if (type != null) {
switch (type) {
case BOOLEAN:
b.append("[email protected]::booleanValue()()");
break;
case CHAR:
b.append("[email protected]::charValue()()");
break;
case LONG:
b.append(")");
break;
case BYTE:
case DOUBLE:
case INT:
case FLOAT:
case SHORT:
b.append("[email protected]::doubleValue()()");
break;
default:
}
}
}
private void maybeStartBoxing(final PrintBuffer invoke, final JType returnType) {
final JPrimitiveType prim = returnType.isPrimitive();
if (prim != null) {
switch (prim) {
case LONG:
invoke.print("@" + GWT_REFLECT + "::boxLong(J)(");
break;
case BYTE:
invoke.print("@java.lang.Byte::new(B)(");
break;
case CHAR:
invoke.print("@java.lang.Character::new(C)(");
break;
case DOUBLE:
invoke.print("@java.lang.Double::new(D)(");
break;
case FLOAT:
invoke.print("@java.lang.Float::new(F)(");
break;
case INT:
invoke.print("@java.lang.Integer::new(I)(");
break;
case SHORT:
invoke.print("@java.lang.Short::new(S)(");
break;
default:
}
}
}
private void maybeStartUnboxing(final StringBuilder b, final JType returnType) {
if (JPrimitiveType.LONG == returnType.isPrimitive()) {
b.append("@" + GWT_REFLECT + "::unboxLong(Ljava/lang/Number;)(");
}
}
private String toParamName(int i) {
final StringBuilder b = new StringBuilder();
do {
b.append((char)('a'+(i%26)));
i = i / 26;
} while (i > 0);
return b.toString();
}
/**
* @param ast
* @param logger
* @param callSite
* @return
* @throws UnableToCompleteException
*/
public JExpression throwNotFoundException(final TreeLogger logger, final JMethodCall callSite, final UnifyAstView ast) throws UnableToCompleteException {
final SourceInfo sourceInfo = callSite.getSourceInfo().makeChild();
final MemberPoolMethods memberMap = memberPoolMethods.get();
final IsQualified exceptionType = getNotFoundExceptionType();
final JConstructor ctor = memberMap.findConstructor(logger, exceptionType.getQualifiedName(), exceptionType.getSimpleName()+"() ", ast);
final JNewInstance newThrowable = new JNewInstance(sourceInfo, ctor);
final JMethod throwMethod = memberMap.getThrowMethod(logger, ast);
return new JMethodCall(sourceInfo, null, throwMethod, newThrowable);
}
protected abstract IsQualified getNotFoundExceptionType();
/**
* @param methodProvider
* @return
*/
public boolean isThrowStatement(final JExpression methodProvider) {
if (methodProvider instanceof JMethodCall) {
final JMethod method = ((JMethodCall)methodProvider).getTarget();
return method.getType().getName().equals(GwtReflect.class.getName()) && method.getName().equals("doThrow");
}
return false;
}
}