Package org.timepedia.exporter.rebind

Source Code of org.timepedia.exporter.rebind.ClassExporter

package org.timepedia.exporter.rebind;

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.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

/**
* This class performs the generation of export methods for a single class
*
* @author Ray Cromwell <ray@timepedia.org>
*/
public class ClassExporter {

  private TreeLogger logger;

  private GeneratorContext ctx;

  private ExportableTypeOracle xTypeOracle;

  private SourceWriter sw;

  private ArrayList<JExportableClassType> exported;

  private HashSet<String> visited;

  private static final String ARG_PREFIX = "arg";

  public ClassExporter(TreeLogger logger, GeneratorContext ctx) {
    this(logger, ctx, new HashSet<String>());
  }

  public ClassExporter(TreeLogger logger, GeneratorContext ctx,
      HashSet<String> visited) {
    this.logger = logger;
    this.ctx = ctx;
    // a type oracle that can answer questions about whether types are
    // exportable
    xTypeOracle = new ExportableTypeOracle(ctx.getTypeOracle(), logger);
    this.visited = visited;
    exported = new ArrayList<JExportableClassType>();
  }

  /**
   * This method generates an implementation of the specified interface that
   * accepts a JavaScriptObject in its constructor containing a callback. It
   * then delegates the single-method of the interface to this callback. <p/>
   * For example: <p/> <p/> / ** * @gwt.exportClosure * / public interface
   * ClickListener implements Exportable { public void onClick(Sender s); } <p/>
   * generates a delegation class <p/> public class ClickListenerImpl implements
   * Exporter, ClickListener { <p/> private JavaScriptObject jso; public
   * ClickListenerClosure(JavaScriptObject jso) { this.jso = jso; } <p/> public
   * void onClick(Sender s) { invoke(jso, ExporterBase.wrap(s)); } <p/> public
   * native void invoke(JavaScriptObject closure, JavascriptObject s) {
   * closure(s); } <p/> }
   */
  public void exportClosure(JExportableClassType requestedType)
      throws UnableToCompleteException {

    if (requestedType == null) {
      logger.log(TreeLogger.ERROR,
          "Type '" + requestedType.getQualifiedSourceName()
              + "' does not implement Exportable", null);
      throw new UnableToCompleteException();
    }

    // get the name of the Java class implementing Exporter
    String genName = requestedType.getExporterImplementationName();

    sw.indent();

    // export constructor
    sw.println("private " + ExportableTypeOracle.JSO_CLASS + " jso;");
    sw.println();
    sw.println("public " + genName + "() { export(); }");

    sw.println(
        "public " + genName + "(" + ExportableTypeOracle.JSO_CLASS + " jso) {");
    sw.indent();
    sw.println("this.jso = jso;");
    if (requestedType.isStructuralType()) {
      sw.println("___importStructuralType();");
    }
    sw.outdent();
    sw.println("}");
    sw.println();

    // export static factory method
    sw.println("public static " + genName + " makeClosure("
        + ExportableTypeOracle.JSO_CLASS + " closure) {");
    sw.indent();
    sw.println("return new " + genName + "(closure);");
    sw.outdent();
    sw.println("}");
    sw.println();

    JExportableMethod[] methods = requestedType.getExportableMethods();

    if (methods.length != 1) {
      logger.log(TreeLogger.ERROR,
          "Interface " + requestedType.getQualifiedSourceName()
              + " has more than one "
              + "declared method. @gwt.exportClosure only currently works for "
              + "single method interfaces.", null);
      throw new UnableToCompleteException();
    }

    JExportableMethod method = methods[0];
    JExportableType retType = method.getExportableReturnType();
    if (retType == null) {
      logger.log(TreeLogger.ERROR,
          "Return type of method " + method + " is not exportable.", null);
      throw new UnableToCompleteException();
    }

    if (retType.needsExport() && !exported
        .contains(retType.getQualifiedSourceName())) {
      if (exportDependentClass(retType.getQualifiedSourceName())) {
        exported.add((JExportableClassType) retType);
      }
    }

    exportDependentParams(method);

    boolean isVoid = retType.getQualifiedSourceName().equals("void");
    boolean noParams = method.getExportableParameters().length == 0;
    sw.print(
        "public " + method.getExportableReturnType().getQualifiedSourceName());

    sw.print(" " + method.getName() + "(");
    declareParameters(method, -1, true);
    sw.println(") {");
    sw.indent();
    sw.print((isVoid ? "" : "return ") + "invoke(jso" + (noParams ? "" : ","));
    declareJavaPassedValues(method, false);
    sw.println(");");
    sw.outdent();
    sw.println("}");
    sw.println();
    sw.print("public native " + (isVoid ? "void"
        : method.getExportableReturnType().getQualifiedSourceName()));
    sw.print(" invoke(" + ExportableTypeOracle.JSO_CLASS + " closure");
    if (method.getExportableParameters().length > 0) {
      sw.print(", ");
    }

    declareParameters(method, -1, true);
    sw.println(") /*-{");
    sw.indent();
    sw.print((!isVoid ? "var result= " : "") + "closure(");
    declareJavaPassedValues(method, true);
    sw.println(");");
    boolean isArray = retType instanceof JExportableArrayType;
    if (retType.needsExport() && !isVoid && !isArray) {
      sw.println("if(result != null && result != undefined) "
          + "result=result.instance;");
      sw.println("else if(result == undefined) result=null;");
    }
    if (!isVoid) {
      sw.println("return result;");
    }
    sw.outdent();
    sw.println("}-*/;");
    sw.println();
    sw.outdent();
  }

