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

com.google.gwt.user.rebind.AsyncProxyGenerator Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.rebind;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
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.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
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.client.AsyncProxy;
import com.google.gwt.user.client.AsyncProxy.AllowNonVoid;
import com.google.gwt.user.client.AsyncProxy.ConcreteType;
import com.google.gwt.user.client.AsyncProxy.DefaultValue;
import com.google.gwt.user.client.impl.AsyncProxyBase;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Generates implementation of AsyncProxy interfaces.
 */
public class AsyncProxyGenerator extends Generator {

  @Override
  public String generate(TreeLogger logger, GeneratorContext generatorContext,
      String typeName) throws UnableToCompleteException {

    // The TypeOracle knows about all types in the type system
    TypeOracle typeOracle = generatorContext.getTypeOracle();

    // Get a reference to the type that the generator should implement
    JClassType asyncProxyType = typeOracle.findType(AsyncProxy.class.getName());
    JClassType asyncProxyBaseType = typeOracle.findType(AsyncProxyBase.class.getName());
    JClassType sourceType = typeOracle.findType(typeName);

    // Ensure that the requested type exists
    if (sourceType == null) {
      logger.log(TreeLogger.ERROR, "Could not find requested typeName");
      throw new UnableToCompleteException();
    } else if (sourceType.isInterface() == null) {
      // The incoming type wasn't a plain interface, we don't support
      // abstract base classes
      logger.log(TreeLogger.ERROR, sourceType.getQualifiedSourceName()
          + " is not an interface.", null);
      throw new UnableToCompleteException();
    }

    JClassType concreteType = getConcreteType(logger, typeOracle, sourceType);
    JClassType paramType = getParamType(logger, asyncProxyType, sourceType);

    validate(logger, sourceType, concreteType, paramType);

    // com.foo.Bar$Proxy -> com_foo_Bar_ProxyImpl
    String generatedSimpleSourceName = sourceType.getQualifiedSourceName().replace(
        '.', '_').replace('$', '_')
        + "Impl";

    // Begin writing the generated source.
    ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
        sourceType.getPackage().getName(), generatedSimpleSourceName);
    String createdClassName = f.getCreatedClassName();

    // The generated class needs to be able to determine the module base URL
    f.addImport(GWT.class.getName());
    f.addImport(RunAsyncCallback.class.getName());

    // Used by the map methods
    f.setSuperclass(asyncProxyBaseType.getQualifiedSourceName() + "<"
        + paramType.getQualifiedSourceName() + ">");

    // The whole point of this exercise
    f.addImplementedInterface(sourceType.getQualifiedSourceName());

    // All source gets written through this Writer
    PrintWriter out = generatorContext.tryCreate(logger,
        sourceType.getPackage().getName(), generatedSimpleSourceName);

