Package com.google.gwt.dev.shell

Source Code of com.google.gwt.dev.shell.JsniInjector

/*
* Copyright 2007 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.dev.shell;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
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.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.jdt.CompilationUnitProviderWithAlternateSource;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.util.Jsni;
import com.google.gwt.dev.util.StringCopier;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

/**
* Adapts compilation units containing JSNI-accessible code by rewriting the
* source.
*/
public class JsniInjector {

  /**
   * A consolidated way to get all expected types and succeed or fail
   * atomically.
   */
  private class CoreTypes {
    static final String PKG_JSOBJECT = "com.google.gwt.core.client";
    static final String CLS_JSOBJECT = "JavaScriptObject";
    static final String PKG_STRING = "java.lang";
    static final String CLS_STRING = "String";

    public final JClassType javaLangString;

    public final JClassType javaScriptObject;

    public CoreTypes(TreeLogger logger) throws UnableToCompleteException {
      javaScriptObject = getCoreType(logger, PKG_JSOBJECT, CLS_JSOBJECT);
      javaLangString = getCoreType(logger, PKG_STRING, CLS_STRING);
    }

    private JClassType getCoreType(TreeLogger logger, String pkg, String cls)
        throws UnableToCompleteException {
      try {
        return oracle.getType(pkg, cls);
      } catch (NotFoundException e) {
        String msg = "Unable to find core type '" + pkg + "." + cls + "'";
        logger.log(TreeLogger.ERROR, msg, e);
        throw new UnableToCompleteException();
      }
    }
  }

  /**
   * A chunk of replacement text and where to put it.
   */
  private static class Replacement implements Comparable {
    public final int end;

    public final int start;

    public final char[] text;

    public Replacement(int start, int end, char[] text) {
      this.start = start;
      this.end = end;
      this.text = text;
    }

    public int compareTo(Object o) {
      Replacement other = (Replacement) o;
      if (start < other.start) {
        assert (end <= other.start) : "Overlapping changes not supported";
        return -1;
      } else if (start > other.start) {
        assert (start >= other.end) : "Overlapping changes not supported";
        return 1;
      } else {
        return 0;
      }
    }
  }

  private final TypeOracle oracle;

  private CoreTypes coreTypes;

  private final Map parsedJsByMethod = new IdentityHashMap();

  public JsniInjector(TypeOracle oracle) {
    this.oracle = oracle;
  }

  public CompilationUnitProvider inject(TreeLogger logger,
      CompilationUnitProvider cup) throws UnableToCompleteException {

    logger = logger.branch(TreeLogger.SPAM,
        "Checking for JavaScript native methods", null);

    // Make sure the core types exist.
    //
    if (coreTypes == null) {
      coreTypes = new CoreTypes(logger);
    }

    // Analyze the source and build a list of changes.
    //
    char[] source = cup.getSource();
    List changes = new ArrayList();
    rewriteCompilationUnit(logger, source, changes, cup);

    // Sort and apply the changes.
    //
    int n = changes.size();
    if (n > 0) {
      Replacement[] repls = (Replacement[]) changes.toArray(new Replacement[n]);
      Arrays.sort(repls);
      StringCopier copier = new StringCopier(source);
      for (int i = 0; i < n; ++i) {
        Replacement repl = repls[i];
        copier.commit(repl.text, repl.start, repl.end);
      }

      char[] results = copier.finish();

      return new CompilationUnitProviderWithAlternateSource(cup, results);
    } else {
      // No changes were made, so we return the original.
      //
      logger.log(TreeLogger.SPAM, "No JavaScript native methods were found",
          null);
      return cup;
    }
  }

