/* * Copyright 2011 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.Correlation.Literal; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JArrayType; import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JDeclarationStatement; import com.google.gwt.dev.jjs.ast.JEnumType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JField.Disposition; import com.google.gwt.dev.jjs.ast.JFieldRef; import com.google.gwt.dev.jjs.ast.JInterfaceType; import com.google.gwt.dev.jjs.ast.JLiteral; 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.JNullLiteral; import com.google.gwt.dev.jjs.ast.JParameter; 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.JRuntimeTypeReference; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.RuntimeConstants; import com.google.gwt.dev.jjs.ast.js.JsniClassLiteral; import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; import com.google.gwt.dev.js.ast.JsContext; import com.google.gwt.dev.js.ast.JsInvocation; import com.google.gwt.dev.js.ast.JsModVisitor; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsNumberLiteral; 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 com.google.gwt.thirdparty.guava.common.base.Joiner; import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap; import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap; import com.google.gwt.thirdparty.guava.common.collect.Iterables; 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 com.google.gwt.thirdparty.guava.common.collect.Sets; import java.util.Map; import java.util.Set; /** * Create fields to represent the mechanical implementation of class literals. * Must be done after all class literals are created, but before optimizations * begin. {@link ControlFlowAnalyzer} depends on this. * <p> * Class literals are implemented as static field references. The static fields * are all put into the special com.google.gwt.lang.ClassLiteralHolder class. * Ordinarily, accessing one of these fields would trigger a clinit to run, but * we've special-cased class literal fields to evaluate as top-level code before * the application starts running to avoid the clinit. * * Class literal factory methods are responsible for installing references * to themselves on the Object.clazz field of their JS runtime prototype * since getClass() is no longer an overridden method. Prototypes can be * looked up via 'typeId' from the global prototypesByTypeId object, and so each * class literal factory method is passed the typeId of its type. * <p> */ public class ImplementClassLiteralsAsFields { private static Map<Class<? extends JType>, ClassLiteralFactoryMethod> literalFactoryMethodByTypeClass = new ImmutableMap.Builder() .put(JEnumType.class, ClassLiteralFactoryMethod.CREATE_FOR_ENUM) .put(JClassType.class, ClassLiteralFactoryMethod.CREATE_FOR_CLASS) .put(JInterfaceType.class, ClassLiteralFactoryMethod.CREATE_FOR_INTERFACE) .put(JPrimitiveType.class, ClassLiteralFactoryMethod.CREATE_FOR_PRIMITIVE) .build(); /** * Class used to construct invocations to class literal factory methods. */ public enum ClassLiteralFactoryMethod { CREATE_FOR_ENUM() { @Override JMethodCall createCall(SourceInfo info, JProgram program, JType type, JLiteral superclassLiteral) { JEnumType enumType = type.isEnumOrSubclass(); assert enumType != null; // createForEnum(packageName, typeName, runtimeTypeReference, Enum.class, type.values(), // type.valueOf(java/lang/String)); JMethodCall call = createBaseCall(info, program, type, "Class.createForEnum"); call.addArg(new JRuntimeTypeReference(info, program.getTypeJavaLangObject(), (JReferenceType) type)); call.addArg(superclassLiteral); call.addArg(getStandardMethodAsArg(info, program, type, "values()")); call.addArg(getStandardMethodAsArg(info, program, type, "valueOf(Ljava/lang/String;)")); return call; } private JExpression getStandardMethodAsArg(SourceInfo info, JProgram program, JType type, String methodSignature) { JEnumType enumType = type.isEnumOrSubclass(); if (enumType != type) { // The type is an anonymous subclass that represents one of the enum values return JNullLiteral.INSTANCE; } // This type is the base enum type not one of the anonymous classes that represent // enum values. for (JMethod method : enumType.getMethods()) { if (method.isStatic() && method.getSignature().startsWith(methodSignature)) { return new JsniMethodRef(info, method.getJsniSignature(true, false), method, program.getJavaScriptObject()); } } // The method was pruned. return JNullLiteral.INSTANCE; } }, CREATE_FOR_CLASS() { @Override JMethodCall createCall(SourceInfo info, JProgram program, JType type, JLiteral superclassLiteral) { // Class.createForClass(packageName, typeName, runtimeTypeReference, superclassliteral) JMethodCall call = createBaseCall(info, program, type, RuntimeConstants.CLASS_CREATE_FOR_CLASS); call.addArg(new JRuntimeTypeReference(info, program.getTypeJavaLangObject(), (JReferenceType) type)); call.addArg(superclassLiteral); return call; } }, CREATE_FOR_PRIMITIVE() { @Override JMethodCall createCall(SourceInfo info, JProgram program, JType type, JLiteral superclassLiteral) { // Class.createForPrimitive(typeName, typeSignature) JMethodCall call = new JMethodCall(info, null, program.getIndexedMethod( RuntimeConstants.CLASS_CREATE_FOR_PRIMITIVE)); call.addArg(program.getStringLiteral(info, type.getShortName())); call.addArg(program.getStringLiteral(info, type.getJavahSignatureName())); return call; } }, CREATE_FOR_INTERFACE() { @Override JMethodCall createCall(SourceInfo info, JProgram program, JType type, JLiteral superclassLiteral) { // Class.createForInterface(packageName, typeName) return createBaseCall(info, program, type, RuntimeConstants.CLASS_CREATE_FOR_INTERFACE); } }; abstract JMethodCall createCall(SourceInfo info, JProgram program, JType type, JLiteral superclassLiteral); private static JMethodCall createBaseCall(SourceInfo info, JProgram program, JType type, String indexedMethodName) { String[] compoundName = maybeMangleJSOTypeName(type); JMethodCall call = new JMethodCall(info, null, program.getIndexedMethod(indexedMethodName), program.getStringLiteral(info, type.getPackageName()), getCompoundNameLiteral(program, info, compoundName)); return call; } private static String[] maybeMangleJSOTypeName(JType type) { assert !(type instanceof JArrayType); String[] compoundName = type.getCompoundName(); // Mangle the class name to match hosted mode. if (type.isJsoType()) { compoundName[compoundName.length - 1] = compoundName[compoundName.length - 1] + '$'; } return compoundName; } private static JExpression getCompoundNameLiteral(final JProgram program, final SourceInfo info, String[] compoundName) { return program.getStringLiteral(info, Joiner.on('/').join(compoundName)); } } private class NormalizeVisitor extends JModVisitor { @Override public void endVisit(JClassLiteral x, Context ctx) { JType type = x.getRefType(); if (type instanceof JArrayType) { // Replace array class literals by an expression to obtain the class literal from the // leaf type of the array. JArrayType arrayType = (JArrayType) type; JClassLiteral leafTypeClassLiteral = new JClassLiteral(x.getSourceInfo(), arrayType.getLeafType()); resolveClassLiteral(leafTypeClassLiteral); int dims = type.isJsNative() ? 1 : arrayType.getDims(); JExpression arrayClassLiteralExpression = program.createArrayClassLiteralExpression( x.getSourceInfo(), leafTypeClassLiteral, dims); ctx.replaceMe(arrayClassLiteralExpression); } else { // Just resolve the class literal. resolveClassLiteral(x); } } @Override public void endVisit(JMethod x, Context ctx) { if (x.isJsMethodVarargs()) { // ImplementJsVarargs might insert an array creation for the varargs parameter which is not // seen by this pass. JParameter varargsParameter = Iterables.getLast(x.getParams()); assert varargsParameter.isVarargs(); resolveClassLiteralField(((JArrayType) varargsParameter.getType()).getLeafType()); } } @Override public void endVisit(JsniClassLiteral x, Context ctx) { // JsniClassLiterals will be traversed explicitly in JsniMethodBody. For each JsniClassLiteral // in JsniMethodBody.classRefs there is at least a corresponding JsNameRef (with a jsni // identifier) that needs to be altered accordingly. } @Override public void endVisit(final JsniMethodBody jsniMethodBody, Context ctx) { if (jsniMethodBody.getClassRefs().size() == 0) { return; } final Multimap<String, JsniClassLiteral> jsniClassLiteralsByJsniReference = ArrayListMultimap.create(); final JMethod getClassLiteralForArrayMethod = program.getIndexedMethod(RuntimeConstants.ARRAY_GET_CLASS_LITERAL_FOR_ARRAY); final String getClassLiteralForArrayMethodIdent = "@" + getClassLiteralForArrayMethod.getJsniSignature(true, false); boolean areThereArrayClassLiterals = false; for (JsniClassLiteral jsniClassLiteral : jsniMethodBody.getClassRefs()) { // Check for the presence of array class literals. if (jsniClassLiteral.getRefType() instanceof JArrayType) { areThereArrayClassLiterals = true; } else { // Resolve non array class literals. resolveClassLiteral(jsniClassLiteral); } // Map JSNI reference string to the actual JsniClassLiterals. jsniClassLiteralsByJsniReference.put(jsniClassLiteral.getIdent(), jsniClassLiteral); } if (!areThereArrayClassLiterals) { // No array class literal no need to explore the body. return; } final Set<JsniClassLiteral> newClassRefs = Sets.newLinkedHashSet(); // Replace all arrays JSNI class literals with the its construction via the leaf type class // literal. JsModVisitor replaceJsniClassLiteralVisitor = new JsModVisitor() { @Override public void endVisit(JsNameRef x, JsContext ctx) { if (!x.isJsniReference()) { return; } if (jsniClassLiteralsByJsniReference.get(x.getIdent()).isEmpty()) { // The JsNameRef is not a class literal. return; } JsniClassLiteral jsniClassLiteral = jsniClassLiteralsByJsniReference.get( x.getIdent()).iterator().next(); jsniClassLiteralsByJsniReference.remove(x.getIdent(), jsniClassLiteral); if (jsniClassLiteral.getRefType() instanceof JArrayType) { // Replace the array class literal by an expression that retrieves it from // that of the leaf type. JArrayType arrayType = (JArrayType) jsniClassLiteral.getRefType(); JType leafType = arrayType.getLeafType(); jsniClassLiteral = new JsniClassLiteral(jsniClassLiteral.getSourceInfo(), leafType); // Array.getClassLiteralForArray(leafType.class, dimensions) SourceInfo info = x.getSourceInfo(); JsNameRef getArrayClassLiteralMethodNameRef = new JsNameRef(info, getClassLiteralForArrayMethodIdent); JsInvocation invocation = new JsInvocation(info, getArrayClassLiteralMethodNameRef, new JsNameRef(info, jsniClassLiteral.getIdent()), new JsNumberLiteral(info, arrayType.getDims())); // Finally resolve the class literal. resolveClassLiteral(jsniClassLiteral); ctx.replaceMe(invocation); } newClassRefs.add(jsniClassLiteral); } }; replaceJsniClassLiteralVisitor.accept(jsniMethodBody.getFunc()); if (!replaceJsniClassLiteralVisitor.didChange()) { // Nothing was changed, no need to replace JsniMethodBody. return; } JsniMethodBody newBody = new JsniMethodBody(jsniMethodBody.getSourceInfo(), jsniMethodBody.getFunc(), Lists.newArrayList(newClassRefs), jsniMethodBody.getJsniFieldRefs(), jsniMethodBody.getJsniMethodRefs(), jsniMethodBody.getUsedStrings()); // Add getClassLiteralForArray as a JsniMethodRef. newBody.addJsniRef( new JsniMethodRef(jsniMethodBody.getSourceInfo(), getClassLiteralForArrayMethodIdent, getClassLiteralForArrayMethod, program.getJavaScriptObject())); ctx.replaceMe(newBody); } } public static void exec(JProgram program, boolean shouldOptimize) { Event normalizerEvent = SpeedTracerLogger.start(CompilerEventType.NORMALIZER); new ImplementClassLiteralsAsFields(program, shouldOptimize).execImpl(); normalizerEvent.end(); } private final Map<JType, JField> classLiteralFields = Maps.newIdentityHashMap(); private final JMethodBody classLiteralHolderClinitBody; private final JProgram program; private final JClassType typeClassLiteralHolder; private final boolean shouldOptimize; private ImplementClassLiteralsAsFields(JProgram program, boolean shouldOptimize) { this.program = program; this.typeClassLiteralHolder = program.getTypeClassLiteralHolder(); this.classLiteralHolderClinitBody = (JMethodBody) typeClassLiteralHolder.getClinitMethod().getBody(); this.shouldOptimize = shouldOptimize; assert program.getDeclaredTypes().contains(typeClassLiteralHolder); } private JLiteral getSuperclassClassLiteral(SourceInfo info, JType type) { if (!(type instanceof JClassType) || ((JClassType) type).getSuperClass() == null) { return JNullLiteral.INSTANCE; } JClassType superClass = ((JClassType) type).getSuperClass(); if (superClass.isJsNative()) { // Class object for subclasses of native JsType will return Object.class as their super class // class literal; this is done so that in invariant that "super" class literals are literals // of a actual superclass. superClass = program.getTypeJavaLangObject(); } return createDependentClassLiteral(info, superClass); } private JClassLiteral createDependentClassLiteral(SourceInfo info, JType type) { JClassLiteral classLiteral = new JClassLiteral(info.makeChild(), type); classLiteral.setField(resolveClassLiteralField(classLiteral.getRefType())); return classLiteral; } private void execImpl() { if (!shouldOptimize) { // Create all class literals regardless of whether they are referenced or not. for (JPrimitiveType type : JPrimitiveType.types) { resolveClassLiteralField(type); } for (JType type : program.getDeclaredTypes()) { resolveClassLiteralField(type); } } NormalizeVisitor visitor = new NormalizeVisitor(); visitor.accept(program); program.recordClassLiteralFields(classLiteralFields); } /** * Create an expression that will evaluate, at run time, to the class literal. * Causes recursive literal create (super type, array element type). Examples: * * Class: * * <pre> * Class.createForClass("java.lang.", "Object", /JRuntimeTypeReference/"java.lang.Object", null) * Class.createForClass("java.lang.", "Exception", /JRuntimeTypeReference/"java.lang.Exception", * Throwable.class) * </pre> * * Interface: * * <pre> * Class.createForInterface("java.lang.", "Comparable") * </pre> * * Arrays are lazily created. * * Primitive: * * <pre> * Class.createForPrimitive("", "int", "I") * </pre> * * Enum: * * <pre> * Class.createForEnum("com.example.", "MyEnum", /JRuntimeTypeReference/"com.example.MyEnum", * Enum.class, public static MyEnum[] values(), public static MyEnum valueOf(String name)) * </pre> * * Enum subclass: * * <pre> * Class.createForEnum("com.example.", "MyEnum$1", /JRuntimeTypeReference/"com.example.MyEnum$1", * MyEnum.class, null, null)) * </pre> */ private JMethodCall createLiteralCall(SourceInfo info, JProgram program, JType type) { type = type.getUnderlyingType(); Class<? extends JType> typeClass = type.getClass(); if (type.isEnumOrSubclass() != null) { typeClass = JEnumType.class; } return literalFactoryMethodByTypeClass.get(typeClass).createCall(info, program, type, getSuperclassClassLiteral(info, type)); } private void resolveClassLiteral(JClassLiteral x) { JField field = resolveClassLiteralField(x.getRefType()); x.setField(field); } /** * Resolve a class literal field. Takes the form: * * <pre> * class ClassLiteralHolder { * Class Ljava_lang_Object_2_classLit = * Class.createForClass("java.lang.", "Object", /JNameOf/"java.lang.Object", null) * } * </pre> */ private JField resolveClassLiteralField(JType type) { type = type.isJsNative() || type.isJsFunction() || type.isJsFunctionImplementation() ? program.getJavaScriptObject() : program.normalizeJsoType(type); JField field = classLiteralFields.get(type); if (field == null) { // Create the allocation expression FIRST since this may be recursive on // super type (this forces the super type classLit to be created first). SourceInfo info = type.getSourceInfo().makeChild(); assert !(type instanceof JArrayType); JMethodCall classLiteralCreationExpression = createLiteralCall(info, program, type); // Create a field in the class literal holder to hold the object. field = new JField(info, getClassLiteralFieldName(type), typeClassLiteralHolder, program .getTypeJavaLangClass(), true, Disposition.FINAL); typeClassLiteralHolder.addField(field); info.addCorrelation(info.getCorrelator().by(Literal.CLASS)); // Initialize the field. JFieldRef fieldRef = new JFieldRef(info, null, field, typeClassLiteralHolder); JDeclarationStatement decl = new JDeclarationStatement(info, fieldRef, classLiteralCreationExpression); classLiteralHolderClinitBody.getBlock().addStmt(decl); classLiteralFields.put(type, field); } return field; } private static String getClassLiteralFieldName(JType type) { return JjsUtils.classLiteralFieldNameFromJavahTypeSignatureName(type.getJavahSignatureName()); } }