/*
* 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.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.devtools.j2objc.ast.AbstractTypeDeclaration;
import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration;
import com.google.devtools.j2objc.ast.Block;
import com.google.devtools.j2objc.ast.BodyDeclaration;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.EnumDeclaration;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.FieldAccess;
import com.google.devtools.j2objc.ast.FieldDeclaration;
import com.google.devtools.j2objc.ast.ForStatement;
import com.google.devtools.j2objc.ast.InfixExpression;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.PackageDeclaration;
import com.google.devtools.j2objc.ast.ParenthesizedExpression;
import com.google.devtools.j2objc.ast.PropertyAnnotation;
import com.google.devtools.j2objc.ast.QualifiedName;
import com.google.devtools.j2objc.ast.SingleVariableDeclaration;
import com.google.devtools.j2objc.ast.Statement;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.Type;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.ast.VariableDeclarationExpression;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.ast.VariableDeclarationStatement;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.ErrorUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import com.google.j2objc.annotations.AutoreleasePool;
import com.google.j2objc.annotations.Weak;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
/**
* Rewrites the Java AST to replace difficult to translate code with methods
* that are more Objective C/iOS specific. For example, Objective C doesn't have
* the concept of class variables, so they need to be replaced with static
* accessor methods referencing private static data.
*
* @author Tom Ball
*/
public class Rewriter extends UnitTreeVisitor {
public Rewriter(CompilationUnit unit) {
super(unit);
}
@Override
public boolean visit(MethodDeclaration node) {
ExecutableElement element = node.getExecutableElement();
if (ElementUtil.hasAnnotation(element, AutoreleasePool.class)) {
if (TypeUtil.isReferenceType(element.getReturnType())) {
ErrorUtil.warning(
"Ignoring AutoreleasePool annotation on method with retainable return type");
} else if (node.getBody() != null) {
node.getBody().setHasAutoreleasePool(true);
}
}
if (ElementUtil.hasNullableAnnotation(element) || ElementUtil.hasNonnullAnnotation(element)) {
unit.setHasNullabilityAnnotations();
}
return true;
}
@Override
public void endVisit(ForStatement node) {
// It should not be possible to have multiple VariableDeclarationExpression
// nodes in the initializers.
if (node.getInitializers().size() == 1) {
Object initializer = node.getInitializer(0);
if (initializer instanceof VariableDeclarationExpression) {
List<VariableDeclarationFragment> fragments =
((VariableDeclarationExpression) initializer).getFragments();
for (VariableDeclarationFragment fragment : fragments) {
if (ElementUtil.hasAnnotation(fragment.getVariableElement(), AutoreleasePool.class)) {
Statement loopBody = node.getBody();
if (!(loopBody instanceof Block)) {
Block block = new Block();
node.setBody(block);
block.addStatement(loopBody);
}
((Block) node.getBody()).setHasAutoreleasePool(true);
}
}
}
}
}
@Override
public void endVisit(InfixExpression node) {
InfixExpression.Operator op = node.getOperator();
if (typeUtil.isString(node.getTypeMirror()) && op == InfixExpression.Operator.PLUS) {
rewriteStringConcat(node);
} else if (op == InfixExpression.Operator.CONDITIONAL_AND) {
// Avoid logical-op-parentheses compiler warnings.
if (node.getParent() instanceof InfixExpression) {
InfixExpression parent = (InfixExpression) node.getParent();
if (parent.getOperator() == InfixExpression.Operator.CONDITIONAL_OR) {
ParenthesizedExpression.parenthesizeAndReplace(node);
}
}
} else if (op == InfixExpression.Operator.AND) {
// Avoid bitwise-op-parentheses compiler warnings.
if (node.getParent() instanceof InfixExpression
&& ((InfixExpression) node.getParent()).getOperator() == InfixExpression.Operator.OR) {
ParenthesizedExpression.parenthesizeAndReplace(node);
}
}
// Avoid lower precedence compiler warnings.
if (op == InfixExpression.Operator.AND || op == InfixExpression.Operator.OR) {
for (Expression operand : node.getOperands()) {
if (operand instanceof InfixExpression) {
ParenthesizedExpression.parenthesizeAndReplace(operand);
}
}
}
}
private void rewriteStringConcat(InfixExpression node) {
// Collect all non-string operands that precede the first string operand.
// If there are multiple such operands, move them into a sub-expression.
List<Expression> nonStringOperands = new ArrayList<>();
TypeMirror nonStringExprType = null;
for (Expression operand : node.getOperands()) {
TypeMirror operandType = operand.getTypeMirror();
if (typeUtil.isString(operandType)) {
break;
}
nonStringOperands.add(operand);
nonStringExprType = getAdditionType(nonStringExprType, operandType);
}
if (nonStringOperands.size() < 2) {
return;
}
InfixExpression nonStringExpr =
new InfixExpression(nonStringExprType, InfixExpression.Operator.PLUS);
for (Expression operand : nonStringOperands) {
nonStringExpr.addOperand(TreeUtil.remove(operand));
}
node.addOperand(0, nonStringExpr);
}
private TypeKind getPrimitiveKind(TypeMirror t) {
if (t == null) {
return null;
}
if (t.getKind().isPrimitive()) {
return t.getKind();
}
PrimitiveType p = typeUtil.unboxedType(t);
return p != null ? p.getKind() : null;
}
private PrimitiveType getAdditionType(TypeMirror aType, TypeMirror bType) {
TypeKind aKind = getPrimitiveKind(aType);
TypeKind bKind = getPrimitiveKind(bType);
if (aKind == TypeKind.DOUBLE || bKind == TypeKind.DOUBLE) {
return typeUtil.getDouble();
}
if (aKind == TypeKind.FLOAT || bKind == TypeKind.FLOAT) {
return typeUtil.getFloat();
}
if (aKind == TypeKind.LONG || bKind == TypeKind.LONG) {
return typeUtil.getLong();
}
return typeUtil.getInt();
}
@Override
public void endVisit(SingleVariableDeclaration node) {
if (node.getExtraDimensions() > 0) {
node.setType(Type.newType(node.getVariableElement().asType()));
node.setExtraDimensions(0);
}
VariableElement var = node.getVariableElement();
if (ElementUtil.hasNullableAnnotation(var) || ElementUtil.hasNonnullAnnotation(var)) {
unit.setHasNullabilityAnnotations();
}
}
@Override
public void endVisit(VariableDeclarationStatement node) {
if (options.isJDT()) {
ListMultimap<Integer, VariableDeclarationFragment> newDeclarations =
rewriteExtraDimensions(node.getFragments());
if (newDeclarations != null) {
List<Statement> statements = ((Block) node.getParent()).getStatements();
int location = 0;
while (location < statements.size() && !node.equals(statements.get(location))) {
location++;
}
for (Integer dimensions : newDeclarations.keySet()) {
List<VariableDeclarationFragment> fragments = newDeclarations.get(dimensions);
VariableDeclarationStatement newDecl = new VariableDeclarationStatement(fragments.get(0));
newDecl.getFragments().addAll(fragments.subList(1, fragments.size()));
statements.add(++location, newDecl);
}
}
}
}
@Override
public void endVisit(FieldDeclaration node) {
if (options.isJDT()) {
ListMultimap<Integer, VariableDeclarationFragment> newDeclarations =
rewriteExtraDimensions(node.getFragments());
if (newDeclarations != null) {
List<BodyDeclaration> bodyDecls = TreeUtil.asDeclarationSublist(node);
for (Integer dimensions : newDeclarations.keySet()) {
List<VariableDeclarationFragment> fragments = newDeclarations.get(dimensions);
FieldDeclaration newDecl = new FieldDeclaration(fragments.get(0));
newDecl.getFragments().addAll(fragments.subList(1, fragments.size()));
bodyDecls.add(newDecl);
}
}
}
}
@Override
public boolean visit(QualifiedName node) {
VariableElement var = TreeUtil.getVariableElement(node);
Expression qualifier = node.getQualifier();
if (var != null && var.getKind().isField() && TreeUtil.getVariableElement(qualifier) != null) {
// FieldAccess nodes are more easily mutated than QualifiedName.
FieldAccess fieldAccess =
new FieldAccess(var, node.getTypeMirror(), TreeUtil.remove(qualifier));
node.replaceWith(fieldAccess);
fieldAccess.accept(this);
return false;
}
return true;
}
private ListMultimap<Integer, VariableDeclarationFragment> rewriteExtraDimensions(
List<VariableDeclarationFragment> fragments) {
// Removes extra dimensions on variable declaration fragments and creates extra field
// declaration nodes if necessary.
// eg. "int i1, i2[], i3[][];" becomes "int i1; int[] i2; int[][] i3".
ListMultimap<Integer, VariableDeclarationFragment> newDeclarations = null;
int masterDimensions = -1;
Iterator<VariableDeclarationFragment> iter = fragments.iterator();
while (iter.hasNext()) {
VariableDeclarationFragment frag = iter.next();
int dimensions = frag.getExtraDimensions();
if (masterDimensions == -1) {
masterDimensions = dimensions;
} else if (dimensions != masterDimensions) {
if (newDeclarations == null) {
newDeclarations = LinkedListMultimap.create();
}
VariableDeclarationFragment newFrag = new VariableDeclarationFragment(
frag.getVariableElement(), TreeUtil.remove(frag.getInitializer()));
newDeclarations.put(dimensions, newFrag);
iter.remove();
} else {
frag.setExtraDimensions(0);
}
}
return newDeclarations;
}
/**
* Verify, update property attributes. Accessor methods are not checked since a
* property annotation may apply to separate variables in a field declaration, so
* each variable needs to be checked separately during generation.
*/
@Override
public void endVisit(PropertyAnnotation node) {
FieldDeclaration field = (FieldDeclaration) node.getParent();
TypeMirror fieldType = field.getTypeMirror();
VariableDeclarationFragment firstVarNode = field.getFragment(0);
if (typeUtil.isString(fieldType)) {
node.addAttribute("copy");
} else if (ElementUtil.hasAnnotation(firstVarNode.getVariableElement(), Weak.class)) {
if (node.hasAttribute("strong")) {
ErrorUtil.error(field, "Weak field annotation conflicts with strong Property attribute");
return;
}
node.addAttribute("weak");
}
node.removeAttribute("readwrite");
node.removeAttribute("strong");
node.removeAttribute("atomic");
// Make sure attempt isn't made to specify an accessor method for fields with multiple
// fragments, since each variable needs unique accessors.
String getter = node.getGetter();
String setter = node.getSetter();
if (field.getFragments().size() > 1) {
if (getter != null) {
ErrorUtil.error(field, "@Property getter declared for multiple fields");
return;
}
if (setter != null) {
ErrorUtil.error(field, "@Property setter declared for multiple fields");
return;
}
} else {
// Check that specified accessors exist.
TypeElement enclosingType = TreeUtil.getEnclosingTypeElement(node);
if (getter != null) {
if (ElementUtil.findMethod(enclosingType, getter) == null) {
ErrorUtil.error(field, "Non-existent getter specified: " + getter);
}
}
if (setter != null) {
if (ElementUtil.findMethod(
enclosingType, setter, TypeUtil.getQualifiedName(fieldType)) == null) {
ErrorUtil.error(field, "Non-existent setter specified: " + setter);
}
}
}
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
checkForNullabilityAnnotation(node);
}
@Override
public void endVisit(EnumDeclaration node) {
checkForNullabilityAnnotation(node);
}
@Override
public void endVisit(TypeDeclaration node) {
checkForNullabilityAnnotation(node);
}
private void checkForNullabilityAnnotation(AbstractTypeDeclaration node) {
if (ElementUtil.hasAnnotation(node.getTypeElement(), ParametersAreNonnullByDefault.class)) {
unit.setHasNullabilityAnnotations();
}
}
@Override
public void endVisit(PackageDeclaration node) {
String pkgName = node.getName().toString();
if (options.getPackageInfoLookup().hasParametersAreNonnullByDefault(pkgName)) {
unit.setHasNullabilityAnnotations();
}
}
}