package xapi.dev.reflect; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import xapi.annotation.inject.InstanceDefault; import xapi.annotation.inject.InstanceOverride; import xapi.annotation.inject.SingletonDefault; import xapi.annotation.inject.SingletonOverride; import xapi.annotation.reflect.KeepAnnotation; import xapi.annotation.reflect.KeepClass; import xapi.annotation.reflect.KeepConstructor; import xapi.annotation.reflect.KeepField; import xapi.annotation.reflect.KeepMethod; import xapi.annotation.reflect.NewInstanceStrategy; import xapi.dev.generators.AbstractInjectionGenerator; import xapi.dev.source.ClassBuffer; import xapi.dev.source.ImportSection; import xapi.dev.source.MethodBuffer; import xapi.dev.source.SourceBuilder; import xapi.dev.util.CurrentGwtPlatform; import xapi.inject.X_Inject; import xapi.platform.Platform; import xapi.source.read.SourceUtil; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.UnsafeNativeLong; 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.HasAnnotations; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JConstructor; import com.google.gwt.core.ext.typeinfo.JField; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JPackage; import com.google.gwt.core.ext.typeinfo.JRealClassType; import com.google.gwt.core.ext.typeinfo.NotFoundException; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.reflect.rebind.ReflectionUtilJava; import com.google.gwt.reflect.shared.ClassMap; import com.google.gwt.reflect.shared.ReflectUtil; public class MagicClassGenerator extends IncrementalGenerator { protected static class ReflectionManifest { KeepClass cls; KeepMethod method; KeepField field; KeepConstructor constructor; KeepAnnotation anno; Map<JMethod, Annotation[]> methods = new LinkedHashMap<JMethod, Annotation[]>(); Map<JField, Annotation[]> fields = new LinkedHashMap<JField, Annotation[]>(); Map<JConstructor, Annotation[]> constructors = new LinkedHashMap<JConstructor, Annotation[]>(); Map<JClassType, Annotation[]> innerClasses = new LinkedHashMap<JClassType, Annotation[]>(); public ReflectionManifest cleanCopy() { ReflectionManifest copy = new ReflectionManifest(); copy.cls = cls; copy.method = method; copy.field = field; copy.constructor = constructor; copy.anno = anno; return copy; } } @SuppressWarnings("serial") private static class ManifestMap <K extends HasAnnotations> extends HashMap<K,ReflectionManifest> { @Override public synchronized ReflectionManifest get(Object key) { ReflectionManifest value = super.get(key); if (value == null) { value = new ReflectionManifest(); @SuppressWarnings("unchecked") // private class K type = (K)key; value.cls = type.getAnnotation(KeepClass.class); value.method = type.getAnnotation(KeepMethod.class); value.field = type.getAnnotation(KeepField.class); value.constructor = type.getAnnotation(KeepConstructor.class); value.anno = type.getAnnotation(KeepAnnotation.class); put(type, value); } return value; } } private static final ManifestMap<JPackage> packages = new ManifestMap<JPackage>(); public static RebindResult execImpl(TreeLogger logger, GeneratorContext context, JClassType type) throws UnableToCompleteException { // perform annotation and type discovery. // we all package-level, class level, and method/field level declarations, // which we now get to merge to find out how much to implement JPackage pkg = type.getPackage(); ReflectionManifest manifest; KeepClass defaultClass; KeepClass keepClass; KeepConstructor keepConstructor; KeepMethod keepMethod; KeepField keepField; KeepAnnotation keepAnnotation; String classDebug = ""; NewInstanceStrategy newInst = null; boolean keepCodesource = false; boolean keepHierarchy = false; boolean keepPackageName = true; synchronized (packages){ manifest = packages.get(pkg).cleanCopy(); defaultClass = manifest.cls; keepClass = type.getAnnotation(KeepClass.class); keepConstructor = type.getAnnotation(KeepConstructor.class); keepMethod = type.getAnnotation(KeepMethod.class); keepField = type.getAnnotation(KeepField.class); keepAnnotation = type.getAnnotation(KeepAnnotation.class); if (defaultClass != null) { keepCodesource = defaultClass.keepCodeSource(); keepHierarchy = defaultClass.keepHierarchy(); keepPackageName = defaultClass.keepPackage(); classDebug = defaultClass.debugData(); newInst = defaultClass.newInstance(); keepClass = defaultClass; if (keepConstructor == null) keepConstructor = manifest.constructor; if (keepMethod == null) keepMethod = manifest.method; if (keepField == null) keepField = manifest.field; if (keepAnnotation == null) keepAnnotation = manifest.anno; } if (keepClass != null) { classDebug = keepClass.debugData(); newInst = keepClass.newInstance(); keepCodesource = keepClass.keepCodeSource(); keepPackageName = keepClass.keepPackage(); keepHierarchy = keepClass.keepHierarchy(); } } JClassType injectionType = null; JClassType targetType = type; String packageName = type.getPackage().getName(); String simpleName = SourceUtil.toSourceName(type.getSimpleSourceName()); String generatedName = ReflectionUtilJava.generatedMagicClassName(simpleName); String clsToEnhance = SourceUtil.toSourceName(type.getQualifiedSourceName()); //if the magic class implements newInstance() if (newInst != null) { //check for singleton or instance level injection. switch(newInst) { case X_INSTANCE: Set<Class<? extends Annotation>> platforms = CurrentGwtPlatform.getPlatforms(context); // TODO call into existing generator framework InstanceOverride winningInstance = null; for (JClassType subtype : type.getSubtypes()) { if (injectionType == null) { //if we haven't picked a type yet, let's look for defaults InstanceDefault def = subtype.getAnnotation(InstanceDefault.class); if (def != null) { boolean useType = true; for (Annotation anno : subtype.getAnnotations()) { if (anno.annotationType().getAnnotation(Platform.class) != null) { if (platforms.contains(anno.annotationType())) { useType = true; break; } else { useType = false; } } } if (useType) { injectionType = subtype; } } } InstanceOverride override = subtype.getAnnotation(InstanceOverride.class); if (override != null) { boolean useType = true; logger.log(Type.TRACE, "Got magic class subtype " + subtype + " : "); for (Annotation anno : subtype.getAnnotations()) { if (anno.annotationType().getAnnotation(Platform.class)!=null) { if (platforms.contains(anno.annotationType())) { useType = true; break; } else { useType = false; } } } if (!useType) continue; if (winningInstance != null) { if (winningInstance.priority() > override.priority()) continue; } winningInstance = override; injectionType = subtype; } } break; case X_SINGLETON: platforms = CurrentGwtPlatform.getPlatforms(context); SingletonOverride winningSingleton = null; for (JClassType subtype : type.getSubtypes()) { if (injectionType == null) { // Only check defaults if we haven't yet accepted an override. // Note that in the case of two valid default types, this will // undeterministically chose either of them. SingletonDefault def = subtype.getAnnotation(SingletonDefault.class); if (def != null) { boolean useType = true; for (Annotation anno : subtype.getAnnotations()) { if (anno.annotationType().getAnnotation(Platform.class) != null) { if (platforms.contains(anno.annotationType())) { useType = true; break; } else { useType = false; } } } if (useType) { injectionType = subtype; } } } SingletonOverride override = subtype.getAnnotation(SingletonOverride.class); if (override != null) { logger.log(Type.TRACE, "Got magic class subtype " + subtype+ " - prodMode: " + context.isProdMode()); boolean useType = true; for (Annotation anno : subtype.getAnnotations()) { if (anno.annotationType().getAnnotation(Platform.class)!=null) { if (platforms.contains(anno.annotationType())) { useType = true; break; } else { useType = false; } } } if (!useType) continue; if (winningSingleton != null) { if (winningSingleton.priority() > override.priority()) continue; } winningSingleton = override; injectionType = subtype; } } if (injectionType == null) { injectionType = targetType;// no matches, resort to instantiate the class sent. } //Only call ensureProviderClass if injectMethod is X_Inject.singleton AbstractInjectionGenerator. ensureProviderClass(logger, packageName, type.getSimpleSourceName(), type.getQualifiedSourceName(), SourceUtil.toSourceName(injectionType.getQualifiedSourceName()), context); default: } } //in case there was no injection target, just use the original Type as injectionType if (injectionType == null) { injectionType = targetType;// no matches, resort to instantiate the class sent. } PrintWriter printWriter = context.tryCreate(logger, packageName, generatedName); int unique = 0; String next = generatedName; while(printWriter == null){ next = generatedName+"_"+unique++; printWriter = context.tryCreate(logger, packageName, next); } generatedName = next; if (logger.isLoggable(Type.TRACE)) logger.log(Type.TRACE,"Writing magic class instance for " + type.getQualifiedSourceName() + " -> " + injectionType.getQualifiedSourceName()); SourceBuilder<Object> classBuilder = new SourceBuilder<Object>( "public class "+generatedName +" extends ClassMap<"+simpleName+">") .setPackage(packageName); ImportSection imports = classBuilder.getImports() .addImports(clsToEnhance) .addImports( ClassMap.class, JavaScriptObject.class, UnsafeNativeLong.class, ReflectUtil.class); if (newInst != null) imports.addImport(Constructor.class); if (keepMethod != null) imports.addImport(Method.class); if (keepField != null) imports.addImport(Field.class); if (keepCodesource) imports.addImport(CodeSource.class); ClassBuffer cls = classBuilder.getClassBuffer(); cls.createMethod("private " +generatedName+"()"); if (keepHierarchy) { injectionType.getNestedTypes(); } // This is the method that fills in all of the extra class data MethodBuffer enhanceMethod = cls.createMethod ("public static Class<"+simpleName+"> enhanceClass(Class<" +simpleName+"> toEnhance)") .println("if (Class.needsEnhance(toEnhance)) {") .indent() .println("ReflectUtil.setClassData(toEnhance, new "+generatedName+"());") ; if (injectionType.getEnclosingType() != null) { if (newInst != null) { logger.log(Type.WARN, "Warning: attempting to implement newInstance() on an inner class: " + injectionType+"! Please use @KeepClass(newInstance()==NONE) on all inner classes."); } } else if (newInst != null) { //since we are generating newInstance(), we can make up any sort of provider for the given method. //options are: GWT.create, X_Inject.instance/singleton, or new Type(); MethodBuffer newInstance = cls.createMethod("public " +simpleName+" newInstance()"); switch (newInst) { case NONE: newInstance.println("throw new UnsupportedOperationException(\"newInstance not supported for \"" + "+ \"" + clsToEnhance + "\");"); break; case GWT_CREATE: cls.addImports(GWT.class); newInstance.println("return GWT.create("+clsToEnhance+".class);"); break; case NEW: newInstance.println("return new "+clsToEnhance+"();"); break; case X_INSTANCE: cls.addImports(X_Inject.class); newInstance.println("return X_Inject.instance("+clsToEnhance+".class"+");"); break; case X_SINGLETON: cls.addImports(X_Inject.class); newInstance.println("return X_Inject.singleton("+clsToEnhance+".class"+");"); break; default: break; } } // check type for methods we want to keep extractMethods(logger, keepMethod, injectionType, manifest); if (keepMethod != null || manifest.methods.size()>0) { // GwtMethodGenerator.generateMethods(logger, classBuilder, context, injectionType, manifest); enhanceMethod.println("enhanceMethods(toEnhance);"); } // check type for constructors we want to keep extractConstructors(logger, keepConstructor, injectionType, manifest); if (keepConstructor != null || manifest.constructors.size()>0) { // GwtConstructorGenerator.generateConstructors(logger, classBuilder, context, injectionType, manifest.constructors); enhanceMethod.println("enhanceConstructors(toEnhance);"); } // now, do the fields extractFields(logger, keepField, injectionType, manifest); if (keepField != null || manifest.fields.size()>0) { // if (GwtFieldGenerator.generateFields(logger, classBuilder, context, injectionType, manifest.fields)){ enhanceMethod.println("enhanceFields(toEnhance);"); // } } enhanceMethod .outdent() .println("}") .println("return toEnhance;"); if (keepCodesource) { if (injectionType instanceof JRealClassType) { String location = ((JRealClassType)injectionType).getLocation(); cls.addImports(ProtectionDomain.class); cls .println("private ProtectionDomain domain;") .createMethod("public ProtectionDomain getProtectionDomain()") .println("if (domain == null) ") .indentln("domain = new ProtectionDomain(\"" +location+"\");") .println("return domain;"); } else { logger.log(Type.WARN, "Requested code location for "+injectionType.getQualifiedSourceName() +" was not found; expected JRealClassType, got "+injectionType.getClass().getName()); } } if (keepPackageName) { cls.createMethod("public Package getPackage()") .println("return Package.getPackage(\""+packageName+"\");"); } if (classDebug.length() > 0) { logger.log(Type.INFO, classDebug); logger.log(Type.INFO, "Source Dump For " +clsToEnhance+":"); logger.log(Type.INFO, classBuilder.toString()); } // Actually write the file printWriter.append(classBuilder.toString()); context.commit(logger, printWriter); return new RebindResult(RebindMode.USE_ALL_NEW_WITH_NO_CACHING, packageName+"."+generatedName); } private static void extractConstructors(TreeLogger logger, KeepConstructor keepCtor, JClassType injectionType, ReflectionManifest manifest) { boolean keepCtors = keepCtor != null; boolean keepAnnos = manifest.anno != null; Set<String> seen = new HashSet<String>(); Set<? extends JClassType> allTypes = injectionType.getFlattenedSupertypeHierarchy(); for(JClassType nextClass : allTypes) { for (JConstructor ctor : nextClass.getConstructors()) { if (keepCtors || ctor.getAnnotation(KeepConstructor.class) != null){ // do not include overridden constructors if (seen.add(ctor.getJsniSignature())) { final Annotation[] annos; if (keepAnnos || ctor.getAnnotation(KeepAnnotation.class) != null) { annos = ctor.getAnnotations(); } else { // only keep annotations annotated with KeepAnnotation. final List<Annotation> keepers = new ArrayList<Annotation>(); for (Annotation anno : ctor.getAnnotations()) { if (anno.annotationType().getAnnotation(KeepAnnotation.class) != null) keepers.add(anno); } annos = keepers.toArray(new Annotation[keepers.size()]); } manifest.constructors.put(ctor, annos); } } } nextClass = nextClass.getSuperclass(); } } private static void extractFields(TreeLogger logger, KeepField keepField, JClassType injectionType, ReflectionManifest manifest) { boolean keepFields = keepField != null; boolean keepAnnos = manifest.anno != null; Set<String> seen = new HashSet<String>(); Set<? extends JClassType> allTypes = injectionType.getFlattenedSupertypeHierarchy(); for(JClassType nextClass : allTypes) { if (nextClass.getQualifiedSourceName().equals("java.lang.Object")) { // gwt puts some intrinsic fields in Object we can't access continue; } for (JField field : nextClass.getFields()) { if (keepFields || field.getAnnotation(KeepField.class) != null){ // do not include overridden constructors if (seen.add(field.getName())) { final Annotation[] annos; if (keepAnnos || field.getAnnotation(KeepAnnotation.class) != null) { annos = field.getAnnotations(); } else { // only keep annotations annotated with KeepAnnotation. final List<Annotation> keepers = new ArrayList<Annotation>(); for (Annotation anno : field.getAnnotations()) { if (anno.annotationType().getAnnotation(KeepAnnotation.class) != null) keepers.add(anno); } annos = keepers.toArray(new Annotation[keepers.size()]); } manifest.fields.put(field, annos); } } } nextClass = nextClass.getSuperclass(); } } private static void extractMethods(TreeLogger logger, KeepMethod keepMethod, JClassType injectionType, ReflectionManifest manifest) { boolean keepMethods = keepMethod != null; boolean keepAnnos = manifest.anno != null; Set<String> seen = new HashSet<String>(); Set<? extends JClassType> allTypes = injectionType.getFlattenedSupertypeHierarchy(); for(JClassType nextClass : allTypes) { for (JMethod method : nextClass.getMethods()) { if (keepMethods || method.getAnnotation(KeepMethod.class) != null){ // do not include overridden methods if (seen.add(method.getJsniSignature())) { final Annotation[] annos; if (keepAnnos || method.getAnnotation(KeepAnnotation.class) != null) { annos = method.getAnnotations(); } else { // only keep annotations annotated with KeepAnnotation. final List<Annotation> keepers = new ArrayList<Annotation>(); for (Annotation anno : method.getAnnotations()) { if (anno.annotationType().getAnnotation(KeepAnnotation.class) != null) keepers.add(anno); } annos = keepers.toArray(new Annotation[keepers.size()]); } manifest.methods.put(method, annos); } } } nextClass = nextClass.getSuperclass(); } } @Override public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException { TypeOracle oracle = context.getTypeOracle(); logger.log(Type.TRACE, "Generating magic class for " + typeName); try { return execImpl(logger, context, oracle.getType(SourceUtil.toSourceName(typeName))); } catch (NotFoundException e) { logger.log(Type.ERROR, "Could not find class for " + typeName, e); } throw new UnableToCompleteException(); } @Override public long getVersionId() { return 0; } }