/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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.devtools.j2objc.translate;
import com.google.devtools.j2objc.ast.ArrayAccess;
import com.google.devtools.j2objc.ast.ArrayInitializer;
import com.google.devtools.j2objc.ast.AssertStatement;
import com.google.devtools.j2objc.ast.Assignment;
import com.google.devtools.j2objc.ast.CastExpression;
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.ConditionalExpression;
import com.google.devtools.j2objc.ast.ConstructorInvocation;
import com.google.devtools.j2objc.ast.DoStatement;
import com.google.devtools.j2objc.ast.EnumConstantDeclaration;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.FunctionInvocation;
import com.google.devtools.j2objc.ast.IfStatement;
import com.google.devtools.j2objc.ast.InfixExpression;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.ParenthesizedExpression;
import com.google.devtools.j2objc.ast.PostfixExpression;
import com.google.devtools.j2objc.ast.PrefixExpression;
import com.google.devtools.j2objc.ast.ReturnStatement;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.SuperConstructorInvocation;
import com.google.devtools.j2objc.ast.SuperMethodInvocation;
import com.google.devtools.j2objc.ast.SwitchStatement;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.Type;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.ast.WhileStatement;
import com.google.devtools.j2objc.types.ExecutablePair;
import com.google.devtools.j2objc.types.FunctionElement;
import com.google.devtools.j2objc.types.PointerType;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.NameTable;
import com.google.devtools.j2objc.util.TypeUtil;
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
/**
* Adds support for boxing and unboxing numeric primitive values.
*
* @author Tom Ball
*/
public class Autoboxer extends UnitTreeVisitor {
private static final String VALUE_METHOD = "Value";
private static final String VALUEOF_METHOD = "valueOf";
public Autoboxer(CompilationUnit unit) {
super(unit);
}
/**
* Convert a primitive type expression into a wrapped instance. Each
* wrapper class has a static valueOf factory method, so "expr" gets
* translated to "Wrapper.valueOf(expr)".
*/
private void box(Expression expr) {
boxWithClass(expr, typeUtil.boxedClass((PrimitiveType) expr.getTypeMirror()));
}
private void box(Expression expr, TypeMirror boxedType) {
if (typeUtil.isBoxedType(boxedType)) {
boxWithClass(expr, TypeUtil.asTypeElement(boxedType));
} else {
box(expr);
}
}
private void boxWithClass(Expression expr, TypeElement boxedClass) {
PrimitiveType primitiveType = typeUtil.unboxedType(boxedClass.asType());
assert primitiveType != null;
ExecutableElement wrapperMethod = ElementUtil.findMethod(
boxedClass, VALUEOF_METHOD, TypeUtil.getQualifiedName(primitiveType));
assert wrapperMethod != null : "could not find valueOf method for " + boxedClass;
MethodInvocation invocation = new MethodInvocation(
new ExecutablePair(wrapperMethod), new SimpleName(boxedClass));
expr.replaceWith(invocation);
invocation.addArgument(expr);
}
/**
* Convert a wrapper class instance to its primitive equivalent. Each
* wrapper class has a "classValue()" method, such as intValue() or
* booleanValue(). This method therefore converts "expr" to
* "expr.classValue()".
*/
private void unbox(Expression expr) {
unbox(expr, null);
}
/**
* Convert a wrapper class instance to a specified primitive equivalent.
*/
private void unbox(Expression expr, PrimitiveType primitiveType) {
TypeElement boxedClass = findBoxedSuperclass(expr.getTypeMirror());
if (primitiveType == null && boxedClass != null) {
primitiveType = typeUtil.unboxedType(boxedClass.asType());
}
if (primitiveType == null) {
return;
}
ExecutableElement valueMethod = ElementUtil.findMethod(
boxedClass, TypeUtil.getName(primitiveType) + VALUE_METHOD);
assert valueMethod != null : "could not find value method for " + boxedClass;
MethodInvocation invocation = new MethodInvocation(new ExecutablePair(valueMethod), null);
expr.replaceWith(invocation);
invocation.setExpression(expr);
}
private TypeElement findBoxedSuperclass(TypeMirror type) {
while (type != null) {
if (typeUtil.isBoxedType(type)) {
return TypeUtil.asTypeElement(type);
}
type = typeUtil.getSuperclass(type);
}
return null;
}
@Override
public void endVisit(Assignment node) {
TypeMirror lhType = node.getLeftHandSide().getTypeMirror();
boolean lhPrimitive = lhType.getKind().isPrimitive();
Expression rhs = node.getRightHandSide();
TypeMirror rhType = rhs.getTypeMirror();
boolean rhPrimitive = rhType.getKind().isPrimitive();
Assignment.Operator op = node.getOperator();
if (op != Assignment.Operator.ASSIGN && !lhPrimitive) {
rewriteBoxedAssignment(node);
} else if (lhPrimitive && !rhPrimitive) {
unbox(rhs);
} else if (!lhPrimitive && rhPrimitive) {
box(rhs, lhType);
}
}
private void rewriteBoxedAssignment(Assignment node) {
Expression lhs = node.getLeftHandSide();
Expression rhs = node.getRightHandSide();
TypeMirror type = lhs.getTypeMirror();
TypeMirror primitiveType = typeUtil.unboxedType(type);
if (primitiveType == null) {
return;
}
TypeMirror pointerType = new PointerType(type);
String funcName = "JreBoxed" + getAssignFunctionName(node.getOperator())
+ translationUtil.getOperatorFunctionModifier(lhs)
+ NameTable.capitalize(primitiveType.toString());
FunctionElement element = new FunctionElement(funcName, type, TypeUtil.asTypeElement(type))
.addParameters(pointerType, primitiveType);
FunctionInvocation invocation = new FunctionInvocation(element, type);
invocation.addArgument(new PrefixExpression(
pointerType, PrefixExpression.Operator.ADDRESS_OF, TreeUtil.remove(lhs)));
invocation.addArgument(TreeUtil.remove(rhs));
unbox(rhs);
node.replaceWith(invocation);
}
private static String getAssignFunctionName(Assignment.Operator op) {
switch (op) {
case PLUS_ASSIGN:
return "PlusAssign";
case MINUS_ASSIGN:
return "MinusAssign";
case TIMES_ASSIGN:
return "TimesAssign";
case DIVIDE_ASSIGN:
return "DivideAssign";
case BIT_AND_ASSIGN:
return "BitAndAssign";
case BIT_OR_ASSIGN:
return "BitOrAssign";
case BIT_XOR_ASSIGN:
return "BitXorAssign";
case REMAINDER_ASSIGN:
return "ModAssign";
case LEFT_SHIFT_ASSIGN:
return "LShiftAssign";
case RIGHT_SHIFT_SIGNED_ASSIGN:
return "RShiftAssign";
case RIGHT_SHIFT_UNSIGNED_ASSIGN:
return "URShiftAssign";
default:
throw new IllegalArgumentException();
}
}
@Override
public void endVisit(ArrayAccess node) {
Expression index = node.getIndex();
if (!index.getTypeMirror().getKind().isPrimitive()) {
unbox(index);
}
}
@Override
public void endVisit(ArrayInitializer node) {
TypeMirror type = node.getTypeMirror().getComponentType();
for (Expression expr : node.getExpressions()) {
boxOrUnboxExpression(expr, type);
}
}
@Override
public void endVisit(AssertStatement node) {
Expression expression = node.getMessage();
if (expression != null && expression.getTypeMirror().getKind().isPrimitive()) {
box(expression);
}
}
@Override
public void endVisit(CastExpression node) {
TypeMirror castType = node.getTypeMirror();
Expression expr = node.getExpression();
TypeMirror exprType = expr.getTypeMirror();
if (castType.getKind().isPrimitive() && !exprType.getKind().isPrimitive()) {
if (typeUtil.isAssignable(exprType, typeUtil.getJavaNumber().asType())) {
// Casting a Number object to a primitive, convert to value method.
unbox(expr, (PrimitiveType) castType);
} else {
// Casting an object to a primitive. Convert the cast type to the wrapper
// so that we do a proper cast check, as Java would.
castType = typeUtil.boxedClass((PrimitiveType) castType).asType();
node.setType(Type.newType(castType));
boxOrUnboxExpression(expr, castType);
}
} else {
boxOrUnboxExpression(expr, castType);
}
Expression newExpr = node.getExpression();
if (newExpr != expr) {
TreeNode parent = node.getParent();
if (parent instanceof ParenthesizedExpression) {
parent.replaceWith(TreeUtil.remove(newExpr));
} else {
node.replaceWith(TreeUtil.remove(newExpr));
}
}
}
@Override
public void endVisit(ClassInstanceCreation node) {
convertArguments(node.getExecutableElement(), node.getArguments());
}
@Override
public void endVisit(ConditionalExpression node) {
Expression expr = node.getExpression();
if (!expr.getTypeMirror().getKind().isPrimitive()) {
unbox(expr);
}
boolean nodeIsPrimitive = node.getTypeMirror().getKind().isPrimitive();
Expression thenExpr = node.getThenExpression();
boolean thenIsPrimitive = thenExpr.getTypeMirror().getKind().isPrimitive();
Expression elseExpr = node.getElseExpression();
boolean elseIsPrimitive = elseExpr.getTypeMirror().getKind().isPrimitive();
if (thenIsPrimitive && !nodeIsPrimitive) {
box(thenExpr);
} else if (!thenIsPrimitive && nodeIsPrimitive) {
unbox(thenExpr);
}
if (elseIsPrimitive && !nodeIsPrimitive) {
box(elseExpr);
} else if (!elseIsPrimitive && nodeIsPrimitive) {
unbox(elseExpr);
}
}
@Override
public void endVisit(ConstructorInvocation node) {
convertArguments(node.getExecutableElement(), node.getArguments());
}
@Override
public void endVisit(DoStatement node) {
Expression expression = node.getExpression();
if (!expression.getTypeMirror().getKind().isPrimitive()) {
unbox(expression);
}
}
@Override
public void endVisit(EnumConstantDeclaration node) {
convertArguments(node.getExecutableElement(), node.getArguments());
}
@Override
public void endVisit(IfStatement node) {
Expression expr = node.getExpression();
if (!expr.getTypeMirror().getKind().isPrimitive()) {
unbox(expr);
}
}
@Override
public void endVisit(InfixExpression node) {
InfixExpression.Operator op = node.getOperator();
List<Expression> operands = node.getOperands();
// Don't unbox for equality tests where both operands are boxed types.
if ((op == InfixExpression.Operator.EQUALS || op == InfixExpression.Operator.NOT_EQUALS)
&& !operands.get(0).getTypeMirror().getKind().isPrimitive()
&& !operands.get(1).getTypeMirror().getKind().isPrimitive()) {
return;
}
// Don't unbox for string concatenation.
if (op == InfixExpression.Operator.PLUS && typeUtil.isString(node.getTypeMirror())) {
return;
}
for (Expression operand : operands) {
if (!operand.getTypeMirror().getKind().isPrimitive()) {
unbox(operand);
}
}
}
@Override
public void endVisit(MethodInvocation node) {
convertArguments(node.getExecutableElement(), node.getArguments());
}
@Override
public void endVisit(PrefixExpression node) {
PrefixExpression.Operator op = node.getOperator();
Expression operand = node.getOperand();
if (op == PrefixExpression.Operator.INCREMENT) {
rewriteBoxedPrefixOrPostfix(node, operand, "PreIncr");
} else if (op == PrefixExpression.Operator.DECREMENT) {
rewriteBoxedPrefixOrPostfix(node, operand, "PreDecr");
} else if (!operand.getTypeMirror().getKind().isPrimitive()) {
unbox(operand);
}
}
@Override
public void endVisit(PostfixExpression node) {
PostfixExpression.Operator op = node.getOperator();
if (op == PostfixExpression.Operator.INCREMENT) {
rewriteBoxedPrefixOrPostfix(node, node.getOperand(), "PostIncr");
} else if (op == PostfixExpression.Operator.DECREMENT) {
rewriteBoxedPrefixOrPostfix(node, node.getOperand(), "PostDecr");
}
}
private void rewriteBoxedPrefixOrPostfix(TreeNode node, Expression operand, String funcName) {
TypeMirror type = operand.getTypeMirror();
TypeMirror primitiveType = typeUtil.unboxedType(type);
if (primitiveType == null) {
return;
}
TypeMirror pointerType = new PointerType(type);
funcName = "JreBoxed" + funcName + translationUtil.getOperatorFunctionModifier(operand)
+ NameTable.capitalize(primitiveType.toString());
FunctionElement element = new FunctionElement(funcName, type, TypeUtil.asTypeElement(type))
.addParameters(pointerType);
FunctionInvocation invocation = new FunctionInvocation(element, type);
invocation.addArgument(new PrefixExpression(
pointerType, PrefixExpression.Operator.ADDRESS_OF, TreeUtil.remove(operand)));
node.replaceWith(invocation);
}
@Override
public void endVisit(ReturnStatement node) {
Expression expr = node.getExpression();
if (expr != null) {
boolean returnsPrimitive = TreeUtil.getOwningReturnType(node).getKind().isPrimitive();
boolean exprIsPrimitive = expr.getTypeMirror().getKind().isPrimitive();
if (returnsPrimitive && !exprIsPrimitive) {
unbox(expr);
}
if (!returnsPrimitive && exprIsPrimitive) {
box(expr);
}
}
}
@Override
public void endVisit(SuperConstructorInvocation node) {
convertArguments(node.getExecutableElement(), node.getArguments());
}
@Override
public void endVisit(SuperMethodInvocation node) {
convertArguments(node.getExecutableElement(), node.getArguments());
}
@Override
public void endVisit(VariableDeclarationFragment node) {
Expression initializer = node.getInitializer();
if (initializer != null) {
TypeMirror varType = node.getVariableElement().asType();
boolean varIsPrimitive = varType.getKind().isPrimitive();
boolean initIsPrimitive = initializer.getTypeMirror().getKind().isPrimitive();
if (varIsPrimitive && !initIsPrimitive) {
unbox(initializer);
} else if (!varIsPrimitive && initIsPrimitive) {
box(initializer, varType);
}
}
}
@Override
public void endVisit(WhileStatement node) {
Expression expression = node.getExpression();
if (!expression.getTypeMirror().getKind().isPrimitive()) {
unbox(expression);
}
}
@Override
public void endVisit(SwitchStatement node) {
Expression expression = node.getExpression();
if (!expression.getTypeMirror().getKind().isPrimitive()) {
unbox(expression);
}
}
private void convertArguments(ExecutableElement method, List<Expression> args) {
List<? extends VariableElement> params = method.getParameters();
for (int i = 0; i < args.size(); i++) {
TypeMirror paramType;
if (method.isVarArgs() && i >= params.size() - 1) {
paramType = ((ArrayType) params.get(params.size() - 1).asType()).getComponentType();
} else {
paramType = params.get(i).asType();
}
boxOrUnboxExpression(args.get(i), paramType);
}
}
private void boxOrUnboxExpression(Expression expr, TypeMirror type) {
boolean exprIsPrimitive = expr.getTypeMirror().getKind().isPrimitive();
boolean typeIsPrimitive = type.getKind().isPrimitive();
if (typeIsPrimitive && !exprIsPrimitive) {
unbox(expr);
} else if (!typeIsPrimitive && exprIsPrimitive) {
box(expr);
}
}
}