/* * 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.PrecompileTaskOptions; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.jjs.ast.HasJsInfo.JsMemberType; import com.google.gwt.dev.jjs.ast.HasName; import com.google.gwt.dev.jjs.ast.HasType; import com.google.gwt.dev.jjs.ast.JArrayType; import com.google.gwt.dev.jjs.ast.JBinaryOperation; import com.google.gwt.dev.jjs.ast.JBinaryOperator; import com.google.gwt.dev.jjs.ast.JBlock; import com.google.gwt.dev.jjs.ast.JBooleanLiteral; import com.google.gwt.dev.jjs.ast.JCharLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JConstructor; import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JDoubleLiteral; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JExpressionStatement; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JFloatLiteral; import com.google.gwt.dev.jjs.ast.JIntLiteral; import com.google.gwt.dev.jjs.ast.JInterfaceType; import com.google.gwt.dev.jjs.ast.JLiteral; import com.google.gwt.dev.jjs.ast.JLongLiteral; import com.google.gwt.dev.jjs.ast.JMember; 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.JNewInstance; 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.JStatement; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JThisRef; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.js.JMultiExpression; import com.google.gwt.dev.js.ast.JsBooleanLiteral; import com.google.gwt.dev.js.ast.JsExpression; import com.google.gwt.dev.js.ast.JsLiteral; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsNullLiteral; import com.google.gwt.dev.js.ast.JsNumberLiteral; import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsStringLiteral; import com.google.gwt.lang.LongLib; 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.base.Predicate; import com.google.gwt.thirdparty.guava.common.base.Predicates; import com.google.gwt.thirdparty.guava.common.base.Strings; import com.google.gwt.thirdparty.guava.common.collect.Collections2; import com.google.gwt.thirdparty.guava.common.collect.FluentIterable; 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.Sets; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; /** * General utilities related to Java AST manipulation. */ public class JjsUtils { public static boolean closureStyleLiteralsNeeded(PrecompileTaskOptions options) { return closureStyleLiteralsNeeded(options.isIncrementalCompileEnabled(), options.isClosureCompilerFormatEnabled()); } /** * Returns the class literal field name. */ public static String classLiteralFieldNameFromJavahTypeSignatureName(String javahSignatureName) { return javahSignatureName + "_classLit"; } public static boolean closureStyleLiteralsNeeded(boolean incremental, boolean closureOutputFormat) { return !incremental && closureOutputFormat; } public static String computeSignature( String name, List<JType> params, JType returnType, boolean isCtor) { StringBuilder sb = new StringBuilder(name); sb.append('('); for (JType type : params) { sb.append(type.getJsniSignatureName()); } sb.append(')'); if (!isCtor) { sb.append(returnType.getJsniSignatureName()); } else { sb.append(" <init>"); } return sb.toString(); } public static String constructManglingSignature(JMethod x, String partialSignature) { StringBuilder sb = new StringBuilder(partialSignature); sb.append("__"); for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) { JType type = x.getOriginalParamTypes().get(i); sb.append(type.getJavahSignatureName()); } sb.append(x.getOriginalReturnType().getJavahSignatureName()); return sb.toString(); } /** * Returns an instantiation expression for {@code type} using the default constructor, * Returns {@code null} if {@code type} does not have a default constructor. */ public static JExpression createDefaultConstructorInstantiation( SourceInfo info, JClassType type) { /* * Find the appropriate (noArg) constructor. In our AST, constructors are * instance methods that should be qualified with a new expression. */ JConstructor noArgCtor = (JConstructor) FluentIterable.from(type.getMethods()).firstMatch( new Predicate<JMethod>() { @Override public boolean apply(JMethod method) { return method instanceof JConstructor && method.getOriginalParamTypes().size() == 0; } }).orNull(); if (noArgCtor == null) { return null; } // Call it, using a new expression as a qualifier return new JNewInstance(info, noArgCtor); } /** * Creates a synthetic forwarding stub in {@code type} with the same signature as * {@code superTypeMethod} that dispatchs to that method. */ public static JMethod createForwardingMethod(JDeclaredType type, JMethod methodToDelegateTo) { JMethod forwardingMethod = createEmptyMethodFromExample(type, methodToDelegateTo, false); forwardingMethod.setForwarding(); if (type.isJsNative()) { if (methodToDelegateTo.isJsNative()) { // Accidental override of native methods on native JsTypes are done by just redeclaring the // native method. return forwardingMethod; } // Otherwise the forwarding method is an overlay method with a proper body. forwardingMethod.setJsOverlay(); forwardingMethod.setBody(new JMethodBody(methodToDelegateTo.getSourceInfo())); } // Create the forwarding body. JMethodBody body = (JMethodBody) forwardingMethod.getBody(); // Invoke methodToDelegate JMethodCall forwardingCall = new JMethodCall(methodToDelegateTo.getSourceInfo(), new JThisRef(methodToDelegateTo.getSourceInfo(), type), methodToDelegateTo); forwardingCall.setStaticDispatchOnly(); // copy params for (JParameter p : forwardingMethod.getParams()) { forwardingCall.addArg(p.makeRef(p.getSourceInfo())); } // return statement if not void return type body.getBlock().addStmt(makeMethodEndStatement(forwardingMethod.getType(), forwardingCall)); return forwardingMethod; } /** * Creates a multi expression from a list of expressions, removing expressions that do * not have side effects if possible. */ public static JExpression createOptimizedMultiExpression(JExpression... expressions) { return createOptimizedMultiExpression(false, Arrays.asList(expressions)); } /** * Creates a multi expression from a list of expressions, removing expressions that do * not have side effects if possible. */ public static JExpression createOptimizedMultiExpression(boolean ignoringResult, List<JExpression> expressions) { int numberOfExpressions = expressions.size(); JExpression result = expressions.get(numberOfExpressions - 1); numberOfExpressions = expressions.size(); if (numberOfExpressions == 0) { return new JMultiExpression(SourceOrigin.UNKNOWN); } expressions = Lists.newArrayList(Collections2.filter( expressions.subList(0, numberOfExpressions - 1), Predicates.and( Predicates.notNull(), new Predicate<JExpression>() { @Override public boolean apply(JExpression expression) { return expression.hasSideEffects(); } }))); if (result != null && (!ignoringResult || result.hasSideEffects())) { expressions.add(result); } if (expressions.size() == 1) { // Do not create a multi expression if it consists only of the result. return expressions.iterator().next(); } SourceInfo info = expressions.size() > 0 ? expressions.get(0).getSourceInfo() : SourceOrigin.UNKNOWN; return new JMultiExpression(info, expressions); } /** * Returns an ast node representing the expression {@code expression != null}. */ public static JExpression createOptimizedNotNullComparison( JProgram program, SourceInfo info, JExpression expression) { JReferenceType type = (JReferenceType) expression.getType(); if (type.isNullType()) { return program.getLiteralBoolean(false); } if (!type.canBeNull()) { return createOptimizedMultiExpression(expression, program.getLiteralBoolean(true)); } return new JBinaryOperation(info, program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ, expression, program.getLiteralNull()); } public static String getIndexedName(JMember member) { return member.getEnclosingType().getShortName() + '.' + member.getName(); } /** * Creates a synthetic abstract stub in {@code type} with the same signature as * {@code superTypeMethod}. */ public static JMethod createSyntheticAbstractStub(JDeclaredType type, JMethod superTypeMethod) { assert type.isAbstract(); assert superTypeMethod.isAbstract(); return createEmptyMethodFromExample(type, superTypeMethod, true); } /** * Returns a native constructor of a native JsType class. */ public static JConstructor getJsNativeConstructorOrNull(JType type) { if (!type.isJsNative() || !(type.getUnderlyingType() instanceof JClassType)) { return null; } JMethod jsConstructor = Iterables.getFirst(Iterables.filter( ((JClassType) type).getMethods(), JjsPredicates.IS_JS_CONSTRUCTOR), null); assert jsConstructor != null; return (JConstructor) jsConstructor; } /** * Returns a description for a type suitable for reporting errors to the users. */ public static String getReadableDescription(JType type) { if (type instanceof JArrayType) { JArrayType arrayType = (JArrayType) type; return getReadableDescription(arrayType.getLeafType()) + Strings.repeat("[]", arrayType.getDims()); } return Joiner.on(".").join(type.getCompoundName()); } /** * Returns a description for a member suitable for reporting errors to the users. */ public static String getReadableDescription(JMember member) { if (member instanceof JField) { return String.format("%s %s.%s", getReadableDescription(member.getType()), getReadableDescription(member.getEnclosingType()), member.getName()); } JMethod method = (JMethod) member; String printableDescription = ""; if (!method.isConstructor()) { printableDescription += getReadableDescription(method.getType()) + " "; } printableDescription += String.format("%s.%s(%s)", getReadableDescription(method.getEnclosingType()), method.getName(), Joiner.on(", ").join( Iterables.transform(method.getOriginalParamTypes(), new Function<JType, String>() { @Override public String apply(JType type) { return getReadableDescription(type); } } ))); return printableDescription; } public static void replaceMethodBody(JMethod method, JExpression returnValue) { JMethodBody body = (JMethodBody) method.getBody(); JBlock block = body.getBlock(); block.clear(); block.addStmt(returnValue.makeReturnStatement()); } /** * Returns types from typed nodes. */ public static Iterable<JReferenceType> getExpressionTypes(Iterable<? extends HasType> nodes) { if (nodes == null) { return Collections.emptyList(); } return FluentIterable.from(nodes).transform( new Function<HasType, JReferenceType>() { @Override public JReferenceType apply(HasType typedNode) { return (JReferenceType) typedNode.getType(); } }); } /** * Returns true if the method is a synthetic accidental override that trivially dispatches to its * same name super. */ public static boolean isJsMemberUnnecessaryAccidentalOverride(JMethod method) { // Assumptions on synthethic overrides, if any of these change. assert !method.isSyntheticAccidentalOverride() || !method.exposesPackagePrivateMethod(); if (!method.isSyntheticAccidentalOverride()) { return false; } boolean overridesConcreteJsMethod = Iterables.any(method.getOverriddenMethods(), new Predicate<JMethod>() { @Override public boolean apply(JMethod method) { return !method.isAbstract() && method.getJsMemberType() != JsMemberType.NONE; } }); // A synthetic accidental override is unnecessary if its due to a JsMethod that does not expose // a non JsMember. return overridesConcreteJsMethod && !method.exposesNonJsMember(); } /** * Mangles a qualified name into a Javah signature. */ public static String javahSignatureFromName(String name) { return "L" + mangledNameString(name) + "_2"; } public static String mangleMemberName(String enclosingTypeName, String fieldName) { return mangledNameString(enclosingTypeName) + '_' + mangledNameString(fieldName); } /** * Returns an valid identifier for a named Java entity. */ public static String mangledNameString(HasName hasName) { return mangledNameString(hasName.getName()); } /** * Returns an valid identifier for a named Java entity. */ public static String mangledNameString(String name) { return name.replaceAll("_", "_1").replace('.', '_'); } /** * Returns the ending statement for a method based on an expression. If the return type is void * then the ending statement just executes the expression otherwise it returns it. */ public static JStatement makeMethodEndStatement(JType returnType, JExpression expression) { // TODO(rluble): Check if something needs to be done here regarding boxing/unboxing/coercions // when one of the types of expression and returnType is a boxed type and the other a primitive // type or both are primitive of differnent coerceable types. Add the proper tests first. return returnType == JPrimitiveType.VOID ? expression.makeStatement() : expression.makeReturnStatement(); } /** * Translates a Java literal into a JavaScript literal. */ public static JsLiteral translateLiteral(JLiteral literal) { return translatorByLiteralClass.get(literal.getClass()).translate(literal); } static void synthesizeStaticInitializerChain( JDeclaredType type, Iterable<JInterfaceType> superInterfacesRequiringStaticInitialization) { // Implement static initialization as described in (Java 8) JLS 12.4.2. List<JStatement> superClinitCalls = Lists.newArrayList(); SourceInfo sourceInfo = type.getSourceInfo(); // First call the static initializer for the superclass. JClassType superClass = type.getSuperClass(); if (superClass != null) { superClinitCalls.add( new JMethodCall(sourceInfo, null, superClass.getClinitMethod()).makeStatement()); } // Recurse over interfaces in preorder initializing the ones that have default methods. for (JInterfaceType interfaceType : superInterfacesRequiringStaticInitialization) { superClinitCalls.add( new JMethodCall(sourceInfo, null, interfaceType.getClinitMethod()).makeStatement()); } JMethodBody body = (JMethodBody) type.getClinitMethod().getBody(); body.getBlock().getStatements().addAll(0, superClinitCalls); } private static Map<Class<? extends JLiteral>, LiteralTranslators> translatorByLiteralClass = new ImmutableMap.Builder<Class<? extends JLiteral>, LiteralTranslators>() .put(JBooleanLiteral.class, LiteralTranslators.BOOLEAN_LITERAL_TRANSLATOR) .put(JCharLiteral.class, LiteralTranslators.CHAR_LITERAL_TRANSLATOR) .put(JFloatLiteral.class, LiteralTranslators.FLOAT_LITERAL_TRANSLATOR) .put(JDoubleLiteral.class, LiteralTranslators.DOUBLE_LITERAL_TRANSLATOR) .put(JIntLiteral.class, LiteralTranslators.INT_LITERAL_TRANSLATOR) .put(JLongLiteral.class, LiteralTranslators.LONG_LITERAL_TRANSLATOR) .put(JNullLiteral.class, LiteralTranslators.NULL_LITERAL_TRANSLATOR) .put(JStringLiteral.class, LiteralTranslators.STRING_LITERAL_TRANSLATOR) .build(); /** * Return true if the statement is an empty block. */ public static boolean isEmptyBlock(JStatement stmt) { if (stmt == null) { return true; } return (stmt instanceof JBlock && ((JBlock) stmt).getStatements().isEmpty()); } private enum LiteralTranslators { BOOLEAN_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { return JsBooleanLiteral.get(((JBooleanLiteral) literal).getValue()); } }, CHAR_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { return new JsNumberLiteral(literal.getSourceInfo(), ((JCharLiteral) literal).getValue()); } }, FLOAT_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { return new JsNumberLiteral(literal.getSourceInfo(), ((JFloatLiteral) literal).getValue()); } }, DOUBLE_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { return new JsNumberLiteral(literal.getSourceInfo(), ((JDoubleLiteral) literal).getValue()); } }, INT_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { return new JsNumberLiteral(literal.getSourceInfo(), ((JIntLiteral) literal).getValue()); } }, LONG_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { SourceInfo sourceInfo = literal.getSourceInfo(); long[] values = LongLib.getAsLongArray(((JLongLiteral) literal).getValue()); if (values.length == 1) { return new JsNumberLiteral(literal.getSourceInfo(), ((JLongLiteral) literal).getValue()); } JsObjectLiteral.Builder objectLiteralBuilder = JsObjectLiteral.builder(sourceInfo) .setInternable(); assert values.length == longComponentNames.length; for (int i = 0; i < longComponentNames.length; i++) { addPropertyToObject(sourceInfo, longComponentNames[i], values[i], objectLiteralBuilder); } return objectLiteralBuilder.build(); } }, STRING_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { return new JsStringLiteral(literal.getSourceInfo(), ((JStringLiteral) literal).getValue()); } }, NULL_LITERAL_TRANSLATOR() { @Override JsLiteral translate(JExpression literal) { return JsNullLiteral.INSTANCE; } }; private static String[] longComponentNames = { "l", "m", "h" }; abstract JsLiteral translate(JExpression literal); } private static void addPropertyToObject(SourceInfo sourceInfo, String propertyName, long propertyValue, JsObjectLiteral.Builder objectLiteralBuilder) { JsExpression value = new JsNumberLiteral(sourceInfo, propertyValue); objectLiteralBuilder.add(new JsNameRef(sourceInfo, propertyName), value); } private static JMethod createEmptyMethodFromExample( JDeclaredType inType, JMethod exampleMethod, boolean isAbstract) { JMethod emptyMethod = new JMethod(exampleMethod.getSourceInfo(), exampleMethod.getName(), inType, exampleMethod.getType(), isAbstract, false, false, exampleMethod.getAccess()); emptyMethod.addThrownExceptions(exampleMethod.getThrownExceptions()); emptyMethod.setSynthetic(); // Copy parameters. for (JParameter param : exampleMethod.getParams()) { emptyMethod.cloneParameter(param); } // If the enclosing type is native, make sure the synthetic empty method is native by leaving // the body = null. if (!inType.isJsNative()) { JMethodBody body = new JMethodBody(exampleMethod.getSourceInfo()); emptyMethod.setBody(body); } emptyMethod.freezeParamTypes(); inType.addMethod(emptyMethod); return emptyMethod; } /** * Extracts the this(..) or super(..) call from a statement if the statement is of the expected * form. Otherwise returns null. */ public static JMethodCall getThisOrSuperConstructorCall( JStatement statement) { if (!(statement instanceof JExpressionStatement)) { return null; } JExpressionStatement expressionStatement = (JExpressionStatement) statement; if (!(expressionStatement.getExpr() instanceof JMethodCall) || expressionStatement.getExpr() instanceof JNewInstance) { return null; } JMethodCall call = (JMethodCall) expressionStatement.getExpr(); if (call.getTarget() instanceof JConstructor && call.isStaticDispatchOnly()) { return call; } return null; } /** * Gets all the supertypes of {@code type}. */ public static Set<JDeclaredType> getSupertypes(JDeclaredType type) { Set<JDeclaredType> superTypes = Sets.newHashSet(); addAllSuperTypes(type, superTypes); return superTypes; } /** * Adds all the supertypes of {@code type} to {@code types}. */ public static void addAllSuperTypes(JDeclaredType type, Set<JDeclaredType> types) { if (type.getSuperClass() != null) { types.add(type.getSuperClass()); addAllSuperTypes(type.getSuperClass(), types); } for (JInterfaceType interfaceType : type.getImplements()) { types.add(interfaceType); addAllSuperTypes(interfaceType, types); } } /** * Returns the JsConstructor for a class or null if it does not have any. */ public static JConstructor getJsConstructor(JDeclaredType type) { return FluentIterable .from(type.getConstructors()) .filter(new Predicate<JConstructor>() { @Override public boolean apply(JConstructor constructor) { return constructor.isJsConstructor(); } }).first().orNull(); } /** * Returns the constructor which this constructor delegates or null if none. */ public static JConstructor getDelegatedThisOrSuperConstructor(JConstructor constructor) { JStatement contructorInvocaton = FluentIterable .from(constructor.getBody().getStatements()) .filter(new Predicate<JStatement>() { @Override public boolean apply(JStatement statement) { return getThisOrSuperConstructorCall(statement) != null; } }).first().orNull(); return contructorInvocaton != null ? (JConstructor) getThisOrSuperConstructorCall(contructorInvocaton).getTarget() : null; } /** * Returns the constructor which all others delegate to if any, otherwise null. */ public static JConstructor getPrimaryConstructor(final JDeclaredType type) { List<JConstructor> delegatedSuperConstructors = FluentIterable .from(type.getConstructors()) .filter(new Predicate<JConstructor>() { @Override public boolean apply(JConstructor constructor) { // Calls super constructor. return getDelegatedThisOrSuperConstructor(constructor).getEnclosingType() != type; } }) .limit(2) .toList(); if (delegatedSuperConstructors.size() == 1) { return delegatedSuperConstructors.get(0); } return null; } /** * Returns the nearest native superclass of {@code type} if any, null otherwise. */ public static JClassType getNativeSuperClassOrNull(JDeclaredType type) { JClassType superClass = type.getSuperClass(); if (superClass == null || superClass.isJsNative()) { return superClass; } return getNativeSuperClassOrNull(superClass); } /** * Whether or not to use the JsName when implementing this member. * * <p>A member should only require a JsName when a JsName has been assigned and the compilation * has been configured to honor those names. * */ public static boolean requiresJsName(JMember member) { // JsFunction interfaces and implementations do not have JsNames but canBeReferencedExternally // or canBeImplementedExternally. return member.getJsMemberType() != JsMemberType.NONE && (member.canBeImplementedExternally() || member.canBeReferencedExternally()); } private JjsUtils() { } }