Package com.google.gwt.search.jsio.rebind

Source Code of com.google.gwt.search.jsio.rebind.JSWrapperGenerator

/*
* 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.search.jsio.rebind;

import com.google.gwt.search.jsio.client.Constructor;
import com.google.gwt.search.jsio.client.Global;
import com.google.gwt.search.jsio.client.JSWrapper;
import com.google.gwt.search.jsio.client.NoIdentity;
import com.google.gwt.search.jsio.client.ReadOnly;
import com.google.gwt.search.jsio.client.impl.JSONWrapperUtil;
import com.google.gwt.search.jsio.client.impl.MetaDataName;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
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.HasAnnotations;
import com.google.gwt.core.ext.typeinfo.HasMetaData;
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.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* The Generator that provides implementations of JSWrapper.
*/
public class JSWrapperGenerator extends Generator {

  /**
   * The name of the field within the backing object that refers back to the
   * JSWrapper object.
   */
  public static final String BACKREF = "__gwtPeer";

  /**
   * The name of the static field that contains the class's Extractor instance.
   */
  protected static final String EXTRACTOR = "__extractor";

  /**
   * Singleton instance of the FragmentGeneratorOracle for the system.
   */
  protected static final FragmentGeneratorOracle FRAGMENT_ORACLE = new FragmentGeneratorOracle();

  /**
   * The name of the backing object field.
   */
  protected static final String OBJ = "jsoPeer";

  /**
   * Allows the metadata warning to be turned off to prevent log spam.
   */
  private static final boolean SUPPRESS_WARNINGS = Boolean.getBoolean("JSWrapper.suppressMetaWarnings");

  /**
   * Extract an Annotation. If the requested Annotation does not exist on the
   * target node, the target's metadata will be examined for a tag based on the
   * requested Annotation's {@link MetaDataName} meta-annotation. If the target
   * has metadata which can be interpreted as the return type of the requested
   * Annotation's value method, a {@link Proxy} will be synthesized. The proxy
   * mode is only to support existing functionality, all new features should be
   * added via new annotations.
   *
   * @param <A> the desired type of Annotation
   * @param <M> the type of object to search
   * @param logger a logger
   * @param target the object to search
   * @param annotation the desired type of annotation
   * @return an instance of the requested annotation, or <code>null</code> if
   *         the annotation is not present and an instance of the annotation
   *         cannot be synthesized due to lack of metadata
   * @throws UnableToCompleteException if metadata with the correct tag exists
   *           but cannot be interpreted as the return type of the annotation's
   *           value method
   */
  @SuppressWarnings("deprecation")
  static <A extends Annotation, M extends HasAnnotations & HasMetaData> A hasTag(
      TreeLogger logger, M target, final Class<A> annotation)
      throws UnableToCompleteException {
    logger = logger.branch(TreeLogger.TRACE, "Looking for annotation/meta "
        + annotation.getName(), null);

    // If the item has an annotation of the requested type, return it
    A toReturn = target.getAnnotation(annotation);
    if (toReturn != null) {
      logger.log(TreeLogger.TRACE, "Found Annotation instance", null);
      return toReturn;
    }

    // Otherwise, fall back to HasMetaData
    MetaDataName metaDataName = annotation.getAnnotation(MetaDataName.class);
    if (metaDataName == null) {
      // Indicates that the requested annotation doesn't have legacy support
      logger.log(TreeLogger.TRACE, "No legacy support for this annotation",
          null);
      return null;
    }

    String tagName = metaDataName.value();
    boolean hasTag = false;
    for (String tag : target.getMetaDataTags()) {
      if (tagName.equals(tag)) {
        hasTag = true;
        if (!SUPPRESS_WARNINGS) {
          logger.log(TreeLogger.WARN, target
              + " uses deprecated metadata.  Replace with annotation "
              + annotation.getName(), null);
        }
        break;
      }
    }
    if (!hasTag) {
      logger.log(TreeLogger.TRACE, "No metadata with tag " + tagName, null);
      return null;
    }

    Object value;
    try {
      Method valueMethod = annotation.getMethod("value");
      Class<?> returnType = valueMethod.getReturnType();
      String[][] metaData = target.getMetaData(metaDataName.value());

      // Use the default value if there's no value in the metadata
      Object annotationDefaultValue;
      try {
        annotationDefaultValue = annotation.getMethod("value").getDefaultValue();
      } catch (NoSuchMethodException e) {
        annotationDefaultValue = null;
      }

      if (annotationDefaultValue == null && metaData.length == 0) {
        logger.log(TreeLogger.ERROR, "Metadata " + tagName
            + " must appear exactly once", null);
        throw new UnableToCompleteException();

      } else if (returnType.equals(String.class)) {
        if (metaData[0].length == 1) {
          logger.log(TreeLogger.TRACE, "Using value from metadata", null);
          value = metaData[0][0];

        } else if (annotationDefaultValue != null) {
          value = annotationDefaultValue;
        } else {
          logger.log(TreeLogger.ERROR, "Metadata " + tagName
              + " must have exactly one value", null);
          throw new UnableToCompleteException();
        }

      } else if (annotationDefaultValue != null) {
        logger.log(TreeLogger.TRACE, "Using annotation's default value", null);
        value = annotationDefaultValue;

      } else {
        logger.log(TreeLogger.ERROR, "Can't deal with return type "
            + returnType.getName(), null);
        throw new UnableToCompleteException();
      }
    } catch (NoSuchMethodException e) {
      // Just a tag annotation
      value = null;
    }

    final Object finalValue = value;
    Object proxy = Proxy.newProxyInstance(annotation.getClassLoader(),
        new Class<?>[] {annotation}, new InvocationHandler() {
          public Object invoke(Object proxy, Method method, Object[] args)
              throws Throwable {
            String name = method.getName();
            if (name.equals("annotationType")) {
              return annotation;
            } else if (name.equals("hashCode")) {
              return 0;
            } else if (name.equals("toString")) {
              return "Proxy for type " + annotation.getName() + " : "
                  + finalValue;
            } else if (name.equals("value")) {
              return finalValue;
            } else if (method.getDefaultValue() != null) {
              return method.getDefaultValue();
            }
            throw new RuntimeException("Don't know how to service " + name
                + " in type " + method.getDeclaringClass().getName());
          }
        });

    return annotation.cast(proxy);
  }