    // If an implementation already exists, we don't need to do any work
    if (out != null) {
      SourceWriter sw = f.createSourceWriter(generatorContext, out);

      // The invocation of runAsync, with a unique class for the split point
      sw.println("protected void doAsync0() {");
      sw.indent();
      sw.println("GWT.runAsync(new RunAsyncCallback() {");
      sw.indentln("public void onFailure(Throwable caught) {doFailure0(caught);}");
      sw.indentln("public void onSuccess() {setInstance0(doCreate0());}");
      sw.println("});");
      sw.outdent();
      sw.println("}");

      // Field for callback, plus getter and setter
      String proxyCallback = "ProxyCallback<"
          + paramType.getQualifiedSourceName() + ">";
      sw.println("private " + proxyCallback + " callback;");
      sw.println("public void setProxyCallback(" + proxyCallback
          + " callback) {this.callback = callback;}");
      sw.println("protected " + proxyCallback
          + " getCallback0() {return callback;}");

      // doCreate0() method to invoke GWT.create()
      sw.println("private " + paramType.getQualifiedSourceName()
          + " doCreate0() {");
      sw.indent();
      sw.println("return GWT.create(" + concreteType.getQualifiedSourceName()
          + ".class);");
      sw.outdent();
      sw.println("}");

      boolean allowNonVoid = sourceType.getAnnotation(AllowNonVoid.class) != null;
      for (JMethod method : paramType.getOverridableMethods()) {
        DefaultValue defaults = getDefaultValue(sourceType, method);

        if (method.getReturnType() != JPrimitiveType.VOID && !allowNonVoid) {
          logger.log(TreeLogger.ERROR, "The method " + method.getName()
              + " returns a type other than void, but "
              + sourceType.getQualifiedSourceName() + " does not define the "
              + AllowNonVoid.class.getSimpleName() + " annotation.");
          throw new UnableToCompleteException();
        }

        // Print the method decl
        sw.print("public " + method.getReturnType().getQualifiedSourceName()
            + " " + method.getName() + "(");
        for (Iterator i = Arrays.asList(method.getParameters()).iterator(); i.hasNext();) {
          JParameter param = i.next();
          sw.print("final " + param.getType().getQualifiedSourceName() + " "
              + param.getName());
          if (i.hasNext()) {
            sw.print(", ");
          }
        }
        sw.println(") {");
        {
          sw.indent();

          // Try a direct dispatch if we have a proxy instance
          sw.println("if (getProxiedInstance() != null) {");
          {
            sw.indent();
            if (method.getReturnType() != JPrimitiveType.VOID) {
              sw.print("return ");
            }
            writeInvocation(sw, "getProxiedInstance()", method);
            sw.outdent();
          }

          // Otherwise queue up a parameterized command object
          sw.println("} else {");
          {
            sw.indent();
            sw.println("enqueue0(new ParamCommand<"
                + paramType.getQualifiedSourceName() + ">() {");
            {
              sw.indent();
              sw.println("public void execute("
                  + paramType.getQualifiedSourceName() + " t) {");
              {
                sw.indent();
                writeInvocation(sw, "t", method);
                sw.outdent();
              }
              sw.println("}");
              sw.outdent();
            }
            sw.println("});");

            if (method.getReturnType() != JPrimitiveType.VOID) {
              sw.println("return "
                  + getDefaultExpression(defaults, method.getReturnType())
                  + ";");
            }

            sw.outdent();
          }
          sw.println("}");
          sw.outdent();
        }
        sw.println("}");
      }

      sw.commit(logger);
    }