  /**
   * This method generates an implementation class that implements Exporter and
   * returns the fully qualified name of the class.
   */
  public String exportClass(String requestedClass, boolean export)
      throws UnableToCompleteException {

    // JExportableClassType is a wrapper around JClassType
    // which provides only the information and logic neccessary for
    // the generator
    JExportableClassType requestedType = xTypeOracle
        .findExportableClassType(requestedClass);

    if (requestedType == null) {
      logger.log(TreeLogger.ERROR,
          "Type '" + requestedClass + "' does not implement Exportable", null);
      throw new UnableToCompleteException();
    }

    // add this so we don't try to recursively reexport ourselves later
    exported.add(requestedType);
    visited.add(requestedType.getQualifiedSourceName());

    // get the name of the Java class implementing Exporter
    String genName = requestedType.getExporterImplementationName();

    // get the package name of the Exporter implementation
    String packageName = requestedType.getPackageName();

    // get a fully qualified reference to the Exporter implementation
    String qualName = requestedType.getQualifiedExporterImplementationName();

    boolean isClosure = xTypeOracle.isClosure(requestedClass);
    String superClass = xTypeOracle.isStructuralType(requestedType.getType())
        ? requestedClass : null;

    // try to construct a sourcewriter for the qualified name
    if (isClosure) {
      sw = getSourceWriter(logger, ctx, packageName, genName, superClass,
          "Exporter", requestedType.getQualifiedSourceName());
    } else {
      sw = getSourceWriter(logger, ctx, packageName, genName, superClass,
          "Exporter");
    }
    if (sw == null) {
      return qualName; // null, already generated
    }

    if (export && xTypeOracle.isExportAll(requestedClass)) {
      exportAll(genName);
    } else if (export) {
      if (isClosure) {
        exportClosure(requestedType);
      } else if (requestedType.isStructuralType()) {
        exportStructuralTypeConstructor(genName, requestedType);
      }

      if (requestedType.isStructuralType()) {
        exportStructuralTypeImporter(requestedType);
        exportStructuralTypeMatchMethod(requestedType);
      }

      sw.indent();

      if (!isClosure) {
        sw.println("public " + genName + "() { export(); }");
      }
      // here we define a JSNI Javascript method called export0()
      sw.println("public native void export0() /*-{");
      sw.indent();

      // if not defined, we create a Javascript package hierarchy
      // foo.bar.baz to hold the Javascript bridge
      declarePackages(requestedType);


      // export Javascript constructors
      exportConstructor(requestedType);

      // export all static fields
      exportFields(requestedType);

      // export all exportable methods
      exportMethods(requestedType);

      // add map from TypeName to JS constructor in ExporterUtil
      registerTypeMap(requestedType);

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

      sw.println();

      // the Javascript constructors refer to static factory methods
      // on the Exporter implementation, referenced via JSNI
      // We generate them here
      if (requestedType.isInstantiable()) {
        exportStaticFactoryConstructors(requestedType);
      }

      // if this class is a structural type, generate overrides for every
      // structure type field
      if (requestedType.isStructuralType()) {
        exportStructuralTypeFields(requestedType);
      }

      // finally, generate the Exporter.export() method
      // which invokes recursively, via GWT.create(),
      // every other Exportable type we encountered in the exported ArrayList
      // ending with a call to export0()

      genExportMethod(requestedType, exported);
      sw.outdent();
    } else {
      sw.indent();
      sw.println("public void export() {}");
      sw.outdent();
    }

    sw.commit(logger);

    // return the name of the generated Exporter implementation class
    return qualName;
  }

  private void exportStructuralTypeMatchMethod(
      JExportableClassType requestedType) throws UnableToCompleteException {
    sw.println("public static native boolean ___match(JavaScriptObject jso) /*-{");
    sw.indent();
    sw.print("return ");
    for (JStructuralTypeField field : requestedType.getStructuralTypeFields()) {

      JExportableType eType = field.getExportableType();
      if (eType == null) {
        logger.log(TreeLogger.ERROR,
            "Structural type field " + field.getMethodName() + " for class "
                + requestedType.getQualifiedSourceName()
                + " is not exportable.");
        throw new UnableToCompleteException();
      }
      if (eType instanceof JExportableClassType) {
        JExportableClassType cType = (JExportableClassType) field
            .getExportableType();
        if (cType.needsExport() && cType.isStructuralType()) {
          sw.print("(jso." + field.getName() + " && @"
              + ((JExportableClassType) eType)
              .getQualifiedExporterImplementationName()
              + "::___match(Lcom/google/gwt/core/client/JavaScriptObject;)(jso."
              + field.getName() + ") &&");
        } else if (cType.needsExport()) {
          sw.print("(jso." + field.getName() + " && jso." + field.getName()
              + ".__gwt__instance) && ");
        } else if(!cType.needsExport()) {
          sw.print(
            "typeof(jso." + field.getName() + ") == '" + eType.getJsTypeOf()
                + "' && ");
        }
      } else if (eType instanceof JExportablePrimitiveType) {
        sw.print(
            "typeof(jso." + field.getName() + ") == '" + eType.getJsTypeOf()
                + "' && ");
      }
    }
    sw.println("true;");
    sw.outdent();
    sw.println("}-*/;");
  }

  private void exportStructuralTypeImporter(
      JExportableClassType requestedType) {
    sw.println("public void ___importStructuralType() {");
    sw.indent();
    for (JStructuralTypeField field : requestedType.getStructuralTypeFields()) {
      sw.println("super." + field.getMethodName() + "((" + field.getFieldType()
          + ")org.timepedia.exporter.client.ExporterUtil.getStructuralField"
          + field.getFieldLowestType() + "(jso, \"" + field.getName()
          + "\"));");
    }
    sw.outdent();
    sw.println("}");
  }

  private void exportStructuralTypeConstructor(String genName,
      JExportableClassType requestedType) {
    // export constructor
    sw.println("private " + ExportableTypeOracle.JSO_CLASS + " jso;");
    sw.println();

    sw.println(
        "public " + genName + "(" + ExportableTypeOracle.JSO_CLASS + " jso) {");
    sw.indent();
    sw.println("this.jso = jso;");
    if (requestedType.isStructuralType()) {
      sw.println("___importStructuralType();");
    }
    sw.outdent();
    sw.println("}");
    sw.println();
  }

  private void exportStructuralTypeFields(JExportableClassType requestedType) {
    for (JStructuralTypeField field : requestedType.getStructuralTypeFields()) {
      exportStructuralTypeField(field);
    }
  }

