blob: d472f40ee33d84bf0f027f7aeaa026b80702ca3c [file] [log] [blame]
/*
* Copyright 2008 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.javac.ArtificialRescueChecker.RescueData;
import com.google.gwt.dev.javac.JsniCollector;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.JJSOptions;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JArrayLength;
import com.google.gwt.dev.jjs.ast.JArrayRef;
import com.google.gwt.dev.jjs.ast.JArrayType;
import com.google.gwt.dev.jjs.ast.JAssertStatement;
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.JBreakStatement;
import com.google.gwt.dev.jjs.ast.JCaseStatement;
import com.google.gwt.dev.jjs.ast.JCastOperation;
import com.google.gwt.dev.jjs.ast.JCharLiteral;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JClassType;
import com.google.gwt.dev.jjs.ast.JConditional;
import com.google.gwt.dev.jjs.ast.JConstructor;
import com.google.gwt.dev.jjs.ast.JContinueStatement;
import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
import com.google.gwt.dev.jjs.ast.JDeclaredType;
import com.google.gwt.dev.jjs.ast.JDoStatement;
import com.google.gwt.dev.jjs.ast.JDoubleLiteral;
import com.google.gwt.dev.jjs.ast.JEnumField;
import com.google.gwt.dev.jjs.ast.JEnumType;
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.JField.Disposition;
import com.google.gwt.dev.jjs.ast.JFieldRef;
import com.google.gwt.dev.jjs.ast.JFloatLiteral;
import com.google.gwt.dev.jjs.ast.JForStatement;
import com.google.gwt.dev.jjs.ast.JIfStatement;
import com.google.gwt.dev.jjs.ast.JInstanceOf;
import com.google.gwt.dev.jjs.ast.JIntLiteral;
import com.google.gwt.dev.jjs.ast.JInterfaceType;
import com.google.gwt.dev.jjs.ast.JLabel;
import com.google.gwt.dev.jjs.ast.JLabeledStatement;
import com.google.gwt.dev.jjs.ast.JLiteral;
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JLongLiteral;
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.JNewArray;
import com.google.gwt.dev.jjs.ast.JNewInstance;
import com.google.gwt.dev.jjs.ast.JNode;
import com.google.gwt.dev.jjs.ast.JNonNullType;
import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JParameterRef;
import com.google.gwt.dev.jjs.ast.JPostfixOperation;
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
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.JReturnStatement;
import com.google.gwt.dev.jjs.ast.JStatement;
import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.jjs.ast.JSwitchStatement;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JThrowStatement;
import com.google.gwt.dev.jjs.ast.JTryStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JTypeOracle;
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.JVariable;
import com.google.gwt.dev.jjs.ast.JVariableRef;
import com.google.gwt.dev.jjs.ast.JWhileStatement;
import com.google.gwt.dev.jjs.ast.js.JsniClassLiteral;
import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
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.JsExpression;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.dev.util.collect.Maps;
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 org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ArrayReference;
import org.eclipse.jdt.internal.compiler.ast.AssertStatement;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.BreakStatement;
import org.eclipse.jdt.internal.compiler.ast.CaseStatement;
import org.eclipse.jdt.internal.compiler.ast.CastExpression;
import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
import org.eclipse.jdt.internal.compiler.ast.CombinedBinaryExpression;
import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment;
import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ContinueStatement;
import org.eclipse.jdt.internal.compiler.ast.DoStatement;
import org.eclipse.jdt.internal.compiler.ast.EmptyStatement;
import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.ForStatement;
import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression;
import org.eclipse.jdt.internal.compiler.ast.LabeledStatement;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.OR_OR_Expression;
import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
import org.eclipse.jdt.internal.compiler.ast.PostfixExpression;
import org.eclipse.jdt.internal.compiler.ast.PrefixExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedSuperReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.SuperReference;
import org.eclipse.jdt.internal.compiler.ast.SwitchStatement;
import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
import org.eclipse.jdt.internal.compiler.ast.WhileStatement;
import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
import org.eclipse.jdt.internal.compiler.impl.ByteConstant;
import org.eclipse.jdt.internal.compiler.impl.CharConstant;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
import org.eclipse.jdt.internal.compiler.impl.IntConstant;
import org.eclipse.jdt.internal.compiler.impl.LongConstant;
import org.eclipse.jdt.internal.compiler.impl.ShortConstant;
import org.eclipse.jdt.internal.compiler.impl.StringConstant;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.VariableBinding;
import org.eclipse.jdt.internal.compiler.util.Util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This is the big kahuna where most of the nitty gritty of creating our AST
* happens. BuildTypeMap must have already run so we have valid mappings between
* JDT nodes and our already-created AST nodes.
*/
public class GenerateJavaAST {
/**
* Visit the JDT AST and produce our own AST into the passed-in TypeMap's
* JProgram. By the end of this pass, the produced AST should contain every
* piece of information we'll ever need about the code. The JDT nodes should
* never again be referenced after this.
*
* This is implemented as a reflective visitor for JDT's AST. The advantage of
* doing it reflectively is that if we run into any JDT nodes we can't handle,
* we'll automatically throw an exception. If we had subclassed
* {@link org.eclipse.jdt.internal.compiler.ast.ASTNode} we'd have to override
* every single method and explicitly throw an exception to get the same
* behavior.
*
* NOTE ON JDT FORCED OPTIMIZATIONS - If JDT statically determines that a
* section of code in unreachable, it won't fully resolve that section of
* code. This invalid-state code causes us major problems. As a result, we
* have to optimize out those dead blocks early and never try to translate
* them to our AST.
*/
// Reflective invocation causes unused warnings.
@SuppressWarnings("unused")
private static class JavaASTGenerationVisitor {
/**
* Used to cache {@link Method} lookups.
*/
private static class MethodKey {
private final Class<? extends Object> childClass;
private final String name;
public MethodKey(String name, Class<? extends Object> childClass) {
this.name = name;
this.childClass = childClass;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MethodKey) {
MethodKey otherKey = (MethodKey) obj;
return name.equals(otherKey.name) && childClass.equals(otherKey.childClass);
}
return super.equals(obj);
}
@Override
public int hashCode() {
return name.hashCode() + (101 * childClass.hashCode());
}
}
/**
* Used to cache {@link Method} lookups.
*/
private static class MethodValue {
private final NoSuchMethodException ex;
private final Method method;
public MethodValue(Method method) {
this.method = method;
this.ex = null;
}
public MethodValue(NoSuchMethodException ex) {
this.ex = ex;
this.method = null;
}
public Method getMethod() throws NoSuchMethodException {
if (this.ex != null) {
throw (ex);
}
return method;
}
}
/**
* The literal for the JLS identifier that represents the length field on an
* array.
*/
private static final String ARRAY_LENGTH_FIELD = "length";
private static InternalCompilerException translateException(JNode node, Throwable e) {
if (e instanceof VirtualMachineError) {
// Always rethrow VM errors (an attempt to wrap may fail).
throw (VirtualMachineError) e;
}
InternalCompilerException ice;
if (e instanceof InternalCompilerException) {
ice = (InternalCompilerException) e;
ice.addNode(node);
} else {
ice = new InternalCompilerException(node, "Error constructing Java AST", e);
}
return ice;
}
private final AutoboxUtils autoboxUtils;
private JDeclaredType currentClass;
private ClassScope currentClassScope;
private String currentFileName;
private JMethod currentMethod;
private JMethodBody currentMethodBody;
private MethodScope currentMethodScope;
private Map<JField, JParameter> currentOuterThisRefParams;
private int[] currentSeparatorPositions;
private final boolean disableClassMetadata;
private final boolean enableAsserts;
private final Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap =
new HashMap<JsniMethodBody, AbstractMethodDeclaration>();
private final Map<JMethod, Map<String, JLabel>> labelMap =
new IdentityHashMap<JMethod, Map<String, JLabel>>();
private final Map<MethodKey, MethodValue> methodCache = new HashMap<MethodKey, MethodValue>();
private final JProgram program;
private final TypeMap typeMap;
public JavaASTGenerationVisitor(TypeMap typeMap, JProgram program, JJSOptions options) {
this.typeMap = typeMap;
this.program = program;
this.enableAsserts = options.isEnableAssertions();
/*
* TODO: Determine if this should be controlled by a compiler flag or a
* module property.
*/
this.disableClassMetadata = options.isClassMetadataDisabled();
autoboxUtils = new AutoboxUtils(program);
}
/**
* <p>
* Add a bridge method to <code>clazzBinding</code> for any method it
* inherits that implements an interface method but that has a different
* erased signature from the interface method.
* </p>
*
* <p>
* The need for these bridges was pointed out in issue 3064. The goal is
* that virtual method calls through an interface type are translated to
* JavaScript that will function correctly. If the interface signature
* matches the signature of the implementing method, then nothing special
* needs to be done. If they are different, due to the use of generics, then
* GenerateJavaScriptAST is careful to do the right thing. There is a
* remaining case, though, that GenerateJavaScriptAST is not in a good
* position to fix: a method could be inherited from a superclass, used to
* implement an interface method that has a different type signature, and
* does not have the interface method in its list of overrides. In that
* case, a bridge method should be added that overrides the interface method
* and then calls the implementation method.
* </p>
*
* <p>
* This method should only be called once all regular, non-bridge methods
* have been installed on the GWT types.
* </p>
*/
public void addBridgeMethods(SourceTypeBinding clazzBinding) {
if (clazzBinding.isInterface()) {
// Only add bridges in classes, to simplify matters.
return;
}
JClassType clazz = (JClassType) typeMap.get(clazzBinding);
/*
* The JDT adds bridge methods in all the places GWT needs them. Look
* through the bridge methods the JDT added.
*/
if (clazzBinding.syntheticMethods() != null) {
for (SyntheticMethodBinding synthmeth : clazzBinding.syntheticMethods()) {
if (synthmeth.purpose == SyntheticMethodBinding.BridgeMethod && !synthmeth.isStatic()) {
JMethod implmeth = (JMethod) typeMap.get(synthmeth.targetMethod);
createBridgeMethod(clazz, synthmeth, implmeth);
}
}
}
}
public void processEnumType(JEnumType type) {
// Generate the synthetic values() method.
JField valuesField = null;
for (JMethod method : type.getMethods()) {
currentMethod = method;
if ("values".equals(method.getName())) {
if (method.getParams().size() != 0) {
continue;
}
currentMethodBody = (JMethodBody) method.getBody();
valuesField = writeEnumValuesMethod(type);
}
currentMethodBody = null;
currentMethod = null;
}
// Generate the synthetic valueOf() method.
if (isScript(program)) {
for (JMethod method : type.getMethods()) {
currentMethod = method;
if ("valueOf".equals(method.getName())) {
if (method.getParams().size() != 1) {
continue;
}
if (method.getParams().get(0).getType() != program.getTypeJavaLangString()) {
continue;
}
currentMethodBody = (JMethodBody) method.getBody();
writeEnumValueOfMethod(type, valuesField);
}
currentMethodBody = null;
currentMethod = null;
}
}
}
/**
* We emulate static initializers and instance initializers as methods. As
* in other cases, this gives us: simpler AST, easier to optimize, more like
* output JavaScript.
*/
public void processType(TypeDeclaration x) {
currentClass = (JDeclaredType) typeMap.get(x.binding);
processArtificialRescues(x.annotations);
try {
currentClassScope = x.scope;
currentSeparatorPositions = x.compilationResult.lineSeparatorPositions;
currentFileName = String.valueOf(x.compilationResult.fileName);
/*
* Make clinits chain to super class (JDT doesn't write code to do
* this). Call super class $clinit; $clinit is always in position 0.
*/
if (currentClass.getSuperClass() != null) {
JMethod myClinit = currentClass.getMethods().get(0);
JMethod superClinit = currentClass.getSuperClass().getMethods().get(0);
JMethodCall superClinitCall =
new JMethodCall(myClinit.getSourceInfo(), null, superClinit);
JMethodBody body = (JMethodBody) myClinit.getBody();
body.getBlock().addStmt(0, superClinitCall.makeStatement());
}
if (x.fields != null) {
// Process fields
for (int i = 0, n = x.fields.length; i < n; ++i) {
FieldDeclaration fieldDeclaration = x.fields[i];
if (fieldDeclaration.isStatic()) {
// clinit
currentMethod = currentClass.getMethods().get(0);
currentMethodBody = (JMethodBody) currentMethod.getBody();
currentMethodScope = x.staticInitializerScope;
} else {
// init
currentMethod = currentClass.getMethods().get(1);
currentMethodBody = (JMethodBody) currentMethod.getBody();
currentMethodScope = x.initializerScope;
}
if (fieldDeclaration instanceof Initializer) {
assert (currentClass instanceof JClassType);
processInitializer((Initializer) fieldDeclaration);
} else {
processField(fieldDeclaration);
}
}
}
currentMethodScope = null;
currentMethod = null;
if (x.methods != null) {
// Process methods
for (int i = 0, n = x.methods.length; i < n; ++i) {
if (x.methods[i].isConstructor()) {
assert (currentClass instanceof JClassType);
processConstructor((ConstructorDeclaration) x.methods[i]);
} else if (x.methods[i].isClinit()) {
// nothing to do
} else {
processMethod(x.methods[i]);
}
}
}
// Write the body of the getClass() override.
if (currentClass instanceof JClassType && currentClass != program.getTypeJavaLangObject()) {
JMethod method = currentClass.getMethods().get(2);
assert ("getClass".equals(method.getName()));
if (program.isJavaScriptObject(currentClass)
&& currentClass != program.getJavaScriptObject()) {
// Just use JavaScriptObject's implementation for all subclasses.
currentClass.getMethods().remove(2);
} else {
tryFindUpRefs(method);
SourceInfo info = method.getSourceInfo();
if (isScript(program) && currentClass == program.getIndexedType("Array")) {
// Special implementation: return this.arrayClass
implementMethod(method, new JFieldRef(info, new JThisRef(info,
(JClassType) currentClass), program.getIndexedField("Array.arrayClass"),
currentClass));
} else {
implementMethod(method, new JClassLiteral(info.makeChild(), currentClass));
}
}
}
// Reimplement GWT.isClient(), GWT.isProdMode(), GWT.isScript().
if (currentClass == program.getIndexedType("GWT")) {
JMethod method = program.getIndexedMethod("GWT.isClient");
implementMethod(method, program.getLiteralBoolean(true));
method = program.getIndexedMethod("GWT.isProdMode");
implementMethod(method, program.getLiteralBoolean(true));
method = program.getIndexedMethod("GWT.isScript");
implementMethod(method, program.getLiteralBoolean(isScript(program)));
}
// Implement various methods on Class
if (currentClass == program.getTypeJavaLangClass()) {
JMethod method = program.getIndexedMethod("Class.desiredAssertionStatus");
implementMethod(method, program.getLiteralBoolean(enableAsserts));
if (disableClassMetadata) {
JMethod isMetadataEnabledMethod =
program.getIndexedMethod("Class.isClassMetadataEnabled");
implementMethod(isMetadataEnabledMethod, program.getLiteralBoolean(false));
}
}
if (currentClass instanceof JEnumType) {
processEnumType((JEnumType) currentClass);
}
currentClassScope = null;
currentClass = null;
currentSeparatorPositions = null;
currentFileName = null;
} catch (Throwable e) {
throw translateException(currentClass, e);
}
}
/**
* This is the guts of the "reflective" part of this visitor. Try to find a
* "process" method that exactly matches the run-time type of the argument.
*/
protected JNode dispatch(String name, Object child) {
if (child == null) {
return null;
}
try {
Method method = getCachedMethod(name, child.getClass());
return (JNode) method.invoke(this, child);
} catch (Throwable e) {
if (e instanceof InvocationTargetException) {
e = ((InvocationTargetException) e).getTargetException();
}
throw translateException(child, e);
}
}
/**
* Process an Expression type node reflectively; must return a JExpression.
*/
protected JExpression dispProcessExpression(Expression x) {
/*
* Note that we always prefer a JDT-computed constant value to the actual
* written expression. (Let's hope JDT is always right.) This means we
* don't have to write processExpression methods for the numerous JDT
* literal nodes because they ALWAYS have a constant value.
*/
JExpression result = null;
if (x != null && x.constant != null && x.constant != Constant.NotAConstant) {
result = (JExpression) dispatch("processConstant", x.constant);
}
if (result == null) {
// The expression was not a constant, so use the general logic.
result = (JExpression) dispatch("processExpression", x);
}
// Check if we need to box the resulting expression.
if (x != null) {
if ((x.implicitConversion & TypeIds.BOXING) != 0) {
result = autoboxUtils.box(result, implicitConversionTargetType(x));
} else if ((x.implicitConversion & TypeIds.UNBOXING) != 0) {
// This code can actually leave an unbox operation in
// an lvalue position, for example ++(x.intValue()).
// Such trees are cleaned up in FixAssignmentToUnbox.
JType typeToUnbox = (JType) typeMap.get(x.resolvedType);
if (!(typeToUnbox instanceof JClassType)) {
throw new InternalCompilerException(result, "Attempt to unbox a non-class type: "
+ typeToUnbox.getName(), null);
}
result = unbox(result, (JClassType) typeToUnbox);
}
}
return result;
}
/**
* Process an Statement type node reflectively; must return a JStatement.
*/
protected JStatement dispProcessStatement(Statement x) {
JStatement stmt;
if (x instanceof Expression) {
JExpression expr = dispProcessExpression((Expression) x);
if (expr == null) {
return null;
}
stmt = expr.makeStatement();
} else {
stmt = (JStatement) dispatch("processStatement", x);
}
return stmt;
}
Map<JsniMethodBody, AbstractMethodDeclaration> getJsniMethodMap() {
return jsniMethodMap;
}
JBooleanLiteral processConstant(BooleanConstant x) {
return program.getLiteralBoolean(x.booleanValue());
}
JIntLiteral processConstant(ByteConstant x) {
return program.getLiteralInt(x.byteValue());
}
JCharLiteral processConstant(CharConstant x) {
return program.getLiteralChar(x.charValue());
}
JDoubleLiteral processConstant(DoubleConstant x) {
return program.getLiteralDouble(x.doubleValue());
}
JFloatLiteral processConstant(FloatConstant x) {
return program.getLiteralFloat(x.floatValue());
}
JIntLiteral processConstant(IntConstant x) {
return program.getLiteralInt(x.intValue());
}
JLongLiteral processConstant(LongConstant x) {
return program.getLiteralLong(x.longValue());
}
JIntLiteral processConstant(ShortConstant x) {
return program.getLiteralInt(x.shortValue());
}
JStringLiteral processConstant(StringConstant x) {
// May be processing an annotation
SourceInfo info =
currentMethod == null ? currentClass.getSourceInfo() : currentMethod.getSourceInfo();
return program.getLiteralString(info.makeChild(), x.stringValue().toCharArray());
}
/**
* This is slightly different from the normal Java language. Specifically,
* we explicitly handle synthetic fields that are part of nested/local
* classes. It boils down to the fact that we really HAVE to assign
* synthetic arguments to synthetic fields BEFORE calling the superclass
* constructor (because it might call you back polymorphically). In straight
* Java that glue code is a semantic error, because a this/super call must
* be the first statement of your constructor. On the upside, optimizations
* work the same on our synthetic fields as with any user fields.
*
* The order of emulation is: - assign all synthetic fields from synthetic
* args - call our super constructor emulation method - call our instance
* initializer emulation method - run user code
*/
void processConstructor(ConstructorDeclaration x) {
JConstructor ctor = (JConstructor) typeMap.get(x.binding);
try {
SourceInfo info = ctor.getSourceInfo();
currentMethod = ctor;
currentMethodBody = ctor.getBody();
currentMethodScope = x.scope;
/*
* Determine if we have an explicit this call. The presence of an
* explicit this call indicates we can skip certain initialization steps
* (as the callee will perform those steps for us). These skippable
* steps are 1) assigning synthetic args to fields and 2) running
* initializers.
*/
boolean hasExplicitThis = (x.constructorCall != null) && !x.constructorCall.isSuperAccess();
JClassType enclosingType = ctor.getEnclosingType();
JBlock block = currentMethodBody.getBlock();
currentOuterThisRefParams = Maps.create();
/*
* All synthetic fields must be assigned, unless we have an explicit
* this constructor call, in which case the callee will assign them for
* us.
*/
ReferenceBinding declaringClass = x.binding.declaringClass;
if (declaringClass instanceof NestedTypeBinding) {
Iterator<JParameter> paramIt = currentMethod.getParams().iterator();
NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass;
if (nestedBinding.enclosingInstances != null) {
for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) {
JParameter param = paramIt.next();
JField field = (JField) typeMap.get(arg);
if (!hasExplicitThis) {
block.addStmt(JProgram.createAssignmentStmt(info, createVariableRef(info, field),
createVariableRef(info, param)));
}
currentOuterThisRefParams = Maps.put(currentOuterThisRefParams, field, param);
}
}
if (!hasExplicitThis) {
paramIt = getSyntheticLocalsIterator();
if (nestedBinding.outerLocalVariables != null) {
for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) {
JParameter param = paramIt.next();
JField field = (JField) typeMap.get(arg);
block.addStmt(JProgram.createAssignmentStmt(info, createVariableRef(info, field),
createVariableRef(info, param)));
}
}
}
}
// optional this or super constructor call
if (x.constructorCall != null) {
JMethodCall superOrThisCall =
(JMethodCall) dispatch("processExpression", x.constructorCall);
// Enums: wire up synthetic name/ordinal params to the super method.
if (enclosingType.isEnumOrSubclass() != null) {
JVariableRef enumNameRef =
createVariableRef(superOrThisCall.getSourceInfo(), ctor.getParams().get(0));
superOrThisCall.addArg(0, enumNameRef);
JVariableRef enumOrdinalRef =
createVariableRef(superOrThisCall.getSourceInfo(), ctor.getParams().get(1));
superOrThisCall.addArg(1, enumOrdinalRef);
}
superOrThisCall.setStaticDispatchOnly();
block.addStmt(superOrThisCall.makeStatement());
}
JExpression thisRef = createThisRef(info, enclosingType);
/*
* Call the synthetic instance initializer method, unless we have an
* explicit this constructor call, in which case the callee will.
*/
if (!hasExplicitThis) {
// $init is always in position 1 (clinit is in 0)
JMethod initMethod = enclosingType.getMethods().get(1);
JMethodCall initCall = new JMethodCall(info, thisRef, initMethod);
block.addStmt(initCall.makeStatement());
}
// user code (finally!)
block.addStmts(processStatements(x.statements));
currentOuterThisRefParams = null;
currentMethodScope = null;
currentMethod = null;
} catch (Throwable e) {
throw translateException(ctor, e);
}
}
JExpression processExpression(AllocationExpression x) {
SourceInfo info = makeSourceInfo(x);
TypeBinding typeBinding = erasure(x.resolvedType);
if (typeBinding.constantPoolName() == null) {
/*
* Weird case: if JDT determines that this local class is totally
* uninstantiable, it won't bother allocating a local name.
*/
return program.getLiteralNull();
}
JClassType newType = (JClassType) typeMap.get(typeBinding);
MethodBinding b = x.binding;
JConstructor ctor = (JConstructor) typeMap.get(b);
JMethodCall call;
JClassType javaLangString = program.getTypeJavaLangString();
if (newType == javaLangString && !newType.isExternal()) {
/*
* MAGIC: java.lang.String is implemented as a JavaScript String
* primitive with a modified prototype. This requires funky handling of
* constructor calls. We find a method named _String() whose signature
* matches the requested constructor
*/
int ctorArgc = ctor.getParams().size();
JMethod targetMethod = null;
outer : for (JMethod method : javaLangString.getMethods()) {
if (method.getName().equals("_String") && method.getParams().size() == ctorArgc) {
for (int i = 0; i < ctorArgc; ++i) {
JParameter mparam = method.getParams().get(i);
JParameter cparam = ctor.getParams().get(i);
if (mparam.getType() != cparam.getType()) {
continue outer;
}
}
targetMethod = method;
break;
}
}
if (targetMethod == null) {
throw new InternalCompilerException(
"String constructor error; no matching implementation.");
}
call = new JMethodCall(makeSourceInfo(x), null, targetMethod);
} else {
call = new JNewInstance(info, ctor, currentClass);
}
// Enums: hidden arguments for the name and id.
if (x.enumConstant != null) {
call.addArgs(program.getLiteralString(info, x.enumConstant.name), program
.getLiteralInt(x.enumConstant.binding.original().id));
}
// Synthetic args for inner classes
ReferenceBinding targetBinding = b.declaringClass;
if (targetBinding.isNestedType() && !targetBinding.isStatic()) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding);
// Synthetic this args for inner classes
if (nestedBinding.enclosingInstances != null) {
for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) {
JClassType syntheticThisType = (JClassType) typeMap.get(arg.type);
call.addArg(createThisRef(info, syntheticThisType));
}
}
}
// Plain old regular user arguments
addCallArgs(x.arguments, call, b);
// Synthetic args for inner classes
if (targetBinding.isNestedType() && !targetBinding.isStatic()) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding);
// Synthetic locals for local classes
if (nestedBinding.outerLocalVariables != null) {
for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) {
JVariable variable = (JVariable) typeMap.get(arg.actualOuterLocalVariable);
call.addArg(createVariableRef(info, variable, arg.actualOuterLocalVariable));
}
}
}
return call;
}
JExpression processExpression(AND_AND_Expression x) {
JType type = (JType) typeMap.get(x.resolvedType);
SourceInfo info = makeSourceInfo(x);
return processBinaryOperation(info, JBinaryOperator.AND, type, x.left, x.right);
}
JExpression processExpression(ArrayAllocationExpression x) {
SourceInfo info = makeSourceInfo(x);
JArrayType type = (JArrayType) typeMap.get(x.resolvedType);
if (x.initializer != null) {
List<JExpression> initializers = new ArrayList<JExpression>();
if (x.initializer.expressions != null) {
for (Expression expression : x.initializer.expressions) {
initializers.add(dispProcessExpression(expression));
}
}
return JNewArray.createInitializers(info, type, initializers);
} else {
List<JExpression> dims = new ArrayList<JExpression>();
for (Expression dimension : x.dimensions) {
// can be null if index expression was empty
if (dimension == null) {
dims.add(program.getLiteralAbsentArrayDimension());
} else {
dims.add(dispProcessExpression(dimension));
}
}
return JNewArray.createDims(info, type, dims);
}
}
JExpression processExpression(ArrayInitializer x) {
SourceInfo info = makeSourceInfo(x);
JArrayType type = (JArrayType) typeMap.get(x.resolvedType);
List<JExpression> initializers = new ArrayList<JExpression>();
if (x.expressions != null) {
for (Expression expression : x.expressions) {
initializers.add(dispProcessExpression(expression));
}
}
return JNewArray.createInitializers(info, type, initializers);
}
JExpression processExpression(ArrayReference x) {
SourceInfo info = makeSourceInfo(x);
JArrayRef arrayRef =
new JArrayRef(info, dispProcessExpression(x.receiver), dispProcessExpression(x.position));
return arrayRef;
}
JExpression processExpression(Assignment x) {
JType type = (JType) typeMap.get(x.resolvedType);
SourceInfo info = makeSourceInfo(x);
return processBinaryOperation(info, JBinaryOperator.ASG, type, x.lhs, x.expression);
}
JExpression processExpression(BinaryExpression x) {
JBinaryOperator op;
int binOp = (x.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT;
switch (binOp) {
case OperatorIds.LEFT_SHIFT:
op = JBinaryOperator.SHL;
break;
case OperatorIds.RIGHT_SHIFT:
op = JBinaryOperator.SHR;
break;
case OperatorIds.UNSIGNED_RIGHT_SHIFT:
op = JBinaryOperator.SHRU;
break;
case OperatorIds.PLUS:
if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) {
op = JBinaryOperator.CONCAT;
} else {
op = JBinaryOperator.ADD;
}
break;
case OperatorIds.MINUS:
op = JBinaryOperator.SUB;
break;
case OperatorIds.REMAINDER:
op = JBinaryOperator.MOD;
break;
case OperatorIds.XOR:
op = JBinaryOperator.BIT_XOR;
break;
case OperatorIds.AND:
op = JBinaryOperator.BIT_AND;
break;
case OperatorIds.MULTIPLY:
op = JBinaryOperator.MUL;
break;
case OperatorIds.OR:
op = JBinaryOperator.BIT_OR;
break;
case OperatorIds.DIVIDE:
op = JBinaryOperator.DIV;
break;
case OperatorIds.LESS_EQUAL:
op = JBinaryOperator.LTE;
break;
case OperatorIds.GREATER_EQUAL:
op = JBinaryOperator.GTE;
break;
case OperatorIds.GREATER:
op = JBinaryOperator.GT;
break;
case OperatorIds.LESS:
op = JBinaryOperator.LT;
break;
default:
throw new InternalCompilerException("Unexpected operator for BinaryExpression");
}
JType type = (JType) typeMap.get(x.resolvedType);
SourceInfo info = makeSourceInfo(x);
return processBinaryOperation(info, op, type, x.left, x.right);
}
JExpression processExpression(CastExpression x) {
SourceInfo info = makeSourceInfo(x);
JType type = (JType) typeMap.get(x.resolvedType);
JCastOperation cast = new JCastOperation(info, type, dispProcessExpression(x.expression));
return cast;
}
JExpression processExpression(ClassLiteralAccess x) {
SourceInfo info = makeSourceInfo(x);
JType type = (JType) typeMap.get(x.targetType);
return new JClassLiteral(info, type);
}
JExpression processExpression(CombinedBinaryExpression x) {
return processExpression((BinaryExpression) x);
}
JExpression processExpression(CompoundAssignment x) {
JBinaryOperator op;
switch (x.operator) {
case OperatorIds.PLUS:
if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) {
op = JBinaryOperator.ASG_CONCAT;
} else {
op = JBinaryOperator.ASG_ADD;
}
break;
case OperatorIds.MINUS:
op = JBinaryOperator.ASG_SUB;
break;
case OperatorIds.MULTIPLY:
op = JBinaryOperator.ASG_MUL;
break;
case OperatorIds.DIVIDE:
op = JBinaryOperator.ASG_DIV;
break;
case OperatorIds.AND:
op = JBinaryOperator.ASG_BIT_AND;
break;
case OperatorIds.OR:
op = JBinaryOperator.ASG_BIT_OR;
break;
case OperatorIds.XOR:
op = JBinaryOperator.ASG_BIT_XOR;
break;
case OperatorIds.REMAINDER:
op = JBinaryOperator.ASG_MOD;
break;
case OperatorIds.LEFT_SHIFT:
op = JBinaryOperator.ASG_SHL;
break;
case OperatorIds.RIGHT_SHIFT:
op = JBinaryOperator.ASG_SHR;
break;
case CompoundAssignment.UNSIGNED_RIGHT_SHIFT:
op = JBinaryOperator.ASG_SHRU;
break;
default:
throw new InternalCompilerException("Unexpected operator for CompoundAssignment");
}
JType type = (JType) typeMap.get(x.resolvedType);
SourceInfo info = makeSourceInfo(x);
return processBinaryOperation(info, op, type, x.lhs, x.expression);
}
JExpression processExpression(ConditionalExpression x) {
SourceInfo info = makeSourceInfo(x);
JType type = (JType) typeMap.get(x.resolvedType);
JExpression ifTest = dispProcessExpression(x.condition);
JExpression thenExpr = dispProcessExpression(x.valueIfTrue);
JExpression elseExpr = dispProcessExpression(x.valueIfFalse);
JConditional conditional = new JConditional(info, type, ifTest, thenExpr, elseExpr);
return conditional;
}
JExpression processExpression(EqualExpression x) {
JBinaryOperator op;
switch ((x.bits & BinaryExpression.OperatorMASK) >> BinaryExpression.OperatorSHIFT) {
case BinaryExpression.EQUAL_EQUAL:
op = JBinaryOperator.EQ;
break;
case BinaryExpression.NOT_EQUAL:
op = JBinaryOperator.NEQ;
break;
default:
throw new InternalCompilerException("Unexpected operator for EqualExpression");
}
JType type = (JType) typeMap.get(x.resolvedType);
SourceInfo info = makeSourceInfo(x);
return processBinaryOperation(info, op, type, x.left, x.right);
}
/**
* How we have to treat super calls vs. this calls is so different, they may
* as well have been two different JDT nodes.
*/
JMethodCall processExpression(ExplicitConstructorCall x) {
if (x.isSuperAccess()) {
return processSuperConstructorCall(x);
} else {
return processThisConstructorCall(x);
}
}
JExpression processExpression(FieldReference x) {
FieldBinding fieldBinding = x.binding;
SourceInfo info = makeSourceInfo(x);
JExpression instance = dispProcessExpression(x.receiver);
JExpression expr;
if (fieldBinding.declaringClass == null) {
if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) {
throw new InternalCompilerException("Expected [array].length.");
}
expr = new JArrayLength(info, instance);
} else {
JField field = (JField) typeMap.get(fieldBinding);
expr = new JFieldRef(info, instance, field, currentClass);
}
if (x.genericCast != null) {
JType castType = (JType) typeMap.get(x.genericCast);
/*
* Note, this may result in an invalid AST due to an LHS cast operation.
* We fix this up in FixAssignmentToUnbox.
*/
return maybeCast(castType, expr);
}
return expr;
}
JExpression processExpression(InstanceOfExpression x) {
SourceInfo info = makeSourceInfo(x);
JExpression expr = dispProcessExpression(x.expression);
JReferenceType testType = (JReferenceType) typeMap.get(x.type.resolvedType);
return new JInstanceOf(info, testType, expr);
}
JExpression processExpression(MessageSend x) {
SourceInfo info = makeSourceInfo(x);
JMethod method = (JMethod) typeMap.get(x.binding);
JExpression qualifier;
if (x.receiver instanceof ThisReference) {
if (method.isStatic()) {
// don't bother qualifying it, it's a no-op
qualifier = null;
} else if (x.receiver instanceof QualifiedThisReference) {
// use the supplied qualifier
qualifier = dispProcessExpression(x.receiver);
} else {
/*
* In cases where JDT had to synthesize a this ref for us, it could
* actually be the wrong type, if the target method is in an enclosing
* class. We have to synthesize our own ref of the correct type.
*/
qualifier = createThisRef(info, method.getEnclosingType());
}
} else {
qualifier = dispProcessExpression(x.receiver);
}
JMethodCall call = new JMethodCall(info, qualifier, method);
// On a super ref, don't allow polymorphic dispatch. Oddly enough,
// QualifiedSuperReference not derived from SuperReference!
boolean isSuperRef =
x.receiver instanceof SuperReference || x.receiver instanceof QualifiedSuperReference;
if (isSuperRef) {
call.setStaticDispatchOnly();
}
// The arguments come first...
addCallArgs(x.arguments, call, x.binding);
if (x.valueCast != null) {
JType castType = (JType) typeMap.get(x.valueCast);
return maybeCast(castType, call);
}
return call;
}
JExpression processExpression(NullLiteral x) {
return program.getLiteralNull();
}
JExpression processExpression(OR_OR_Expression x) {
JType type = (JType) typeMap.get(x.resolvedType);
SourceInfo info = makeSourceInfo(x);
return processBinaryOperation(info, JBinaryOperator.OR, type, x.left, x.right);
}
JExpression processExpression(PostfixExpression x) {
SourceInfo info = makeSourceInfo(x);
JUnaryOperator op;
switch (x.operator) {
case PostfixExpression.MINUS:
op = JUnaryOperator.DEC;
break;
case PostfixExpression.PLUS:
op = JUnaryOperator.INC;
break;
default:
throw new InternalCompilerException("Unexpected postfix operator");
}
JPostfixOperation postOp = new JPostfixOperation(info, op, dispProcessExpression(x.lhs));
return postOp;
}
JExpression processExpression(PrefixExpression x) {
SourceInfo info = makeSourceInfo(x);
JUnaryOperator op;
switch (x.operator) {
case PrefixExpression.MINUS:
op = JUnaryOperator.DEC;
break;
case PrefixExpression.PLUS:
op = JUnaryOperator.INC;
break;
default:
throw new InternalCompilerException("Unexpected prefix operator");
}
JPrefixOperation preOp = new JPrefixOperation(info, op, dispProcessExpression(x.lhs));
return preOp;
}
JExpression processExpression(QualifiedAllocationExpression x) {
/*
* Weird: sometimes JDT will create a QualifiedAllocationExpression with
* no qualifier. I guess this is supposed to let us know that we need to
* synthesize a synthetic this arg based on our own current "this"? But
* plain old regular AllocationExpression also must be treated as if it
* might be be implicitly qualified, so I'm not sure what the point is.
* Let's just defer to the AllocationExpression logic if there's no
* qualifier.
*/
if (x.enclosingInstance() == null) {
return processExpression((AllocationExpression) x);
}
SourceInfo info = makeSourceInfo(x);
MethodBinding b = x.binding;
JConstructor ctor = (JConstructor) typeMap.get(b);
JNewInstance newInstance = new JNewInstance(info, ctor, currentClass);
JExpression qualifier = dispProcessExpression(x.enclosingInstance);
List<JExpression> qualList = new ArrayList<JExpression>();
qualList.add(qualifier);
/*
* Really weird: Sometimes an allocation expression needs both its
* explicit qualifier AND its implicit enclosing class! We add this second
* because the explicit qualifier takes precedence.
*/
if (!currentMethod.isStatic()) {
JExpression implicitOuter = new JThisRef(info, (JClassType) currentClass);
qualList.add(implicitOuter);
}
// Synthetic this args for inner classes
ReferenceBinding targetBinding = b.declaringClass;
if (targetBinding.isNestedType() && !targetBinding.isStatic()) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding);
if (nestedBinding.enclosingInstances != null) {
for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) {
JClassType syntheticThisType = (JClassType) typeMap.get(arg.type);
newInstance.addArg(createThisRef(syntheticThisType, qualList));
}
}
}
// Plain old regular arguments
addCallArgs(x.arguments, newInstance, b);
// Synthetic locals for local classes
if (targetBinding.isNestedType() && !targetBinding.isStatic()) {
NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding);
if (nestedBinding.outerLocalVariables != null) {
for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) {
JVariable variable = (JVariable) typeMap.get(arg.actualOuterLocalVariable);
newInstance.addArg(createVariableRef(info, variable, arg.actualOuterLocalVariable));
}
}
}
return newInstance;
}
JExpression processExpression(QualifiedNameReference x) {
SourceInfo info = makeSourceInfo(x);
Binding binding = x.binding;
JNode node = typeMap.get(binding);
if (!(node instanceof JVariable)) {
return null;
}
JVariable variable = (JVariable) node;
JExpression curRef = createVariableRef(info, variable, binding);
if (x.genericCast != null) {
JType castType = (JType) typeMap.get(x.genericCast);
curRef = maybeCast(castType, curRef);
}
/*
* Wackiness: JDT represents multiple field access as an array of fields,
* each qualified by everything to the left. So each subsequent item in
* otherBindings takes the current expression as a qualifier.
*/
if (x.otherBindings != null) {
for (int i = 0; i < x.otherBindings.length; ++i) {
FieldBinding fieldBinding = x.otherBindings[i];
if (fieldBinding.declaringClass == null) {
// probably array.length
if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) {
throw new InternalCompilerException("Expected [array].length.");
}
curRef = new JArrayLength(info, curRef);
} else {
JField field = (JField) typeMap.get(fieldBinding);
curRef = new JFieldRef(info, curRef, field, currentClass);
}
if (x.otherGenericCasts != null && x.otherGenericCasts[i] != null) {
JType castType = (JType) typeMap.get(x.otherGenericCasts[i]);
curRef = maybeCast(castType, curRef);
}
}
}
return curRef;
}
JExpression processExpression(QualifiedSuperReference x) {
JClassType refType = (JClassType) typeMap.get(x.resolvedType);
JClassType qualType = (JClassType) typeMap.get(x.qualification.resolvedType);
assert (refType == qualType.getSuperClass());
// Oddly enough, super refs can be modeled as this refs, because whatever
// expression they qualify has already been resolved.
return processQualifiedThisOrSuperRef(x, qualType);
}
JExpression processExpression(QualifiedThisReference x) {
JClassType refType = (JClassType) typeMap.get(x.resolvedType);
JClassType qualType = (JClassType) typeMap.get(x.qualification.resolvedType);
assert (refType == qualType);
return processQualifiedThisOrSuperRef(x, qualType);
}
JExpression processExpression(SingleNameReference x) {
SourceInfo info = makeSourceInfo(x);
Binding binding = x.binding;
Object target = typeMap.get(binding);
if (!(target instanceof JVariable)) {
return null;
}
JVariable variable = (JVariable) target;
/*
* Wackiness: if a field happens to have synthetic accessors (only fields
* can have them, apparently), this is a ref to a field in an enclosing
* instance. CreateThisRef should compute a "this" access of the
* appropriate type, unless the field is static.
*/
JExpression result = null;
if (x.syntheticAccessors != null) {
JField field = (JField) variable;
if (!field.isStatic()) {
JExpression instance = createThisRef(info, field.getEnclosingType());
result = new JFieldRef(info, instance, field, currentClass);
}
}
if (result == null) {
result = createVariableRef(info, variable, binding);
}
if (x.genericCast != null) {
JType castType = (JType) typeMap.get(x.genericCast);
result = maybeCast(castType, result);
}
return result;
}
JExpression processExpression(SuperReference x) {
JClassType type = (JClassType) typeMap.get(x.resolvedType);
assert (type == currentClass.getSuperClass());
SourceInfo info = makeSourceInfo(x);
// Oddly enough, super refs can be modeled as a this refs.
JExpression superRef = createThisRef(info, currentClass);
return superRef;
}
JExpression processExpression(ThisReference x) {
JClassType type = (JClassType) typeMap.get(x.resolvedType);
assert (type == currentClass);
SourceInfo info = makeSourceInfo(x);
JExpression thisRef = createThisRef(info, currentClass);
return thisRef;
}
JExpression processExpression(UnaryExpression x) {
SourceInfo info = makeSourceInfo(x);
JUnaryOperator op;
int operator = ((x.bits & UnaryExpression.OperatorMASK) >> UnaryExpression.OperatorSHIFT);
switch (operator) {
case UnaryExpression.MINUS:
op = JUnaryOperator.NEG;
break;
case UnaryExpression.NOT:
op = JUnaryOperator.NOT;
break;
case UnaryExpression.PLUS:
// Odd case.. a useless + operator; just return the operand
return dispProcessExpression(x.expression);
case UnaryExpression.TWIDDLE:
op = JUnaryOperator.BIT_NOT;
break;
default:
throw new InternalCompilerException("Unexpected operator for unary expression");
}
JPrefixOperation preOp = new JPrefixOperation(info, op, dispProcessExpression(x.expression));
return preOp;
}
List<JExpressionStatement> processExpressionStatements(Statement[] statements) {
List<JExpressionStatement> jstatements = new ArrayList<JExpressionStatement>();
if (statements != null) {
for (int i = 0, n = statements.length; i < n; ++i) {
JStatement jstmt = dispProcessStatement(statements[i]);
if (jstmt != null) {
jstatements.add((JExpressionStatement) jstmt);
}
}
}
return jstatements;
}
void processField(FieldDeclaration declaration) {
JField field = (JField) typeMap.tryGet(declaration.binding);
if (field == null) {
/*
* When anonymous classes declare constant fields, the field declaration
* is not visited by JDT. Just bail since any references to that field
* are guaranteed to be replaced with literals.
*/
return;
}
try {
JExpression initializer = null;
if (declaration.initialization != null) {
initializer = dispProcessExpression(declaration.initialization);
}
if (field instanceof JEnumField) {
// An enum field must be initialized!
assert (initializer instanceof JMethodCall);
}
if (initializer != null) {
SourceInfo info = makeSourceInfo(declaration);
// JDeclarationStatement's ctor sets up the field's initializer.
JStatement decl =
new JDeclarationStatement(info, createVariableRef(info, field), initializer);
// will either be init or clinit
currentMethodBody.getBlock().addStmt(decl);
}
} catch (Throwable e) {
throw translateException(field, e);
}
}
void processInitializer(Initializer initializer) {
JBlock block = (JBlock) dispProcessStatement(initializer.block);
try {
// will either be init or clinit
currentMethodBody.getBlock().addStmt(block);
} catch (Throwable e) {
throw translateException(initializer, e);
}
}
void processMethod(AbstractMethodDeclaration x) {
MethodBinding b = x.binding;
JMethod method = (JMethod) typeMap.get(b);
try {
if (!b.isStatic() && (b.isImplementing() || b.isOverriding())) {
tryFindUpRefs(method, b);
}
if (x.isNative()) {
processNativeMethod(x, (JsniMethodBody) method.getBody());
return;
}
currentMethod = method;
currentMethodBody = (JMethodBody) method.getBody();
currentMethodScope = x.scope;
if (currentMethodBody != null) {
currentMethodBody.getBlock().addStmts(processStatements(x.statements));
}
currentMethodScope = null;
currentMethodBody = null;
currentMethod = null;
} catch (Throwable e) {
throw translateException(method, e);
}
}
void processNativeMethod(AbstractMethodDeclaration x, JsniMethodBody nativeMethodBody) {
// Squirrel away a reference to the JDT node to enable error reporting.
jsniMethodMap.put(nativeMethodBody, x);
}
JStatement processStatement(AssertStatement x) {
SourceInfo info = makeSourceInfo(x);
JExpression expr = dispProcessExpression(x.assertExpression);
JExpression arg = dispProcessExpression(x.exceptionArgument);
return new JAssertStatement(info, expr, arg);
}
JBlock processStatement(Block x) {
if (x == null) {
return null;
}
SourceInfo info = makeSourceInfo(x);
JBlock block = new JBlock(info);
block.addStmts(processStatements(x.statements));
return block;
}
JStatement processStatement(BreakStatement x) {
SourceInfo info = makeSourceInfo(x);
return new JBreakStatement(info, getOrCreateLabel(info, currentMethod, x.label));
}
JStatement processStatement(CaseStatement x) {
SourceInfo info = makeSourceInfo(x);
JExpression expression = dispProcessExpression(x.constantExpression);
if (expression != null && x.constantExpression.resolvedType.isEnum()) {
// TODO: propagate enum information?
assert (expression instanceof JFieldRef);
JFieldRef fieldRef = (JFieldRef) expression;
JEnumField field = (JEnumField) fieldRef.getField();
return new JCaseStatement(info, program.getLiteralInt(field.ordinal()));
} else {
return new JCaseStatement(info, (JLiteral) expression);
}
}
JStatement processStatement(ContinueStatement x) {
SourceInfo info = makeSourceInfo(x);
return new JContinueStatement(info, getOrCreateLabel(info, currentMethod, x.label));
}
JStatement processStatement(DoStatement x) {
SourceInfo info = makeSourceInfo(x);
JExpression loopTest = dispProcessExpression(x.condition);
JStatement loopBody = dispProcessStatement(x.action);
JDoStatement stmt = new JDoStatement(info, loopTest, loopBody);
return stmt;
}
JStatement processStatement(EmptyStatement x) {
return null;
}
JStatement processStatement(ForeachStatement x) {
SourceInfo info = makeSourceInfo(x);
JBlock body;
JStatement action = dispProcessStatement(x.action);
if (action instanceof JBlock) {
body = (JBlock) action;
} else {
body = new JBlock(info);
if (action != null) {
body.addStmt(action);
}
}
JLocal elementVar = (JLocal) typeMap.get(x.elementVariable.binding);
String elementVarName = elementVar.getName();
JDeclarationStatement elementDecl =
(JDeclarationStatement) processStatement(x.elementVariable);
assert (elementDecl.initializer == null);
JForStatement result;
if (x.collectionVariable != null) {
/**
* <pre>
* for (final T[] i$array = collection,
* int i$index = 0,
* final int i$max = i$array.length;
* i$index < i$max; ++i$index) {
* T elementVar = i$array[i$index];
* // user action
* }
* </pre>
*/
JLocal arrayVar =
JProgram.createLocal(info, elementVarName + "$array", ((JType) typeMap
.get(x.collection.resolvedType)), true, currentMethodBody);
JLocal indexVar =
JProgram.createLocal(info, elementVarName + "$index", program.getTypePrimitiveInt(),
false, currentMethodBody);
JLocal maxVar =
JProgram.createLocal(info, elementVarName + "$max", program.getTypePrimitiveInt(),
true, currentMethodBody);
List<JStatement> initializers = new ArrayList<JStatement>(3);
// T[] i$array = arr
initializers.add(createDeclaration(info, arrayVar, dispProcessExpression(x.collection)));
// int i$index = 0
initializers.add(createDeclaration(info, indexVar, program.getLiteralInt(0)));
// int i$max = i$array.length
initializers.add(createDeclaration(info, maxVar, new JArrayLength(info, new JLocalRef(info,
arrayVar))));
// i$index < i$max
JExpression condition =
new JBinaryOperation(info, program.getTypePrimitiveBoolean(), JBinaryOperator.LT,
createVariableRef(info, indexVar), createVariableRef(info, maxVar));
// ++i$index
List<JExpressionStatement> increments = new ArrayList<JExpressionStatement>(1);
increments.add(new JPrefixOperation(info, JUnaryOperator.INC, createVariableRef(info,
indexVar)).makeStatement());
// T elementVar = i$array[i$index];
elementDecl.initializer =
new JArrayRef(info, createVariableRef(info, arrayVar),
createVariableRef(info, indexVar));
body.addStmt(0, elementDecl);
result = new JForStatement(info, initializers, condition, increments, body);
} else {
/**
* <pre>
* for (Iterator<T> i$iterator = collection.iterator(); i$iterator.hasNext();) {
* T elementVar = i$iterator.next();
* // user action
* }
* </pre>
*/
JLocal iteratorVar =
JProgram.createLocal(info, (elementVarName + "$iterator"), program
.getIndexedType("Iterator"), false, currentMethodBody);
List<JStatement> initializers = new ArrayList<JStatement>(1);
// Iterator<T> i$iterator = collection.iterator()
initializers.add(createDeclaration(info, iteratorVar, new JMethodCall(info,
dispProcessExpression(x.collection), program.getIndexedMethod("Iterable.iterator"))));
// i$iterator.hasNext()
JExpression condition =
new JMethodCall(info, createVariableRef(info, iteratorVar), program
.getIndexedMethod("Iterator.hasNext"));
// T elementVar = (T) i$iterator.next();
elementDecl.initializer =
new JMethodCall(info, createVariableRef(info, iteratorVar), program
.getIndexedMethod("Iterator.next"));
// Perform any implicit reference type casts (due to generics).
// Note this occurs before potential unboxing.
if (elementVar.getType() != program.getTypeJavaLangObject()) {
TypeBinding collectionType;
try {
// TODO: This is slow! Cache lookup.
Field privateField = ForeachStatement.class.getDeclaredField("collectionElementType");
privateField.setAccessible(true);
collectionType = (TypeBinding) privateField.get(x);
} catch (Exception e) {
throw new InternalCompilerException(elementDecl,
"Failed to retreive collectionElementType through reflection", e);
}
JType toType = (JType) typeMap.get(collectionType);
assert (toType instanceof JReferenceType);
elementDecl.initializer = maybeCast(toType, elementDecl.initializer);
}
body.addStmt(0, elementDecl);
result =
new JForStatement(info, initializers, condition, Collections
.<JExpressionStatement> emptyList(), body);
}
// May need to box or unbox the element assignment.
if (x.elementVariableImplicitWidening != -1) {
if ((x.elementVariableImplicitWidening & TypeIds.BOXING) != 0) {
/*
* Boxing is necessary. In this special case of autoboxing, the boxed
* expression cannot be a constant, so the box type must be exactly
* that associated with the expression.
*/
elementDecl.initializer =
autoboxUtils.box(elementDecl.initializer, ((JPrimitiveType) elementDecl.initializer
.getType()));
} else if ((x.elementVariableImplicitWidening & TypeIds.UNBOXING) != 0) {
elementDecl.initializer =
unbox(elementDecl.initializer, (JClassType) elementDecl.initializer.getType());
}
}
return result;
}
JStatement processStatement(ForStatement x) {
SourceInfo info = makeSourceInfo(x);
// SEE NOTE ON JDT FORCED OPTIMIZATIONS
// If the condition is false, don't process the body
boolean removeBody = isOptimizedFalse(x.condition);
List<JStatement> init = processStatements(x.initializations);
JExpression expr = dispProcessExpression(x.condition);
List<JExpressionStatement> incr = processExpressionStatements(x.increments);
JStatement body = removeBody ? null : dispProcessStatement(x.action);
return new JForStatement(info, init, expr, incr, body);
}
JStatement processStatement(IfStatement x) {
// SEE NOTE ON JDT FORCED OPTIMIZATIONS
// If the condition is false, don't process the then statement
// If the condition is false, don't process the else statement
boolean removeThen = isOptimizedFalse(x.condition);
boolean removeElse = isOptimizedTrue(x.condition);
SourceInfo info = makeSourceInfo(x);
JExpression expr = dispProcessExpression(x.condition);
JStatement thenStmt = removeThen ? null : dispProcessStatement(x.thenStatement);
JStatement elseStmt = removeElse ? null : dispProcessStatement(x.elseStatement);
JIfStatement ifStmt = new JIfStatement(info, expr, thenStmt, elseStmt);
return ifStmt;
}
JStatement processStatement(LabeledStatement x) {
JStatement body = dispProcessStatement(x.statement);
if (body == null) {
return null;
}
SourceInfo info = makeSourceInfo(x);
return new JLabeledStatement(info, getOrCreateLabel(info, currentMethod, x.label), body);
}
JStatement processStatement(LocalDeclaration x) {
SourceInfo info = makeSourceInfo(x);
JLocal local = (JLocal) typeMap.get(x.binding);
JLocalRef localRef = new JLocalRef(info, local);
JExpression initializer = dispProcessExpression(x.initialization);
return new JDeclarationStatement(info, localRef, initializer);
}
JStatement processStatement(ReturnStatement x) {
SourceInfo info = makeSourceInfo(x);
return new JReturnStatement(info, dispProcessExpression(x.expression));
}
JStatement processStatement(SwitchStatement x) {
SourceInfo info = makeSourceInfo(x);
JExpression expression = dispProcessExpression(x.expression);
if (isEnumType(expression.getType())) {
// Must be an enum; synthesize a call to ordinal().
expression = new JMethodCall(info, expression, program.getIndexedMethod("Enum.ordinal"));
}
JBlock block = new JBlock(info);
// Don't use processStatements here, because it stops at control breaks
if (x.statements != null) {
for (Statement stmt : x.statements) {
JStatement jstmt = dispProcessStatement(stmt);
if (jstmt != null) {
block.addStmt(jstmt);
}
}
}
return new JSwitchStatement(info, expression, block);
}
JStatement processStatement(SynchronizedStatement x) {
JBlock block = (JBlock) dispProcessStatement(x.block);
JExpression expr = dispProcessExpression(x.expression);
block.addStmt(0, expr.makeStatement());
return block;
}
JStatement processStatement(ThrowStatement x) {
SourceInfo info = makeSourceInfo(x);
JExpression toThrow = dispProcessExpression(x.exception);
return new JThrowStatement(info, toThrow);
}
JStatement processStatement(TryStatement x) {
SourceInfo info = makeSourceInfo(x);
JBlock tryBlock = (JBlock) dispProcessStatement(x.tryBlock);
List<JLocalRef> catchArgs = new ArrayList<JLocalRef>();
List<JBlock> catchBlocks = new ArrayList<JBlock>();
if (x.catchBlocks != null) {
for (int i = 0, c = x.catchArguments.length; i < c; ++i) {
JLocal local = (JLocal) typeMap.get(x.catchArguments[i].binding);
catchArgs.add((JLocalRef) createVariableRef(info, local));
}
for (int i = 0, c = x.catchBlocks.length; i < c; ++i) {
catchBlocks.add((JBlock) dispProcessStatement(x.catchBlocks[i]));
}
}
JBlock finallyBlock = (JBlock) dispProcessStatement(x.finallyBlock);
return new JTryStatement(info, tryBlock, catchArgs, catchBlocks, finallyBlock);
}
JStatement processStatement(TypeDeclaration x) {
// do nothing -- the local class is treated at the program level
return null;
}
JStatement processStatement(WhileStatement x) {
// SEE NOTE ON JDT FORCED OPTIMIZATIONS
// If the condition is false, don't process the body
boolean removeBody = isOptimizedFalse(x.condition);
SourceInfo info = makeSourceInfo(x);
JExpression loopTest = dispProcessExpression(x.condition);
JStatement loopBody = removeBody ? null : dispProcessStatement(x.action);
JWhileStatement stmt = new JWhileStatement(info, loopTest, loopBody);
return stmt;
}
List<JStatement> processStatements(Statement[] statements) {
List<JStatement> jstatements = new ArrayList<JStatement>();
if (statements != null) {
for (Statement stmt : statements) {
JStatement jstmt = dispProcessStatement(stmt);
if (jstmt != null) {
jstatements.add(jstmt);
if (jstmt.unconditionalControlBreak()) {
/*
* Stop processing statements, because the remaining ones are
* unreachable. The JDT compiler might not have fully fleshed out
* the unreachable statements.
*/
break;
}
}
}
}
return jstatements;
}
JMethodCall processSuperConstructorCall(ExplicitConstructorCall x) {
SourceInfo info = makeSourceInfo(x);
JMethod ctor = (JMethod) typeMap.get(x.binding);
JExpression trueQualifier = createThisRef(info, currentClass);
JMethodCall call = new JMethodCall(info, trueQualifier, ctor);
// We have to find and pass through any synthetics our supertype needs
ReferenceBinding superClass = x.binding.declaringClass;
if (superClass.isNestedType() && !superClass.isStatic()) {
ReferenceBinding myBinding = currentClassScope.referenceType().binding;
ReferenceBinding superBinding = superClass;
// enclosing types
if (superBinding.syntheticEnclosingInstanceTypes() != null) {
JExpression qualifier = dispProcessExpression(x.qualification);
for (ReferenceBinding arg : superBinding.syntheticEnclosingInstanceTypes()) {
JClassType classType = (JClassType) typeMap.get(arg);
if (qualifier == null) {
/*
* Got to be one of my params; it would be illegal to use a this
* ref at this moment-- we would most likely be passing in a
* supertype field that HASN'T BEEN INITIALIZED YET.
*
* Unfortunately, my params might not work as-is, so we have to
* check each one to see if any will make a suitable this ref.
*/
List<JExpression> workList = new ArrayList<JExpression>();
Iterator<JParameter> paramIt = currentMethod.getParams().iterator();
for (ReferenceBinding b : myBinding.syntheticEnclosingInstanceTypes()) {
workList.add(createVariableRef(info, paramIt.next()));
}
call.addArg(createThisRef(classType, workList));
} else {
call.addArg(createThisRef(classType, qualifier));
}
}
}
}
addCallArgs(x.arguments, call, x.binding);
// We have to find and pass through any synthetics our supertype needs
if (superClass.isNestedType() && !superClass.isStatic()) {
ReferenceBinding superBinding = superClass;
// outer locals
if (superBinding.syntheticOuterLocalVariables() != null) {
for (SyntheticArgumentBinding arg : superBinding.syntheticOuterLocalVariables()) {
// Got to be one of my params
JType varType = (JType) typeMap.get(arg.type);
String varName = String.valueOf(arg.name);
JParameter param = null;
for (JParameter paramIt : currentMethod.getParams()) {
if (varType == paramIt.getType() && varName.equals(paramIt.getName())) {
param = paramIt;
}
}
if (param == null) {
throw new InternalCompilerException(
"Could not find matching local arg for explicit super ctor call.");
}
call.addArg(createVariableRef(info, param));
}
}
}
return call;
}
JMethodCall processThisConstructorCall(ExplicitConstructorCall x) {
SourceInfo info = makeSourceInfo(x);
JMethod ctor = (JMethod) typeMap.get(x.binding);
JExpression trueQualifier = createThisRef(info, currentClass);
JMethodCall call = new JMethodCall(info, trueQualifier, ctor);
assert (x.qualification == null);
// All synthetic this args must be passed through to the target ctor
ReferenceBinding declaringClass = x.binding.declaringClass;
if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
Iterator<JParameter> paramIt = currentMethod.getParams().iterator();
NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(declaringClass);
if (nestedBinding.enclosingInstances != null) {
for (SyntheticArgumentBinding unused : nestedBinding.enclosingInstances) {
call.addArg(createVariableRef(info, paramIt.next()));
}
}
}
addCallArgs(x.arguments, call, x.binding);
// All synthetic locals must be passed through to the target ctor
if (declaringClass.isNestedType() && !declaringClass.isStatic()) {
Iterator<JParameter> paramIt = getSyntheticLocalsIterator();
NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(declaringClass);
if (nestedBinding.outerLocalVariables != null) {
for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) {
call.addArg(createVariableRef(info, paramIt.next()));
}
}
}
return call;
}
private void addAllOuterThisRefs(List<? super JVariableRef> list, JExpression expr,
JClassType classType) {
for (JField field : classType.getFields()) {
// This fields are always first.
if (!field.isThisRef()) {
break;
}
// In a constructor, use the local param instead of the field.
JParameter param = null;
if (currentOuterThisRefParams != null && expr instanceof JThisRef) {
param = currentOuterThisRefParams.get(field);
}
if (param != null) {
list.add(new JParameterRef(expr.getSourceInfo(), param));
} else {
list.add(new JFieldRef(expr.getSourceInfo(), expr, field, currentClass));
}
}
}
private void addAllOuterThisRefsPlusSuperChain(List<? super JVariableRef> workList,
JExpression expr, JClassType classType) {
for (; classType != null; classType = classType.getSuperClass()) {
addAllOuterThisRefs(workList, expr, classType);
}
}
private void addCallArgs(Expression[] jdtArgs, JMethodCall call, MethodBinding binding) {
JExpression[] args = new JExpression[jdtArgs == null ? 0 : jdtArgs.length];
for (int i = 0; i < args.length; ++i) {
args[i] = dispProcessExpression(jdtArgs[i]);
}
if (!binding.isVarargs()) {
call.addArgs(args);
return;
}
// Handle the odd var-arg case.
TypeBinding[] params = binding.parameters;
int varArg = params.length - 1;
// Everything but the last arg.
for (int i = 0; i < varArg; ++i) {
call.addArg(args[i]);
}
// Handle the last arg.
// See if there's a single varArg which is already an array.
if (args.length == params.length) {
if (jdtArgs[varArg].resolvedType.isCompatibleWith(params[varArg])) {
// Already the correct array type.
call.addArg(args[varArg]);
return;
}
}
// Need to synthesize an appropriately-typed array.
List<JExpression> initializers = new ArrayList<JExpression>();
for (int i = varArg; i < args.length; ++i) {
initializers.add(args[i]);
}
JArrayType lastParamType = (JArrayType) typeMap.get(params[varArg]);
JNewArray newArray =
JNewArray.createInitializers(call.getSourceInfo(), lastParamType, initializers);
call.addArg(newArray);
}
private void addThrownExceptions(MethodBinding methodBinding, JMethod method) {
for (ReferenceBinding exceptionReference : methodBinding.thrownExceptions) {
method.addThrownException((JClassType) typeMap.get(exceptionReference.erasure()));
}
}
/**
* Create a bridge method. It calls a same-named method with the same
* arguments, but with a different type signature.
*
* @param clazz The class to put the bridge method in
* @param jdtBridgeMethod The corresponding bridge method added in the JDT
* @param implmeth The implementation method to bridge to
*/
private void createBridgeMethod(JClassType clazz, SyntheticMethodBinding jdtBridgeMethod,
JMethod implmeth) {
SourceInfo info = implmeth.getSourceInfo().makeChild();
// create the method itself
JMethod bridgeMethod =
program.createMethod(info, String.valueOf(jdtBridgeMethod.selector), clazz,
(JType) typeMap.get(jdtBridgeMethod.returnType.erasure()), false, false, implmeth
.isFinal(), false, false);
bridgeMethod.setSynthetic();
int paramIdx = 0;
List<JParameter> implParams = implmeth.getParams();
for (TypeBinding jdtParamType : jdtBridgeMethod.parameters) {
JParameter param = implParams.get(paramIdx++);
JType paramType = (JType) typeMap.get(jdtParamType.erasure());
JParameter newParam =
new JParameter(info.makeChild(param.getSourceInfo().getOrigin()), param.getName(),
paramType, true, false, bridgeMethod);
bridgeMethod.addParam(newParam);
}
addThrownExceptions(jdtBridgeMethod, bridgeMethod);
bridgeMethod.freezeParamTypes();
info.addCorrelation(info.getCorrelator().by(bridgeMethod));
// create a call
JMethodCall call = new JMethodCall(info, new JThisRef(info, clazz), implmeth);
for (int i = 0; i < bridgeMethod.getParams().size(); i++) {
JParameter param = bridgeMethod.getParams().get(i);
JParameterRef paramRef = new JParameterRef(info, param);
call.addArg(maybeCast(implParams.get(i).getType(), paramRef));
}
// wrap it in a return if necessary
JStatement callOrReturn;
if (bridgeMethod.getType() == program.getTypeVoid()) {
callOrReturn = call.makeStatement();
} else {
callOrReturn = new JReturnStatement(info, call);
}
// create a body that is just that call
JMethodBody body = (JMethodBody) bridgeMethod.getBody();
body.getBlock().addStmt(callOrReturn);
// Add overrides.
List<JMethod> overrides = new ArrayList<JMethod>();
tryFindUpRefs(bridgeMethod, overrides);
assert !overrides.isEmpty();
for (JMethod over : overrides) {
bridgeMethod.addOverride(over);
/*
* TODO(scottb): with a diamond-shape inheritance hierarchy, it may be
* possible to get dups in this way. Really, method.overrides should
* probably just be an IdentitySet to avoid having to check contains in
* various places. Left as a todo because I don't think dups is super
* harmful.
*/
bridgeMethod.addOverrides(over.getOverrides());
}
}
private JDeclarationStatement createDeclaration(SourceInfo info, JLocal local, JExpression value) {
return new JDeclarationStatement(info, new JLocalRef(info, local), value);
}
/**
* Helper to create a qualified "this" ref (really a synthetic this field
* access) of the appropriate type. Always use this method instead of
* creating a naked JThisRef or you won't get the synthetic accesses right.
*/
private JExpression createQualifiedThisRef(SourceInfo info, JClassType targetType) {
assert (currentClass instanceof JClassType);
JExpression expr = new JThisRef(info, ((JClassType) currentClass));
List<JExpression> list = new ArrayList<JExpression>();
addAllOuterThisRefsPlusSuperChain(list, expr, (JClassType) currentClass);
return createThisRef(targetType, list);
}
/**
* Helper to create an expression of the target type, possibly by accessing
* synthetic this fields on the passed-in expression. This is needed by a
* QualifiedAllocationExpression, because the qualifier may not be the
* correct type, and we may need use one of its fields.
*/
private JExpression createThisRef(JReferenceType qualType, JExpression expr) {
List<JExpression> list = new ArrayList<JExpression>();
list.add(expr);
return createThisRef(qualType, list);
}
/**
* Helper to create an expression of the target type, possibly by accessing
* synthetic this fields on ANY of several passed-in expressions. Why in the
* world would we need to do this? It turns out that when making an
* unqualified explicit super constructor call to something that needs a
* synthetic outer this arg, the correct value to pass in can be one of
* several of the calling constructor's own synthetic args. The catch is,
* it's possible none of the args are exactly the right type-- we have to
* make one of them the right type by following each of their synthetic this
* refs up an arbitrarily big tree of enclosing classes and
* supertypes-with-enclosing-classes until we find something that's the
* right type.
*
* We have this implemented as a Breadth-First Search to minimize the number
* of derefs required, and this seems to be correct. Note that we explicitly
* prefer the current expression as one of its supertypes over a synthetic
* this ref rooted off the current expression that happens to be the correct
* type. We have observed this to be consistent with how Java handles it.
*
* TODO(scottb): could we get this info directly from JDT?
*/
private JExpression createThisRef(JReferenceType qualType, List<JExpression> list) {
LinkedList<JExpression> workList = new LinkedList<JExpression>();
workList.addAll(list);
while (!workList.isEmpty()) {
JExpression expr = workList.removeFirst();
JClassType classType = (JClassType) ((JReferenceType) expr.getType()).getUnderlyingType();
for (; classType != null; classType = classType.getSuperClass()) {
// prefer myself or myself-as-supertype over any of my this$ fields
// that may have already been added to the work list
if (program.typeOracle.canTriviallyCast(classType, qualType)) {
return expr;
}
addAllOuterThisRefs(workList, expr, classType);
}
}
throw new InternalCompilerException("Cannot create a ThisRef of the appropriate type.");
}
/**
* Helper to creates this ref (or maybe a synthetic this field access) of
* the appropriate type. Always use this method instead of creating a naked
* JThisRef or you won't get the synthetic accesses right.
*/
private JExpression createThisRef(SourceInfo info, JReferenceType targetType) {
assert (currentClass instanceof JClassType);
return createThisRef(targetType, new JThisRef(info, ((JClassType) currentClass)));
}
/**
* Creates an appropriate JVariableRef for the polymorphic type of the
* requested JVariable.
*/
private JVariableRef createVariableRef(SourceInfo info, JVariable variable) {
if (variable instanceof JLocal) {
JLocal local = (JLocal) variable;
if (local.getEnclosingMethod() != currentMethod) {
throw new InternalCompilerException("LocalRef referencing local in a different method.");
}
return new JLocalRef(info, local);
} else if (variable instanceof JParameter) {
JParameter parameter = (JParameter) variable;
if (parameter.getEnclosingMethod() != currentMethod) {
throw new InternalCompilerException(
"ParameterRef referencing param in a different method.");
}
return new JParameterRef(info, parameter);
} else if (variable instanceof JField) {
JField field = (JField) variable;
JExpression instance = null;
if (!field.isStatic()) {
JClassType fieldEnclosingType = (JClassType) field.getEnclosingType();
instance = createThisRef(info, fieldEnclosingType);
if (!program.typeOracle.canTriviallyCast((JReferenceType) instance.getType(),
fieldEnclosingType)) {
throw new InternalCompilerException("FieldRef referencing field in a different type.");
}
}
return new JFieldRef(info, instance, field, currentClass);
}
throw new InternalCompilerException("Unknown JVariable subclass.");
}
/**
* Creates an appropriate JVariableRef for the polymorphic type of the
* requested JVariable.
*/
private JVariableRef createVariableRef(SourceInfo info, JVariable variable, Binding binding) {
// Fix up the reference if it's to an outer local/param
variable = possiblyReferenceOuterLocal(variable, binding);
if (variable == null) {
/*
* Strange case: in certain circumstances, JDT will fail to provide an
* emulation path to an outer local variable. In the case I know of, the
* reference is a spurious qualifier to a static method call. Let's just
* return null and ditch the expression.
*/
return null;
}
return createVariableRef(info, variable);
}
private TypeBinding erasure(TypeBinding typeBinding) {
if (typeBinding instanceof ParameterizedTypeBinding) {
typeBinding = typeBinding.erasure();
}
return typeBinding;
}
private Method getCachedMethod(String name, Class<? extends Object> childClass)
throws NoSuchMethodException {
MethodKey key = new MethodKey(name, childClass);
MethodValue value = methodCache.get(key);
if (value == null) {
try {
Method method = getClass().getDeclaredMethod(name, childClass);
value = new MethodValue(method);
} catch (NoSuchMethodException ex) {
value = new MethodValue(ex);
}
methodCache.put(key, value);
}
// Might throw an exception here.
return value.getMethod();
}
private JInterfaceType getOrCreateExternalType(SourceInfo info, char[][] compoundName) {
String name = BuildTypeMap.dotify(compoundName);
JInterfaceType external = (JInterfaceType) program.getFromTypeMap(name);
if (external == null) {
external = program.createInterface(info, name);
external.setExternal(true);
}
return external;
}
/**
* Get a new label of a particular name, or create a new one if it doesn't
* exist already.
*/
private JLabel getOrCreateLabel(SourceInfo info, JMethod enclosingMethod, char[] name) {
if (name == null) {
return null;
}
String sname = String.valueOf(name);
Map<String, JLabel> lblMap = this.labelMap.get(enclosingMethod);
if (lblMap == null) {
lblMap = new HashMap<String, JLabel>();
this.labelMap.put(enclosingMethod, lblMap);
}
JLabel jlabel = lblMap.get(sname);
if (jlabel == null) {
jlabel = new JLabel(info, sname);
lblMap.put(sname, jlabel);
}
return jlabel;
}
private JPrimitiveType getPrimitiveTypeForWrapperType(JClassType wrapperType) {
String wrapperTypeName = wrapperType.getName();
if ("java.lang.Integer".equals(wrapperTypeName)) {
return program.getTypePrimitiveInt();
} else if ("java.lang.Boolean".equals(wrapperTypeName)) {
return program.getTypePrimitiveBoolean();
} else if ("java.lang.Character".equals(wrapperTypeName)) {
return program.getTypePrimitiveChar();
} else if ("java.lang.Long".equals(wrapperTypeName)) {
return program.getTypePrimitiveLong();
} else if ("java.lang.Short".equals(wrapperTypeName)) {
return program.getTypePrimitiveShort();
} else if ("java.lang.Byte".equals(wrapperTypeName)) {
return program.getTypePrimitiveByte();
} else if ("java.lang.Double".equals(wrapperTypeName)) {
return program.getTypePrimitiveDouble();
} else if ("java.lang.Float".equals(wrapperTypeName)) {
return program.getTypePrimitiveFloat();
} else {
return null;
}
}
/**
* Gets a JParameter iterator for a constructor method over its synthetic
* local parameters.
*/
private Iterator<JParameter> getSyntheticLocalsIterator() {
return currentMethod.getParams().listIterator(currentMethod.getOriginalParamTypes().size());
}
private void implementMethod(JMethod method, JExpression returnValue) {
assert method != null;
JMethodBody body = (JMethodBody) method.getBody();
JBlock block = body.getBlock();
SourceInfo info;
if (block.getStatements().size() > 0) {
info = block.getStatements().get(0).getSourceInfo();
} else {
info = method.getSourceInfo();
}
block.clear();
block.addStmt(new JReturnStatement(info, returnValue));
}
/*
* Determine the destination type for an implicit conversion of the given
* expression. Beware that when autoboxing, the type of the expression is
* not necessarily the same as the type of the box to be created. The JDT
* figures out what the necessary conversion is, depending on the context
* the expression appears in, and stores it in
* <code>x.implicitConversion</code>, so extract it from there.
*/
private JPrimitiveType implicitConversionTargetType(Expression x)
throws InternalCompilerException {
/*
* This algorithm for finding the target type is copied from
* org.eclipse.jdt
* .internal.compiler.codegen.CodeStream.generateReturnBytecode() .
*/
switch ((x.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4) {
case TypeIds.T_boolean:
return program.getTypePrimitiveBoolean();
case TypeIds.T_byte:
return program.getTypePrimitiveByte();
case TypeIds.T_char:
return program.getTypePrimitiveChar();
case TypeIds.T_double:
return program.getTypePrimitiveDouble();
case TypeIds.T_float:
return program.getTypePrimitiveFloat();
case TypeIds.T_int:
return program.getTypePrimitiveInt();
case TypeIds.T_long:
return program.getTypePrimitiveLong();
case TypeIds.T_short:
return program.getTypePrimitiveShort();
default:
throw new InternalCompilerException("Could not determine the desired box type");
}
}
/**
* Check whether the specified type is definitely for an enum class.
*
* @param type The type being tested
* @return whether it is certainly an enum
*/
private boolean isEnumType(JType type) {
if (type instanceof JClassType) {
return ((JClassType) type).isEnumOrSubclass() != null;
}
if (type instanceof JNonNullType) {
return isEnumType(((JNonNullType) type).getUnderlyingType());
}
return false;
}
private SourceInfo makeSourceInfo(Statement x) {
int startLine =
Util.getLineNumber(x.sourceStart, currentSeparatorPositions, 0,
currentSeparatorPositions.length - 1);
SourceOrigin toReturn =
SourceOrigin.create(x.sourceStart, x.sourceEnd, startLine, currentFileName);
if (currentMethod != null) {
return currentMethod.getSourceInfo().makeChild(toReturn);
}
return toReturn;
}
private JExpression maybeCast(JType expected, JExpression expression) {
if (expected != expression.getType()) {
// Must be a generic; insert a cast operation.
JReferenceType toType = (JReferenceType) expected;
return new JCastOperation(expression.getSourceInfo(), toType, expression);
} else {
return expression;
}
}
/**
* Sometimes a variable reference can be to a local or parameter in an an
* enclosing method. This is a tricky situation to detect. There's no
* obvious way to tell, but the clue we can get from JDT is that the local's
* containing method won't be the same as the method we're currently
* processing.
*
* Once we have this clue, we can use getEmulationPath to compute the
* current class's binding for that field.
*/
private JVariable possiblyReferenceOuterLocal(JVariable variable, Binding binding) {
if (variable instanceof JLocal || variable instanceof JParameter) {
LocalVariableBinding localBinding = (LocalVariableBinding) binding;
if (localBinding.declaringScope.methodScope() != currentMethodScope) {
variable = null;
VariableBinding[] vars = currentMethodScope.getEmulationPath(localBinding);
if (vars == null) {
return null;
}
assert (vars.length == 1);
VariableBinding varBinding = vars[0];
// See if there's an available parameter
if (varBinding instanceof SyntheticArgumentBinding) {
JType type = (JType) typeMap.get(varBinding.type);
String name = String.valueOf(varBinding.name);
for (int i = 0; i < currentMethod.getParams().size(); ++i) {
JParameter param = currentMethod.getParams().get(i);
if (type == param.getType() && name.equals(param.getName())) {
variable = param;
break;
}
}
}
// just use the field
if (variable == null) {
variable = (JField) typeMap.get(varBinding);
}
// now we have an updated variable that we can create our ref from
}
} else {
assert variable instanceof JField;
// In a constructor, prefer the ctor arg rather than the field.
if (currentOuterThisRefParams != null) {
JParameter ctorArg = currentOuterThisRefParams.get(variable);
if (ctorArg != null) {
variable = ctorArg;
}
}
}
return variable;
}
/**
* Process a {@link ArtificialRescue.Rescue} element.
*/
private void processArtificialRescue(RescueData rescue) {
JReferenceType classType = (JReferenceType) program.getTypeFromJsniRef(rescue.getClassName());
assert classType != null;
if (rescue.isInstantiable()) {
currentClass.addArtificialRescue(classType);
}
if (classType instanceof JDeclaredType) {
List<String> toRescue = new ArrayList<String>();
Collections.addAll(toRescue, rescue.getFields());
Collections.addAll(toRescue, rescue.getMethods());
for (String name : toRescue) {
JsniRef ref = JsniRef.parse("@" + classType.getName() + "::" + name);
final String[] errors = {null};
JNode node =
JsniRefLookup.findJsniRefTarget(ref, program, new JsniRefLookup.ErrorReporter() {
public void reportError(String error) {
errors[0] = error;
}
});
if (errors[0] != null) {
// Should have been caught by ArtificialRescueChecker
throw new InternalCompilerException("Unable to artificially rescue " + name + ": "
+ errors[0]);
}
if (node instanceof JType) {
// Already added the type above.
} else {
currentClass.addArtificialRescue(node);
}
if (node instanceof JField) {
JField field = (JField) node;
if (!field.isFinal()) {
field.setVolatile();
}
}
}
}
}
private void processArtificialRescues(Annotation[] annotations) {
if (annotations == null) {
return;
}
RescueData[] rescues = RescueData.createFromAnnotations(annotations);
for (RescueData rescue : rescues) {
processArtificialRescue(rescue);
}
}
/**
* Helper for creating all JBinaryOperation. Several different JDT nodes can
* result in binary operations: AND_AND_Expression, Assignment,
* BinaryExpresion, CompoundAssignment, EqualExpression, and
* OR_OR_Expression. Hopefully the specific operators that can result in
* each different JDT type won't change between releases, because we only
* look for the specific operators that we think should match each JDT node,
* and throw an error if there's a mismatch.
*/
private JExpression processBinaryOperation(SourceInfo info, JBinaryOperator op, JType type,
Expression arg1, Expression arg2) {
JExpression exprArg1 = dispProcessExpression(arg1);
JExpression exprArg2 = dispProcessExpression(arg2);
JBinaryOperation binaryOperation = new JBinaryOperation(info, type, op, exprArg1, exprArg2);
return binaryOperation;
}
private JExpression processQualifiedThisOrSuperRef(QualifiedThisReference x, JClassType qualType) {
/*
* WEIRD: If a thisref or superref is qualified with the EXACT type of the
* innermost type (in other words, a needless qualifier), it must refer to
* that innermost type, because a class can never be nested inside of
* itself. In this case, we must treat it as if it were not qualified.
*
* In all other cases, the qualified thisref or superref cannot possibly
* refer to the innermost type (even if the innermost type could be cast
* to a compatible type), so we must create a reference to some outer
* type.
*/
SourceInfo info = makeSourceInfo(x);
if (qualType == currentClass) {
return createThisRef(info, qualType);
} else {
return createQualifiedThisRef(info, qualType);
}
}
private InternalCompilerException translateException(Object node, Throwable e) {
if (e instanceof VirtualMachineError) {
// Always rethrow VM errors (an attempt to wrap may fail).
throw (VirtualMachineError) e;
}
InternalCompilerException ice;
if (e instanceof InternalCompilerException) {
ice = (InternalCompilerException) e;
} else {
ice = new InternalCompilerException("Error constructing Java AST", e);
}
String className = node.getClass().getName();
String description = node.toString();
SourceInfo sourceInfo = null;
if (node instanceof Statement) {
sourceInfo = makeSourceInfo((Statement) node);
}
ice.addNode(className, description, sourceInfo);
return ice;
}
/**
* For a given method, try to find all methods that it overrides/implements.
* This version does not use JDT.
*/
private void tryFindUpRefs(JMethod method) {
List<JMethod> overrides = new ArrayList<JMethod>();
tryFindUpRefs(method, overrides);
method.addOverrides(overrides);
}
private void tryFindUpRefs(JMethod method, List<JMethod> overrides) {
if (method.getEnclosingType() != null) {
tryFindUpRefsRecursive(method, method.getEnclosingType(), overrides);
}
}
/**
* For a given method(and method binding), try to find all methods that it
* overrides/implements.
*/
private void tryFindUpRefs(JMethod method, MethodBinding binding) {
// Should never get a parameterized instance here.
assert binding == binding.original();
tryFindUpRefsRecursive(method, binding, binding.declaringClass);
}
/**
* For a given method(and method binding), recursively try to find all
* methods that it overrides/implements.
*/
private void tryFindUpRefsRecursive(JMethod method, JDeclaredType searchThisType,
List<JMethod> overrides) {
// See if this class has any uprefs, unless this class is myself
if (method.getEnclosingType() != searchThisType) {
for (JMethod upRef : searchThisType.getMethods()) {
if (JTypeOracle.methodsDoMatch(method, upRef) && !overrides.contains(upRef)) {
overrides.add(upRef);
break;
}
}
}
// recurse super class
if (searchThisType.getSuperClass() != null) {
tryFindUpRefsRecursive(method, searchThisType.getSuperClass(), overrides);
}
// recurse super interfaces
for (JInterfaceType intf : searchThisType.getImplements()) {
tryFindUpRefsRecursive(method, intf, overrides);
}
}
/**
* For a given method(and method binding), recursively try to find all
* methods that it overrides/implements.
*/
private void tryFindUpRefsRecursive(JMethod method, MethodBinding binding,
ReferenceBinding searchThisType) {
/*
* Always look for uprefs in the original, so we can correctly compare
* erased signatures. The general design for uprefs is to model what the
* JVM does in terms of matching up overrides based on binary match.
*/
searchThisType = (ReferenceBinding) searchThisType.original();
// See if this class has any uprefs, unless this class is myself
if (binding.declaringClass != searchThisType) {
for (MethodBinding tryMethod : searchThisType.getMethods(binding.selector)) {
if (binding.returnType.erasure() == tryMethod.returnType.erasure()
&& binding.areParameterErasuresEqual(tryMethod)) {
JMethod upRef = (JMethod) typeMap.get(tryMethod);
if (!method.getOverrides().contains(upRef)) {
method.addOverride(upRef);
break;
}
}
}
}
// recurse super class
if (searchThisType.superclass() != null) {
tryFindUpRefsRecursive(method, binding, searchThisType.superclass());
}
// recurse super interfaces
if (searchThisType.superInterfaces() != null) {
for (int i = 0; i < searchThisType.superInterfaces().length; i++) {
ReferenceBinding intf = searchThisType.superInterfaces()[i];
tryFindUpRefsRecursive(method, binding, intf);
}
}
}
private JExpression unbox(JExpression toUnbox, JClassType wrapperType) {
JPrimitiveType primitiveType = getPrimitiveTypeForWrapperType(wrapperType);
if (primitiveType == null) {
throw new InternalCompilerException(toUnbox, "Attempt to unbox unexpected type '"
+ wrapperType.getName() + "'", null);
}
String valueMethodName = primitiveType.getName() + "Value";
JMethod valueMethod = null;
for (Object element : wrapperType.getMethods()) {
JMethod method = (JMethod) element;
if (method.getName().equals(valueMethodName)) {
if (method.getParams().isEmpty()) {
// It's a match!
valueMethod = method;
break;
}
}
}
if (valueMethod == null) {
throw new InternalCompilerException(toUnbox, "Expected to find a method on '"
+ wrapperType.getName() + "' whose signature matches 'public "
+ primitiveType.getName() + " " + valueMethodName + "()'", null);
}
JMethodCall unboxCall = new JMethodCall(toUnbox.getSourceInfo(), toUnbox, valueMethod);
return unboxCall;
}
private void writeEnumValueOfMethod(JEnumType type, JField valuesField) {
JField mapField;
{
/*
* Make an inner class to hold a lazy-init name-value map. We use a
* class to take advantage of its clinit.
*
* class Map { $MAP = Enum.createValueOfMap($VALUES); }
*/
SourceInfo typeInfo = type.getSourceInfo().makeChild();
JClassType mapClass = program.createClass(typeInfo, type.getName() + "$Map", false, true);
typeInfo.addCorrelation(typeInfo.getCorrelator().by(mapClass));
mapClass.setSuperClass(program.getTypeJavaLangObject());
SourceInfo fieldInfo = typeInfo.makeChild();
mapField =
program.createField(fieldInfo, "$MAP", mapClass, program.getJavaScriptObject(), true,
Disposition.FINAL);
fieldInfo.addCorrelation(fieldInfo.getCorrelator().by(mapField));
SourceInfo methodInfo = typeInfo.makeChild();
JMethodCall call =
new JMethodCall(methodInfo, null, program.getIndexedMethod("Enum.createValueOfMap"));
call.addArg(new JFieldRef(methodInfo, null, valuesField, type));
JFieldRef mapRef = new JFieldRef(methodInfo, null, mapField, type);
JDeclarationStatement declStmt = new JDeclarationStatement(methodInfo, mapRef, call);
JMethod clinit =
program.createMethod(methodInfo, "$clinit", mapClass, program.getTypeVoid(), false,
true, true, true, false);
clinit.freezeParamTypes();
methodInfo.addCorrelation(methodInfo.getCorrelator().by(clinit));
JBlock clinitBlock = ((JMethodBody) clinit.getBody()).getBlock();
clinitBlock.addStmt(declStmt);
mapField.setInitializer(declStmt);
}
/*
* return Enum.valueOf(Enum$Map.Map.$MAP, name);
*/
{
SourceInfo sourceInfo = currentMethodBody.getSourceInfo();
JFieldRef mapRef = new JFieldRef(sourceInfo, null, mapField, type);
JVariableRef nameRef = createVariableRef(sourceInfo, currentMethod.getParams().get(0));
JMethod delegateTo = program.getIndexedMethod("Enum.valueOf");
JMethodCall call = new JMethodCall(sourceInfo, null, delegateTo);
call.addArgs(mapRef, nameRef);
currentMethodBody.getBlock().addStmt(new JReturnStatement(sourceInfo, call));
}
}
private JField writeEnumValuesMethod(JEnumType type) {
JField valuesField;
{
// $VALUES = new E[]{A,B,B};
SourceInfo fieldInfo = type.getSourceInfo().makeChild();
JArrayType enumArrayType = program.getTypeArray(type);
valuesField =
program.createField(fieldInfo, "$VALUES", type, enumArrayType, true, Disposition.FINAL);
fieldInfo.addCorrelation(fieldInfo.getCorrelator().by(valuesField));
List<JExpression> initializers = new ArrayList<JExpression>();
for (JEnumField field : type.getEnumList()) {
JFieldRef fieldRef = new JFieldRef(fieldInfo, null, field, type);
initializers.add(fieldRef);
}
JNewArray newExpr = JNewArray.createInitializers(fieldInfo, enumArrayType, initializers);
JFieldRef valuesRef = new JFieldRef(fieldInfo, null, valuesField, type);
JDeclarationStatement declStmt = new JDeclarationStatement(fieldInfo, valuesRef, newExpr);
JBlock clinitBlock = ((JMethodBody) type.getMethods().get(0).getBody()).getBlock();
/*
* HACKY: the $VALUES array must be initialized immediately after all of
* the enum fields, but before any user initialization (which might rely
* on $VALUES). The "1 + " is the statement containing the call to
* Enum.$clinit().
*/
int insertionPoint = 1 + initializers.size();
assert clinitBlock.getStatements().size() >= initializers.size() + 1;
clinitBlock.addStmt(insertionPoint, declStmt);
valuesField.setInitializer(declStmt);
}
{
// return $VALUES;
SourceInfo sourceInfo = currentMethod.getSourceInfo();
JFieldRef valuesRef = new JFieldRef(sourceInfo, null, valuesField, type);
currentMethodBody.getBlock().addStmt(new JReturnStatement(sourceInfo, valuesRef));
}
return valuesField;
}
}
/**
* Resolve JSNI refs; replace with compile-time constants where appropriate.
*/
private static class JsniRefGenerationVisitor extends JModVisitor {
private class JsniRefResolver extends JsModVisitor {
private final AbstractMethodDeclaration methodDecl;
private final JsniMethodBody nativeMethodBody;
private JsniRefResolver(AbstractMethodDeclaration methodDecl, JsniMethodBody nativeMethodBody) {
this.methodDecl = methodDecl;
this.nativeMethodBody = nativeMethodBody;
}
@Override
public void endVisit(JsNameRef x, JsContext ctx) {
String ident = x.getIdent();
if (ident.charAt(0) == '@') {
processNameRef(x, ctx);
}
}
private JNode findJsniRefTarget(final SourceInfo info, String ident) {
JsniRef parsed = JsniRef.parse(ident);
if (parsed == null) {
JsniCollector.reportJsniError(info, methodDecl, "Badly formatted native reference '"
+ ident + "'");
return null;
}
JProgram prog = program;
return JsniRefLookup.findJsniRefTarget(parsed, prog, new JsniRefLookup.ErrorReporter() {
public void reportError(String error) {
JsniCollector.reportJsniError(info, methodDecl, error);
}
});
}
private void processClassLiteral(JsNameRef nameRef, SourceInfo info, JType type, JsContext ctx) {
assert !ctx.isLvalue();
JsniClassLiteral classLiteral = new JsniClassLiteral(info, nameRef.getIdent(), type);
nativeMethodBody.addClassRef(classLiteral);
}
private void processField(JsNameRef nameRef, SourceInfo info, JField field, JsContext ctx) {
/*
* We must replace any compile-time constants with the constant value of
* the field.
*/
if (field.isCompileTimeConstant()) {
assert !ctx.isLvalue();
JLiteral initializer = field.getConstInitializer();
JType type = initializer.getType();
if (type instanceof JPrimitiveType || program.isJavaLangString(type)) {
GenerateJavaScriptLiterals generator = new GenerateJavaScriptLiterals();
generator.accept(initializer);
JsExpression result = generator.peek();
assert (result != null);
ctx.replaceMe(result);
return;
}
}
// Normal: create a jsniRef.
JsniFieldRef fieldRef =
new JsniFieldRef(info, nameRef.getIdent(), field, currentClass, ctx.isLvalue());
nativeMethodBody.addJsniRef(fieldRef);
}
private void processMethod(JsNameRef nameRef, SourceInfo info, JMethod method, JsContext ctx) {
assert !ctx.isLvalue();
JsniMethodRef methodRef =
new JsniMethodRef(info, nameRef.getIdent(), method, program.getJavaScriptObject());
nativeMethodBody.addJsniRef(methodRef);
}
private void processNameRef(JsNameRef nameRef, JsContext ctx) {
SourceInfo info = nativeMethodBody.getSourceInfo();
// TODO: make this tighter when we have real source info
// JSourceInfo info = translateInfo(nameRef.getInfo());
String ident = nameRef.getIdent();
JNode node = jsniMap.get(ident);
if (node == null) {
node = findJsniRefTarget(info, ident);
if (node == null) {
return; // already reported error
}
jsniMap.put(ident, node);
}
if (node instanceof JField) {
processField(nameRef, info, (JField) node, ctx);
} else if (node instanceof JMethod) {
processMethod(nameRef, info, (JMethod) node, ctx);
} else if (node instanceof JType) {
processClassLiteral(nameRef, info, (JType) node, ctx);
} else {
throw new InternalCompilerException(node,
"JSNI reference to something other than a class, field, or method?", null);
}
}
}
private JDeclaredType currentClass;
private final Map<String, JNode> jsniMap = new HashMap<String, JNode>();
private final Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap;
private final JProgram program;
public JsniRefGenerationVisitor(JProgram program,
Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap) {
this.program = program;
this.jsniMethodMap = jsniMethodMap;
}
@Override
public void endVisit(JClassType x, Context ctx) {
currentClass = null;
}
@Override
public void endVisit(JsniMethodBody x, Context ctx) {
new JsniRefResolver(jsniMethodMap.get(x), x).accept(x.getFunc());
}
@Override
public boolean visit(JClassType x, Context ctx) {
currentClass = x;
return true;
}
@Override
public boolean visit(JMethodBody x, Context ctx) {
return false;
}
}
/**
* Combines the information from the JDT type nodes and the type map to create
* a JProgram structure.
*/
public static void exec(TypeDeclaration[] types, TypeMap typeMap, JProgram jprogram,
JJSOptions options) {
Event generateJavaAstEvent = SpeedTracerLogger.start(CompilerEventType.GENERATE_JAVA_AST);
// Construct the basic AST.
JavaASTGenerationVisitor v = new JavaASTGenerationVisitor(typeMap, jprogram, options);
for (TypeDeclaration type : types) {
v.processType(type);
}
for (TypeDeclaration type : types) {
v.addBridgeMethods(type.binding);
}
Collections.sort(jprogram.getDeclaredTypes(), new HasNameSort());
// Process JSNI.
Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap = v.getJsniMethodMap();
new JsniRefGenerationVisitor(jprogram, jsniMethodMap).accept(jprogram);
generateJavaAstEvent.end();
}
/**
* Returns <code>true</code> if JDT optimized the condition to
* <code>false</code>.
*/
private static boolean isOptimizedFalse(Expression condition) {
if (condition != null) {
Constant cst = condition.optimizedBooleanConstant();
if (cst != Constant.NotAConstant) {
if (cst.booleanValue() == false) {
return true;
}
}
}
return false;
}
/**
* Returns <code>true</code> if JDT optimized the condition to
* <code>true</code>.
*/
private static boolean isOptimizedTrue(Expression condition) {
if (condition != null) {
Constant cst = condition.optimizedBooleanConstant();
if (cst != Constant.NotAConstant) {
if (cst.booleanValue()) {
return true;
}
}
}
return false;
}
private static boolean isScript(JProgram program) {
return !program.getTypeJavaLangObject().isExternal();
}
}