  /**
   * Static initialization: generate one call to 'JavaScriptHost.createNative()'
   * for each native method, to define the JavaScript code that will be invoked
   * later.
   */
  private char[] genInitializerBlock(String file, char[] source,
      JMethod[] methods) {

    String escapedFile = Jsni.escapeQuotesAndSlashes(file);

    StringBuffer sb = new StringBuffer();
    sb.append(" static {");
    for (int i = 0; i < methods.length; ++i) {
      JMethod method = methods[i];

      JsBlock jsniBody = (JsBlock) parsedJsByMethod.get(method);
      if (jsniBody == null) {
        // Not a JSNI method.
        //
        continue;
      }

      JParameter[] params = method.getParameters();
      String paramNamesArray = getParamNamesArrayExpr(params);

      final String jsTry = "try ";
      final String jsCatch = " catch (e) {\\n"
          + "  __static[\\\"@"
          + Jsni.JAVASCRIPTHOST_NAME
          + "::exceptionCaught"
          + "(ILjava/lang/String;Ljava/lang/String;)\\\"]"
          + "((e && e.number) || 0, (e && e.name) || null , (e && e.message) || null);\\n"
          + "}\\n";

      // Surround the original JS body statements with a try/catch so that
      // we can map JavaScript exceptions back into Java.
      // Note that the method body itself will print curly braces, so we don't
      // need them around the try/catch.
      //
      String js = jsTry + Jsni.generateEscapedJavaScriptForHostedMode(jsniBody)
          + jsCatch;
      String jsniSig = Jsni.getJsniSignature(method);

      // figure out starting line number
      int bodyStart = method.getBodyStart();
      int line = Jsni.countNewlines(source, 0, bodyStart) + 1;

      sb.append("  " + Jsni.JAVASCRIPTHOST_NAME + ".createNative(\""
          + escapedFile + "\", " + line + ", " + "\"@" + jsniSig + "\", "
          + paramNamesArray + ", \"" + js + "\");");
    }
    sb.append("}");
    return sb.toString().toCharArray();
  }

  /**
   * Create a legal Java method call that will result in a JSNI invocation.
   *
   * @param method
   * @param expectedHeaderLines
   * @param expectedBodyLines
   * @return a String of the Java code to call a JSNI method, using
   *         JavaScriptHost.invokeNative*
   */
  private String genNonNativeVersionOfJsniMethod(JMethod method,
      int expectedHeaderLines, int expectedBodyLines) {
    StringBuffer sb = new StringBuffer();

    // Add extra lines at the start to match comments + declaration
    //
    for (int i = 0; i < expectedHeaderLines; ++i) {
      sb.append('\n');
    }

    String methodDecl = method.getReadableDeclaration(false, true, false,
        false, false);

    sb.append(methodDecl + " {");
    // wrap the call in a try-catch block
    sb.append("try {");

    // Write the Java call to the property invoke method, adding
    // downcasts where necessary.
    //
    JType returnType = method.getReturnType();
    boolean isJavaScriptObject = isJavaScriptObject(returnType);
    JPrimitiveType primType;
    if (isJavaScriptObject) {
      // Add a downcast from Handle to the originally-declared type.
      //
      String returnTypeName = returnType.getQualifiedSourceName();
      sb.append("return (" + returnTypeName + ")" + Jsni.JAVASCRIPTHOST_NAME
          + ".invokeNativeHandle");
    } else if (null != (primType = returnType.isPrimitive())) {
      // Primitives have special overloads.
      //
      char[] primTypeSuffix = primType.getSimpleSourceName().toCharArray();
      primTypeSuffix[0] = Character.toUpperCase(primTypeSuffix[0]);
      String invokeMethodName = "invokeNative" + String.valueOf(primTypeSuffix);
      if (primType != JPrimitiveType.VOID) {
        sb.append("return ");
      }
      sb.append(Jsni.JAVASCRIPTHOST_NAME);
      sb.append(".");
      sb.append(invokeMethodName);
    } else if (returnType == coreTypes.javaLangString) {
      sb.append("return ");
      sb.append(Jsni.JAVASCRIPTHOST_NAME);
      sb.append(".invokeNativeString");
    } else {
      // Some reference type.
      // We need to add a downcast to the originally-declared type.
      //
      String returnTypeName = returnType.getQualifiedSourceName();
      sb.append("return (");
      sb.append(returnTypeName);
      sb.append(")");
      sb.append(Jsni.JAVASCRIPTHOST_NAME);
      sb.append(".invokeNativeObject");
    }

    // Write the argument list for the invoke call.
    //
    sb.append("(\"@");
    String jsniSig = Jsni.getJsniSignature(method);
    sb.append(jsniSig);
    if (method.isStatic()) {
      sb.append("\", null, ");
    } else {
      sb.append("\", this, ");
    }

    if (isJavaScriptObject) {
      // Handle-oriented calls also need the return type as an argument.
      //
      String returnTypeName = returnType.getQualifiedSourceName();
      sb.append(returnTypeName);
      sb.append(".class, ");
    }

    // Build an array of classes that tells the invoker how to adapt the
    // incoming arguments for calling into JavaScript.
    //
    sb.append(Jsni.buildTypeList(method));
    sb.append(',');

    // Build an array containing the arguments based on the names of the
    // parameters.
    //
    sb.append(Jsni.buildArgList(method));
    sb.append(");");

    // Catch exceptions; rethrow if the exception is RTE or declared.
    sb.append("} catch (java.lang.Throwable __gwt_exception) {");
    sb.append("if (__gwt_exception instanceof java.lang.RuntimeException) throw (java.lang.RuntimeException) __gwt_exception;");
    JType[] throwTypes = method.getThrows();
    for (int i = 0; i < throwTypes.length; ++i) {
      String typeName = throwTypes[i].getQualifiedSourceName();
      sb.append("if (__gwt_exception instanceof " + typeName + ") throw (" + typeName
          + ") __gwt_exception;");
    }
    sb.append("throw new java.lang.RuntimeException(\"Undeclared checked exception thrown out of JavaScript; web mode behavior may differ.\", __gwt_exception);");
    sb.append("}");

    sb.append("}");

    // Add extra lines at the end to match JSNI body.
    //
    for (int i = 0; i < expectedBodyLines; ++i) {
      sb.append('\n');
    }

    return sb.toString();
  }

