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

com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs Maven / Gradle / Ivy

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

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JMethod;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Replaces calls to
 * {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback)}
 * and
 * {@link com.google.gwt.core.client.GWT#runAsync(Class, com.google.gwt.core.client.RunAsyncCallback)}
 * by calls to a fragment loader. Additionally, replaces access to
 * {@link com.google.gwt.core.client.prefetch.RunAsyncCode#runAsyncCode(Class)}
 * by an equivalent call using an integer rather than a class literal.
 */
public class ReplaceRunAsyncs {
  /**
   * Information about the replacement of one runAsync call by a call to a
   * generated code-loading method.
   */
  public static class RunAsyncReplacement implements Serializable {
    private final JMethod enclosingMethod;
    private final JMethod loadMethod;
    private final String name;
    private final int number;

    RunAsyncReplacement(int number, JMethod enclosingMethod,
        JMethod loadMethod, String name) {
      this.number = number;
      this.enclosingMethod = enclosingMethod;
      this.loadMethod = loadMethod;
      this.name = name;
    }

    /**
     * Can be null if the enclosing method cannot be designated with a JSNI
     * reference.
     */
    public JMethod getEnclosingMethod() {
      return enclosingMethod;
    }

    /**
     * The load method to request loading the code for this method.
     */
    public JMethod getLoadMethod() {
      return loadMethod;
    }

    /**
     * Return the name of this runAsync, which is specified by a class literal
     * in the two-argument version of runAsync(). Returns null if
     * there is no name for the call.
     */
    public String getName() {
      return name;
    }

    /**
     * The index of this runAsync, numbered from 1 to n.
     */
    public int getNumber() {
      return number;
    }

    @Override
    public String toString() {
      return "#" + number + ": " + enclosingMethod.toString();
    }
  }

  private class AsyncCreateVisitor extends JModVisitor {
    private JMethod currentMethod;
    private int entryCount = 1;

    @Override
    public void endVisit(JMethodCall x, Context ctx) {
      JMethod method = x.getTarget();
      if (isRunAsyncMethod(method)) {
        JExpression asyncCallback;
        String name;
        switch (x.getArgs().size()) {
          case 1:
            name = null;
            asyncCallback = x.getArgs().get(0);
            break;
          case 2:
            name = nameFromClassLiteral((JClassLiteral) x.getArgs().get(0));
            asyncCallback = x.getArgs().get(1);
            break;
          default:
            throw new InternalCompilerException(
                "runAsync call found with neither 1 nor 2 arguments: " + x);
        }

        int entryNumber = entryCount++;
        JClassType loader = getFragmentLoader(entryNumber);
        JMethod loadMethod = getRunAsyncMethod(loader);
        assert loadMethod != null;
        runAsyncReplacements.put(entryNumber, new RunAsyncReplacement(
            entryNumber, currentMethod, loadMethod, name));

        JMethodCall methodCall = new JMethodCall(x.getSourceInfo(), null,
            loadMethod);
        methodCall.addArg(asyncCallback);

        tightenCallbackType(entryNumber, asyncCallback.getType());

        program.addEntryMethod(getOnLoadMethod(loader), entryNumber);

        ctx.replaceMe(methodCall);
      }
    }

    @Override
    public boolean visit(JMethod x, Context ctx) {
      currentMethod = x;
      return true;
    }

    private boolean isRunAsyncMethod(JMethod method) {
      /*
       * The method is overloaded, so check the enclosing type plus the name.
       */
      return method.getEnclosingType() == program.getIndexedType("GWT")
          && method.getName().equals("runAsync");
    }

    /**
     * Tighten some types and method calls immediately, in case the optimizer is
     * not run. Without a little bit of tightening, code splitting will be
     * completely ineffective.
     * 
     * Note that {@link FragmentLoaderCreator} can't simply generate the tighter
     * types to begin with, because when it runs, it doesn't know which runAsync
     * call it is generating a loader for.
     * 
     * This method can be deleted if {@link FragmentLoaderCreator} is
     * eliminated.
     */
    private void tightenCallbackType(int entryNumber, JType callbackType) {
      JClassType loaderClass = getFragmentLoader(entryNumber);

      /*
       * Before: class AsyncLoader3 { static void runAsync(RunAsyncCallback cb)
       * { ... } }
       * 
       * After: class AsyncLoader3 { static void runAsync(RunAsyncCallback$3 cb)
       * { ... } }
       */
      JMethod loadMethod = getRunAsyncMethod(loaderClass);
      loadMethod.getParams().get(0).setType(callbackType);

      /*
       * Before: class AsyncLoader3__Callback { RunAsyncCallback callback; }
       * 
       * After: class AsyncLoader3__Callback { RunAsyncCallback$3 callback; }
       */
      JClassType callbackListType = getFragmentLoaderCallbackList(entryNumber);
      JField callbackField = getField(callbackListType, "callback");

      /*
       * The method AsyncLoaderNNN.runCallbacks has a lot of calls to onSuccess
       * methods where the target is onSuccess in the RunAsyncCallback
       * interface. Use MethodCallTightener to tighten those calls down to
       * target the onSuccess method of a specific callback class.
       */
      callbackField.setType(callbackType);
      JMethod runCallbacksMethod = getMethod(loaderClass,
          FragmentLoaderCreator.RUN_CALLBACKS);
      MethodCallTightener.exec(program, runCallbacksMethod);
    }
  }
  private class ReplaceRunAsyncResources extends JModVisitor {
    private Map> replacementsByName;

    public ReplaceRunAsyncResources() {
      replacementsByName = new HashMap>();
      for (RunAsyncReplacement replacement : runAsyncReplacements.values()) {
        String name = replacement.getName();
        if (name != null) {
          List list = replacementsByName.get(name);
          if (list == null) {
            list = new ArrayList();
            replacementsByName.put(name, list);
          }
          list.add(replacement);
        }
      }
    }

    @Override
    public void endVisit(JMethodCall x, Context ctx) {
      if (x.getTarget() == program.getIndexedMethod("RunAsyncCode.runAsyncCode")) {
        JExpression arg0 = x.getArgs().get(0);
        if (!(arg0 instanceof JClassLiteral)) {
          error(arg0.getSourceInfo(),
              "Only a class literal may be passed to runAsyncCode");
          return;
        }
        JClassLiteral lit = (JClassLiteral) arg0;
        String name = nameFromClassLiteral(lit);
        List matches = replacementsByName.get(name);
        if (matches == null || matches.size() == 0) {
          error(x.getSourceInfo(), "No runAsync call is named " + name);
          return;
        }
        if (matches.size() > 1) {
          TreeLogger branch = error(x.getSourceInfo(),
              "Multiple runAsync calls are named " + name);
          for (RunAsyncReplacement match : matches) {
            branch.log(TreeLogger.ERROR, "One call is in "
                + methodDescription(match.getEnclosingMethod()));
          }
          return;
        }
        Integer splitPoint = matches.get(0).getNumber();

        JMethodCall newCall = new JMethodCall(x.getSourceInfo(), null,
            program.getIndexedMethod("RunAsyncCode.forSplitPointNumber"));
        newCall.addArg(program.getLiteralInt(splitPoint));
        ctx.replaceMe(newCall);
      }
    }

    private String methodDescription(JMethod method) {
      StringBuilder desc = new StringBuilder();
      desc.append(method.getEnclosingType().getName());
      desc.append(".");
      desc.append(method.getName());
      desc.append(" (");
      desc.append(method.getSourceInfo().getFileName());
      desc.append(':');
      desc.append(method.getSourceInfo().getStartLine());
      desc.append(")");

      return desc.toString();
    }
  }

  public static void exec(TreeLogger logger, JProgram program)
      throws UnableToCompleteException {
    Event codeSplitterEvent = SpeedTracerLogger.start(
        CompilerEventType.CODE_SPLITTER, "phase", "ReplaceRunAsyncs");
    TreeLogger branch = logger.branch(TreeLogger.TRACE,
        "Replacing GWT.runAsync with island loader calls");
    new ReplaceRunAsyncs(branch, program).execImpl();
    codeSplitterEvent.end();
  }

  /**
   * Extract the initializer of AsyncFragmentLoader.BROWSER_LOADER. A couple of
   * parts of the compiler modify this initializer call.
   */
  static JMethodCall getBrowserLoaderConstructor(JProgram program) {
    JField field = program.getIndexedField("AsyncFragmentLoader.BROWSER_LOADER");
    JMethodCall initializerCall = (JMethodCall) field.getDeclarationStatement().getInitializer();
    assert initializerCall.getArgs().size() == 2;
    return initializerCall;
  }

  private static JMethod getMethod(JClassType type, String name) {
    for (JMethod method : type.getMethods()) {
      if (method.getName().equals(name)) {
        return method;
      }
    }
    throw new InternalCompilerException("Method not found: " + type.getName()
        + "." + name);
  }

  private static JMethod getOnLoadMethod(JClassType loaderType) {
    assert loaderType != null;
    assert loaderType.getMethods() != null;
    JMethod method = getMethod(loaderType, "onLoad");
    assert method.isStatic();
    assert method.getParams().size() == 0;
    return method;
  }

  private static JMethod getRunAsyncMethod(JClassType loaderType) {
    assert loaderType != null;
    assert loaderType.getMethods() != null;
    JMethod method = getMethod(loaderType, "runAsync");
    assert (method.isStatic());
    assert (method.getParams().size() == 1);
    assert (method.getParams().get(0).getType().getName().equals(FragmentLoaderCreator.RUN_ASYNC_CALLBACK));
    return method;
  }

  /**
   * Convert a class literal to a runAsync name.
   */
  private static String nameFromClassLiteral(JClassLiteral classLiteral) {
    return classLiteral.getRefType().getName();
  }

  private boolean errorsFound = false;
  private final TreeLogger logger;
  private JProgram program;

  private Map runAsyncReplacements = new HashMap();

  private ReplaceRunAsyncs(TreeLogger logger, JProgram program) {
    this.logger = logger;
    this.program = program;
  }

  private TreeLogger error(SourceInfo info, String message) {
    errorsFound = true;
    TreeLogger fileLogger = logger.branch(TreeLogger.ERROR, "Error in '"
        + info.getFileName() + "'");
    String linePrefix = "";
    if (info.getStartLine() > 0) {
      linePrefix = "Line " + info.getStartLine() + ": ";
    }
    fileLogger.log(TreeLogger.ERROR, linePrefix + message);
    return fileLogger;
  }

  private void execImpl() throws UnableToCompleteException {
    AsyncCreateVisitor visitor = new AsyncCreateVisitor();
    visitor.accept(program);
    setNumEntriesInAsyncFragmentLoader(visitor.entryCount);
    program.setRunAsyncReplacements(runAsyncReplacements);
    new ReplaceRunAsyncResources().accept(program);
    if (errorsFound) {
      throw new UnableToCompleteException();
    }
  }

  private JField getField(JClassType type, String name) {
    for (JField field : type.getFields()) {
      if (field.getName().equals(name)) {
        return field;
      }
    }
    throw new InternalCompilerException("Field not found: " + type.getName()
        + "." + name);
  }

  private JClassType getFragmentLoader(int fragmentNumber) {
    String fragmentLoaderClassName = FragmentLoaderCreator.ASYNC_LOADER_PACKAGE
        + "." + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX
        + fragmentNumber;
    JType result = program.getFromTypeMap(fragmentLoaderClassName);
    assert (result != null);
    assert (result instanceof JClassType);
    return (JClassType) result;
  }

  private JClassType getFragmentLoaderCallbackList(int fragmentNumber) {
    String className = FragmentLoaderCreator.ASYNC_LOADER_PACKAGE + "."
        + FragmentLoaderCreator.ASYNC_LOADER_CLASS_PREFIX + fragmentNumber
        + FragmentLoaderCreator.CALLBACK_LIST_SUFFIX;
    JType result = program.getFromTypeMap(className);
    assert (result != null);
    return (JClassType) result;
  }

  private void setNumEntriesInAsyncFragmentLoader(int entryCount) {
    JMethodCall constructorCall = getBrowserLoaderConstructor(program);
    assert constructorCall.getArgs().get(0).getType() == JPrimitiveType.INT;
    constructorCall.setArg(0, program.getLiteralInt(entryCount));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy