/* * 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.PropertyAndBindingInfo; import com.google.gwt.dev.cfg.BindingProperty; import com.google.gwt.dev.cfg.PermutationProperties; import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.jjs.ast.AccessModifier; 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.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.JPermutationDependentValue; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JSwitchStatement; import com.google.gwt.dev.jjs.ast.RuntimeConstants; import com.google.gwt.thirdparty.guava.common.base.Function; import com.google.gwt.thirdparty.guava.common.base.Joiner; import com.google.gwt.thirdparty.guava.common.collect.FluentIterable; import com.google.gwt.thirdparty.guava.common.collect.Lists; import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Multimap; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; /** * Replaces any "GWT.create()" calls with a new expression for the actual result of the deferred * binding decision and System.getProperty() with the actual value for that property. * <p> * When properties are collapsed (e.g. in soft permutations) a method is create that resolves * the value at runtime. * </p> * */ public class ResolvePermutationDependentValues { private class ValueReplacer extends JModVisitor { @Override public void endVisit(JPermutationDependentValue x, Context ctx) { if (x.isTypeRebind()) { ctx.replaceMe(rebindClassExpression(x)); return; } assert x.isProperty(); ctx.replaceMe(propertyValueExpression(x)); } private JExpression propertyValueExpression(JPermutationDependentValue x) { List<String> propertyValues = props.getConfigurationProperties().getStrings(x.getRequestedValue()); String propertyValue = propertyValues.isEmpty() ? null : Joiner.on(",").join(propertyValues); if (propertyValue != null) { // It is a configuration property. return program.getLiteral(x.getSourceInfo(), propertyValue); } if (isSoftPermutationProperty(x.getRequestedValue())) { JMethod method = getOrCreateSoftPropertyMethod(x.getSourceInfo(), x.getRequestedValue()); return new JMethodCall(x.getSourceInfo(), null, method); } propertyValue = commonPropertyAndBindingInfo.getPropertyValue(x.getRequestedValue()); if (propertyValue != null) { return program.getLiteral(x.getSourceInfo(), propertyValue); } return x.getDefaultValueExpression(); } private JExpression rebindClassExpression(JPermutationDependentValue x) { if (isSoftTypeRebind(x.getRequestedValue())) { JMethod method = getOrCreateTypeRebindMethod(x.getSourceInfo(), x.getRequestedValue(), x.getResultValues(), x.getResultExpressions()); return new JMethodCall(x.getSourceInfo(), null, method); } return computeInstantiationExpression(x); } } public static boolean exec(JProgram program, PermutationProperties props, List<PropertyAndBindingInfo> propertyAndBindingInfo) { return new ResolvePermutationDependentValues(program, props, propertyAndBindingInfo).execImpl(); } private final JProgram program; private final PermutationProperties props; private final Set<String> bindingPropertyNames; private final List<PropertyAndBindingInfo> permutationPropertyAndBindingInfo; private final PropertyAndBindingInfo commonPropertyAndBindingInfo; private final JClassType holderType; private final JMethod permutationIdMethod; private final Map<String, JMethod> softPermutationMethods = Maps.newHashMap(); private ResolvePermutationDependentValues(JProgram program, PermutationProperties props, List<PropertyAndBindingInfo> gwtCreateAnswers) { this.program = program; this.permutationPropertyAndBindingInfo = gwtCreateAnswers; this.props = props; this.bindingPropertyNames = FluentIterable.from(props.getBindingProperties()).transform( new Function<BindingProperty, String>() { @Override public String apply(BindingProperty bindingProperty) { return bindingProperty.getName(); } }).toSet(); this.commonPropertyAndBindingInfo = PropertyAndBindingInfo.getCommonAnswers(gwtCreateAnswers); this.holderType = (JClassType) program.getIndexedType("CollapsedPropertyHolder"); this.permutationIdMethod = program.getIndexedMethod( RuntimeConstants.COLLAPSED_PROPERTY_HOLDER_GET_PERMUTATION_ID); } public JExpression computeInstantiationExpression(JPermutationDependentValue x) { String reqType = x.getRequestedValue(); // Rebinds are always on a source type name. String reboundClassName = commonPropertyAndBindingInfo.getReboundType(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; int index = x.getResultValues().indexOf(reboundClassName); if (index == -1) { throw new InternalCompilerException("No matching rebind result in all rebind results!"); } return x.getResultExpressions().get(index); } private boolean execImpl() { ValueReplacer valueReplacer = new ValueReplacer(); valueReplacer.accept(program); return valueReplacer.didChange(); } private boolean isSoftTypeRebind(String requestType) { return !commonPropertyAndBindingInfo.containsType(requestType); } private boolean isSoftPermutationProperty(String propertyName) { return bindingPropertyNames.contains(propertyName) && !commonPropertyAndBindingInfo.containsProperty(propertyName); } private JMethod getOrCreateSoftPropertyMethod(final SourceInfo info, String propertyName) { JMethod toReturn = softPermutationMethods.get(propertyName); if (toReturn != null) { return toReturn; } Multimap<String, Integer> resultsToPermutations = PropertyAndBindingInfo .getPermutationIdsByPropertyName(permutationPropertyAndBindingInfo, propertyName); List<String> propertyValues = Lists.newArrayList(resultsToPermutations.keySet()); return createReboundValueSelectorMethod(info, "property_", propertyName, program.getTypeJavaLangString(), propertyValues, FluentIterable.from(propertyValues).transform( new Function<String, JExpression>() { @Override public JExpression apply(String s) { return program.getLiteral(info, s); } }).toList(), resultsToPermutations); } private JMethod getOrCreateTypeRebindMethod(SourceInfo info, String requestType, List<String> resultTypes, List<JExpression> instantiationExpressions) { assert resultTypes.size() == instantiationExpressions.size(); JMethod toReturn = softPermutationMethods.get(requestType); if (toReturn != null) { return toReturn; } Multimap<String, Integer> resultsToPermutations = PropertyAndBindingInfo .getPermutationIdsByRequestTypes(permutationPropertyAndBindingInfo, requestType); return createReboundValueSelectorMethod(info, "create_", requestType, program.getTypeJavaLangObject().strengthenToNonNull(), resultTypes, instantiationExpressions, resultsToPermutations); } private JMethod createReboundValueSelectorMethod(SourceInfo info, String prefix, String parameterType, JReferenceType returntype, List<String> results, List<JExpression> resultExpressions, Multimap<String, Integer> resultsToPermutations) { // Pick the most-used result type to emit less code String mostUsed = mostUsedValue(resultsToPermutations); assert mostUsed != null; JMethod toReturn; info = info.makeChild(SourceOrigin.UNKNOWN); // c_g_g_d_c_i_DOMImpl toReturn = new JMethod(info, prefix + parameterType.replace("_", "_1").replace('.', '_'), holderType, returntype, false, true, true, AccessModifier.PUBLIC); toReturn.setBody(new JMethodBody(info)); holderType.addMethod(toReturn); toReturn.freezeParamTypes(); info.addCorrelation(info.getCorrelator().by(toReturn)); softPermutationMethods.put(parameterType, toReturn); // Used in the return statement at the end JExpression mostUsedExpression = null; JBlock switchBody = new JBlock(info); for (int i = 0, j = results.size(); i < j; i++) { String resultType = results.get(i); JExpression expression = resultExpressions.get(i); Collection<Integer> permutations = resultsToPermutations.get(resultType); if (permutations.isEmpty()) { // 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 = expression; continue; } for (int permutationId : permutations) { // case 33: switchBody.addStmt(new JCaseStatement(info, program.getLiteralInt(permutationId))); } // return new FooImpl(); switchBody.addStmt(expression.makeReturnStatement()); } assert switchBody.getStatements().size() > 0 : "No case statement emitted " + "for supposedly soft-rebind " + parameterType; // 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 = mostUsedExpression.makeReturnStatement(); JMethodBody body = (JMethodBody) toReturn.getBody(); body.getBlock().addStmt(sw); body.getBlock().addStmt(fallbackReturn); return toReturn; } private String mostUsedValue(Multimap<String, Integer> resultsToPermutations) { String mostUsed = null; int max = 0; for (String key : resultsToPermutations.keySet()) { int size = resultsToPermutations.get(key).size(); if (size > max) { max = size; mostUsed = key; } } return mostUsed; } }