/* * 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.InternalCompilerException; 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.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.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JSeedIdOf; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; 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.IdentityHashMap; import java.util.Map; /** * 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 'seedId' from the global seedTable object, and so each * class literal factory method is passed the seedId of its type. * <p> */ public class ImplementClassLiteralsAsFields { private class NormalizeVisitor extends JModVisitor { @Override public void endVisit(JClassLiteral x, Context ctx) { JField field = resolveClassLiteralField(x.getRefType()); x.setField(field); } } public static void exec(JProgram program) { Event normalizerEvent = SpeedTracerLogger.start(CompilerEventType.NORMALIZER); new ImplementClassLiteralsAsFields(program).execImpl(); normalizerEvent.end(); } private static String createIdent(JMethod method) { StringBuilder sb = new StringBuilder(); sb.append(method.getEnclosingType().getName()); sb.append("::"); sb.append(method.getName()); sb.append('('); for (JType type : method.getOriginalParamTypes()) { sb.append(type.getJsniSignatureName()); } sb.append(')'); return sb.toString(); } private static String getClassName(String fullName) { int pos = fullName.lastIndexOf("."); return fullName.substring(pos + 1); } private static String getPackageName(String fullName) { int pos = fullName.lastIndexOf("."); return fullName.substring(0, pos + 1); } private final Map<JType, JField> classLiteralFields = new IdentityHashMap<JType, JField>(); private final JMethodBody classLiteralHolderClinitBody; private final JProgram program; private final JClassType typeClassLiteralHolder; private ImplementClassLiteralsAsFields(JProgram program) { this.program = program; this.typeClassLiteralHolder = program.getTypeClassLiteralHolder(); this.classLiteralHolderClinitBody = (JMethodBody) typeClassLiteralHolder.getMethods().get(0).getBody(); assert program.getDeclaredTypes().contains(typeClassLiteralHolder); } /** * 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", /JSeedIdOf/"java.lang.Object", null) * Class.createForClass("java.lang.", "Exception", /JSeedIdOf/"java.lang.Exception", Throwable.class) * </pre> * * Interface: * * <pre> * Class.createForInterface("java.lang.", "Comparable") * </pre> * * Primitive: * * <pre> * Class.createForPrimitive("", "int", " I") * </pre> * * Array: * * <pre> * Class.createForArray("", "[I", /JSeedIdOf/"com.google.gwt.lang.Array", int.class) * Class.createForArray("[Lcom.example.", "Foo;", /JSeedIdOf/"com.google.gwt.lang.Array", Foo.class) * </pre> * * Enum: * * <pre> * Class.createForEnum("com.example.", "MyEnum", /JSeedIdOf/"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", /JSeedIdOf/"com.example.MyEnum$1", MyEnum.class, * null, null)) * </pre> */ private JMethodCall computeClassObjectAllocation(SourceInfo info, JType type) { String typeName = getTypeName(type); JMethod method = program.getIndexedMethod(type.getClassLiteralFactoryMethod()); /* * Use the classForEnum() constructor even for enum subtypes to aid in * pruning supertype data. */ boolean isEnumOrSubclass = false; if (type instanceof JClassType) { JEnumType maybeEnum = ((JClassType) type).isEnumOrSubclass(); if (maybeEnum != null) { isEnumOrSubclass = true; method = program.getIndexedMethod(maybeEnum.getClassLiteralFactoryMethod()); } } assert method != null; JMethodCall call = new JMethodCall(info, null, method); JStringLiteral packageName = program.getLiteralString(info, getPackageName(typeName)); JStringLiteral className = program.getLiteralString(info, getClassName(typeName)); call.addArgs(packageName, className); if (type instanceof JArrayType || type instanceof JClassType) { // Add the name of the seed function for concrete types call.addArg(new JSeedIdOf(info, program.getTypeJavaLangString(), type)); } else if (type instanceof JPrimitiveType) { // And give primitive types an illegal, though meaningful, value call.addArg(program.getLiteralString(info, " " + type.getJavahSignatureName())); } if (type instanceof JClassType) { /* * For non-array classes and enums, determine the class literal of the * supertype, if there is one. Arrays are excluded because they always * have Object as their superclass. */ JClassType classType = (JClassType) type; JLiteral superclassLiteral; if (classType.getSuperClass() != null) { superclassLiteral = createDependentClassLiteral(info, classType.getSuperClass()); } else { superclassLiteral = JNullLiteral.INSTANCE; } call.addArg(superclassLiteral); if (classType instanceof JEnumType) { JEnumType enumType = (JEnumType) classType; JMethod valuesMethod = null; JMethod valueOfMethod = null; for (JMethod methodIt : enumType.getMethods()) { if (methodIt.isStatic()) { if (methodIt.getSignature().startsWith("values()")) { valuesMethod = methodIt; } else if (methodIt.getSignature().startsWith("valueOf(Ljava/lang/String;)")) { valueOfMethod = methodIt; } } } if (valuesMethod == null) { throw new InternalCompilerException("Could not find enum values() method"); } if (valueOfMethod == null) { throw new InternalCompilerException("Could not find enum valueOf() method"); } call.addArg(new JsniMethodRef(info, createIdent(valuesMethod), valuesMethod, program .getJavaScriptObject())); call.addArg(new JsniMethodRef(info, createIdent(valueOfMethod), valueOfMethod, program .getJavaScriptObject())); } else if (isEnumOrSubclass) { // A subclass of an enum class call.addArg(JNullLiteral.INSTANCE); call.addArg(JNullLiteral.INSTANCE); } } else if (type instanceof JArrayType) { JArrayType arrayType = (JArrayType) type; JClassLiteral componentLiteral = createDependentClassLiteral(info, arrayType.getElementType()); call.addArg(componentLiteral); } else { assert (type instanceof JInterfaceType || type instanceof JPrimitiveType); } assert call.getArgs().size() == method.getParams().size() : "Argument / param mismatch " + call.toString() + " versus " + method.toString(); return call; } private JClassLiteral createDependentClassLiteral(SourceInfo info, JType type) { JClassLiteral classLiteral = new JClassLiteral(info.makeChild(), type); JField field = resolveClassLiteralField(classLiteral.getRefType()); classLiteral.setField(field); return classLiteral; } private void execImpl() { NormalizeVisitor visitor = new NormalizeVisitor(); visitor.accept(program); program.recordClassLiteralFields(classLiteralFields); } private String getTypeName(JType type) { String typeName; if (type instanceof JArrayType) { typeName = type.getJsniSignatureName().replace('/', '.'); // Mangle the class name to match hosted mode. if (program.isJavaScriptObject(((JArrayType) type).getLeafType())) { typeName = typeName.replace(";", "$;"); } } else { typeName = type.getName(); // Mangle the class name to match hosted mode. if (program.isJavaScriptObject(type)) { typeName += '$'; } } return typeName; } private JType normalizeJsoType(JType type) { if (program.isJavaScriptObject(type)) { return program.getJavaScriptObject(); } if (type instanceof JArrayType) { JArrayType aType = (JArrayType) type; if (program.isJavaScriptObject(aType.getLeafType())) { return program.getTypeArray(program.getJavaScriptObject(), aType.getDims()); } } return type; } /** * 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 = 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 = typeClassLiteralHolder.getSourceInfo().makeChild(); JMethodCall alloc = computeClassObjectAllocation(info, type); // Create a field in the class literal holder to hold the object. field = new JField(info, program.getClassLiteralName(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, alloc); classLiteralHolderClinitBody.getBlock().addStmt(decl); classLiteralFields.put(type, field); } return field; } }