  private String getParamNamesArrayExpr(JParameter[] params) {
    StringBuffer sb = new StringBuffer();
    sb.append("new String[] {");
    for (int i = 0, n = params.length; i < n; ++i) {
      if (i > 0) {
        sb.append(", ");
      }

      JParameter param = params[i];
      sb.append('\"');
      sb.append(param.getName());
      sb.append('\"');
    }
    sb.append("}");
    return sb.toString();
  }

  private boolean isJavaScriptObject(JType type) {
    JClassType classType = type.isClass();
    if (classType == null) {
      return false;
    }

    if (classType.isAssignableTo(coreTypes.javaScriptObject)) {
      return true;
    } else {
      return false;
    }
  }

  private void rewriteCompilationUnit(TreeLogger logger, char[] source,
      List changes, CompilationUnitProvider cup)
      throws UnableToCompleteException {

    // Hit all the types in the compilation unit.
    //
    JClassType[] types = oracle.getTypesInCompilationUnit(cup);
    for (int i = 0; i < types.length; i++) {
      JClassType type = types[i];
      rewriteType(logger, source, changes, type);
    }
  }

  private void rewriteType(TreeLogger logger, char[] source, List changes,
      JClassType type) throws UnableToCompleteException {

    String loc = type.getCompilationUnit().getLocation();

    // Examine each method for JSNIness.
    //
    List patchedMethods = new ArrayList();
    JMethod[] methods = type.getMethods();
    for (int i = 0; i < methods.length; i++) {
      JMethod method = methods[i];
      if (method.isNative()) {
        Jsni.Interval interval = Jsni.findJsniSource(method);
        if (interval != null) {
          // The method itself needs to be replaced.
          //

          // Parse it.
          //
          String js = new String(source, interval.start, interval.end
              - interval.start);
          int startLine = Jsni.countNewlines(source, 0, interval.start) + 1;
          JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine);

          // Remember this as being a valid JSNI method.
          //
          parsedJsByMethod.put(method, body);

          // Replace the method.
          final int declStart = method.getDeclStart();
          final int declEnd = method.getDeclEnd();

          int expectedHeaderLines = Jsni.countNewlines(source, declStart,
              interval.start);
          int expectedBodyLines = Jsni.countNewlines(source, interval.start,
              interval.end);
          String newDecl = genNonNativeVersionOfJsniMethod(method,
              expectedHeaderLines, expectedBodyLines);

          final char[] newSource = newDecl.toCharArray();
          changes.add(new Replacement(declStart, declEnd, newSource));
          patchedMethods.add(method);
        } else {
          // report error
          String msg = "No JavaScript body found for native method '" + method
              + "' in type '" + type + "'";
          logger.log(TreeLogger.ERROR, msg, null);
          throw new UnableToCompleteException();
        }
      }
    }

    if (!patchedMethods.isEmpty()) {
      JMethod[] patched = new JMethod[patchedMethods.size()];
      patched = (JMethod[]) patchedMethods.toArray(patched);

      TreeLogger branch = logger.branch(TreeLogger.SPAM, "Patched methods in '"
          + type.getQualifiedSourceName() + "'", null);

      for (int i = 0; i < patched.length; i++) {
        branch.log(TreeLogger.SPAM, patched[i].getReadableDeclaration(), null);
      }

      // Insert an initializer block immediately after the opening brace of the
      // class.
      //
      char[] block = genInitializerBlock(loc, source, patched);

      // If this is a non-static inner class, actually put the initializer block
      // in the first enclosing static or top-level class instead.
      while (type.getEnclosingType() != null && !type.isStatic()) {
        type = type.getEnclosingType();
      }

      int bodyStart = type.getBodyStart();
      changes.add(new Replacement(bodyStart, bodyStart, block));
    }
  }
}
TOP

Related Classes of com.google.gwt.dev.shell.JsniInjector

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.