All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xapi.dev.model.ModelGeneratorGwt Maven / Gradle / Ivy

Go to download

Everything needed to run a comprehensive dev environment. Just type X_ and pick a service from autocomplete; new dev modules will be added as they are built. The only dev service not included in the uber jar is xapi-dev-maven, as it includes all runtime dependencies of maven, adding ~4 seconds to build time, and 6 megabytes to the final output jar size (without xapi-dev-maven, it's ~1MB).

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;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy