/* * 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.gen; import com.google.common.base.CharMatcher; import com.google.devtools.j2objc.ast.ArrayAccess; import com.google.devtools.j2objc.ast.ArrayCreation; import com.google.devtools.j2objc.ast.ArrayInitializer; import com.google.devtools.j2objc.ast.ArrayType; import com.google.devtools.j2objc.ast.AssertStatement; import com.google.devtools.j2objc.ast.Assignment; import com.google.devtools.j2objc.ast.Block; import com.google.devtools.j2objc.ast.BooleanLiteral; import com.google.devtools.j2objc.ast.BreakStatement; import com.google.devtools.j2objc.ast.CStringLiteral; import com.google.devtools.j2objc.ast.CastExpression; import com.google.devtools.j2objc.ast.CatchClause; import com.google.devtools.j2objc.ast.CharacterLiteral; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.CommaExpression; import com.google.devtools.j2objc.ast.ConditionalExpression; import com.google.devtools.j2objc.ast.ConstructorInvocation; import com.google.devtools.j2objc.ast.ContinueStatement; import com.google.devtools.j2objc.ast.CreationReference; import com.google.devtools.j2objc.ast.Dimension; import com.google.devtools.j2objc.ast.DoStatement; import com.google.devtools.j2objc.ast.EmptyStatement; import com.google.devtools.j2objc.ast.EnhancedForStatement; import com.google.devtools.j2objc.ast.Expression; import com.google.devtools.j2objc.ast.ExpressionMethodReference; import com.google.devtools.j2objc.ast.ExpressionStatement; import com.google.devtools.j2objc.ast.FieldAccess; import com.google.devtools.j2objc.ast.ForStatement; 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.Initializer; import com.google.devtools.j2objc.ast.InstanceofExpression; import com.google.devtools.j2objc.ast.IntersectionType; import com.google.devtools.j2objc.ast.LabeledStatement; import com.google.devtools.j2objc.ast.LambdaExpression; import com.google.devtools.j2objc.ast.MarkerAnnotation; import com.google.devtools.j2objc.ast.MethodInvocation; import com.google.devtools.j2objc.ast.Name; import com.google.devtools.j2objc.ast.NameQualifiedType; import com.google.devtools.j2objc.ast.NativeExpression; import com.google.devtools.j2objc.ast.NativeStatement; import com.google.devtools.j2objc.ast.NormalAnnotation; import com.google.devtools.j2objc.ast.NullLiteral; import com.google.devtools.j2objc.ast.NumberLiteral; 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.PrimitiveType; import com.google.devtools.j2objc.ast.QualifiedName; import com.google.devtools.j2objc.ast.QualifiedType; import com.google.devtools.j2objc.ast.ReturnStatement; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.SimpleType; import com.google.devtools.j2objc.ast.SingleMemberAnnotation; import com.google.devtools.j2objc.ast.SingleVariableDeclaration; import com.google.devtools.j2objc.ast.Statement; import com.google.devtools.j2objc.ast.StringLiteral; import com.google.devtools.j2objc.ast.SuperConstructorInvocation; import com.google.devtools.j2objc.ast.SuperFieldAccess; import com.google.devtools.j2objc.ast.SuperMethodInvocation; import com.google.devtools.j2objc.ast.SuperMethodReference; import com.google.devtools.j2objc.ast.SwitchCase; import com.google.devtools.j2objc.ast.SwitchStatement; import com.google.devtools.j2objc.ast.SynchronizedStatement; import com.google.devtools.j2objc.ast.ThisExpression; import com.google.devtools.j2objc.ast.ThrowStatement; import com.google.devtools.j2objc.ast.TreeNode; import com.google.devtools.j2objc.ast.TreeNode.Kind; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TryStatement; import com.google.devtools.j2objc.ast.Type; import com.google.devtools.j2objc.ast.TypeLiteral; import com.google.devtools.j2objc.ast.TypeMethodReference; import com.google.devtools.j2objc.ast.UnionType; 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.ast.WhileStatement; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.NameTable; import com.google.devtools.j2objc.util.TypeUtil; import com.google.devtools.j2objc.util.UnicodeUtils; import java.util.Iterator; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; /** * Returns an Objective-C equivalent of a Java AST node. * * @author Tom Ball */ public class StatementGenerator extends UnitTreeVisitor { private final SourceBuilder buffer; public static String generate(TreeNode node, int currentLine) { StatementGenerator generator = new StatementGenerator(node, currentLine); if (node == null) { throw new NullPointerException("cannot generate a null statement"); } node.accept(generator); return generator.getResult(); } private StatementGenerator(TreeNode node, int currentLine) { super(TreeUtil.getCompilationUnit(node)); buffer = new SourceBuilder(options.emitLineDirectives(), currentLine); } private String getResult() { return buffer.toString(); } private void printMethodInvocationNameAndArgs(String selector, List<Expression> args) { String[] selParts = selector.split(":"); if (args.isEmpty()) { assert selParts.length == 1 && !selector.endsWith(":"); buffer.append(' '); buffer.append(selector); } else { assert selParts.length == args.size(); for (int i = 0; i < args.size(); i++) { buffer.append(' '); buffer.append(selParts[i]); buffer.append(':'); args.get(i).accept(this); } } } /** * A temporary stub to show pseudocode in place of Java 8 features. */ private boolean assertIncompleteJava8Support(TreeNode node) { buffer.append(node.toString()); return false; } @Override public boolean preVisit(TreeNode node) { super.preVisit(node); if (!(node instanceof Block)) { buffer.syncLineNumbers(node); } return true; } @Override public boolean visit(ArrayAccess node) { throw new AssertionError("ArrayAccess nodes are rewritten by ArrayRewriter."); } @Override public boolean visit(ArrayCreation node) { throw new AssertionError("ArrayCreation nodes are rewritten by ArrayRewriter."); } @Override public boolean visit(ArrayInitializer node) { javax.lang.model.type.ArrayType type = (javax.lang.model.type.ArrayType) node.getTypeMirror(); TypeMirror componentType = type.getComponentType(); buffer.append(UnicodeUtils.format("(%s[]){ ", NameTable.getPrimitiveObjCType(componentType))); for (Iterator<Expression> it = node.getExpressions().iterator(); it.hasNext(); ) { it.next().accept(this); if (it.hasNext()) { buffer.append(", "); } } buffer.append(" }"); return false; } @Override public boolean visit(ArrayType node) { TypeElement iosArray = typeUtil.getIosArray(node.getTypeMirror().getComponentType()); buffer.append(ElementUtil.getName(iosArray)); return false; } @Override public boolean visit(AssertStatement node) { buffer.append("JreAssert(("); node.getExpression().accept(this); buffer.append("), ("); if (node.getMessage() != null) { node.getMessage().accept(this); } else { int startPos = node.getStartPosition(); String assertStatementString = unit.getSource().substring(startPos, startPos + node.getLength()); assertStatementString = CharMatcher.whitespace().trimFrom(assertStatementString); // Generates the following string: // filename.java:456 condition failed: foobar != fish. String msg = TreeUtil.getSourceFileName(unit) + ":" + node.getLineNumber() + " condition failed: " + assertStatementString; buffer.append(LiteralGenerator.generateStringLiteral(msg)); } buffer.append("));\n"); return false; } @Override public boolean visit(Assignment node) { node.getLeftHandSide().accept(this); buffer.append(' '); buffer.append(node.getOperator().toString()); buffer.append(' '); node.getRightHandSide().accept(this); return false; } @Override public boolean visit(Block node) { if (node.hasAutoreleasePool()) { buffer.append("{\n@autoreleasepool "); } buffer.append("{\n"); printStatements(node.getStatements()); buffer.append("}\n"); if (node.hasAutoreleasePool()) { buffer.append("}\n"); } return false; } private void printStatements(List<?> statements) { for (Iterator<?> it = statements.iterator(); it.hasNext(); ) { Statement s = (Statement) it.next(); s.accept(this); } } @Override public boolean visit(BooleanLiteral node) { buffer.append(node.booleanValue() ? "true" : "false"); return false; } @Override public boolean visit(BreakStatement node) { if (node.getLabel() != null) { // Objective-C doesn't have a labeled break, so use a goto. buffer.append("goto "); node.getLabel().accept(this); } else { buffer.append("break"); } buffer.append(";\n"); return false; } @Override public boolean visit(CStringLiteral node) { buffer.append("\""); buffer.append(node.getLiteralValue()); buffer.append("\""); return false; } @Override public boolean visit(CastExpression node) { buffer.append("("); buffer.append(nameTable.getObjCType(node.getType().getTypeMirror())); buffer.append(") "); node.getExpression().accept(this); return false; } private void printMultiCatch(CatchClause node) { SingleVariableDeclaration exception = node.getException(); for (Type exceptionType : ((UnionType) exception.getType()).getTypes()) { buffer.append("@catch ("); exceptionType.accept(this); buffer.append(" *"); buffer.append(nameTable.getVariableQualifiedName(exception.getVariableElement())); buffer.append(") {\n"); printStatements(node.getBody().getStatements()); buffer.append("}\n"); } } @Override public boolean visit(CharacterLiteral node) { buffer.append(UnicodeUtils.escapeCharLiteral(node.charValue())); return false; } @Override public boolean visit(ClassInstanceCreation node) { throw new AssertionError("ClassInstanceCreation nodes are rewritten by Functionizer."); } @Override public boolean visit(CommaExpression node) { buffer.append('('); for (Iterator<Expression> it = node.getExpressions().iterator(); it.hasNext(); ) { Expression e = it.next(); e.accept(this); if (it.hasNext()) { buffer.append(", "); } } buffer.append(')'); return false; } @Override public boolean visit(ConditionalExpression node) { node.getExpression().accept(this); buffer.append(" ? "); node.getThenExpression().accept(this); buffer.append(" : "); node.getElseExpression().accept(this); return false; } @Override public boolean visit(ConstructorInvocation node) { throw new AssertionError("ConstructorInvocation nodes are rewritten by Functionizer."); } @Override public boolean visit(ContinueStatement node) { if (node.getLabel() != null) { // Objective-C doesn't have a labeled continue, so use a goto. buffer.append("goto "); node.getLabel().accept(this); } else { buffer.append("continue"); } buffer.append(";\n"); return false; } @Override public boolean visit(CreationReference node) { throw new AssertionError("CreationReference nodes are rewritten by LambdaRewriter."); } @Override public boolean visit(Dimension node) { // TODO(kirbs): Implement correct conversion of Java 8 features to Objective-C. return assertIncompleteJava8Support(node); } @Override public boolean visit(DoStatement node) { buffer.append("do "); node.getBody().accept(this); buffer.append(" while ("); node.getExpression().accept(this); buffer.append(");\n"); return false; } @Override public boolean visit(EmptyStatement node) { // Preserve line number difference with owner, to allow suppression of // clang empty-statement warnings in Java source. TreeNode parent = node.getParent(); if (parent.getKind() != Kind.SWITCH_STATEMENT && node.getLineNumber() != parent.getLineNumber()) { buffer.newline(); buffer.printIndent(); } buffer.append(";\n"); return false; } @Override public boolean visit(EnhancedForStatement node) { buffer.append("for ("); node.getParameter().accept(this); buffer.append(" in "); node.getExpression().accept(this); buffer.append(") "); node.getBody().accept(this); return false; } @Override public boolean visit(ExpressionMethodReference node) { throw new AssertionError("ExpressionMethodReference nodes are rewritten by LambdaRewriter."); } @Override public boolean visit(ExpressionStatement node) { Expression expression = node.getExpression(); TypeMirror type = expression.getTypeMirror(); if (!type.getKind().isPrimitive() && !type.getKind().equals(TypeKind.VOID) && options.useARC() && (expression instanceof MethodInvocation || expression instanceof SuperMethodInvocation || expression instanceof FunctionInvocation)) { // Avoid clang warning that the return value is unused. buffer.append("(void) "); } expression.accept(this); buffer.append(";\n"); return false; } @Override public boolean visit(FieldAccess node) { node.getExpression().accept(this); buffer.append("->"); node.getName().accept(this); return false; } @Override public boolean visit(ForStatement node) { buffer.append("for ("); for (Iterator<Expression> it = node.getInitializers().iterator(); it.hasNext(); ) { Expression next = it.next(); next.accept(this); if (it.hasNext()) { buffer.append(", "); } } buffer.append("; "); if (node.getExpression() != null) { node.getExpression().accept(this); } buffer.append("; "); for (Iterator<Expression> it = node.getUpdaters().iterator(); it.hasNext(); ) { it.next().accept(this); if (it.hasNext()) { buffer.append(", "); } } buffer.append(") "); node.getBody().accept(this); return false; } @Override public boolean visit(FunctionInvocation node) { buffer.append(node.getName()); buffer.append('('); for (Iterator<Expression> iter = node.getArguments().iterator(); iter.hasNext(); ) { iter.next().accept(this); if (iter.hasNext()) { buffer.append(", "); } } buffer.append(')'); return false; } @Override public boolean visit(IfStatement node) { buffer.append("if ("); node.getExpression().accept(this); buffer.append(") "); node.getThenStatement().accept(this); if (node.getElseStatement() != null) { buffer.append(" else "); node.getElseStatement().accept(this); } return false; } @Override public boolean visit(InfixExpression node) { InfixExpression.Operator op = node.getOperator(); List<Expression> operands = node.getOperands(); assert operands.size() >= 2; if ((op.equals(InfixExpression.Operator.EQUALS) || op.equals(InfixExpression.Operator.NOT_EQUALS))) { Expression lhs = operands.get(0); Expression rhs = operands.get(1); if (lhs instanceof StringLiteral || rhs instanceof StringLiteral) { if (!(lhs instanceof StringLiteral)) { // In case the lhs can't call isEqual. lhs = operands.get(1); rhs = operands.get(0); } buffer.append(op.equals(InfixExpression.Operator.NOT_EQUALS) ? "![" : "["); lhs.accept(this); buffer.append(" isEqual:"); rhs.accept(this); buffer.append("]"); return false; } } String opStr = ' ' + op.toString() + ' '; boolean isFirst = true; for (Expression operand : operands) { if (!isFirst) { buffer.append(opStr); } isFirst = false; operand.accept(this); } return false; } @Override public boolean visit(InstanceofExpression node) { TypeElement type = TypeUtil.asTypeElement(node.getRightOperand().getTypeMirror()); if (type != null && type.getKind().isInterface()) { // Our version of "isInstance" is faster than "conformsToProtocol". buffer.append(UnicodeUtils.format("[%s_class_() isInstance:", nameTable.getFullName(type))); node.getLeftOperand().accept(this); buffer.append(']'); } else { buffer.append('['); node.getLeftOperand().accept(this); buffer.append(" isKindOfClass:["); node.getRightOperand().accept(this); buffer.append(" class]]"); } return false; } @Override public boolean visit(IntersectionType node) { throw new AssertionError( "Intersection types should only occur in a cast expression," + " and are handled by CastResolver"); } @Override public boolean visit(LabeledStatement node) { node.getLabel().accept(this); buffer.append(": "); node.getBody().accept(this); return false; } @Override public boolean visit(LambdaExpression node) { throw new AssertionError( "Lambda expressions should have been rewritten by LambdaRewriter"); } @Override public boolean visit(MarkerAnnotation node) { throw new AssertionError("Annotation nodes should not exist within method bodies."); } @Override public boolean visit(MethodInvocation node) { ExecutableElement element = node.getExecutableElement(); assert element != null; // Object receiving the message, or null if it's a method in this class. Expression receiver = node.getExpression(); buffer.append('['); if (ElementUtil.isStatic(element)) { buffer.append(nameTable.getFullName(ElementUtil.getDeclaringClass(element))); } else if (receiver != null) { receiver.accept(this); } else { buffer.append("self"); } printMethodInvocationNameAndArgs(nameTable.getMethodSelector(element), node.getArguments()); buffer.append(']'); return false; } @Override public boolean visit(NameQualifiedType node) { // TODO(kirbs): Implement correct conversion of Java 8 features to Objective-C. return assertIncompleteJava8Support(node); } @Override public boolean visit(NativeExpression node) { buffer.append(node.getCode()); return false; } @Override public boolean visit(NativeStatement node) { buffer.append(node.getCode()); buffer.append('\n'); return false; } @Override public boolean visit(NormalAnnotation node) { throw new AssertionError("Annotation nodes should not exist within method bodies."); } @Override public boolean visit(NullLiteral node) { buffer.append("nil"); return false; } @Override public boolean visit(NumberLiteral node) { String token = node.getToken(); if (token != null) { buffer.append(LiteralGenerator.fixNumberToken(token, node.getTypeMirror().getKind())); } else { buffer.append(LiteralGenerator.generate(node.getValue())); } return false; } @Override public boolean visit(ParenthesizedExpression node) { buffer.append("("); node.getExpression().accept(this); buffer.append(")"); return false; } @Override public boolean visit(PostfixExpression node) { node.getOperand().accept(this); buffer.append(node.getOperator().toString()); return false; } @Override public boolean visit(PrefixExpression node) { buffer.append(node.getOperator().toString()); node.getOperand().accept(this); return false; } @Override public boolean visit(PrimitiveType node) { buffer.append(NameTable.getPrimitiveObjCType(node.getTypeMirror())); return false; } @Override public boolean visit(QualifiedName node) { Element element = node.getElement(); if (ElementUtil.isVariable(element)) { VariableElement var = (VariableElement) element; if (ElementUtil.isGlobalVar(var)) { buffer.append(nameTable.getVariableQualifiedName(var)); return false; } } if (ElementUtil.isTypeElement(element)) { buffer.append(nameTable.getFullName((TypeElement) element)); return false; } Name qualifier = node.getQualifier(); qualifier.accept(this); buffer.append("->"); node.getName().accept(this); return false; } @Override public boolean visit(QualifiedType node) { TypeElement type = TypeUtil.asTypeElement(node.getTypeMirror()); if (type != null) { buffer.append(nameTable.getFullName(type)); return false; } return true; } @Override public boolean visit(ReturnStatement node) { buffer.append("return"); Expression expr = node.getExpression(); if (expr != null) { buffer.append(' '); expr.accept(this); } buffer.append(";\n"); return false; } @Override public boolean visit(SimpleName node) { Element element = node.getElement(); if (element != null && ElementUtil.isVariable(element)) { buffer.append(nameTable.getVariableQualifiedName((VariableElement) element)); return false; } if (element != null && ElementUtil.isTypeElement(element)) { buffer.append(nameTable.getFullName((TypeElement) element)); } else { buffer.append(node.getIdentifier()); } return false; } @Override public boolean visit(SimpleType node) { TypeElement type = TypeUtil.asTypeElement(node.getTypeMirror()); if (type != null) { buffer.append(nameTable.getFullName(type)); return false; } return true; } @Override public boolean visit(SingleMemberAnnotation node) { throw new AssertionError("Annotation nodes should not exist within method bodies."); } @Override public boolean visit(SingleVariableDeclaration node) { buffer.append(nameTable.getObjCType(node.getVariableElement())); if (node.isVarargs()) { buffer.append("..."); } if (buffer.charAt(buffer.length() - 1) != '*') { buffer.append(" "); } buffer.append(nameTable.getVariableQualifiedName(node.getVariableElement())); for (int i = 0; i < node.getExtraDimensions(); i++) { buffer.append("[]"); } if (node.getInitializer() != null) { buffer.append(" = "); node.getInitializer().accept(this); } return false; } @Override public boolean visit(StringLiteral node) { buffer.append(LiteralGenerator.generateStringLiteral(node.getLiteralValue())); return false; } @Override public boolean visit(SuperConstructorInvocation node) { throw new AssertionError("SuperConstructorInvocation nodes are rewritten by Functionizer."); } @Override public boolean visit(SuperFieldAccess node) { buffer.append(nameTable.getVariableQualifiedName(node.getVariableElement())); return false; } @Override public boolean visit(SuperMethodInvocation node) { ExecutableElement element = node.getExecutableElement(); assert node.getReceiver() == null : "Receivers expected to be handled by SuperMethodInvocationRewriter."; assert !ElementUtil.isStatic(element) : "Static invocations are rewritten by Functionizer."; buffer.append("[super"); printMethodInvocationNameAndArgs(nameTable.getMethodSelector(element), node.getArguments()); buffer.append(']'); return false; } @Override public boolean visit(SuperMethodReference node) { throw new AssertionError("SuperMethodReference nodes are rewritten by LambdaRewriter."); } @Override public boolean visit(SwitchCase node) { if (node.isDefault()) { buffer.append(" default:\n"); } else { buffer.append(" case "); node.getExpression().accept(this); buffer.append(":\n"); } return false; } @Override public boolean visit(SwitchStatement node) { Expression expr = node.getExpression(); buffer.append("switch ("); expr.accept(this); buffer.append(") "); buffer.append("{\n"); buffer.indent(); for (Statement stmt : node.getStatements()) { buffer.printIndent(); stmt.accept(this); } buffer.unindent(); buffer.printIndent(); buffer.append("}\n"); return false; } @Override public boolean visit(SynchronizedStatement node) { buffer.append("@synchronized("); node.getExpression().accept(this); buffer.append(") "); node.getBody().accept(this); return false; } @Override public boolean visit(ThisExpression node) { buffer.append("self"); return false; } @Override public boolean visit(ThrowStatement node) { buffer.append("@throw "); node.getExpression().accept(this); buffer.append(";\n"); return false; } @Override public boolean visit(TryStatement node) { List<VariableDeclarationExpression> resources = node.getResources(); boolean hasResources = !resources.isEmpty(); boolean extendedTryWithResources = hasResources && (!node.getCatchClauses().isEmpty() || node.getFinally() != null); if (hasResources && !extendedTryWithResources) { printBasicTryWithResources(node.getBody(), resources); return false; } buffer.append("@try "); if (extendedTryWithResources) { // Put resources inside the body of this statement (JSL 14.20.3.2). printBasicTryWithResources(node.getBody(), resources); } else { node.getBody().accept(this); } buffer.append(' '); for (CatchClause cc : node.getCatchClauses()) { if (cc.getException().getType() instanceof UnionType) { printMultiCatch(cc); } else { buffer.append("@catch ("); cc.getException().accept(this); buffer.append(") {\n"); printStatements(cc.getBody().getStatements()); buffer.append("}\n"); } } if (node.getFinally() != null) { buffer.append(" @finally {\n"); printStatements(node.getFinally().getStatements()); buffer.append("}\n"); } return false; } /** * Print basic try-with-resources, as defined by JLS 14.20.3.1. */ private void printBasicTryWithResources(Block body, List<VariableDeclarationExpression> resources) { VariableDeclarationExpression resource = resources.get(0); // Resource declaration can only have one fragment. String resourceName = nameTable.getVariableQualifiedName(resource.getFragment(0).getVariableElement()); String primaryExceptionName = UnicodeUtils.format("__primaryException%d", resources.size()); buffer.append("{\n"); resource.accept(this); buffer.append(";\n"); buffer.append(UnicodeUtils.format("NSException *%s = nil;\n", primaryExceptionName)); buffer.append("@try "); List<VariableDeclarationExpression> tail = resources.subList(1, resources.size()); if (tail.isEmpty()) { body.accept(this); } else { printBasicTryWithResources(body, tail); } buffer.append(UnicodeUtils.format( "@catch (NSException *e) {\n" + "%s = e;\n" + "@throw e;\n" + "}\n", primaryExceptionName)); buffer.append(UnicodeUtils.format( // Including !=nil in the tests isn't necessary, but makes it easier // to compare to the JLS spec. "@finally {\n" + " if (%s != nil) {\n" + " if (%s != nil) {\n" + " @try {\n" + " [%s close];\n" + " } @catch (NSException *e) {\n" + " [%s addSuppressedWithNSException:e];\n" + " }\n" + " } else {\n" + " [%s close];\n" + " }\n" + " }\n" + "}\n", resourceName, primaryExceptionName, resourceName, primaryExceptionName, resourceName)); buffer.append("}\n"); } @Override public boolean visit(TypeLiteral node) { TypeMirror type = node.getType().getTypeMirror(); int arrayDimensions = 0; while (TypeUtil.isArray(type)) { arrayDimensions++; type = ((javax.lang.model.type.ArrayType) type).getComponentType(); } if (arrayDimensions > 0) { if (type.getKind().isPrimitive()) { buffer.append("IOSClass_").append(TypeUtil.getName(type)).append("Array("); } else { buffer.append("IOSClass_arrayType(") .append(nameTable.getFullName(TypeUtil.asTypeElement(type))) .append("_class_(), "); } buffer.append(arrayDimensions).append(")"); } else if (type.getKind().isPrimitive() || TypeUtil.isVoid(type)) { buffer.append(UnicodeUtils.format("[IOSClass %sClass]", TypeUtil.getName(type))); } else { buffer.append(nameTable.getFullName(TypeUtil.asTypeElement(type))).append("_class_()"); } return false; } @Override public boolean visit(TypeMethodReference node) { throw new AssertionError("TypeMethodReference nodes are rewritten by LambdaRewriter."); } @Override public boolean visit(VariableDeclarationExpression node) { String typeString = nameTable.getObjCType(node.getTypeMirror()); boolean needsAsterisk = typeString.endsWith("*"); buffer.append(typeString); if (!needsAsterisk) { buffer.append(' '); } for (Iterator<VariableDeclarationFragment> it = node.getFragments().iterator(); it.hasNext(); ) { VariableDeclarationFragment f = it.next(); f.accept(this); if (it.hasNext()) { buffer.append(", "); if (needsAsterisk) { buffer.append('*'); } } } return false; } @Override public boolean visit(VariableDeclarationFragment node) { buffer.append(nameTable.getVariableQualifiedName(node.getVariableElement())); Expression initializer = node.getInitializer(); if (initializer != null) { buffer.append(" = "); initializer.accept(this); } return false; } @Override public boolean visit(VariableDeclarationStatement node) { List<VariableDeclarationFragment> vars = node.getFragments(); assert !vars.isEmpty(); VariableElement element = vars.get(0).getVariableElement(); if (ElementUtil.suppressesWarning("unused", element)) { buffer.append("__unused "); } String objcType = nameTable.getObjCType(element); String objcTypePointers = " "; int idx = objcType.indexOf(" *"); if (idx != -1) { // Split the type at the first pointer. The second part of the type is // applied to each fragment. (eg. Foo *one, *two) objcTypePointers = objcType.substring(idx); objcType = objcType.substring(0, idx); } buffer.append(objcType); for (Iterator<VariableDeclarationFragment> it = vars.iterator(); it.hasNext();) { VariableDeclarationFragment f = it.next(); buffer.append(objcTypePointers); f.accept(this); if (it.hasNext()) { buffer.append(","); } } buffer.append(";\n"); return false; } @Override public boolean visit(WhileStatement node) { buffer.append("while ("); node.getExpression().accept(this); buffer.append(") "); node.getBody().accept(this); return false; } @Override public boolean visit(Initializer node) { // All Initializer nodes should have been converted during initialization // normalization. throw new AssertionError("initializer node not converted"); } }