| /* |
| * 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.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.JBlock; |
| import com.google.gwt.dev.jjs.ast.JCaseStatement; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JGwtCreate; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReboundEntryPoint; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JSwitchStatement; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Replaces any "GWT.create()" calls with a new expression for the actual result |
| * of the deferred binding decision. |
| */ |
| public class ResolveRebinds { |
| |
| private class RebindVisitor extends JModVisitor { |
| @Override |
| public void endVisit(JGwtCreate x, Context ctx) { |
| |
| if (isSoftRebind(x.getSourceType())) { |
| JMethod method = |
| rebindMethod(x.getSourceType(), x.getResultTypes(), x.getInstantiationExpressions()); |
| JMethodCall call = new JMethodCall(x.getSourceInfo(), null, method); |
| ctx.replaceMe(call); |
| return; |
| } |
| |
| String rebindResult = rebind(x.getSourceType()); |
| List<String> rebindResults = x.getResultTypes(); |
| for (int i = 0; i < rebindResults.size(); ++i) { |
| // Find the matching rebound type. |
| if (rebindResult.equals(rebindResults.get(i))) { |
| // Replace with the associated instantiation expression. |
| ctx.replaceMe(x.getInstantiationExpressions().get(i)); |
| return; |
| } |
| } |
| throw new InternalCompilerException("No matching rebind result in all rebind results!"); |
| } |
| |
| @Override |
| public void endVisit(JReboundEntryPoint x, Context ctx) { |
| |
| if (isSoftRebind(x.getSourceType())) { |
| JMethod method = rebindMethod(x.getSourceType(), x.getResultTypes(), x.getEntryCalls()); |
| JMethodCall call = new JMethodCall(x.getSourceInfo(), null, method); |
| ctx.replaceMe(call.makeStatement()); |
| return; |
| } |
| |
| String rebindResult = rebind(x.getSourceType()); |
| List<String> rebindResults = x.getResultTypes(); |
| for (int i = 0; i < rebindResults.size(); ++i) { |
| // Find the matching rebound type. |
| if (rebindResult.equals(rebindResults.get(i))) { |
| // Replace with the associated instantiation expression. |
| ctx.replaceMe(x.getEntryCalls().get(i).makeStatement()); |
| return; |
| } |
| } |
| throw new InternalCompilerException("No matching rebind result in all rebind results!"); |
| } |
| } |
| |
| public static boolean exec(JProgram program, Map<String, String>[] orderedRebindAnswers) { |
| return new ResolveRebinds(program, orderedRebindAnswers).execImpl(); |
| } |
| |
| /** |
| * Returns the rebind answers that do not vary across various maps of rebind |
| * answers. |
| */ |
| public static Map<String, String> getHardRebindAnswers(Map<String, String>[] rebindAnswers) { |
| Iterator<Map<String, String>> it = Arrays.asList(rebindAnswers).iterator(); |
| |
| // Start with an arbitrary copy of a rebind answer map |
| Map<String, String> toReturn = new HashMap<String, String>(it.next()); |
| |
| while (it.hasNext()) { |
| Map<String, String> next = it.next(); |
| // Only keep key/value pairs present in the other rebind map |
| toReturn.entrySet().retainAll(next.entrySet()); |
| } |
| |
| return toReturn; |
| } |
| |
| private final Map<String, String> hardRebindAnswers; |
| private final JClassType holderType; |
| private final Map<String, String>[] orderedRebindAnswers; |
| private final JMethod permutationIdMethod; |
| private final JProgram program; |
| private final Map<String, JMethod> rebindMethods = new HashMap<String, JMethod>(); |
| |
| private ResolveRebinds(JProgram program, Map<String, String>[] orderedRebindAnswers) { |
| this.program = program; |
| this.orderedRebindAnswers = orderedRebindAnswers; |
| |
| this.hardRebindAnswers = getHardRebindAnswers(orderedRebindAnswers); |
| this.holderType = (JClassType) program.getIndexedType("CollapsedPropertyHolder"); |
| this.permutationIdMethod = program.getIndexedMethod("CollapsedPropertyHolder.getPermutationId"); |
| } |
| |
| public String rebind(String reqType) { |
| // Rebinds are always on a source type name. |
| String reboundClassName = hardRebindAnswers.get(reqType); |
| if (reboundClassName == null) { |
| // The fact that we already compute every rebind permutation before |
| // compiling should prevent this case from ever happening in real life. |
| // |
| throw new InternalCompilerException("Unexpected failure to rebind '" + reqType + "'"); |
| } |
| assert program.getFromTypeMap(reboundClassName) != null; |
| return reboundClassName; |
| } |
| |
| private boolean execImpl() { |
| RebindVisitor rebinder = new RebindVisitor(); |
| rebinder.accept(program); |
| return rebinder.didChange(); |
| } |
| |
| private boolean isSoftRebind(String requestType) { |
| return !hardRebindAnswers.containsKey(requestType); |
| } |
| |
| private JMethod rebindMethod(String requestType, List<String> resultTypes, |
| List<JExpression> instantiationExpressions) { |
| assert resultTypes.size() == instantiationExpressions.size(); |
| |
| JMethod toReturn = rebindMethods.get(requestType); |
| if (toReturn != null) { |
| return toReturn; |
| } |
| |
| // Maps the result types to the various virtual permutation ids |
| Map<String, List<Integer>> resultsToPermutations = new LinkedHashMap<String, List<Integer>>(); |
| |
| for (int i = 0, j = orderedRebindAnswers.length; i < j; i++) { |
| Map<String, String> answerMap = orderedRebindAnswers[i]; |
| String answerType = answerMap.get(requestType); |
| List<Integer> list = resultsToPermutations.get(answerType); |
| if (list == null) { |
| list = new ArrayList<Integer>(); |
| resultsToPermutations.put(answerType, list); |
| } |
| // and map it to the permutation ID for a particular set of values |
| list.add(i); |
| } |
| |
| // Pick the most-used result type to emit less code |
| String mostUsed = null; |
| { |
| int max = 0; |
| for (Map.Entry<String, List<Integer>> entry : resultsToPermutations.entrySet()) { |
| int size = entry.getValue().size(); |
| if (size > max) { |
| max = size; |
| mostUsed = entry.getKey(); |
| } |
| } |
| } |
| assert mostUsed != null; |
| |
| // c_g_g_d_c_i_DOMImpl |
| SourceInfo info = program.createSourceInfoSynthetic(getClass()); |
| toReturn = |
| program.createMethod(info, requestType.replace("_", "_1").replace('.', '_'), holderType, |
| program.getTypeJavaLangObject().getNonNull(), false, true, true, false, false); |
| toReturn.freezeParamTypes(); |
| info.addCorrelation(info.getCorrelator().by(toReturn)); |
| rebindMethods.put(requestType, toReturn); |
| |
| // Used in the return statement at the end |
| JExpression mostUsedExpression = null; |
| |
| JBlock switchBody = new JBlock(info); |
| for (int i = 0, j = resultTypes.size(); i < j; i++) { |
| String resultType = resultTypes.get(i); |
| JExpression instantiation = instantiationExpressions.get(i); |
| |
| List<Integer> permutations = resultsToPermutations.get(resultType); |
| if (permutations == null) { |
| // This rebind result is unused in this permutation |
| continue; |
| } else if (resultType.equals(mostUsed)) { |
| // Save off the fallback expression and go onto the next type |
| mostUsedExpression = instantiation; |
| continue; |
| } |
| |
| for (int permutationId : permutations) { |
| // case 33: |
| switchBody.addStmt(new JCaseStatement(info, program.getLiteralInt(permutationId))); |
| } |
| |
| // return new FooImpl(); |
| JReturnStatement ret = new JReturnStatement(info, instantiation); |
| switchBody.addStmt(ret); |
| } |
| |
| assert switchBody.getStatements().size() > 0 : "No case statement emitted " |
| + "for supposedly soft-rebind type " + requestType; |
| |
| // switch (CollapsedPropertyHolder.getPermutationId()) { ... } |
| JSwitchStatement sw = |
| new JSwitchStatement(info, new JMethodCall(info, null, permutationIdMethod), switchBody); |
| |
| // return new FallbackImpl(); at the very end. |
| assert mostUsedExpression != null : "No most-used expression"; |
| JReturnStatement fallbackReturn = new JReturnStatement(info, mostUsedExpression); |
| |
| JMethodBody body = (JMethodBody) toReturn.getBody(); |
| body.getBlock().addStmt(sw); |
| body.getBlock().addStmt(fallbackReturn); |
| |
| return toReturn; |
| } |
| } |