Package com.google.gwt.jsio.rebind

Source Code of com.google.gwt.jsio.rebind.JSFunctionFragmentGenerator

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

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.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.jsio.client.Exported;
import com.google.gwt.jsio.client.JSFunction;
import com.google.gwt.user.rebind.SourceWriter;

/**
* Exports a Java function as a JavaScript function object. If the JSFunction's
* exported method is static and has parameter and return types that require no
* wrapping or unwrapping can be used as-is, otherwise a linkage function will
* be created to perform the necessary type conversions.
*/
class JSFunctionFragmentGenerator extends FragmentGenerator {
  /**
   * Write out the JavaScript wrapper around a Java method.
   */
  static void writeFunctionForMethod(FragmentGeneratorContext context, JMethod m)
      throws UnableToCompleteException {
    context.parentLogger.branch(TreeLogger.DEBUG, "Writing function() for "
        + m.getName(), null);

    if (isIdentityFunction(context, m)) {
      writeIdentityInvocation(context, m);
    } else {
      writeLinkageInvocation(context, m);
    }
  }

  /**
   * Determines if the exported method can be used as-is.
   */
  private static boolean isIdentityFunction(FragmentGeneratorContext context,
      JMethod m) throws UnableToCompleteException {
    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Determining identity status of " + m.getName(), null);
    FragmentGeneratorOracle fgo = context.fragmentGeneratorOracle;

    boolean identityOnly = m.isStatic();
    JParameter[] parameters = m.getParameters();

    identityOnly &= context.fragmentGeneratorOracle.findFragmentGenerator(
        logger, context.typeOracle, m.getReturnType()).isIdentity();

    for (int i = 0; i < parameters.length && identityOnly; i++) {
      FragmentGenerator fragmentGenerator = fgo.findFragmentGenerator(logger,
          context.typeOracle, parameters[i].getType());
      identityOnly &= fragmentGenerator.isIdentity();
    }

    return identityOnly;
  }

  /**
   * Simply prints a JSNI reference to the exported function.
   */
  private static void writeIdentityInvocation(FragmentGeneratorContext context,
      JMethod m) throws UnableToCompleteException {
    SourceWriter sw = context.sw;
    JParameter[] parameters = m.getParameters();

    sw.print("@");
    sw.print(m.getEnclosingType().getQualifiedSourceName());
    sw.print("::");
    sw.print(m.getName());
    sw.print("(");

    // Argument list for the Java invocation
    for (int i = 0; i < parameters.length; i++) {
      sw.print(parameters[i].getType().getJNISignature());
    }

    sw.print(")");
  }