    // Return the name of the concrete class
    return createdClassName;
  }

  private JClassType getConcreteType(TreeLogger logger, TypeOracle typeOracle,
      JClassType sourceType) throws UnableToCompleteException {
    JClassType concreteType;
    ConcreteType concreteTypeAnnotation = sourceType.getAnnotation(ConcreteType.class);
    if (concreteTypeAnnotation == null) {
      logger.log(TreeLogger.ERROR, "AsnycProxy subtypes must specify a "
          + ConcreteType.class.getSimpleName() + " annotation.");
      throw new UnableToCompleteException();
    }

    String concreteTypeName = concreteTypeAnnotation.value().getName().replace(
        '$', '.');
    concreteType = typeOracle.findType(concreteTypeName);

    if (concreteType == null) {
      logger.log(TreeLogger.ERROR,
          "Unable to find concrete type; is it in the GWT source path?");
      throw new UnableToCompleteException();
    }
    return concreteType;
  }

  /**
   * Returns a useful default expression for a type. It is an error to pass
   * JPrimitiveType.VOID into this method.
   */
  private String getDefaultExpression(DefaultValue defaultValue, JType type) {
    if (!(type instanceof JPrimitiveType)) {
      return "null";
    } else if (type == JPrimitiveType.BOOLEAN) {
      return String.valueOf(defaultValue.booleanValue());
    } else if (type == JPrimitiveType.BYTE) {
      return String.valueOf(defaultValue.byteValue());
    } else if (type == JPrimitiveType.CHAR) {
      return String.valueOf((int) defaultValue.charValue());
    } else if (type == JPrimitiveType.DOUBLE) {
      return String.valueOf(defaultValue.doubleValue());
    } else if (type == JPrimitiveType.FLOAT) {
      return String.valueOf(defaultValue.floatValue()) + "F";
    } else if (type == JPrimitiveType.INT) {
      return String.valueOf(defaultValue.intValue());
    } else if (type == JPrimitiveType.LONG) {
      return String.valueOf(defaultValue.longValue());
    } else if (type == JPrimitiveType.SHORT) {
      return String.valueOf(defaultValue.shortValue());
    } else if (type == JPrimitiveType.VOID) {
      assert false : "Should not pass VOID into this method";
    }
    assert false : "Should never reach here";
    return null;
  }

  private DefaultValue getDefaultValue(JClassType sourceType, JMethod method) {
    DefaultValue toReturn = method.getAnnotation(DefaultValue.class);
    if (toReturn == null) {
      toReturn = sourceType.getAnnotation(DefaultValue.class);
    }

    if (toReturn == null) {
      JClassType proxyType = sourceType.getOracle().findType(
          AsyncProxy.class.getName());
      toReturn = proxyType.getAnnotation(DefaultValue.class);
    }

    assert toReturn != null : "Could not find any DefaultValue instance";
    return toReturn;
  }

  private JClassType getParamType(TreeLogger logger, JClassType asyncProxyType,
      JClassType sourceType) throws UnableToCompleteException {
    JClassType paramType = null;
    for (JClassType intr : sourceType.getImplementedInterfaces()) {
      JParameterizedType isParameterized = intr.isParameterized();
      if (isParameterized == null) {
        continue;
      }
      if (isParameterized.getBaseType().equals(asyncProxyType)) {
        paramType = isParameterized.getTypeArgs()[0];
        break;
      }
    }

    if (paramType == null) {
      logger.log(TreeLogger.ERROR, "Unable to determine parameterization type.");
      throw new UnableToCompleteException();
    }
    return paramType;
  }

  private void validate(TreeLogger logger, JClassType sourceType,
      JClassType concreteType, JClassType paramType)
      throws UnableToCompleteException {
    // sourceType may not define any methods
    if (sourceType.getMethods().length > 0) {
      logger.log(TreeLogger.ERROR,
          "AsyncProxy subtypes may not define any additional methods");
      throw new UnableToCompleteException();
    }

    // Make sure that sourceType implements paramType to assignments work
    if (!sourceType.isAssignableTo(paramType)) {
      logger.log(TreeLogger.ERROR, "Expecting "
          + sourceType.getQualifiedSourceName() + " to implement "
          + paramType.getQualifiedSourceName());
      throw new UnableToCompleteException();
    }

    // Make sure that concreteType is assignable to paramType
    if (!concreteType.isAssignableTo(paramType)) {
      logger.log(TreeLogger.ERROR, "Expecting concrete type"
          + concreteType.getQualifiedSourceName() + " to implement "
          + paramType.getQualifiedSourceName());
      throw new UnableToCompleteException();
    }

    // Must be able to GWT.create()
    if (concreteType.isMemberType() && !concreteType.isStatic()) {
      logger.log(TreeLogger.ERROR, "Expecting concrete type "
          + concreteType.getQualifiedSourceName() + " to be static.");
      throw new UnableToCompleteException();
    }
  }

  /**
   * Given a method and a qualifier expression, construct an invocation of the
   * method using its default parameter names.
   */
  private void writeInvocation(SourceWriter sw, String qualifier, JMethod method) {
    sw.print(qualifier + "." + method.getName() + "(");

    for (Iterator i = Arrays.asList(method.getParameters()).iterator(); i.hasNext();) {
      JParameter param = i.next();
      sw.print(param.getName());
      if (i.hasNext()) {
        sw.print(", ");
      }
    }

    sw.println(");");
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy