| /* |
| * 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.codesplitter; |
| |
| 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.JNumericEntry; |
| 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.jjs.ast.RuntimeConstants; |
| 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(RuntimeConstants.RUN_ASYNC_CALLBACK_ON_SUCCESS); |
| |
| @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; |
| } |
| boolean explicitClassLiteral = false; |
| 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); |
| explicitClassLiteral = true; |
| 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(RuntimeConstants.ASYNC_FRAGMENT_LOADER_RUN_ASYNC); |
| assert runAsyncMethod != null; |
| JMethodCall runAsyncCall = new JMethodCall(info, null, runAsyncMethod); |
| runAsyncCall.addArg(new JNumericEntry(info, "RunAsyncFragmentIndex", splitPoint)); |
| runAsyncCall.addArg(asyncCallback); |
| |
| JReferenceType callbackType = (JReferenceType) asyncCallback.getType().getUnderlyingType(); |
| JMethod callbackMethod; |
| if (callbackType instanceof JClassType) { |
| callbackMethod = program.typeOracle.getInstanceMethodBySignature( |
| (JClassType) callbackType, "onSuccess()V"); |
| } else { |
| callbackMethod = program.getIndexedMethod(RuntimeConstants.RUN_ASYNC_CALLBACK_ON_SUCCESS); |
| } |
| if (callbackMethod == null || |
| // The callback method is a synthetic stub inserted for more accurate override |
| // tracking, ignore it. |
| callbackMethod.isAbstract() && callbackMethod.isSynthetic()) { |
| error(x.getSourceInfo(), "Only a RunAsyncCallback with a defined onSuccess() can " |
| + "be passed to runAsync()."); |
| return; |
| } |
| JMethodCall onSuccessCall = new JMethodCall(info, asyncCallback, callbackMethod); |
| |
| JRunAsync runAsyncNode = new JRunAsync(info, splitPoint, name, explicitClassLiteral, |
| 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).getRunAsyncId(); |
| JMethodCall newCall = |
| new JMethodCall(info, null, program |
| .getIndexedMethod(RuntimeConstants.RUN_ASYNC_CODE_FOR_SPLIT_POINT_NUMBER)); |
| newCall.addArg(new JNumericEntry(info, "RunAsyncFragmentIndex", 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(RuntimeConstants.ASYNC_FRAGMENT_LOADER_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.getJsniSignature(true, 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, |
| new JNumericEntry(constructorCall.getSourceInfo(), "RunAsyncFragmentCount", entryCount)); |
| } |
| } |