  /**
   * Writes a linkage function object that will invoke the exported function.
   */
  private static void writeLinkageInvocation(FragmentGeneratorContext context,
      JMethod m) throws UnableToCompleteException {
    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing function() for " + m.getName(), null);

    SourceWriter sw = context.sw;
    JParameter[] parameters = m.getParameters();
    FragmentGeneratorOracle fgo = context.fragmentGeneratorOracle;
    FragmentGenerator returnFragmentGenerator = fgo.findFragmentGenerator(
        logger, context.typeOracle, m.getReturnType());

    sw.print("function(");
    for (int i = 0; i < parameters.length; i++) {
      sw.print("arg");
      sw.print(String.valueOf(i));
      if (i < parameters.length - 1) {
        sw.print(", ");
      }
    }
    sw.println(") {");
    sw.indent();

    if (returnFragmentGenerator.isIdentity()) {
      sw.print("return ");
    } else {
      sw.print("var javaReturn = ");
    }

    // Don't need to reference the instance on a static method
    if (!m.isStatic()) {
      sw.print(context.parameterName);
      sw.print(".");
    }

    sw.print("@");
    sw.print(m.getEnclosingType().getQualifiedSourceName());
    sw.print("::");
    sw.print(m.getName());
    sw.print("(");

    // Argument list for the Java invocation
    for (int i = 0; i < parameters.length; i++) {
      sw.print(parameters[i].getType().getJNISignature());
    }

    sw.println(")(");
    // Indent the parameters, each on its own like to improve readability
    sw.indent();
    sw.indent();

    for (int i = 0; i < parameters.length; i++) {
      // Create a sub-context to generate the wrap/unwrap logic
      JType returnType = parameters[i].getType();
      FragmentGeneratorContext subParams = new FragmentGeneratorContext(context);
      subParams.returnType = returnType;
      subParams.parameterName = "arg" + i;

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

      fragmentGenerator.fromJS(subParams);

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

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

    if (!returnFragmentGenerator.isIdentity()) {
      FragmentGeneratorContext returnContext = new FragmentGeneratorContext(
          context);
      returnContext.parameterName = "javaReturn";
      returnContext.returnType = m.getReturnType();
      sw.print("return ");
      returnFragmentGenerator.toJS(returnContext);
      sw.println(";");
    }

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

  @Override
  boolean accepts(TypeOracle typeOracle, JType type) {
    JClassType asClass = type.isClassOrInterface();

    if (asClass == null) {
      return false;
    }

    return isAssignable(typeOracle, asClass, JSFunction.class);
  }

  @Override
  String defaultValue(TypeOracle typeOracle, JType type)
      throws UnableToCompleteException {
    return "null";
  }

  @Override
  void fromJS(FragmentGeneratorContext context)
      throws UnableToCompleteException {
    context.parentLogger.branch(TreeLogger.ERROR,
        "JavaScript functions may not be imported via JSFunction.", null);

    throw new UnableToCompleteException();
  }

  @Override
  void toJS(FragmentGeneratorContext context) throws UnableToCompleteException {
    TreeLogger logger = context.parentLogger.branch(TreeLogger.DEBUG,
        "Writing function() wrapper for JSFunction", null);

    SourceWriter sw = context.sw;
    TypeOracle typeOracle = context.typeOracle;
    JClassType functionClass = context.returnType.isClassOrInterface();

    if (functionClass.equals(typeOracle.findType(JSFunction.class.getName()))) {
      logger.log(TreeLogger.ERROR, "You must use a subinterface of JSFunction"
          + " so that the generator can extract a method signature.", null);
      throw new UnableToCompleteException();
    }

    // This is to support the JSFunction having the same lifetime as the
    // JSFunction object without having to use GWT.create on every JSFunction
    // object as that would discourage anonymous classes.

    sw.print("(");
    sw.print(context.parameterName);
    sw.print(".@com.google.gwt.jsio.client.JSFunction::exportedFunction || (");
    sw.print(context.parameterName);
    sw.print(".@com.google.gwt.jsio.client.JSFunction::exportedFunction = ");
    writeFunctionForMethod(context, findExportedMethod(logger, functionClass));
    sw.print("))");
  }

  @Override
  void writeExtractorJSNIReference(FragmentGeneratorContext context)
      throws UnableToCompleteException {
    context.parentLogger.branch(TreeLogger.ERROR,
        "JSFunctions should never need extraction", null);
    throw new UnableToCompleteException();
  }

  /**
   * Find the to-be-exported method within a class.
   */
  private JMethod findExportedMethod(TreeLogger logger, JClassType clazz)
      throws UnableToCompleteException {

    JMethod[] methods = clazz.getMethods();
    if (methods.length == 0) {
      logger.log(TreeLogger.ERROR, "The JSFunction interface did not "
          + "declare any functions.", null);
      throw new UnableToCompleteException();
    } else if (methods.length == 1) {
      return methods[0];
    }

    try {
      JMethod toReturn = null;
      for (JMethod method : methods) {
        if (JSWrapperGenerator.hasTag(logger, method, Exported.class) != null) {
          if (toReturn == null) {
            toReturn = method;
          } else {
            // Can't declare two methods with export annotations
            throw new UnableToCompleteException();
          }
        }
      }
      if (toReturn != null) {
        return toReturn;
      } else {
        // Didn't find any methods with an export annotation
        throw new UnableToCompleteException();
      }
    } catch (UnableToCompleteException e) {
      logger.log(TreeLogger.ERROR, "JSFunctions with multiple methods must "
          + " specify exactly one method with an " + Exported.class.getName()
          + " annotation.", null);
      throw e;
    }
  }
}
TOP

Related Classes of com.google.gwt.jsio.rebind.JSFunctionFragmentGenerator

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.