package com.github.czyzby.autumn.gwt.reflection.generator;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
import com.github.czyzby.autumn.gwt.reflection.ReflectionPool;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
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.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
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.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
/** Generates {@link com.github.czyzby.autumn.gwt.reflection.ReflectionPool} instance, aware of all GWT-reflected
* classes.
*
* @author MJ */
public class ReflectionPoolGenerator extends Generator {
private static final String GENERATED_CLASS_PREFIX = "Generated";
@Override
@SuppressWarnings("resource")
public String generate(final TreeLogger logger, final GeneratorContext context, final String typeName)
throws UnableToCompleteException {
final TypeOracle oracle = context.getTypeOracle();
assert oracle != null;
final JClassType type = oracle.findType(typeName);
if (type == null || type.isInterface() == null) {
logger.log(Type.ERROR, "Invalid type: " + typeName + ".");
throw new UnableToCompleteException();
}
final String packageName = type.getPackage().getName();
final String generatedClassName = GENERATED_CLASS_PREFIX + type.getSimpleSourceName();
final ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName,
generatedClassName);
composer.addImplementedInterface(ReflectionPool.class.getCanonicalName());
final PrintWriter printWriter = context.tryCreate(logger, packageName, generatedClassName);
if (printWriter == null) {
return getQualifiedGeneratedClassName(packageName, generatedClassName);
}
final SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter);
final Set<JType> reflectedClasses = findReflectedClasses(context, oracle, logger);
appendClassBody(sourceWriter, reflectedClasses);
sourceWriter.commit(logger);
return getQualifiedGeneratedClassName(packageName, generatedClassName);
}
private static String getQualifiedGeneratedClassName(final String packageName, final String generatedClassName) {
return packageName + "." + generatedClassName;
}
private static void appendClassBody(final SourceWriter sourceWriter, final Set<JType> reflectedClasses) {
sourceWriter.println("private static final Class<?>[] POOL = new Class<?>[] { ");
for (final JType reflectedClass : reflectedClasses) {
sourceWriter.print(reflectedClass.getQualifiedSourceName() + ".class, ");
}
sourceWriter.println(" };");
sourceWriter.println("@Override public Class<?>[] getReflectedClasses() { return POOL; } ");
}
// LibGDX code goes here. Slightly modified, refactored and without unnecessary operations.
private Set<JType> findReflectedClasses(final GeneratorContext context, final TypeOracle typeOracle,
final TreeLogger logger) throws UnableToCompleteException {
final Set<JType> types = new HashSet<JType>();
final JPackage[] packages = typeOracle.getPackages();
// gather all types from wanted packages
for (final JPackage jPackage : packages) {
for (final JClassType jType : jPackage.getTypes()) {
gatherTypes(jType.getErasedType(), types, context, logger);
}
}
// gather all types from explicitely requested packages
try {
final ConfigurationProperty reflectionProperties = context.getPropertyOracle()
.getConfigurationProperty("gdx.reflect.include");
for (final String property : reflectionProperties.getValues()) {
final JClassType type = typeOracle.findType(property);
if (type != null) {
gatherTypes(type.getErasedType(), types, context, logger);
}
}
} catch (final BadPropertyValueException exception) {
logger.log(Type.ERROR, "Unknown property: " + "gdx.reflect.include", exception);
throw new UnableToCompleteException();
}
gatherTypes(typeOracle.findType("java.util.List").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.util.ArrayList").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.util.HashMap").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.util.Map").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.String").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Boolean").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Byte").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Long").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Character").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Short").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Integer").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Float").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.CharSequence").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Double").getErasedType(), types, context, logger);
gatherTypes(typeOracle.findType("java.lang.Object").getErasedType(), types, context, logger);
return types;
}
private void gatherTypes(final JType type, final Set<JType> types, final GeneratorContext context,
final TreeLogger logger) throws UnableToCompleteException {
// came here from a type that has no super class / package info / not visible
if (type == null || type.getQualifiedSourceName().contains("-") || !isVisible(type)) {
return;
}
// filter reflection scope based on configuration in gwt xml module
boolean keep = false;
final String name = type.getQualifiedSourceName();
try {
ConfigurationProperty reflectionProperties;
keep |= !name.contains(".");
reflectionProperties = context.getPropertyOracle().getConfigurationProperty("gdx.reflect.include");
for (final String property : reflectionProperties.getValues()) {
keep |= name.contains(property);
}
reflectionProperties = context.getPropertyOracle().getConfigurationProperty("gdx.reflect.exclude");
for (final String property : reflectionProperties.getValues()) {
keep &= !name.equals(property);
}
} catch (final BadPropertyValueException exception) {
logger.log(Type.ERROR, "Unknown properties: gdx.reflect.include, gdx.reflect.exclude", exception);
throw new UnableToCompleteException();
}
if (!keep) {
return;
}
// already visited this type
if (types.contains(type.getErasedType())) {
return;
}
types.add(type.getErasedType());
if (type instanceof JPrimitiveType) {
// nothing to do for a primitive type
return;
}
// gather fields
final JClassType classType = (JClassType) type;
final JField[] fields = classType.getFields();
if (fields != null && fields.length > 0) {
for (final JField field : fields) {
gatherTypes(field.getType().getErasedType(), types, context, logger);
}
}
// gather super types & interfaces
gatherTypes(classType.getSuperclass(), types, context, logger);
final JClassType[] interfaces = classType.getImplementedInterfaces();
if (interfaces != null && interfaces.length > 0) {
for (final JClassType jInterface : interfaces) {
gatherTypes(jInterface.getErasedType(), types, context, logger);
}
}
// gather method parameter & return types
final JMethod[] methods = classType.getMethods();
if (methods != null) {
for (final JMethod method : methods) {
gatherTypes(method.getReturnType().getErasedType(), types, context, logger);
if (method.getParameterTypes() != null) {
for (final JType parameterType : method.getParameterTypes()) {
gatherTypes(parameterType.getErasedType(), types, context, logger);
}
}
}
}
// gather inner classes
final JClassType[] innerClasses = classType.getNestedTypes();
if (innerClasses != null && innerClasses.length > 0) {
for (final JClassType innerClass : innerClasses) {
gatherTypes(innerClass.getErasedType(), types, context, logger);
}
}
}
private static boolean isVisible(final JType type) {
if (type == null) {
return false;
}
if (type instanceof JClassType) {
if (type instanceof JArrayType) {
JType componentType = ((JArrayType) type).getComponentType();
while (componentType instanceof JArrayType) {
componentType = ((JArrayType) componentType).getComponentType();
}
if (componentType instanceof JClassType) {
return ((JClassType) componentType).isPublic();
}
} else {
return ((JClassType) type).isPublic();
}
}
return true;
}
}