package xapi.dev.reflect;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import xapi.annotation.reflect.KeepArrays;
import xapi.annotation.reflect.KeepClass;
import xapi.dev.source.ClassBuffer;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.SourceBuilder;
import xapi.except.NotConfiguredCorrectly;
import xapi.reflect.impl.GwtDevReflectionService;
import xapi.reflect.service.ReflectionService;
import xapi.util.X_Runtime;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
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.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.reflect.shared.GwtReflect;
public class MonolithReflectionGenerator extends IncrementalGenerator{
@Override
public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext context, String typeName)
throws UnableToCompleteException {
if (!context.isProdMode()) {
return new RebindResult(RebindMode.USE_EXISTING, GwtDevReflectionService.class.getName());
}
// Our default reflection service generator;
// gwt dev will return a simple class that gives direct access to jre class
// prod mode, however, is limited to supporting properly annotated types.
// This means we have to iterate and inspect all types found... ick.
TypeOracle oracle = context.getTypeOracle();
String resultPackage = "xapi.reflect.impl";
String resultType = "MonolithicReflectionService";
String fqcn = resultPackage + "."+resultType;
PrintWriter pw = null;
pw = context.tryCreate(logger, resultPackage, resultType);
String next = resultType;
int unique = 0;
while (pw == null) {
// Due to super-dev-mode recompiler, we have to use a fresh name for our class each iteration.
next = resultType+"_"+unique++;
pw = context.tryCreate(logger, resultPackage, next);
}
resultType = next;
Set<JClassType> needMagicClass = new LinkedHashSet<JClassType>();
for (JClassType type : oracle.getTypes()) {
if (type.isAnnotationPresent(KeepClass.class)) {
needMagicClass.add(type);
}
}
Map<JClassType, KeepArrays> needArrays = new LinkedHashMap<JClassType, KeepArrays>();
for (JClassType type : oracle.getTypes()) {
KeepArrays keeper = type.getAnnotation(KeepArrays.class);
if (keeper != null) {
needArrays.put(type, keeper);
}
}
// Now that we have our types, let's get to building the service...
SourceBuilder<Object> sw =
new SourceBuilder<Object>("public class "+resultType)
.setPackage(resultPackage)
;
ClassBuffer buffer =
sw.getClassBuffer()
.addInterfaces(ReflectionService.class)
;
// Add some static fields for reflection support.
buffer
.indent()
.println("private static final Object[] OBJECT_ARRAY = new Object[0];")
.outdent()
.println();
// switch/case from class to provider method is the best a standard monolithic generator can do.
MethodBuffer magicClass = buffer.createMethod(
"public native <T> Class<T> magicClass(Class<T> cls)")
.setUseJsni(true)
.println("switch(cls){");
;
for (JClassType type : needMagicClass) {
// Generate a magic-class for this type...
RebindResult cls = MagicClassGenerator.execImpl(logger, context, type);
magicClass //jsni
.println("case @"+type.getQualifiedSourceName()+"::class: ")
.indentln("return @"+cls.getResultTypeName()+"::enhanceClass(Ljava/lang/Class;)(cls);");
}
// close our method
magicClass
.println("default:")
.indentln("return cls;")
.println("}");
// next up, packages. They are compiled into magic classes, so we can avoid duplication here.
buffer.createMethod(
"public Package getPackage(Object o)")
.println("if (o instanceof Class){")
.indent()
.println("// Make sure we are using an enhanced class")
// This allows us to avoid a second giant switch/case statement
.println("if (o == Class.class){")
.println("return magicClass((Class)o).getPackage();")
.println("}")
.println("return ((Class)o).getPackage();")
.outdent()
.println("} else if (o == null){")
.indentln("throw new NullPointerException(); ")
.println("} else {")
.indentln("return o.getClass().getPackage(); ")
.println("}")
;
buffer.createMethod(
"public Package getPackage(String name, ClassLoader cl)")
.returnValue(buffer.addImport(GwtReflect.class.getName()+"Jre")+".getPackage(name, cl);")
;
// Now, let's generate support for arrays!
// This is a little tougher, since we do allow an annotation to specify other types
// (so you can keep a given array type without having to annotate the class itself).
MethodBuffer newArraySingle = buffer.createMethod(
"public native <T> T[] newArray(" +
"Class<T> classLit, " +
"int dimension)")
.setUseJsni(true)
.println("switch(classLit){")
.indent()
.println("case @java.lang.Object::class :")
.indentln("return @com.google.gwt.lang.Array::createFrom([Ljava/lang/Object;I)" +
"(@" + fqcn +"::OBJECT_ARRAY, dimension);");
;
MethodBuffer newArrayMulti = buffer.createMethod(
"public native <T> T[] newArray(" +
"Class<T> classLit, " +
"int ... dimensions)")
.setUseJsni(true)
.println("switch(classLit){")
;
newArraySingle
.println("default:")
.indentln("@"+fqcn+"::throwNoArraySupport(Ljava/lang/Class;)(classLit)")
.indentln("return null;")
.println("}");
newArrayMulti
.println("default:")
.indentln("@"+fqcn+"::throwNoArraySupport(Ljava/lang/Class;)(classLit)")
.indentln("return null;")
.println("}");
buffer
.createMethod("private static void throwNoArraySupport(Class<?> c)")
.println("throw new NotConfiguredCorrectly(\"" +
"No array reflection support for \"+ c.getName() + \"; " +
"ensure this class is annotated with @KeepArrays."+
(X_Runtime.isDebug()?" This annotation allows you to add support for " +
"any class with the alsoKeep() method, that allows you to keep " +
"array support for types without directly annotating them (like java.lang classes).":"")+
"\");");
sw.getImports().addImport(NotConfiguredCorrectly.class.getName());
if (X_Runtime.isDebug()) {
logger.log(Type.INFO, "Generated reflection support for "+needMagicClass.size()+" classes.");
logger.log(Type.WARN, sw.toString());
}
pw.append(sw.toString());
context.commit(logger, pw);
return new RebindResult(RebindMode.USE_ALL_NEW_WITH_NO_CACHING, resultPackage + "." + resultType);
}
@Override
public long getVersionId() {
return 1;
}
}