  /**
   * Get the erased type of the parameterization of the JSWrapper. Returns
   * <code>null</code> if JSWrapper is not in the class's inhertence
   * hierarchy.
   */
  private static JClassType findJSWrapperParameterization(TypeOracle oracle,
      JClassType extendsJSWrapper) {

    // Break recursion
    if (extendsJSWrapper == null) {
      return null;
    }

    // Are we looking at JSWrapper<T>; if so, return it's parameterization
    JClassType rawJSWrapper = oracle.findType(JSWrapper.class.getName()).getErasedType();
    JParameterizedType asParam = extendsJSWrapper.isParameterized();
    if (asParam != null && asParam.getErasedType().equals(rawJSWrapper)) {
      return asParam.getTypeArgs()[0].getErasedType();
    }

    // Try the supertype
    JClassType toReturn = findJSWrapperParameterization(oracle,
        extendsJSWrapper.getSuperclass());
    if (toReturn != null) {
      return toReturn;
    }

    // Not in the supertype hierarchy, search the interfaces
    for (JClassType implemented : extendsJSWrapper.getImplementedInterfaces()) {
      toReturn = findJSWrapperParameterization(oracle, implemented);
      if (toReturn != null) {
        return toReturn;
      }
    }

    // No type for you
    return null;
  }

