/* * 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)); } }