blob: 7d50f49d043ced43d0ca4566c13500bae1d48b12 [file] [log] [blame]
/*
* 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.JIntLiteral;
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.JReferenceType;
import com.google.gwt.dev.jjs.ast.JRunAsync;
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.util.ArrayList;
import java.util.Collections;
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 {
private class AsyncCreateVisitor extends JModVisitor {
private JMethod currentMethod;
private final JMethod runAsyncOnsuccess = program
.getIndexedMethod("RunAsyncCallback.onSuccess");
@Override
public void endVisit(JMethodCall x, Context ctx) {
JMethod method = x.getTarget();
if (method == runAsyncOnsuccess
&& (currentMethod != null && currentMethod.getEnclosingType() == program
.getIndexedType("AsyncFragmentLoader"))) {
/*
* Note: The volatile marker on the method flags it so that we don't
* optimize calls from AsyncFragmentLoader to implementations of
* RunAsyncCallback.onSuccess(). This can defeat code splitting.
*/
x.setVolatile();
return;
}
if (isRunAsyncMethod(method)) {
JExpression asyncCallback;
String name;
switch (x.getArgs().size()) {
case 1:
name = getImplicitName(currentMethod);
asyncCallback = x.getArgs().get(0);
break;
case 2:
JExpression arg0 = x.getArgs().get(0);
if (!(arg0 instanceof JClassLiteral)) {
error(arg0.getSourceInfo(),
"Only class literals may be used to name a call to GWT.runAsync()");
return;
}
name = nameFromClassLiteral((JClassLiteral) arg0);
asyncCallback = x.getArgs().get(1);
break;
default:
throw new InternalCompilerException(
"runAsync call found with neither 1 nor 2 arguments: " + x);
}
int splitPoint = runAsyncs.size() + 1;
SourceInfo info = x.getSourceInfo();
JMethod runAsyncMethod = program.getIndexedMethod("AsyncFragmentLoader.runAsync");
assert runAsyncMethod != null;
JMethodCall runAsyncCall = new JMethodCall(info, null, runAsyncMethod);
runAsyncCall.addArg(JIntLiteral.get(splitPoint));
runAsyncCall.addArg(asyncCallback);
JReferenceType callbackType = (JReferenceType) asyncCallback.getType();
callbackType = callbackType.getUnderlyingType();
JMethod callbackMethod;
if (callbackType instanceof JClassType) {
callbackMethod =
program.typeOracle.getPolyMethod((JClassType) callbackType, "onSuccess()V");
} else {
callbackMethod = program.getIndexedMethod("RunAsyncCallback.onSuccess");
}
assert callbackMethod != null;
JMethodCall onSuccessCall = new JMethodCall(info, asyncCallback, callbackMethod);
JRunAsync runAsyncNode = new JRunAsync(info, splitPoint, name, runAsyncCall, onSuccessCall);
runAsyncs.add(runAsyncNode);
ctx.replaceMe(runAsyncNode);
}
}
@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");
}
}
private class ReplaceRunAsyncResources extends JModVisitor {
private final Map<String, List<JRunAsync>> replacementsByName;
private final JMethod runAsyncCode;
public ReplaceRunAsyncResources() {
replacementsByName = new HashMap<String, List<JRunAsync>>();
runAsyncCode = program.getIndexedMethod("RunAsyncCode.runAsyncCode");
for (JRunAsync replacement : runAsyncs) {
String name = replacement.getName();
if (name != null) {
List<JRunAsync> list = replacementsByName.get(name);
if (list == null) {
list = new ArrayList<JRunAsync>();
replacementsByName.put(name, list);
}
list.add(replacement);
}
}
}
@Override
public void endVisit(JMethodCall x, Context ctx) {
if (x.getTarget() == 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<JRunAsync> matches = replacementsByName.get(name);
SourceInfo info = x.getSourceInfo();
if (matches == null || matches.size() == 0) {
error(info, "No runAsync call is named " + name);
return;
}
if (matches.size() > 1) {
TreeLogger branch = error(info, "Multiple runAsync calls are named " + name);
List<String> errors = new ArrayList<String>();
for (JRunAsync match : matches) {
errors.add("One call is at '" + match.getSourceInfo().getFileName() + ':'
+ match.getSourceInfo().getStartLine() + "'");
}
Collections.sort(errors);
for (String error : errors) {
branch.log(TreeLogger.ERROR, error);
}
return;
}
int splitPoint = matches.get(0).getSplitPoint();
JMethodCall newCall =
new JMethodCall(info, null, program
.getIndexedMethod("RunAsyncCode.forSplitPointNumber"));
newCall.addArg(program.getLiteralInt(splitPoint));
ctx.replaceMe(newCall);
}
}
}
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;
}
static String getImplicitName(JMethod method) {
String name;
StringBuilder sb = new StringBuilder();
sb.append('@');
sb.append(method.getEnclosingType().getName());
sb.append("::");
sb.append(JProgram.getJsniSig(method, false));
name = sb.toString();
return name;
}
/**
* 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 final JProgram program;
private final List<JRunAsync> runAsyncs = new ArrayList<JRunAsync>();
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, "Errors 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(runAsyncs.size() + 1);
program.setRunAsyncs(runAsyncs);
new ReplaceRunAsyncResources().accept(program);
if (errorsFound) {
throw new UnableToCompleteException();
}
}
private void setNumEntriesInAsyncFragmentLoader(int entryCount) {
JMethodCall constructorCall = getBrowserLoaderConstructor(program);
assert constructorCall.getArgs().get(0).getType() == JPrimitiveType.INT;
constructorCall.setArg(0, program.getLiteralInt(entryCount));
}
}