  /**
   * Entry point into the Generator.
   */
  @Override
  public final String generate(TreeLogger logger, GeneratorContext context,
      java.lang.String typeName) throws UnableToCompleteException {

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

    // Get a reference to the type that the generator should implement
    final JClassType sourceType = typeOracle.findType(typeName);

    // Ensure that the requested type exists
    if (sourceType == null) {
      logger.log(TreeLogger.ERROR, "Could not find requested typeName", null);
      throw new UnableToCompleteException();
    }

    // Pick a name for the generated class to not conflict. Enclosing class
    // names must be preserved.
    final String generatedSimpleSourceName = "__"
        + sourceType.getName().replaceAll("\\.", "__") + "Impl";

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

    // Pull in source imports
    f.addImport(GWT.class.getName());
    f.addImport(JavaScriptObject.class.getName());
    // This is a cheat, but doesn't require excessive maintenance
    f.addImport("com.google.gwt.search.jsio.client.*");
    f.addImport("com.google.gwt.search.jsio.client.impl.*");

    // Either extend an abstract base class or implement the interface
    if (sourceType.isClass() != null) {
      f.setSuperclass(sourceType.getQualifiedSourceName());

    } else if (sourceType.isInterface() != null) {
      f.addImplementedInterface(sourceType.getQualifiedSourceName());

    } else {
      // Something is very wrong if this statement is reached
      logger.log(TreeLogger.ERROR,
          "Requested JClassType is neither a class nor an interface.", null);
      throw new UnableToCompleteException();
    }

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

    // If an implementation already exists, we don't need to do any work
    if (out != null) {
      // We really use a SourceWriter since it's convenient
      final SourceWriter sw = f.createSourceWriter(context, out);

      final Map<String, Task> propertyAccessors = TaskFactory.extractMethods(
          logger, typeOracle, sourceType, getPolicy());

      // Create the base context to be used during generation
      FragmentGeneratorContext fragmentContext = new FragmentGeneratorContext();
      fragmentContext.parentLogger = logger;
      fragmentContext.fragmentGeneratorOracle = FRAGMENT_ORACLE;
      fragmentContext.typeOracle = typeOracle;
      fragmentContext.sw = sw;
      fragmentContext.objRef = "this.@" + f.getCreatedClassName() + "::" + OBJ;
      fragmentContext.simpleTypeName = generatedSimpleSourceName;
      fragmentContext.qualifiedTypeName = f.getCreatedClassName();
      fragmentContext.returnType = sourceType;
      fragmentContext.creatorFixups = new HashSet<JClassType>();
      fragmentContext.readOnly = hasTag(logger, sourceType, ReadOnly.class) != null;
      fragmentContext.maintainIdentity = !(fragmentContext.readOnly || hasTag(
          logger, sourceType, NoIdentity.class) != null);
      fragmentContext.tasks = propertyAccessors.values();

      // Perform sanity checks on the extracted information
      validateType(propertyAccessors, fragmentContext);

      // Write all code that's not implementing methods
      writeBoilerplate(logger, fragmentContext);

      // Write the JSO initializer if required
      if (!fragmentContext.readOnly) {
        writeEmptyFieldInitializerMethod(logger, propertyAccessors,
            fragmentContext);
      }

      writeMethods(fragmentContext, propertyAccessors);
      writeFixups(logger, typeOracle, sw, fragmentContext.creatorFixups);

      // Write the generated code to disk
      sw.commit(logger);
    }

    // Return the name of the concrete class
    return f.getCreatedClassName();
  }

  /**
   * Specifies the first parameter of imported methods to pass to the imported
   * JavaScript function.
   */
  protected int getImportOffset() {
    return 0;
  }

  protected TaskFactory.Policy getPolicy() {
    return TaskFactory.WRAPPER_POLICY;
  }

  /**
   * Extracts the parameter from a setter method that contains the value to
   * store into the backing object.
   */
  protected JParameter getSetterParameter(JMethod setter) {
    return setter.getParameters()[0];
  }

  /**
   * Aggregate pre-write validation checks.
   */
  protected void validateType(Map<String, Task> propertyAccessors,
      FragmentGeneratorContext context) throws UnableToCompleteException {

    // Try to print as many errors as possible in a run before throwing the
    // exception
    boolean error = false;

    for (Task pair : propertyAccessors.values()) {
      error |= pair.validate(this, context);
    }

    if (error) {
      throw new UnableToCompleteException();
    }
  }

  /**
   * Writes common boilerplate code for all implementations.
   */
  protected void writeBoilerplate(TreeLogger logger,
      FragmentGeneratorContext context) throws UnableToCompleteException {

    SourceWriter sw = context.sw;
    TypeOracle typeOracle = context.typeOracle;
    JType returnType = context.returnType;

    // The backing object
    sw.print("private JavaScriptObject ");
    sw.print(OBJ);
    sw.println(";");

    // Build a constructor to initialize state.
    sw.print("public ");
    sw.print(context.simpleTypeName);
    sw.println("() {");
    sw.indent();
    sw.println("setJavaScriptObject(__nativeInit());");
    sw.outdent();
    sw.println("}");

    // Determine the correct expression to use to initialize the object
    JClassType asClass = context.returnType.isClassOrInterface();
    Constructor constructorAnnotation = hasTag(logger, asClass,
        Constructor.class);
    Global globalAnnotation = hasTag(logger, asClass, Global.class);
    String constructor;
    if (globalAnnotation != null) {
      constructor = globalAnnotation.value();
    } else if (constructorAnnotation != null) {
      constructor = "new " + constructorAnnotation.value() + "()";
    } else {
      boolean hasImports = false;
      for (Task t : context.tasks) {
        hasImports |= t.imported != null;

        if (hasImports) {
          break;
        }
      }

      if (!hasImports) {
        // Probably a JSON or pojo-style object
        constructor = "{}";
      } else {
        constructor = "null";
      }
    }

    JClassType parameterization = findJSWrapperParameterization(
        context.typeOracle, asClass);
    if (parameterization == null) {
      parameterization = asClass;
    }

    // Initialize native state of the wrapper
    sw.println("private native JavaScriptObject __nativeInit() /*-{");
    sw.indent();
    sw.print("return ");
    sw.print(constructor);
    sw.println(";");
    sw.outdent();
    sw.println("}-*/;");

    // Allow the backing JSONObject to be accessed
    sw.println("public JavaScriptObject getJavaScriptObject() {");
    sw.indent();
    sw.print("return ");
    sw.print(OBJ);
    sw.println(";");
    sw.outdent();
    sw.println("}");

    // Defer actual parsing to JSONWrapperUtil to take advantage of using
    // a common function implementation between generated classes.
    sw.println("public void setJSONData(String data)");
    sw.println("throws JSONWrapperException {");
    sw.indent();
    sw.println("setJavaScriptObject(JSONWrapperUtil.evaluate(data));");
    sw.outdent();
    sw.println("}");

    // Satisfies JSWrapper and allows generated implementations to
    // efficiently initialize new objects.
    // Method declaration
    sw.print("public " + parameterization.getParameterizedQualifiedSourceName()
        + " setJavaScriptObject(");
    sw.println("JavaScriptObject obj) {");
    sw.indent();

    sw.println("if (obj != null) {");
    sw.indent();
    for (Task t : context.tasks) {
      if (t.imported != null) {
        String fieldName = t.getFieldName(logger);
        sw.print("assert JSONWrapperUtil.hasField(obj, \"");
        sw.print(fieldName);
        sw.print("\") : \"Backing JSO missing imported function ");
        sw.print(fieldName);
        sw.println("\";");
      }
    }
    sw.outdent();
    sw.println("}");
    sw.println("return setJavaScriptObjectNative(obj);");
    sw.outdent();
    sw.println("}");

    // Method declaration
    sw.print("public native " + context.simpleTypeName
        + " setJavaScriptObjectNative(JavaScriptObject obj) /*-{");
    sw.indent();

    if (context.maintainIdentity) {
      // Delete the backing object's reference to the current wrapper
      sw.print("if (");
      sw.print(context.objRef);
      sw.println(") {");
      sw.indent();
      sw.print("delete ");
      sw.print(context.objRef);
      sw.print(".");
      sw.print(BACKREF);
      sw.println(";");
      sw.outdent();
      sw.println("}");
    }

    // If the incoming JSO is null or undefined, reset the JSWrapper
    sw.println("if (!obj) {");
    sw.indent();
    sw.print(context.objRef);
    sw.println(" = null;");
    sw.println("return this;");
    sw.outdent();
    sw.println("}");

    if (context.maintainIdentity) {
      // Verify that the incoming object doesn't already have a wrapper object.
      // If there is a backreference, throw an exception.
      sw.print("if (obj.");
      sw.print(BACKREF);
      sw.println(") {");
      sw.indent();
//      sw.println("@com.google.gwt.jsio.client.impl.JSONWrapperUtil::throwMultipleWrapperException()();");
      sw.println("@" + JSONWrapperUtil.class.getName() + "::throwMultipleWrapperException()();");
      sw.outdent();
      sw.println("}");
    }

    // Capture the object in the wrapper
    sw.print(context.objRef);
    sw.println(" = obj;");

    if (context.maintainIdentity) {
      // Assign the backreference from the wrapped object to the wrapper
      sw.print(context.objRef);
      sw.print(".");
      sw.print(BACKREF);
      sw.println(" = this;");
    }

    if (!context.readOnly) {
      // Initialize any other fields if the JSWrapper is read-write
      sw.print("this.@");
      sw.print(context.qualifiedTypeName);
      sw.print("::__initializeEmptyFields(Lcom/google/gwt/core/client/JavaScriptObject;)(");
      sw.print(context.objRef);
      sw.println(");");
    }

    sw.println("return this;");
    sw.outdent();
    sw.println("}-*/;");

    // If the generated class will be used with a JSList, we need an Extractor
    // implementation. We'll create an implementation per generated
    // class to ensure that if the class is used with a JSList, only one
    // instance of the Extractor will ever exist.
    sw.println("public final Extractor<"
        + parameterization.getParameterizedQualifiedSourceName()
        + "> getExtractor() {");
    sw.indent();
    sw.print("return ");
    sw.print(EXTRACTOR);
    sw.println(";");
    sw.outdent();
    sw.println("}");

    // The one instance of the Extractor
    sw.print("private final static Extractor ");
    sw.print(EXTRACTOR);
    sw.print(" = new Extractor() {");
    sw.indent();
    FragmentGeneratorContext subParams = new FragmentGeneratorContext(context);
    subParams.parameterName = "obj";
    FragmentGenerator fragmentGenerator = context.fragmentGeneratorOracle.findFragmentGenerator(
        logger, typeOracle, returnType);

    sw.println("public native Object fromJS(JavaScriptObject obj) /*-{");
    sw.indent();
    sw.print("return ");
    fragmentGenerator.fromJS(subParams);
    sw.println(";");
    sw.outdent();
    sw.println("}-*/;");

    // Write the Extracor's toJS function and close the Extractor
    // implementation.
    sw.println("public native JavaScriptObject toJS(Object obj) /*-{");
    sw.indent();
    sw.print("return ");
    fragmentGenerator.toJS(subParams);
    sw.println(";");
    sw.outdent();
    sw.println("}-*/;");

    // Finish the class
    sw.outdent();
    sw.println("};");
  }

