xapi.dev.model.ModelGeneratorGwt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xapi-gwt Show documentation
Show all versions of xapi-gwt Show documentation
This module exists solely to package all other gwt modules into a single
uber jar. This makes deploying to non-mavenized targets much easier.
Of course, you would be wise to inherit your dependencies individually;
the uber jar is intended for projects like collide,
which have complex configuration, and adding many jars would be a pain.
The newest version!
package xapi.dev.model;
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.JMethod;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import xapi.annotation.model.DeleterFor;
import xapi.annotation.model.FieldName;
import xapi.annotation.model.GetterFor;
import xapi.annotation.model.Serializable;
import xapi.annotation.model.SetterFor;
import xapi.annotation.reflect.Fluent;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.SourceBuilder;
import xapi.model.api.ModelSerializer;
import xapi.source.X_Source;
import xapi.source.api.IsType;
import xapi.util.X_Properties;
public class ModelGeneratorGwt extends IncrementalGenerator{
private static final String MODEL_PACKAGE = "xapi.model";
static final boolean MINIFY = Boolean.parseBoolean(X_Properties.getProperty("xapi.dist", "false"));
@Override
public RebindResult generateIncrementally(final TreeLogger logger, final GeneratorContext context, final String typeName)
throws UnableToCompleteException {
ModelMagic.initialize();
return execImpl(logger, context, typeName);
}
@Override
public long getVersionId() {
return 0;
}
static boolean isFluent(final JMethod method) {
final JClassType iface = method.getReturnType().isClassOrInterface();
if (iface == null) {
return false;
}
if (
method.getEnclosingType().isAssignableTo(iface)
|| iface.isAssignableTo(method.getEnclosingType())
) {
// Returning this would be allowed.
// However, we should guard against methods that may actually want to return a field
// that is the same type as itself.
final Fluent fluent = method.getAnnotation(Fluent.class);
if (fluent != null) {
return fluent.value();
}
// TODO: check if there is a single parameter type which is also compatible,
// and throw an error telling the user that they must specify @Fluent(true) or @Fluent(false)
// Because the method signature is ambiguous
return true;
}
return false;
}
static String returnType(final JMethod method, final ModelMagic models) {
// TODO make sure simple name is allowed here.
return method.getReturnType().getSimpleSourceName();
}
static String fieldName(final JMethod method, final ModelMagic models) {
final FieldName field = method.getAnnotation(FieldName.class);
if (field != null) {
return field.debugName().length() > 0
// TODO && models.isDebugMode
? field.debugName() : field.value();
}
final GetterFor getter = method.getAnnotation(GetterFor.class);
if (getter != null) {
return getter.value();
}
final SetterFor setter = method.getAnnotation(SetterFor.class);
if (setter != null) {
return setter.value();
}
final DeleterFor deleter = method.getAnnotation(DeleterFor.class);
if (deleter != null) {
return deleter.value();
}
// No annotations, we have to guess.
final String name = method.getName();
if (
name.matches("(get|set|has|put|add|rem)[A-Z].*")
) {
return Character.toLowerCase(name.charAt(3)) + name.substring(4);
} else if (name.startsWith("is")) {
return Character.toLowerCase(name.charAt(2)) + name.substring(3);
} else if (name.startsWith("remove")) {
return Character.toLowerCase(name.charAt(6)) + name.substring(7);
}
return name;
}
static boolean canBeSupertype(final com.google.gwt.core.ext.typeinfo.JClassType subtype, final com.google.gwt.core.ext.typeinfo.JClassType supertype) {
return
supertype.isInterface() == null &&
!supertype.isFinal() &&
supertype.isAssignableTo(subtype);
}
static String typeToParameterString(final JType[] parameterTypes) {
if (parameterTypes.length == 0) {
return "";
}
final StringBuilder b = new StringBuilder()
.append(parameterTypes[0].getQualifiedSourceName())
.append(" A");
assert parameterTypes.length < 26: "Cannot have more than 26 parameters";
for (int i = 1, m = parameterTypes.length; i < m; i++) {
b
.append(", ")
.append(parameterTypes[i].getQualifiedSourceName())
.append(" ")
.append((char)('A'+i))
;
}
return b.toString();
}
static String typeToSignature(final JType[] parameterTypes) {
if (parameterTypes.length == 0) {
return "";
}
final StringBuilder b = new StringBuilder()
.append(parameterTypes[0].getJNISignature());
for (int i = 1, m = parameterTypes.length; i < m; i++) {
b.append(parameterTypes[i].getJNISignature());
}
return b.toString();
}
public static boolean allAbstract(final JMethod[] existing) {
for (final JMethod method : existing) {
if (!method.isAbstract()) {
return false;
}
}
return true;
}
public static String toSignature(final JMethod method) {
return method.getName()+"("+typeToSignature(method.getParameterTypes())+")";
}
public static RebindResult execImpl(final TreeLogger logger, final GeneratorContext ctx,
final String typeName) throws UnableToCompleteException {
final JClassType type = ctx.getTypeOracle().findType(typeName.replace('$', '.'));
if (type == null) {
logger.log(Type.ERROR, "Unable to find source for model interface type "+typeName);
throw new UnableToCompleteException();
}
// Step one; see if this model already exists...
final ModelMagic magic = ModelMagic.active.get();
final String mangledName = magic.mangleName(type.getQualifiedSourceName(), MINIFY);
final String fqcn = MODEL_PACKAGE+"."+mangledName;
if (ctx.isGeneratorResultCachingEnabled() && ctx.tryReuseTypeFromCache(typeName)) {
return new RebindResult(RebindMode.USE_PARTIAL_CACHED, fqcn);
}
if (!type.isAbstract()) {
// TODO Only generate a provider class if the type itself is not concrete
return new RebindResult(RebindMode.USE_EXISTING, typeName);
}
final PrintWriter pw = ctx.tryCreate(logger, MODEL_PACKAGE, mangledName);
if (pw == null) {
// TODO for injectors this type may exist but be stale.
// Use an @Generated annotation to do a freshness check.
return new RebindResult(RebindMode.USE_EXISTING, fqcn);
}
final ModelArtifact model = magic.getOrMakeModel(logger, ctx, type);
final boolean isFinal = type.getSubtypes().length == 0;
final SourceBuilder builder = new SourceBuilder(
"public "+(isFinal?"final ":"")+"class "+mangledName
);
builder.setPayload(magic);
builder.setPackage(MODEL_PACKAGE);
// Step two; transverse type model.
visitModelStructure(logger, ctx, typeName, type, magic, model, builder);
// Step three, generate serialization protocols for this model type.
generateSerializers(logger, ctx, typeName, type, magic, model, builder);
// Step four, determine the fields we'll need to generate
model.generateModelClass(logger, builder, ctx, type);
final String src = builder.toString();
final Type logLevel = logLevel();
if (logger.isLoggable(logLevel)) {
logger.log(logLevel, "Generated model class:\n"+src);
}
pw.println(src);
ctx.commit(logger, pw);
if (!model.isReused()) {
ctx.commitArtifact(logger, model);
}
return new RebindResult(RebindMode.USE_ALL_NEW, MODEL_PACKAGE + "." + mangledName);
}
/**
* @param logger
* @param ctx
* @param typeName
* @param type
* @param magic
* @param model
* @param builder
*/
private static void generateSerializers(final TreeLogger logger, final GeneratorContext ctx, final String typeName, final JClassType type,
final ModelMagic magic, final ModelArtifact model, final SourceBuilder builder) {
final List toClient = new ArrayList();
final List toServer = new ArrayList();
for (final Entry method : model.methods.entrySet()) {
for (final Annotation anno : method.getValue()) {
if (anno instanceof Serializable) {
final Serializable serializable = (Serializable) anno;
if (serializable.clientToServer().enabled()) {
toServer.add(method.getKey());
}
if (serializable.serverToClient().enabled()) {
toClient.add(method.getKey());
}
}
}
}
if (toClient.isEmpty() && toServer.isEmpty()) {
// TODO signal to use an empty serializer, ModelSerializer.DO_NOTHING
}
final String name = builder.getSimpleName()+"_Serializer";
final PrintWriter pw = ctx.tryCreate(logger, builder.getPackage(), name);
if (pw == null) {
// Assume the result is not stale. Will need to be more careful when injected
return;
}
final SourceBuilder out = new SourceBuilder("public final class "+name);
out.setPackage(builder.getPackage());
final String simpleType = out.getClassBuffer().addImport(typeName);
final String serializerType = out.getClassBuffer().addImport(ModelSerializer.class);
out.getClassBuffer().addInterface(serializerType+"<"+simpleType+">");
final MethodBuffer modelToString = out.getClassBuffer()
.createMethod("public String modelToString(" + simpleType+" model)");
final MethodBuffer stringToModel = out.getClassBuffer()
.createMethod("public " + simpleType + " stringToModel(String model)");
if (toClient.isEmpty()) {
stringToModel.returnValue("throw new " + out.getClassBuffer().addImport(UnsupportedOperationException.class)+"();");
} else {
// Print a deserializer for the model
stringToModel.returnValue("null");
}
if (toServer.isEmpty()) {
modelToString.returnValue("throw new " + out.getClassBuffer().addImport(UnsupportedOperationException.class)+"();");
} else {
// Print a serializer for the model
modelToString.returnValue("null");
}
final String src = out.toString();
final Type logLevel = logLevel();
if (logger.isLoggable(logLevel)) {
logger.log(logLevel, "Generated model serializer:\n"+src);
}
pw.append(src);
ctx.commit(logger, pw);
}
private static Type logLevel() {
return Type.DEBUG;
}
/**
* @param logger
* @param ctx
* @param typeName
* @param type
* @param magic
* @param model
* @param builder
* @throws UnableToCompleteException
*/
private static void visitModelStructure(final TreeLogger logger, final GeneratorContext ctx, final String typeName,
final JClassType type, final ModelMagic magic, final ModelArtifact model, final SourceBuilder builder)
throws UnableToCompleteException {
if (type.isInterface() == null) {
// Client has specified their own base type.
// Let's see if we're expected to generate any methods.
if (type.isAbstract()) {
// Find the methods the client didn't bother to implement.
final Collection interfaceMethods = model.extractMethods(logger, builder, ctx, type);
for (final JMethod ifaceMethod : interfaceMethods) {
JMethod classMethod;
try {
classMethod = type.getMethod(ifaceMethod.getName(), ifaceMethod.getTypeParameters());
// If this method exists, we still need to pull off annotations.
final Annotation[] annos = model.extractAnnotations(logger, type, ifaceMethod, classMethod, ctx);
model.applyAnnotations(logger, classMethod, annos, ctx);
} catch (final NotFoundException e) {
// client didn't bother to implement this method.
// let's do it for them.
model.implementMethod(logger, ifaceMethod, ctx);
}
}
} else {
assert false : "Concrete types do not need to be filled in with generated types.";
}
} else {
final JClassType root = magic.getRootType(logger, ctx);
final Set toSigs = getImplementedSignatures(root.getInheritableMethods());
for (final JMethod method : model.extractMethods(logger, builder, ctx, type)) {
if (toSigs.add(ModelGeneratorGwt.toSignature(method))) {
model.implementMethod(logger, method, ctx);
}
}
}
}
static Set getImplementedSignatures(final JMethod[] inheritableMethods) {
final Set sigs = new LinkedHashSet(inheritableMethods.length);
for (final JMethod method : inheritableMethods) {
if (!method.isAbstract()) {
sigs.add(ModelGeneratorGwt.toSignature(method));
}
}
return sigs;
}
public static IsType[] toTypes(final JType[] parameterTypes) {
int i = parameterTypes.length;
final IsType[] arr = new IsType[i];
for (; i-->0;) {
final JType t = parameterTypes[i];
arr[i] = X_Source.binaryToSource(t.getQualifiedBinaryName());
}
return arr;
}
}