  private void exportStructuralTypeField(JStructuralTypeField field) {
    sw.println("public " + field.JavaDeclaration() + "{");
    sw.indent();
    if (field.isVoidReturn()) {
      sw.println("super." + field.getMethodName() + "(arg);");
      sw.println(
          "org.timepedia.exporter.client.ExporterUtil.setStructuralField("
              + "jso, \"" + field.getName() + "\", arg);");
    } else {
      sw.println(field.getReturnType() + " x = super." + field.getMethodName()
          + "(arg);");
      sw.println(
          "org.timepedia.exporter.client.ExporterUtil.setStructuralField("
              + "jso, '" + field.getName() + "', arg);");
      sw.println("return x;");
    }

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

  private void exportAll(String genName) {
    sw.println("public " + genName + "() { export(); } ");
    sw.println("public void export() { ");

    for (JClassType type : xTypeOracle.findAllExportableTypes()) {
      sw.indent();
      sw.println("GWT.create(" + type.getQualifiedSourceName() + ".class);");
      sw.outdent();
    }
    sw.println("}");
  }

  private void registerTypeMap(JExportableClassType requestedType) {
    sw.print(
        "@org.timepedia.exporter.client.ExporterUtil::addTypeMap(Ljava/lang/Class;Lcom/google/gwt/core/client/JavaScriptObject;)("
            +
//                        "Ljavg/lang/String;" +  
//                        "Lcom/google/gwt/core/client/JavaScriptObject;)(" +
            "@" + requestedType.getQualifiedSourceName() + "::class, $wnd."
            + requestedType.getJSQualifiedExportName() + ");");
  }

  /**
   * Exports a static factory method corresponding to each exportable
   * constructor of the class
   */
  private void exportStaticFactoryConstructors(
      JExportableClassType requestedType) {

    JExportableConstructor[] constructors = requestedType
        .getExportableConstructors();

    for (JExportableConstructor constructor : constructors) {
      exportStaticFactoryConstructor(constructor);
    }
  }

  /**
   * Exports all exportable methods of a class
   */
  private void exportMethods(JExportableClassType requestedType)
      throws UnableToCompleteException {

    HashMap<String, DispatchTable> dispatchMap = buildDispatchTableMap(
        requestedType, false);

    HashMap<String, DispatchTable> staticDispatchMap = buildDispatchTableMap(
        requestedType, true);
    HashSet<String> exported = new HashSet<String>();
    HashSet<String> staticExported = new HashSet<String>();
   
    for (JExportableMethod method : requestedType.getExportableMethods()) {
      if (method.isStatic() ? !staticExported.contains(method.getName())
           :!exported.contains(method.getName())) {
        exportMethod(method,
            method.isStatic() ? staticDispatchMap : dispatchMap);
        if(method.isStatic()) {
          exported.add(method.getName());
        }
        else {
          staticExported.add(method.getName());
        }
      }
    }
    if (!xTypeOracle
        .isClosure(requestedType.getType().getQualifiedSourceName())) {
      if (DispatchTable.isAnyOverridden(dispatchMap)) {
        registerDispatchMap(requestedType, dispatchMap, false);
      }
      if (DispatchTable.isAnyOverridden(staticDispatchMap)) {
        registerDispatchMap(requestedType, staticDispatchMap, true);
      }
    }
  }

  private void registerDispatchMap(JExportableClassType requestedType,
      HashMap<String, DispatchTable> dispatchMap, boolean isStatic) {
    sw.print("@org.timepedia.exporter.client.ExporterUtil::registerDispatchMap("
        + "Ljava/lang/Class;Lcom/google/gwt/core/client/JavaScriptObject;Z)(@"
        + requestedType.getQualifiedSourceName() + "::class,"
        + DispatchTable.toJSON(dispatchMap) + ", " + isStatic + ");");
  }

  private HashMap<String, DispatchTable> buildDispatchTableMap(
      JExportableClassType requestedType, boolean staticDispatch)
      throws UnableToCompleteException {
    HashMap<String, DispatchTable> dispMap
        = new HashMap<String, DispatchTable>();
    for (JExportableMethod meth : requestedType.getExportableMethods()) {
      if (staticDispatch && !meth.isStatic() || !staticDispatch && meth
          .isStatic()) {
        continue;
      }
      DispatchTable dt = dispMap.get(meth.getUnqualifiedExportName());
      if (dt == null) {
        dt = new DispatchTable();
        dispMap.put(meth.getUnqualifiedExportName(), dt);
      }
      if (!dt.addSignature(meth, meth.getExportableParameters())) {
        logger.log(TreeLogger.ERROR,
            "Ambiguous method signature " + meth.getJSNIReference()
                + " would conflict in JS with another method");
        throw new UnableToCompleteException();
      }
    }
    return dispMap;
  }

  /**
   * Exports a Javascript constructor as $wnd.packageName.classname =
   * function(args) { if(arg0 is GWT type) { this.instance = arg0; } else
   * this.instance = invoke static factory method with args }
   */
  private void exportConstructor(JExportableClassType requestedType)
      throws UnableToCompleteException {
    sw.println("if($wnd." + requestedType.getJSQualifiedExportName() + ") {");
    sw.println(
        "var pkg = $wnd." + requestedType.getJSQualifiedExportName() + ";");
    sw.println("}");

    // constructor.getJSQualifiedExportName() returns fully qualified package
    // + exported class name
    sw.print("$wnd." + requestedType.getJSQualifiedExportName()
        + " = $entry(function(");

    // for every parameter 0..n of the constructor, we generate
    // arg0, ..., argn
//        declareJSParameters(constructor);
    sw.println(") {");
    sw.indent();
    // check if this is being used to wrap GWT types
    // e.g. code is calling constructor as
    // new $wnd.package.className(opaqueGWTobject)
    // if so, we store the opaque reference in this.instance
    sw.println("if(arguments.length == 1 && (arguments[0] != null && "
        + "arguments[0].@java.lang.Object::getClass()() == " + "@"
        + requestedType.getQualifiedSourceName() + "::class)) {");
    sw.indent();

    sw.println(" this.__gwt_instance = arguments[0];");
    sw.outdent();
    sw.println("}");

    JExportableConstructor[] constructors = requestedType
        .getExportableConstructors();

    // used to hold arity of constructors that have been generated
    HashMap<Integer, JExportableConstructor> arity
        = new HashMap<Integer, JExportableConstructor>();

    for (JExportableConstructor constructor : constructors) {
      int numArguments = constructor.getExportableParameters().length;
      JExportableConstructor conflicting = arity.get(numArguments);
      if (conflicting != null) {
        logger.log(TreeLogger.ERROR,
            "Constructor " + conflicting + " with " + numArguments + " "
                + "arguments conflicts with " + constructor + "."
                + "Two constructors may not have identical numbers of "
                + "arguments.", null);
        throw new UnableToCompleteException();
      }
      arity.put(numArguments, constructor);
      sw.println("else if(arguments.length == " + numArguments + ") {");
      sw.indent();

      // else someone is calling the constructor normally
      // we generate a JSNI call to the matching static factory method
      // and store it in this.instance
      sw.print("this.__gwt_instance = @"
          + constructor.getStaticFactoryJSNIReference() + "(");

      // pass arguments[0], ..., arguments[n] to the JSNI call
      declareJSPassedValues(constructor, true);
      sw.println(");");
      sw.println(
          "@org.timepedia.exporter.client.ExporterUtil::setWrapper(Ljava/lang/Object;Lcom/google/gwt/core/client/JavaScriptObject;)(this.__gwt_instance, this);");
      sw.outdent();
      sw.println("}");
    }

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

    JExportableClassType superClass = requestedType
        .getExportableSuperClassType();

    if (superClass != null && superClass.needsExport() && !exported
        .contains(superClass)) {
      if (exportDependentClass(superClass.getQualifiedSourceName())) {
        ;
      }
      exported.add(superClass);
    }
    // we assign the prototype of the class to underscore so we can use it
    // later to define a bunch of methods
    sw.print("var _=$wnd." + requestedType.getJSQualifiedExportName()
        + ".prototype = ");
    sw.println(superClass == null ? "new Object();"
        : "new $wnd." + superClass.getJSQualifiedExportName() + "();");

    // restore inner class namespace
    sw.println("if(pkg) {");
    sw.println(
        "for(p in pkg) { $wnd." + requestedType.getJSQualifiedExportName()
            + "[p]=pkg[p]; }");
    sw.println("}");
  }

  /**
   * We create a static factory method public static [typeName] ___create(args)
   * that just invokes the real constructor with the args
   */
  private void exportStaticFactoryConstructor(
      JExportableConstructor constructor) {
    JExportableClassType consType = (JExportableClassType) constructor
        .getExportableReturnType();
    String typeName = consType.getQualifiedSourceName();
    sw.print("public static " + typeName + " "
        + constructor.getStaticFactoryMethodName() + "(");
    declareParameters(constructor, -1);
    sw.println(") {");
    sw.indent();
    sw.print("return new " + typeName + "(");
    declareJavaPassedValues(constructor, false);
    sw.println(");");
    sw.outdent();
    sw.println("}");
  }

  private void debugJSPassedValues(JExportableMethod method) {
    JExportableParameter params[] = method.getExportableParameters();
    for (int i = 0; i < params.length; i++) {
      sw.print(
          "$wnd.alert(\"\"+" + params[i].getExportParameterValue(ARG_PREFIX + i)
              + ");");
    }
  }

  /**
   * Generate comma separated list of argnames, arg0, ..., arg_n where n =
   * number of parameters of method
   *
   * @param useArgumentsArray use arguments[n] instead of argn
   */
  private void declareJSPassedValues(JExportableMethod method,
      boolean useArgumentsArray) {
    JExportableParameter params[] = method.getExportableParameters();
    for (int i = 0; i < params.length; i++) {
      sw.print(params[i].getExportParameterValue(
          useArgumentsArray ? "arguments[" + i + "]" : ARG_PREFIX + i));
      if (i < params.length - 1) {
        sw.print(", ");
      }
    }
  }

  /**
   * Generate comma separated list of argnames, arg0, ..., arg_n where n =
   * number of parameters of method
   *
   * @param wrap whether to wrap the passed value with ExporterBase::wrap
   */
  private void declareJavaPassedValues(JExportableMethod method, boolean wrap) {
    JExportableParameter params[] = method.getExportableParameters();
    for (int i = 0; i < params.length; i++) {
      JExportableType eType = params[i].getExportableType();
      boolean needExport = eType != null && eType.needsExport();
      boolean isArray = eType instanceof JExportableArrayType;
      String arrayType = needExport && isArray ? ("L"
          + ((JExportableArrayType) eType).getJSNIReference()) : "";

      if (wrap && needExport) {
        sw.print("@org.timepedia.exporter.client.ExporterUtil::wrap("
            + (isArray ? arrayType
            : "Lorg/timepedia/exporter/client/Exportable;") + ")(");
      }
      sw.print(ARG_PREFIX + i);
      if (wrap && needExport) {
        sw.print(")");
      }
      if (i < params.length - 1) {
        sw.print(", ");
      }
    }
  }

  /**
   * Generate comma separated list of argnames, arg0, ..., arg_n where n =
   * number of parameters of constructor
   *
   * @param includeTypes true if arg names should have declared types
   */
  private void declareParameters(JExportableMethod method, int arity,
      boolean includeTypes) {
    JExportableParameter params[] = method.getExportableParameters();
    int numParams = includeTypes || arity < 0 ? params.length : arity;
    for (int i = 0; i < numParams;
        i++) {
      sw.print(
          (includeTypes ? params[i].getTypeName() : "") + " " + ARG_PREFIX + i);
      if (i < numParams - 1) {
        sw.print(", ");
      }
    }
  }

  /**
   * declare java typed Java method parameters
   */
  private void declareParameters(JExportableMethod method, int arity) {
    declareParameters(method, arity, true);
  }

  /**
   * declare type-less Javascript method parameters
   */
  private void declareJSParameters(JExportableMethod method, int arity) {
    declareParameters(method, arity, false);
  }

  /**
   * For each exportable field Foo, we generate the following Javascript:
   * $wnd.package.className.Foo = JSNI Reference to Foo
   */
  private void exportFields(JExportableClassType requestedType)
      throws UnableToCompleteException {
    for (JExportableField field : requestedType.getExportableFields()) {

      sw.print("$wnd." + field.getJSQualifiedExportName() + " = ");
      sw.println("@" + field.getJSNIReference() + ";");
    }
  }

  /**
   * Export a method If the return type of the method is Exportable, we invoke
   * ClassExporter recursively on this type <p/> For static methods, the
   * Javascript looks like this: $wnd.package.className.staticMethod =
   * function(args) { // body } <p/> for regular methods, it looks like <p/>
   * _.methodName = function(args) { //body } <p/> where _ is previously
   * assigned to $wnd.package.className.prototype <p/> For methods returning
   * Exportable types, the body looks like <p/> return new
   * $wnd.package.className(this.instance.@methodNameJSNI(args)); <p/> which
   * wraps the returned type, otherwise it looks like this <p/> return
   * this.instance.@methodNameJSNI(args); <p/> for primitives, String,
   * subclasses of Number, and JavaScriptObject
   */
  private void exportMethod(JExportableMethod method,
      HashMap<String, DispatchTable> dispatchMap)
      throws UnableToCompleteException {
    JExportableType retType = method.getExportableReturnType();

    if (retType == null) {
      logger.log(TreeLogger.ERROR,
          "Return type of method " + method.toString() + " is not Exportable.",
          null);
      throw new UnableToCompleteException();
    }

    int arity = method.getExportableParameters().length;
    String name = method.getUnqualifiedExportName();
    String key = name + "_" + arity;

//    JExportableMethod conflicting = method.isStatic() ? staticVisited.get(key)
//        : visited.get(key);
//
//    if (conflicting != null) {
//      logger.log(TreeLogger.ERROR,
//          "Method " + method + " having " + arity + " arguments conflicts with "
//              + conflicting + ". "
//              + "Two exportable methods cannot have the same number of arguments. "
//              + "Use @gwt.export <newName> on one of the methods to disambiguate.",
//          null);
//      throw new UnableToCompleteException();
//    } else {
//      if (method.isStatic()) {
//        staticVisited.put(key, method);
//      } else {
//        visited.put(key, method);
//      }
//    }

    // return type needs to be exported if it is not a primitive
    // String,Number,JSO, etc and it hasn't already been exported
    // we need to export it because we need it to wrap the returned value
    if (retType != null && retType.needsExport() && !exported
        .contains(retType)) {
      if (exportDependentClass(retType.getQualifiedSourceName())) {
        ;
      }
      exported.add((JExportableClassType) retType);
    }

    exportDependentParams(method);
    String returnTypeCast = retType != null ? retType.getHostedModeJsTypeCast()
        : null;
    if (method.isStatic()) {
      sw.print("$wnd." + method.getJSQualifiedExportName() + " = ");
    } else {
      sw.print("_." + method.getUnqualifiedExportName() + "= ");
    }
    if (returnTypeCast != null) {
      // GWT 2.0 hosted mode $entry requires deboxing return valus for JS
      sw.print(
          "@org.timepedia.exporter.client.ExporterHostedModeUtil::deboxHostedMode(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)("
              + returnTypeCast + ",");
    }
    sw.print("$entry(function(");
    DispatchTable dt = dispatchMap.get(method.getUnqualifiedExportName());
    declareJSParameters(method, dt.isOverloaded() ? dt.maxArity() : -1);
    sw.print(") { ");
    boolean isVoid = retType.getQualifiedSourceName().equals("void");
//    debugJSPassedValues(method);
    if (!dt.isOverloaded()) {
      sw.print((isVoid ? "" : "var x=")
          + (method.isStatic() ? "@" : "this.__gwt_instance.@")
          + method.getJSNIReference() + "(");

      declareJSPassedValues(method, false);

      // end method call
      sw.print(");");
    } else {
      sw.print((isVoid ? ""
          : "var x=@org.timepedia.exporter.client.ExporterUtil::getDispatch("
              + "Ljava/lang/Class;Ljava/lang/String;"
              + "Lcom/google/gwt/core/client/JsArray;Z)" + "(@" + method
              .getEnclosingExportType().getQualifiedSourceName()) + "::class,'"
          + method.getUnqualifiedExportName() + "', arguments,"
          + method.isStatic() + ").apply("
          + (method.isStatic() ? "null" : "this.__gwt_instance")
          + ", arguments");
      sw.print(");");
    }
    if (dt.isOverloaded() || !retType.needsExport()) {
      sw.print(isVoid ? "" : "return (");
    } else {
      boolean isArray = retType instanceof JExportableArrayType;
      String arrayType = isArray ? ((JExportableArrayType) retType)
          .getJSNIReference() : "";

      sw.print((isVoid ? "" : "return ")
          + "@org.timepedia.exporter.client.ExporterUtil::wrap("
          + (isArray ? "Lorg/timepedia/exporter/client/Exportable;"
          : "Lorg/timepedia/exporter/client/Exportable;") + ")("

      );
    }

    // end wrap() or non-exportable return case call
    if (!isVoid) {
      sw.println("x);");
    }
    sw.print("})");
    if (returnTypeCast != null) {
      sw.print(")");
    }
    sw.println(";");
  }

  private void exportDependentParams(JExportableMethod method)
      throws UnableToCompleteException {
    // for convenience to the developer, let's export any exportable
    // parameters
    for (JExportableParameter param : method.getExportableParameters()) {
      JExportableType eType = param.getExportableType();
      if (eType != null && eType.needsExport() && !exported.contains(eType)) {
        if (exportDependentClass(eType.getQualifiedSourceName())) {
          exported.add((JExportableClassType) eType);
        }
      }
    }
  }

  private boolean exportDependentClass(String qualifiedSourceName)
      throws UnableToCompleteException {

    if (visited.contains(qualifiedSourceName)) {
      return false;
    }
    JExportableType xType = xTypeOracle.findExportableType(qualifiedSourceName);
    if (xType instanceof JExportableArrayType) {

      JExportableType xcompType = ((JExportableArrayType) xType)
          .getComponentType();
      if (xcompType instanceof JExportablePrimitiveType) {
        return false;
      } else {
        return exportDependentClass(xcompType.getQualifiedSourceName());
      }
    }

    visited.add(qualifiedSourceName);
    ClassExporter exporter = new ClassExporter(logger, ctx, visited);
    exporter.exportClass(qualifiedSourceName, true);
    return true;
  }

  /**
   * For each subpackage of sub1.sub2.sub3... we create a chain of objects
   * $wnd.sub1.sub2.sub3
   */
  private void declarePackages(JExportableClassType requestedClassType) {
    String requestedPackageName = requestedClassType.getJSExportPackage();
    String enclosingClasses[] = requestedClassType.getEnclosingClasses();
    String enclosing = "";
    for (String enclosingClass : enclosingClasses) {
      enclosing += enclosingClass + ".";
    }
    enclosing = enclosing.length() > 0 ? enclosing
        .substring(0, enclosing.length() - 1) : enclosing;
    sw.println(
        "@org.timepedia.exporter.client.ExporterUtil::declarePackage(Ljava/lang/String;Ljava/lang/String;)('"
            + requestedPackageName + "','" + enclosing + "');");
  }

  /**
   * Generate the main export method <p/> <p/> We generate a method that looks
   * like: <p/> public void export() { Exporter export1 =
   * (Exporter)GWT.create(ExportableDependency1.class) export1.export(); <p/>
   * Exporter export2 = (Exporter)GWT.create(ExportableDependency2.class)
   * export2.export(); <p/> ... export0(); }
   *
   * @param exported a list of other types that we depend on to be exported
   */
  private void genExportMethod(JExportableClassType requestedType,
      ArrayList<JExportableClassType> exported) {
    sw.println("private static boolean exported;");

    sw.println("public void export() { ");
    sw.indent();
    sw.println("if(!exported) {");
    sw.indent();
    sw.println("exported=true;");

    // first, export our dependencies
    int exprCount = 0;
    for (JExportableClassType classType : exported) {
      if (requestedType.getQualifiedSourceName()
          .equals(classType.getQualifiedSourceName())
          || classType instanceof JExportableArrayType) {
        continue;
      }
      String qualName = classType.getQualifiedSourceName();

      String var = "export" + exprCount++;
      sw.println(ExportableTypeOracle.EXPORTER_CLASS + " " + var + " = ("
          + ExportableTypeOracle.EXPORTER_CLASS + ") GWT.create(" + qualName
          + ".class);");
    }

    // now export our class
    sw.println("export0();");
    sw.outdent();
    sw.println("}");
    sw.outdent();
    sw.println("}");
  }

  /**
   * Get SourceWriter for following class and preamble package packageName;
   * import com.google.gwt.core.client.GWT; import org.timepedia.exporter.client.Exporter;
   * public class className implements interfaceName (usually Exporter) { <p/>
   * }
   *
   * @param interfaceNames vararg list of interfaces
   */
  protected SourceWriter getSourceWriter(TreeLogger logger,
      GeneratorContext context, String packageName, String className,
      String superClass, String... interfaceNames) {
    PrintWriter printWriter = context.tryCreate(logger, packageName, className);
    if (printWriter == null) {
      return null;
    }
    ClassSourceFileComposerFactory composerFactory
        = new ClassSourceFileComposerFactory(packageName, className);
    composerFactory.addImport("com.google.gwt.core.client.GWT");
    composerFactory.addImport("com.google.gwt.core.client.JavaScriptObject");
   
    if (superClass != null) {
      composerFactory.setSuperclass(superClass);
    }
    for (String interfaceName : interfaceNames) {
      composerFactory.addImplementedInterface(interfaceName);
    }

    composerFactory.addImport("org.timepedia.exporter.client.Exporter");
    return composerFactory.createSourceWriter(context, printWriter);
  }
}
TOP

Related Classes of org.timepedia.exporter.rebind.ClassExporter

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.