  protected void writeConstructor(FragmentGeneratorContext context,
      JMethod constructor) throws UnableToCompleteException {

    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing constructor " + constructor.getName(), null);
    SourceWriter sw = context.sw;

    JParameter[] parameters = constructor.getParameters();
    if (parameters == null) {
      parameters = new JParameter[0];
    }

    // Method declaration
    sw.print("public native ");
    sw.print(constructor.getReturnType().getQualifiedSourceName());
    sw.print(" ");
    sw.print(constructor.getName());
    sw.print("(");
    for (int i = 0; i < parameters.length; i++) {
      JType returnType = parameters[i].getType();
      JParameterizedType pType = returnType.isParameterized();

      if (pType != null) {
        sw.print(pType.getRawType().getQualifiedSourceName());
      } else {
        sw.print(returnType.getQualifiedSourceName());
      }

      sw.print(" ");
      sw.print(parameters[i].getName());

      if (i < parameters.length - 1) {
        sw.print(", ");
      }
    }
    sw.print(")");
    sw.println(" /*-{");
    sw.indent();

    // The return type of the function we're importing.
    JType returnType = constructor.getReturnType();

    sw.print("var jsReturn = ");

    // If the imported method is acting as an invocation of a JavaScript
    // constructor, use the new Foo() syntax, otherwise treat is an an
    // invocation on a field on the underlying JSO.
    sw.print("new ");
    Constructor constructorAnnotation = hasTag(logger, constructor,
        Constructor.class);
    sw.print(constructorAnnotation.value());

    // Write the invocation's parameter list
    sw.print("(");
    for (int i = getImportOffset(); i < parameters.length; i++) {
      // Create a sub-context to generate the wrap/unwrap logic
      JType subType = parameters[i].getType();
      FragmentGeneratorContext subParams = new FragmentGeneratorContext(context);
      subParams.returnType = subType;
      subParams.parameterName = parameters[i].getName();

      FragmentGenerator fragmentGenerator = context.fragmentGeneratorOracle.findFragmentGenerator(
          logger, context.typeOracle, subType);
      if (fragmentGenerator == null) {
        logger.log(TreeLogger.ERROR, "No fragment generator for "
            + returnType.getQualifiedSourceName(), null);
        throw new UnableToCompleteException();
      }

      fragmentGenerator.toJS(subParams);

      if (i < parameters.length - 1) {
        sw.print(", ");
      }
    }
    sw.println(");");

    FragmentGeneratorContext subContext = new FragmentGeneratorContext(context);
    subContext.returnType = returnType;
    subContext.parameterName = "jsReturn";

//    sw.println("return this.@com.google.gwt.jsio.client.JSWrapper::setJavaScriptObject(Lcom/google/gwt/core/client/JavaScriptObject;)(jsReturn);");
    sw.println("return this.@" + JSWrapper.class.getName() + "::setJavaScriptObject(Lcom/google/gwt/core/client/JavaScriptObject;)(jsReturn);");
    sw.outdent();
    sw.println("}-*/;");
  }

  /**
   * Provides a method to encapsulate empty field initialization.
   */
  protected void writeEmptyFieldInitializerMethod(final TreeLogger logger,
      final Map<String, Task> propertyAccessors,
      final FragmentGeneratorContext context) throws UnableToCompleteException {
    SourceWriter sw = context.sw;
    JClassType returnType = context.returnType.isClassOrInterface();

    sw.println("private native void __initializeEmptyFields(JavaScriptObject jso) /*-{");
    sw.indent();

    FragmentGeneratorContext subContext = new FragmentGeneratorContext(context);
    subContext.parameterName = "jso";
    writeEmptyFieldInitializers(subContext);

    subContext.tasks = TaskFactory.extractMethods(logger,
        subContext.typeOracle, returnType, TaskFactory.EXPORTER_POLICY).values();
    writeMethodBindings(subContext);

    sw.outdent();
    sw.println("}-*/;");
  }

  /**
   * Ensures that no field referenced by generated logic will ever return an
   * undefined value. This allows every subsequent getFoo() call to simply
   * return the field value, without having to check it for an undefined value.
   */
  protected void writeEmptyFieldInitializers(FragmentGeneratorContext context)
      throws UnableToCompleteException {
    SourceWriter sw = context.sw;
    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing field initializers", null);

    for (Task task : context.tasks) {
      final String fieldName = task.getFieldName(logger);

      // If there is no getter, we don't need to worry about an empty
      // field initializer.
      if (task.getter == null) {
        continue;
      }

      final JType returnType = task.getter.getReturnType();

      FragmentGenerator fragmentGenerator = FRAGMENT_ORACLE.findFragmentGenerator(
          logger, context.typeOracle, returnType);

      sw.print("if (!");
      sw.print(context.parameterName);
      sw.print(".hasOwnProperty('");
      sw.print(fieldName);
      sw.println("')) {");
      sw.indent();

      sw.print(context.parameterName);
      sw.print(".");
      sw.print(fieldName);
      sw.print(" = ");
      sw.print(fragmentGenerator.defaultValue(context.typeOracle, returnType));
      sw.println(";");

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

  protected void writeFixups(TreeLogger logger, TypeOracle typeOracle,
      SourceWriter sw, Set<JClassType> creatorFixups)
      throws UnableToCompleteException {
    for (JClassType asClass : creatorFixups) {
      // If the type is parameterized, we want to replace it with the raw type
      // so that no angle-brackets are used.
      JParameterizedType pType = asClass.isParameterized();
      if (pType != null) {
        asClass = pType.getRawType();
      }

      sw.print("private static ");
      sw.print(asClass.getQualifiedSourceName());
      sw.print(" __create__");
      sw.print(asClass.getQualifiedSourceName().replaceAll("\\.", "_"));
      sw.println("() {");
      sw.indent();
      sw.print("return (");
      sw.print(asClass.getQualifiedSourceName());
      sw.print(")GWT.create(");
      sw.print(asClass.getQualifiedSourceName());
      sw.println(".class);");
      sw.outdent();
      sw.println("}");
    }
  }

  protected void writeGetter(FragmentGeneratorContext context, JMethod getter)
      throws UnableToCompleteException {

    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing getter " + getter.getName(), null);
    TypeOracle typeOracle = context.typeOracle;
    SourceWriter sw = context.sw;

    final JType returnType = getter.getReturnType();

    FragmentGenerator fragmentGenerator = FRAGMENT_ORACLE.findFragmentGenerator(
        logger, typeOracle, context.returnType);

    sw.print("public native ");
    sw.print(returnType.getQualifiedSourceName());
    sw.print(" ");
    sw.print(getter.getName());
    sw.print("(");

    // This is only important when working with the flyweight subclass
    JParameter[] params = getter.getParameters();
    for (int i = 0; i < params.length; i++) {
      sw.print(params[i].getType().getQualifiedSourceName());
      sw.print(" ");
      sw.print(params[i].getName());

      if (i < params.length - 1) {
        sw.print(", ");
      }
    }
    sw.print(")");
    sw.println(" /*-{");
    sw.indent();

    sw.print("return ");
    fragmentGenerator.fromJS(context);
    sw.println(";");

    sw.outdent();
    sw.println("}-*/;");
  }

  protected void writeImported(FragmentGeneratorContext context,
      JMethod imported) throws UnableToCompleteException {

    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing import " + imported.getName(), null);
    SourceWriter sw = context.sw;

    // Simplifies the rest of writeImported
    JParameter[] parameters = imported.getParameters();
    if (parameters == null) {
      parameters = new JParameter[0];
    }

    // Method declaration
    sw.print("public native ");
    sw.print(imported.getReturnType().getQualifiedSourceName());
    sw.print(" ");
    sw.print(imported.getName());
    sw.print("(");
    for (int i = 0; i < parameters.length; i++) {
      JType returnType = parameters[i].getType();
      JParameterizedType pType = returnType.isParameterized();

      if (pType != null) {
        sw.print(pType.getRawType().getQualifiedSourceName());
      } else {
        sw.print(returnType.getQualifiedSourceName());
      }

      sw.print(" ");
      sw.print(parameters[i].getName());

      if (i < parameters.length - 1) {
        sw.print(", ");
      }
    }
    sw.print(")");
    sw.println(" /*-{");
    sw.indent();

    // The return type of the function we're importing.
    final JType returnType = imported.getReturnType();

    // Don't bother recording a return value for void invocations.
    if (!JPrimitiveType.VOID.equals(returnType.isPrimitive())) {
      sw.print("var jsReturn = ");
    }

    sw.print(context.objRef);
    sw.print(".");
    sw.print(context.fieldName);

    // Write the invocation's parameter list
    sw.print("(");
    for (int i = getImportOffset(); i < parameters.length; i++) {
      // Create a sub-context to generate the wrap/unwrap logic
      JType subType = parameters[i].getType();
      FragmentGeneratorContext subParams = new FragmentGeneratorContext(context);
      subParams.returnType = subType;
      subParams.parameterName = parameters[i].getName();

      FragmentGenerator fragmentGenerator = context.fragmentGeneratorOracle.findFragmentGenerator(
          logger, context.typeOracle, subType);
      if (fragmentGenerator == null) {
        logger.log(TreeLogger.ERROR, "No fragment generator for "
            + returnType.getQualifiedSourceName(), null);
        throw new UnableToCompleteException();
      }

      fragmentGenerator.toJS(subParams);

      if (i < parameters.length - 1) {
        sw.print(", ");
      }
    }
    sw.println(");");

    // Wrap the return type in the correct Java type. Void returns are ignored
    if (!JPrimitiveType.VOID.equals(returnType.isPrimitive())) {
      FragmentGeneratorContext subContext = new FragmentGeneratorContext(
          context);
      subContext.returnType = returnType;
      subContext.parameterName = "jsReturn";

      FragmentGenerator fragmentGenerator = FRAGMENT_ORACLE.findFragmentGenerator(
          logger, context.typeOracle, returnType);

      sw.print("return ");

      fragmentGenerator.fromJS(subContext);
      sw.println(";");
    }

    sw.outdent();
    sw.println("}-*/;");
  }

  protected void writeMethodBindings(FragmentGeneratorContext context)
      throws UnableToCompleteException {
    SourceWriter sw = context.sw;
    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing method bindings initializers", null);

    for (Task task : context.tasks) {
      final String fieldName = task.getFieldName(logger);

      if (task.exported != null) {
        sw.print(context.parameterName);
        sw.print(".");
        sw.print(fieldName);
        sw.print(" = ");

        FragmentGeneratorContext subContext = new FragmentGeneratorContext(
            context);
        subContext.parameterName = "this." + BACKREF;

        JSFunctionFragmentGenerator.writeFunctionForMethod(subContext,
            task.exported);
        sw.println(";");
      }
    }
  }

  /**
   * Write the field, getter, and setter for the properties we know about. Also
   * write BusObjectImpl methods for Map-style access.
   */
  protected void writeMethods(FragmentGeneratorContext context,
      Map<String, Task> propertyAccessors) throws UnableToCompleteException {
    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing methods", null);

    for (Task task : propertyAccessors.values()) {
      context.fieldName = task.getFieldName(logger);
      writeSingleTask(context, task);
    }
  }

  protected void writeSetter(FragmentGeneratorContext context, JMethod setter)
      throws UnableToCompleteException {

    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing setter " + setter.getName(), null);
    TypeOracle typeOracle = context.typeOracle;
    SourceWriter sw = context.sw;

    JType parameterType = context.returnType;

    FragmentGenerator fragmentGenerator = FRAGMENT_ORACLE.findFragmentGenerator(
        logger, typeOracle, context.returnType);
    if (fragmentGenerator == null) {
      throw new UnableToCompleteException();
    }

    // Ensure that there will be no angle-bracket in the output
    JParameterizedType pType = parameterType.isParameterized();
    if (pType != null) {
      parameterType = pType.getRawType();
    }

    sw.print("public native void ");
    sw.print(setter.getName());
    sw.print("(");
    // This is only important when working with the flyweight subclass
    JParameter[] params = setter.getParameters();
    for (int i = 0; i < params.length; i++) {
      sw.print(params[i].getType().getQualifiedSourceName());
      sw.print(" ");
      sw.print(params[i].getName());

      if (i < params.length - 1) {
        sw.print(", ");
      }
    }

    sw.println(") /*-{");
    sw.indent();
    sw.print(context.objRef);
    sw.print(".");
    sw.print(context.fieldName);
    sw.print(" = ");
    fragmentGenerator.toJS(context);
    sw.println(";");
    sw.outdent();
    sw.println("}-*/;");
  }

  protected void writeSingleTask(FragmentGeneratorContext context, Task task)
      throws UnableToCompleteException {
    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing Task " + task.getFieldName(context.parentLogger), null);

    context = new FragmentGeneratorContext(context);
    context.parentLogger = logger;

    logger.log(TreeLogger.DEBUG, "Implementing task " + context.fieldName, null);

    if (task.getter != null) {
      context.returnType = task.getter.getReturnType();
      context.parameterName = context.objRef + "." + context.fieldName;
      writeGetter(context, task.getter);
    }

    if (task.imported != null) {
      context.returnType = task.imported.getReturnType();
      writeImported(context, task.imported);
    }

    if (task.setter != null) {
      if (context.readOnly) {
        logger.log(TreeLogger.ERROR,
            "Unable to write property setter on read-only wrapper.", null);
        throw new UnableToCompleteException();
      }

      JParameter parameter = getSetterParameter(task.setter);
      context.returnType = parameter.getType();
      // What the user called the parameter
      context.parameterName = parameter.getName();
      writeSetter(context, task.setter);
    }

    if (task.constructor != null) {
      context.returnType = task.constructor.getReturnType();
      context.parameterName = "this.";
      context.objRef = "this";
      writeConstructor(context, task.constructor);
    }
  }
}
TOP

Related Classes of com.google.gwt.search.jsio.rebind.JSWrapperGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.