| /* |
| * Copyright 2014 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.ast.Context; |
| import com.google.gwt.dev.jjs.ast.HasName; |
| import com.google.gwt.dev.jjs.ast.JCastMap; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| 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.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JRuntimeTypeReference; |
| import com.google.gwt.dev.jjs.ast.JStringLiteral; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JVisitor; |
| import com.google.gwt.dev.jjs.ast.RuntimeConstants; |
| import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting; |
| import com.google.gwt.thirdparty.guava.common.base.Objects; |
| import com.google.gwt.thirdparty.guava.common.collect.LinkedHashMultiset; |
| 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.Multiset; |
| import com.google.gwt.thirdparty.guava.common.collect.Multisets; |
| |
| import java.io.Serializable; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| /** |
| * Assigns and replaces JRuntimeTypeReference nodes with a type id literal. |
| */ |
| public class ResolveRuntimeTypeReferences { |
| |
| /** |
| * Identifies a way of sorting types when generating ids. |
| */ |
| public enum TypeOrder { |
| ALPHABETICAL, FREQUENCY, NONE |
| } |
| |
| /** |
| * Maps a type into a type id literal. |
| */ |
| public interface TypeMapper<T extends JExpression> { |
| T getOrCreateTypeId(JType type); |
| |
| void copyFrom(TypeMapper<T> typeMapper); |
| |
| T get(JType type); |
| } |
| |
| /** |
| * Sequentially creates int type ids for types. |
| */ |
| public static class IntTypeMapper implements Serializable, TypeMapper<JIntLiteral> { |
| |
| // NOTE: DO NOT STORE ANY AST REFERENCE. Objects of this type persist across compiles. |
| private final Map<String, Integer> typeIdByTypeName = Maps.newHashMap(); |
| private int nextAvailableId = 0; |
| |
| @Override |
| public void copyFrom(TypeMapper<JIntLiteral> that) { |
| if (!(that instanceof IntTypeMapper)) { |
| throw new IllegalArgumentException("Can only copy from IntTypeMapper"); |
| } |
| |
| IntTypeMapper from = (IntTypeMapper) that; |
| this.nextAvailableId = from.nextAvailableId; |
| this.typeIdByTypeName.clear(); |
| this.typeIdByTypeName.putAll(from.typeIdByTypeName); |
| } |
| |
| @VisibleForTesting |
| public boolean hasSameContent(IntTypeMapper that) { |
| return Objects.equal(this.typeIdByTypeName, that.typeIdByTypeName) |
| && Objects.equal(this.nextAvailableId, that.nextAvailableId); |
| } |
| |
| @Override |
| public JIntLiteral get(JType type) { |
| Integer typeId = typeIdByTypeName.get(type.getName()); |
| return typeId == null ? null : new JIntLiteral(type.getSourceInfo(), typeId); |
| } |
| |
| @Override |
| public JIntLiteral getOrCreateTypeId(JType type) { |
| String typeName = type.getName(); |
| if (!typeIdByTypeName.containsKey(typeName)) { |
| int nextId = nextAvailableId++; |
| typeIdByTypeName.put(typeName, nextId); |
| } |
| |
| return get(type); |
| } |
| } |
| |
| /** |
| * Predictably creates String type id literals for castable and instantiable types. |
| */ |
| public static class StringTypeMapper implements TypeMapper<JStringLiteral> { |
| |
| private JProgram program; |
| |
| public StringTypeMapper(JProgram program) { |
| this.program = program; |
| } |
| |
| @Override |
| public void copyFrom(TypeMapper<JStringLiteral> that) { |
| if (!(that instanceof StringTypeMapper)) { |
| throw new IllegalArgumentException("Can only copy from StringTypeMapper"); |
| } |
| } |
| |
| @Override |
| public JStringLiteral getOrCreateTypeId(JType type) { |
| return get(type); |
| } |
| |
| @Override |
| public JStringLiteral get(JType type) { |
| return program.getStringLiteral(type.getSourceInfo(), type.getName()); |
| } |
| } |
| |
| /** |
| * Predictably creates String type id literals for castable and instantiable types |
| * by using closure uniqueid generation in JsInterop CLOSURE mode. |
| */ |
| public static class ClosureUniqueIdTypeMapper implements TypeMapper<JMethodCall> { |
| |
| private JProgram program; |
| |
| public ClosureUniqueIdTypeMapper(JProgram program) { |
| this.program = program; |
| } |
| |
| @Override |
| public void copyFrom(TypeMapper<JMethodCall> that) { |
| if (!(that instanceof ClosureUniqueIdTypeMapper)) { |
| throw new IllegalArgumentException("Can only copy from ClosureUniqueIdTypeMapper"); |
| } |
| } |
| |
| @Override |
| public JMethodCall getOrCreateTypeId(JType type) { |
| return get(type); |
| } |
| |
| @Override |
| public JMethodCall get(JType type) { |
| JMethod getUniqueId = program.getIndexedMethod(RuntimeConstants.RUNTIME_UNIQUE_ID); |
| return new JMethodCall(type.getSourceInfo(), null, |
| getUniqueId, program.getStringLiteral(type.getSourceInfo(), type.getName())); |
| } |
| } |
| |
| /** |
| * Collects all types that need an id at runtime. |
| */ |
| // TODO(rluble): Maybe this pass should insert the defineClass in Java. |
| private class RuntimeTypeCollectorVisitor extends JVisitor { |
| |
| private final Multiset<JReferenceType> typesRequiringRuntimeIds = LinkedHashMultiset.create(); |
| |
| @Override |
| public void endVisit(JRuntimeTypeReference x, Context ctx) { |
| // Collects types in cast maps. |
| typesRequiringRuntimeIds.add(x.getReferredType()); |
| } |
| |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| // Calls through super and X.this need a runtime type id. |
| if (!x.isStaticDispatchOnly() || x.getTarget().isStatic()) { |
| return; |
| } |
| typesRequiringRuntimeIds.add(x.getTarget().getEnclosingType()); |
| } |
| |
| @Override |
| public void endVisit(JReferenceType x, Context ctx) { |
| // Collects types that need a runtime type id for defineClass(). |
| if (program.typeOracle.isInstantiatedType(x)) { |
| typesRequiringRuntimeIds.add(x); |
| } |
| } |
| } |
| |
| /** |
| * Replaces JRuntimeTypeReference nodes with the corresponding JLiteral. |
| */ |
| private class ReplaceRuntimeTypeReferencesVisitor extends JModVisitor { |
| @Override |
| public void endVisit(JRuntimeTypeReference x, Context ctx) { |
| ctx.replaceMe(getTypeIdExpression(x.getReferredType())); |
| } |
| } |
| |
| private final JProgram program; |
| |
| private TypeMapper<?> typeMapper; |
| |
| private TypeOrder typeOrder; |
| |
| private ResolveRuntimeTypeReferences(JProgram program, TypeMapper<?> typeMapper, |
| TypeOrder typeOrder) { |
| this.program = program; |
| this.typeMapper = typeMapper; |
| this.typeOrder = typeOrder; |
| } |
| |
| private void assignTypes(Multiset<JReferenceType> typesWithReferenceCounts) { |
| // TODO(rluble): remove the need for special ids |
| typeMapper.getOrCreateTypeId(program.getJavaScriptObject()); |
| typeMapper.getOrCreateTypeId(program.getTypeJavaLangObject()); |
| typeMapper.getOrCreateTypeId(program.getTypeJavaLangString()); |
| |
| Iterable<JReferenceType> types = null; |
| switch (typeOrder) { |
| case FREQUENCY: |
| types = Multisets.copyHighestCountFirst(typesWithReferenceCounts).elementSet(); |
| break; |
| case ALPHABETICAL: |
| types = Lists.newArrayList(typesWithReferenceCounts.elementSet()); |
| Collections.sort((List<JReferenceType>) types, HasName.BY_NAME_COMPARATOR); |
| break; |
| case NONE: |
| types = typesWithReferenceCounts.elementSet(); |
| break; |
| } |
| |
| for (JType type : types) { |
| typeMapper.getOrCreateTypeId(type); |
| } |
| } |
| |
| private void execImpl() { |
| RuntimeTypeCollectorVisitor runtimeTypeCollector = new RuntimeTypeCollectorVisitor(); |
| // Collects runtime type references visible from types in the program that are part of the |
| // current compile. |
| runtimeTypeCollector.accept(program); |
| // Collects runtime type references that are missed (inside of annotations) in a normal AST |
| // traversal. |
| runtimeTypeCollector.accept(Lists.newArrayList(program.getCastMap().values())); |
| // Collects runtime type references in the ClassLiteralHolder even if the ClassLiteralHolder |
| // isn't part of the current compile. |
| runtimeTypeCollector.accept(program.getIndexedType("ClassLiteralHolder")); |
| // TODO(stalcup): each module should have it's own ClassLiteralHolder or some agreed upon |
| // location that is default accessible to all. |
| |
| assignTypes(runtimeTypeCollector.typesRequiringRuntimeIds); |
| |
| ReplaceRuntimeTypeReferencesVisitor replaceTypeIdsVisitor = new ReplaceRuntimeTypeReferencesVisitor(); |
| replaceTypeIdsVisitor.accept(program); |
| replaceTypeIdsVisitor.accept(program.getIndexedType("ClassLiteralHolder")); |
| // TODO(rluble): Improve the code so that things are not scattered all over; here cast maps |
| // that appear as parameters to soon to be generated |
| // {@link JavaClassHierarchySetup::defineClass()} are NOT traversed when traversing the program. |
| for (Entry<JReferenceType, JCastMap> entry : program.getCastMap().entrySet()) { |
| JCastMap castMap = entry.getValue(); |
| replaceTypeIdsVisitor.accept(castMap); |
| } |
| } |
| |
| private JExpression getTypeIdExpression(JType type) { |
| return typeMapper.getOrCreateTypeId(type); |
| } |
| |
| public static void exec(JProgram program, TypeMapper<?> typeMapper, TypeOrder typeOrder) { |
| new ResolveRuntimeTypeReferences(program, typeMapper, typeOrder).execImpl(); |
| } |
| |
| } |