blob: 7b2f3f73baed51ccc72f59d75dbe2e46d3d059cc [file] [log] [blame]
/*
* 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() {
}
}