From 9bedc21b7cbc35a4cf42793dddef61a62ce15fb8 Mon Sep 17 00:00:00 2001 From: aFedasenka Date: Fri, 12 Oct 2012 00:06:33 +0200 Subject: [PATCH] current header and cpp file fix --- .../test/resources/MapUtils_2.java | 2 +- .../google/devtools/j2cpp/GenerationTest.java | 38 +- .../j2cpp/gen/CppHeaderGenerator.java | 12 +- .../j2cpp/gen/CppImplementationGenerator.java | 21 +- .../j2cpp/gen/CppSourceFileGenerator.java | 8 +- .../j2cpp/gen/CppStatementGenerator.java | 2159 +++++++++++++++++ .../devtools/j2cpp/util/NameTableCpp.java | 48 +- 7 files changed, 2232 insertions(+), 56 deletions(-) create mode 100644 build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppStatementGenerator.java diff --git a/build-scripts/net.osmand.translator/test/resources/MapUtils_2.java b/build-scripts/net.osmand.translator/test/resources/MapUtils_2.java index 0c2d7b6562..75849f3500 100644 --- a/build-scripts/net.osmand.translator/test/resources/MapUtils_2.java +++ b/build-scripts/net.osmand.translator/test/resources/MapUtils_2.java @@ -86,7 +86,7 @@ public class MapUtils { double R = 6371; // km double dLat = Math.toRadians(lat2-lat1); double dLon = Math.toRadians(lon2-lon1); - double a = Math.sin(dLat/2) * Math.sin(dLat/2) + + double a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); diff --git a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/GenerationTest.java b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/GenerationTest.java index 1be3b371ab..56e31d79e8 100644 --- a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/GenerationTest.java +++ b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/GenerationTest.java @@ -18,16 +18,16 @@ package com.google.devtools.j2cpp; -import com.google.common.collect.Lists; -import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; -import com.google.devtools.j2objc.J2ObjC; -import com.google.devtools.j2objc.J2ObjC.Language; -import com.google.devtools.j2objc.Options; -import com.google.devtools.j2objc.gen.ObjectiveCHeaderGenerator; -import com.google.devtools.j2objc.gen.ObjectiveCImplementationGenerator; -import com.google.devtools.j2objc.gen.SourceBuilder; -import com.google.devtools.j2objc.gen.StatementGenerator; import junit.framework.TestCase; import org.eclipse.jdt.core.compiler.IProblem; @@ -39,15 +39,15 @@ import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Statement; -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLDecoder; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.List; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import com.google.devtools.j2cpp.gen.CppStatementGenerator; +import com.google.devtools.j2objc.J2ObjC; +import com.google.devtools.j2objc.J2ObjC.Language; +import com.google.devtools.j2objc.Options; +import com.google.devtools.j2objc.gen.ObjectiveCHeaderGenerator; +import com.google.devtools.j2objc.gen.ObjectiveCImplementationGenerator; +import com.google.devtools.j2objc.gen.SourceBuilder; /** * Tests code generation. A string containing the source code for a list of Java @@ -184,7 +184,7 @@ public abstract class GenerationTest extends TestCase { } protected String generateStatement(Statement statement) { - return StatementGenerator.generate(statement, + return CppStatementGenerator.generate(statement, Collections.emptySet(), false, SourceBuilder.BEGINNING_OF_FILE).trim(); } diff --git a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppHeaderGenerator.java b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppHeaderGenerator.java index 81f2d0a1e0..798d863920 100644 --- a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppHeaderGenerator.java +++ b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppHeaderGenerator.java @@ -83,14 +83,12 @@ public class CppHeaderGenerator extends CppSourceFileGenerator{ String typeName = NameTableCpp.getFullName(node); String superName = NameTable.getSuperClassName(node); - //DONE if extends superclass other than Object - //TODO if implements interfaces - if ((superName != null) && (!superName.equals("NSObject"))) { - printf(": public %s", superName); - } - printConstantDefines(node); + //TODO if implements interfaces printf("class %s", typeName); + if ((superName != null) && (!superName.equals("NSObject"))) { + printf(" : %s", superName); + } println(" {"); printInstanceVariables(node.getFields()); @@ -271,7 +269,7 @@ public class CppHeaderGenerator extends CppSourceFileGenerator{ } protected String createForwardDeclaration(String typeName, boolean isInterface) { - return String.format("@%s %s;", isInterface ? "TODO: change to abstract class \ninterface" : "class", typeName); + return String.format("%s %s;", isInterface ? "TODO: change to abstract class \ninterface" : "class", NameTableCpp.basicTypeToCpp(typeName)); } protected String createImport(ImportCollector.Import imp) { diff --git a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppImplementationGenerator.java b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppImplementationGenerator.java index c2c45decef..4dcb973745 100644 --- a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppImplementationGenerator.java +++ b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppImplementationGenerator.java @@ -44,8 +44,7 @@ import com.google.common.collect.Sets; import com.google.devtools.j2objc.J2ObjC; import com.google.devtools.j2objc.Options; import com.google.devtools.j2objc.gen.HiddenFieldDetector; -import com.google.devtools.j2objc.gen.ObjectiveCSourceFileGenerator; -import com.google.devtools.j2objc.gen.StatementGenerator; +import com.google.devtools.j2cpp.gen.CppStatementGenerator; import com.google.devtools.j2objc.types.IOSMethod; import com.google.devtools.j2objc.types.ImplementationImportCollector; import com.google.devtools.j2objc.types.ImportCollector; @@ -53,12 +52,13 @@ import com.google.devtools.j2objc.types.Types; import com.google.devtools.j2objc.util.ErrorReportingASTVisitor; import com.google.devtools.j2objc.util.NameTable; +import com.google.devtools.j2cpp.gen.CppSourceFileGenerator; /** * Generates C++ implementation (.m) files from compilation units. * * @author Tom Ball */ -public class CppImplementationGenerator extends ObjectiveCSourceFileGenerator { +public class CppImplementationGenerator extends CppSourceFileGenerator { private Set fieldHiders; private final String suffix; @@ -307,8 +307,7 @@ public class CppImplementationGenerator extends ObjectiveCSourceFileGenerator { @SuppressWarnings("unchecked") List args = constant.arguments(); // safe by definition String name = NameTable.getName(constant.getName()); - String constantTypeName = - NameTable.getFullName(Types.getMethodBinding(constant).getDeclaringClass()); + String constantTypeName = NameTable.getFullName(Types.getMethodBinding(constant).getDeclaringClass()); printf(" %s_%s = [[%s alloc] init", typeName, name, constantTypeName); boolean isSimpleEnum = constantTypeName.equals(typeName); @@ -316,7 +315,7 @@ public class CppImplementationGenerator extends ObjectiveCSourceFileGenerator { if (args.isEmpty() && isSimpleEnum) { printf("WithNSString:@\"%s_%s\" withInt:%d];\n", typeName.replace("Enum", ""), name, i); } else { - String argString = StatementGenerator.generateArguments(Types.getMethodBinding(constant), + String argString = CppStatementGenerator.generateArguments(Types.getMethodBinding(constant), args, fieldHiders, getBuilder().getCurrentLine()); print(argString); if (args.isEmpty()) { @@ -337,7 +336,7 @@ public class CppImplementationGenerator extends ObjectiveCSourceFileGenerator { @SuppressWarnings("unchecked") List stmts = initializeMethod.getBody().statements(); // safe by definition for (Statement s : stmts) { - printf(" %s", StatementGenerator.generate(s, fieldHiders, false, + printf(" %s", CppStatementGenerator.generate(s, fieldHiders, false, getBuilder().getCurrentLine())); } } @@ -499,17 +498,17 @@ public class CppImplementationGenerator extends ObjectiveCSourceFileGenerator { } private String generateStatement(Statement stmt, boolean asFunction, boolean inConstructor) { - return StatementGenerator.generate(stmt, fieldHiders, asFunction, + return CppStatementGenerator.generate(stmt, fieldHiders, asFunction, getBuilder().getCurrentLine()); } private String generateStatement(Statement stmt, boolean asFunction) { - return StatementGenerator.generate(stmt, fieldHiders, asFunction, + return CppStatementGenerator.generate(stmt, fieldHiders, asFunction, getBuilder().getCurrentLine()); } private String generateExpression(Expression expr) { - return StatementGenerator.generate(expr, fieldHiders, false, getBuilder().getCurrentLine()); + return CppStatementGenerator.generate(expr, fieldHiders, false, getBuilder().getCurrentLine()); } private void printMainMethod(MethodDeclaration m, String typeName, @@ -591,7 +590,7 @@ public class CppImplementationGenerator extends ObjectiveCSourceFileGenerator { if (!imports.isEmpty()) { Set importStmts = Sets.newTreeSet(); for (ImportCollector.Import imp : imports) { - importStmts.add(String.format("#include \"%s.h\"", imp.getImportFileName())); + importStmts.add(String.format("#include <%s.h>", imp.getImportFileName())); } for (String stmt : importStmts) { println(stmt); diff --git a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppSourceFileGenerator.java b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppSourceFileGenerator.java index cd187c8d5e..4e462b73a1 100644 --- a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppSourceFileGenerator.java +++ b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/gen/CppSourceFileGenerator.java @@ -124,7 +124,7 @@ public abstract class CppSourceFileGenerator extends SourceFileGenerator { baseDeclaration = "- (NSUInteger)hash"; } else { baseDeclaration = String.format("%s (%s)%s", isStatic ? "static" : "", - NameTableCpp.javaRefToObjC(method.getReturnType2()), mappedMethod.getName()); + NameTableCpp.javaRefToCpp(method.getReturnType2()), mappedMethod.getName()); } sb.append(baseDeclaration); @@ -163,7 +163,7 @@ public abstract class CppSourceFileGenerator extends SourceFileGenerator { boolean isStatic = Modifier.isStatic(m.getModifiers()); IMethodBinding binding = Types.getMethodBinding(m); String methodName = NameTable.getName(binding); - String baseDeclaration = String.format("\t %s %s %s", isStatic ? "static " : "", NameTableCpp.javaRefToObjC(m.getReturnType2()), methodName); + String baseDeclaration = String.format(" %s %s %s", isStatic ? "static " : "", NameTableCpp.basicTypeToCpp(NameTableCpp.javaRefToCpp(m.getReturnType2())), methodName); sb.append(baseDeclaration); @SuppressWarnings("unchecked") List params = m.parameters(); // safe by definition @@ -205,12 +205,12 @@ public abstract class CppSourceFileGenerator extends SourceFileGenerator { String type = isTypeVariable ? NameTable.getParameterTypeName(NameTable.ID_TYPE, typeBinding) : NameTable.getParameterTypeName(NameTable.javaTypeToObjC(param.getType(), true), typeBinding); - sb.append(" ").append(type).append(" ").append(fieldName); + sb.append(" ").append(NameTableCpp.basicTypeToCpp(type)).append(typeBinding.isPrimitive() ? "" : "*").append(" ").append(fieldName); if (i fieldHiders; + private final boolean asFunction; + private final Stack invocations = new Stack(); + private int nilCheckDepth = 0; + private final boolean useReferenceCounting; + + private static final String EXPONENTIAL_FLOATING_POINT_REGEX = "[+-]?\\d*\\.?\\d*[eE][+-]?\\d+"; + private static final String FLOATING_POINT_SUFFIX_REGEX = ".*[fFdD]"; + private static final String HEX_LITERAL_REGEX = "0[xX].*"; + + public static String generate(ASTNode node, Set fieldHiders, boolean asFunction, int startLine) throws ASTNodeException { + CppStatementGenerator generator = new CppStatementGenerator(node, fieldHiders, asFunction, startLine); + generator.run(node); + return generator.getResult(); + } + + public static String generateArguments(IMethodBinding method, List args, Set fieldHiders, int startLine) { + CppStatementGenerator generator = new CppStatementGenerator(null, fieldHiders, false, startLine); + if (method.isVarargs()) { + generator.printVarArgs(method, args); + } else { + int nArgs = args.size(); + for (int i = 0; i < nArgs; i++) { + Expression arg = args.get(i); + generator.printArgument(method, arg, i); + if (i + 1 < nArgs) { + generator.buffer.append(' '); + } + } + } + return generator.getResult(); + } + + private CppStatementGenerator(ASTNode node, Set fieldHiders, boolean asFunction, int startLine) { + CompilationUnit unit = node != null ? (CompilationUnit) node.getRoot() : null; + buffer = new SourceBuilder(unit, Options.emitLineDirectives(), startLine); + this.fieldHiders = fieldHiders; + this.asFunction = asFunction; + useReferenceCounting = !Options.useARC(); + } + + private String getResult() { + return buffer.toString(); + } + + private String getSimpleTypeName(ITypeBinding binding) { + if (binding == null) { + // Parse error already reported. + return ""; + } + if (binding.isPrimitive()) { + return Types.getPrimitiveTypeName(binding); + } + return Types.mapSimpleTypeName(NameTableCpp.javaTypeToCpp(binding, true)); + } + + private void printArguments(IMethodBinding method, List args) { + if (method != null && method.isVarargs()) { + printVarArgs(method, args); + } else if (!args.isEmpty()) { + int nArgs = args.size(); + for (int i = 0; i < nArgs; i++) { + Expression arg = args.get(i); + printArgument(method, arg, i); + if (i + 1 < nArgs) { + buffer.append(' '); + } + } + } + } + + private void printArgument(IMethodBinding method, Expression arg, int index) { + if (method != null) { + IOSMethod iosMethod = getIOSMethod(method); + if (iosMethod != null) { + // mapped methods already have converted parameters + if (index > 0) { + buffer.append(iosMethod.getParameters().get(index).getParameterName()); + } + } else if (method.getDeclaringClass() instanceof IOSArrayTypeBinding) { + assert method.getName().startsWith("arrayWith"); + if (index == 1) { + buffer.append("count"); // IOSArray methods' 2nd parameter is the same. + } else if (index == 2) { + assert method.getName().equals("arrayWithObjects"); + buffer.append("type"); + } + } else { +// method = Types.getOriginalMethodBinding(method.getMethodDeclaration()); +// ITypeBinding[] parameterTypes = method.getParameterTypes(); +// assert index < parameterTypes.length : "method called with fewer parameters than declared"; +// ITypeBinding parameter = parameterTypes[index]; +// String typeName = method.isParameterizedMethod() || parameter.isTypeVariable() +// ? "id" : getSimpleTypeName(Types.mapType(parameter)); +// if (typeName.equals("long long")) { +// typeName = "long"; +// } +// String keyword = CppSourceFileGenerator.parameterKeyword(typeName, parameter); +// if (index == 0) { +// keyword = NameTable.capitalize(keyword); +// } +// buffer.append(keyword); + } + } +// buffer.append(':'); + if (arg instanceof ArrayInitializer) { + printArrayLiteral((ArrayInitializer) arg); + } else { + arg.accept(this); + } + } + + private IOSMethod getIOSMethod(IMethodBinding method) { + if (method instanceof IOSMethodBinding) { + IMethodBinding delegate = ((IOSMethodBinding) method).getDelegate(); + return Types.getMappedMethod(delegate); + } + return Types.getMappedMethod(method); + } + + private void printArrayLiteral(ArrayInitializer arrayInit) { + ITypeBinding binding = Types.getTypeBinding(arrayInit); + assert binding.isArray(); + ITypeBinding componentType = binding.getComponentType(); + String componentTypeName = NameTableCpp.javaRefToCpp(componentType); + buffer.append(String.format("(%s[])", + componentType.isPrimitive() ? componentTypeName : "id")); + arrayInit.accept(this); + } + + private void printVarArgs(IMethodBinding method, List args) { + method = method.getMethodDeclaration(); + ITypeBinding[] parameterTypes = method.getParameterTypes(); + Iterator it = args.iterator(); + for (int i = 0; i < parameterTypes.length; i++) { + if (i < parameterTypes.length - 1) { + // Not the last parameter + printArgument(method, it.next(), i); + if (it.hasNext() || i + 1 < parameterTypes.length) { + buffer.append(' '); + } + } else if (hasVarArgsTarget(method)) { + if (i == 0) { + buffer.append(':'); + if (it.hasNext()) { + it.next().accept(this); + } + } + // Method mapped to Obj-C varargs method call, so just append args. + while (it.hasNext()) { + buffer.append(", "); + it.next().accept(this); + } + buffer.append(", nil"); + } else { + // Last parameter; Group remain arguments into an array. + assert parameterTypes[i].isArray(); + if (method instanceof IOSMethodBinding) { + if (i > 0) { + IOSMethod iosMethod = getIOSMethod(method); + buffer.append(iosMethod.getParameters().get(i).getParameterName()); + } + } else { + String typename = getSimpleTypeName(Types.mapType(parameterTypes[i])); + String keyword = + CppSourceFileGenerator.parameterKeyword(typename, parameterTypes[i]); + if (i == 0) { + keyword = NameTable.capitalize(keyword); + } + buffer.append(keyword); + } + buffer.append(':'); + List objs = Lists.newArrayList(it); + if (objs.size() == 1 && Types.getTypeBinding(objs.get(0)).isArray() && + parameterTypes[i].getDimensions() == 1) { + // Varargs method invoked with an array, so just pass it on. + objs.get(0).accept(this); + } else { + buffer.append("[IOSObjectArray arrayWithType:"); + printObjectArrayType(parameterTypes[i].getElementType()); + buffer.append(" count:"); + buffer.append(objs.size()); + it = objs.iterator(); + while (it.hasNext()) { + buffer.append(", "); + it.next().accept(this); + } + buffer.append(" ]"); + } + } + } + } + + private boolean hasVarArgsTarget(IMethodBinding method) { + return method instanceof IOSMethodBinding && ((IOSMethodBinding) method).hasVarArgsTarget(); + } + + private void printNilCheck(Expression e, boolean needsCast) { + IVariableBinding sym = Types.getVariableBinding(e); + // Outer class references should always be non-nil. + if (sym != null && !sym.getName().startsWith("this$") && !hasNilCheckParent(e, sym)) { + ITypeBinding symType = Types.mapType(sym.getType()); + if (needsCast && (Types.getNSObject().isEqualTo(symType) || + Types.getIOSClass().isEqualTo(symType) || Types.getNSString().isEqualTo(symType))) { + needsCast = false; + } + if (nilCheckDepth == 0) { + if (needsCast) { + needsCast = printCast(symType); + } + buffer.append("NIL_CHK("); + } + ++nilCheckDepth; + e.accept(this); + if (--nilCheckDepth == 0) { + if (needsCast) { + buffer.append("))"); + } else { + buffer.append(')'); + } + } + } else { + // Print expression without check. + e.accept(this); + } + } + + private boolean hasNilCheckParent(Expression e, IVariableBinding sym) { + ASTNode parent = e.getParent(); + while (parent != null) { + if (parent instanceof IfStatement) { + Expression condition = ((IfStatement) parent).getExpression(); + if (condition instanceof InfixExpression) { + InfixExpression infix = (InfixExpression) condition; + IBinding lhs = Types.getBinding(infix.getLeftOperand()); + if (lhs != null && infix.getRightOperand() instanceof NullLiteral) { + return sym.isEqualTo(lhs); + } + IBinding rhs = Types.getBinding(infix.getRightOperand()); + if (rhs != null && infix.getLeftOperand() instanceof NullLiteral) { + return sym.isEqualTo(rhs); + } + } + } + parent = parent.getParent(); + if (parent instanceof MethodDeclaration) { + break; + } + } + return false; + } + + @Override + public boolean preVisit2(ASTNode node) { + super.preVisit2(node); + ASTNode replacement = Types.getNode(node); + if (replacement != null) { + replacement.accept(this); + return false; // don't process node + } + return true; // do process it + } + + @Override + public boolean visit(AnonymousClassDeclaration node) { + // Multi-method anonymous classes should have been converted by the + // InnerClassExtractor. + assert node.bodyDeclarations().size() == 1; + + // Generate an iOS block. + assert false : "not implemented yet"; + + return true; + } + + @Override + public boolean visit(ArrayAccess node) { + buffer.append('['); + printNilCheck(node.getArray(), true); + buffer.append(' '); + + ITypeBinding binding = node.resolveTypeBinding(); + if (binding == null) { + binding = Types.getTypeBinding(node); + } + IOSTypeBinding arrayBinding = Types.resolveArrayType(binding); + if (arrayBinding == null) { + J2ObjC.error(node, "No IOSArrayBinding for " + binding.getName()); + } else { + assert(arrayBinding instanceof IOSArrayTypeBinding); + IOSArrayTypeBinding primitiveArray = (IOSArrayTypeBinding) arrayBinding; + buffer.append(primitiveArray.getAccessMethod()); + } + + buffer.append(':'); + node.getIndex().accept(this); + buffer.append(']'); + return false; + } + + @Override + public boolean visit(ArrayCreation node) { + @SuppressWarnings("unchecked") + List dimensions = node.dimensions(); // safe by definition + ArrayInitializer init = node.getInitializer(); + if (init != null) { + // Create an expression like [IOSArrayInt arrayWithInts:(int[]){ 1, 2, 3 }]. + ArrayType at = node.getType(); + ITypeBinding componentType = Types.getTypeBinding(node).getComponentType(); + + // New array needs to be retained if it's a new assignment, since the + // arrayWith* methods return an autoreleased object. + boolean shouldRetain = useReferenceCounting && isNewAssignment(node); + if (shouldRetain) { + buffer.append("[["); + } else { + buffer.append('['); + } + String elementType = at.getElementType().toString(); + buffer.append(elementType); + buffer.append(' '); + + IOSArrayTypeBinding iosArrayBinding = Types.resolveArrayType(componentType); + buffer.append(iosArrayBinding.getInitMethod()); + buffer.append(':'); + printArrayLiteral(init); + buffer.append(" count:"); + buffer.append(init.expressions().size()); + if (elementType.equals("IOSObjectArray")) { + buffer.append(" type:"); + printObjectArrayType(componentType); + } + buffer.append(']'); + if (shouldRetain) { + buffer.append(" retain]"); + } + } else if (node.dimensions().size() > 1) { + printMultiDimArray(Types.getTypeBinding(node).getElementType(), dimensions); + } else { + assert dimensions.size() == 1; + printSingleDimArray(Types.getTypeBinding(node).getElementType(), + dimensions.get(0), useReferenceCounting && !isNewAssignment(node)); + } + return false; + } + + private void printSingleDimArray(ITypeBinding elementType, Expression size, boolean useRefCount) { + // Create an expression like [IOSArrayInt initWithLength:5] }. + buffer.append(useRefCount ? "[[[" : "[["); + String arrayType = Types.resolveArrayType(elementType).toString(); + buffer.append(arrayType); + buffer.append(" alloc] "); + buffer.append("initWithLength:"); + size.accept(this); + if (arrayType.equals("IOSObjectArray")) { + buffer.append(" type:"); + printObjectArrayType(elementType); + } + buffer.append(']'); + if (useRefCount) { + buffer.append(" autorelease]"); + } + } + + /** + * Prints a multi-dimensional array that is defined using array sizes, + * rather than an initializer. For example, "new int[2][3][4]". + */ + private void printMultiDimArray(ITypeBinding elementType, List dimensions) { + if (dimensions.size() == 1) { + printSingleDimArray(elementType, dimensions.get(0), false); + } else { + buffer.append("[IOSObjectArray arrayWithObjects:(id[]){ "); + Expression dimension = dimensions.get(0); + int dim; + // An array dimension may either be a number literal, constant, or expression. + if (dimension instanceof NumberLiteral) { + dim = Integer.parseInt(dimension.toString()); + } else { + IVariableBinding var = Types.getVariableBinding(dimension); + if (var != null) { + Number constant = (Number) var.getConstantValue(); + dim = constant != null ? constant.intValue() : 1; + } else { + dim = 1; + } + } + List subDimensions = dimensions.subList(1, dimensions.size()); + for (int i = 0; i < dim; i++) { + printMultiDimArray(elementType, subDimensions); + if (i + 1 < dim) { + buffer.append(','); + } + buffer.append(' '); + } + buffer.append("} count:"); + dimension.accept(this); + buffer.append(" type:[IOSClass classWithClass:["); + buffer.append(subDimensions.size() > 1 ? "IOSObjectArray" : + Types.resolveArrayType(elementType).toString()); + buffer.append(" class]]]"); + } + } + + private void printObjectArrayType(ITypeBinding componentType) { + buffer.append("[IOSClass "); + if (componentType.isInterface()) { + buffer.append("classWithProtocol:@protocol("); + buffer.append(NameTable.getFullName(componentType)); + buffer.append(')'); + } else { + buffer.append("classWithClass:["); + buffer.append(NameTable.getFullName(componentType)); + buffer.append(" class]"); + } + buffer.append(']'); + } + + @Override + public boolean visit(ArrayInitializer node) { + buffer.append("{ "); + for (Iterator it = node.expressions().iterator(); it.hasNext(); ) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(", "); + } + } + buffer.append(" }"); + return false; + } + + @Override + public boolean visit(ArrayType node) { + ITypeBinding binding = Types.mapType(Types.getTypeBinding(node)); + if (binding instanceof IOSTypeBinding) { + buffer.append(binding.getName()); + } else { + node.getComponentType().accept(this); + buffer.append("[]"); + } + return false; + } + + @Override + public boolean visit(AssertStatement node) { + buffer.append(asFunction ? "NSCAssert(" : "NSAssert("); + node.getExpression().accept(this); + buffer.append(", "); + if (node.getMessage() != null) { + Expression expr = node.getMessage(); + boolean isString = expr instanceof StringLiteral; + if (!isString) { + buffer.append('['); + } + expr.accept(this); + if (!isString) { + buffer.append(" description]"); + } + } else { + buffer.append("@\"\""); // empty string + } + buffer.append(");\n"); + return false; + } + + @Override + public boolean visit(Assignment node) { + Operator op = node.getOperator(); + Expression lhs = node.getLeftHandSide(); + Expression rhs = node.getRightHandSide(); + if (op == Operator.PLUS_ASSIGN && + Types.isJavaStringType(lhs.resolveTypeBinding())) { + boolean needClosingParen = printAssignmentLhs(lhs); + // Change "str1 += str2" to "str1 = str1 + str2". + buffer.append(" = "); + printStringConcatenation(lhs, rhs, Collections.emptyList(), needClosingParen); + if (needClosingParen) { + buffer.append(")"); + } + } else if (op == Operator.REMAINDER_ASSIGN && (isFloatingPoint(lhs) || isFloatingPoint(rhs))) { + lhs.accept(this); + buffer.append(" = fmod("); + lhs.accept(this); + buffer.append(", "); + rhs.accept(this); + buffer.append(")"); + } else if (lhs instanceof ArrayAccess) { + printArrayElementAssignment(lhs, rhs, op); + } else if (op == Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN) { + lhs.accept(this); + buffer.append(" = "); + printUnsignedRightShift(lhs, rhs); + } else { + IVariableBinding var = Types.getVariableBinding(lhs); + boolean useWriter = false; + if (var != null && var.getDeclaringClass() != null) { + // Test with toString, as var may have been have a renamed type. + String declaringClassName = var.getDeclaringClass().toString(); + String methodsClassName = Types.getTypeBinding(getOwningType(node)).toString(); + useWriter = Types.isStaticVariable(var) && !declaringClassName.equals(methodsClassName); + } + if (useWriter) { + // convert static var assignment to its writer message + buffer.append('['); + if (lhs instanceof QualifiedName) { + QualifiedName qn = (QualifiedName) lhs; + qn.getQualifier().accept(this); + } else { + buffer.append(NameTable.getFullName(var.getDeclaringClass())); + } + buffer.append(" set"); + buffer.append(NameTable.capitalize(var.getName())); + String typeName = NameTableCpp.javaTypeToCpp(var.getType(), false); + String param = CppSourceFileGenerator.parameterKeyword(typeName, var.getType()); + buffer.append(NameTable.capitalize(param)); + buffer.append(':'); + rhs.accept(this); + buffer.append(']'); + return false; + } else { + boolean needClosingParen = printAssignmentLhs(lhs); + buffer.append(' '); + buffer.append(op.toString()); + buffer.append(' '); + if (Types.isJavaObjectType(Types.getTypeBinding(lhs)) && + Types.getTypeBinding(rhs).isInterface()) { + // The compiler doesn't know that NSObject is the root of all + // objects used by transpiled code, so add a cast. + buffer.append("(NSObject *) "); + } + if (useReferenceCounting && !isNewAssignment(node) && var != null && + Types.isStaticVariable(var) && !var.getType().isPrimitive() && + !Types.isWeakReference(var) && rhs.getNodeType() != ASTNode.NULL_LITERAL) { + buffer.append('['); + rhs.accept(this); + buffer.append(" retain]"); + } else { + boolean needRetainRhs = needClosingParen && !isNewAssignment(node) && + !Types.isWeakReference(var); + if (rhs instanceof NullLiteral) { + needRetainRhs = false; + } + if (needRetainRhs) { + buffer.append("["); + } + rhs.accept(this); + if (needRetainRhs) { + buffer.append(" retain]"); + } + if (needClosingParen) { + buffer.append(")"); + } + } + return false; + } + } + return false; + } + + private boolean isFloatingPoint(Expression e) { + return Types.isFloatingPointType(Types.getTypeBinding(e)); + } + + private void printArrayElementAssignment(Expression lhs, Expression rhs, Assignment.Operator op) { + ArrayAccess aa = (ArrayAccess) lhs; + String kind = getArrayAccessKind(aa); + buffer.append('['); + if (aa.getArray() instanceof ArrayAccess) { + buffer.append(String.format("(IOS%sArray *) ", kind)); + } + printNilCheck(aa.getArray(), true); + buffer.append(" replace"); + buffer.append(kind); + buffer.append("AtIndex:"); + aa.getIndex().accept(this); + buffer.append(" with"); + buffer.append(kind); + buffer.append(':'); + if (op == Operator.ASSIGN) { + rhs.accept(this); + } else { + // Fetch value and apply operand; for example, "arr[i] += j" becomes + // "[arr replaceIntAtIndex:i withInt:[arr intAtIndex:i] + j]", or + // ... "withInt:(int) (((unsigned int) [arr intAtIndex:i]) >> j)]" for + // unsigned right shift. + String type = kind.toLowerCase(); + if (op == Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN) { + buffer.append("("); + buffer.append(type); + buffer.append(") (((unsigned "); + buffer.append(type); + buffer.append(") "); + } + buffer.append('['); + aa.getArray().accept(this); + buffer.append(' '); + buffer.append(type); + buffer.append("AtIndex:"); + aa.getIndex().accept(this); + buffer.append(']'); + if (op == Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN) { + buffer.append(") >>"); + } else { + buffer.append(' '); + String s = op.toString(); + buffer.append(s.substring(0, s.length() - 1)); // strip trailing '='. + } + buffer.append(' '); + rhs.accept(this); + if (op == Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN) { + buffer.append(')'); + } + } + buffer.append(']'); + } + + private String getArrayAccessKind(ArrayAccess node) { + ITypeBinding componentType = Types.getTypeBinding(node); + if (componentType == null) { + componentType = Types.getTypeBinding(node); + } + String kind = componentType.isPrimitive() + ? NameTable.capitalize(componentType.getName()) : "Object"; + return kind; + } + + private boolean printAssignmentLhs(Expression lhs) { + boolean needClosingParen = false; + + if (Options.inlineFieldAccess()) { + // Inline the setter for a property. + IVariableBinding var = Types.getVariableBinding(lhs); + ITypeBinding type = Types.getTypeBinding(lhs); + if (Options.useReferenceCounting() && !type.isPrimitive() && + lhs instanceof SimpleName && isProperty((SimpleName) lhs) && + !isNewAssignment(lhs.getParent()) && !Types.hasWeakAnnotation(var.getDeclaringClass())) { + String name = NameTable.getName((SimpleName) lhs); + String nativeName = NameTable.javaFieldToObjC(name); + buffer.append(String.format("([%s autorelease], ", nativeName)); + needClosingParen = true; + } + } + + lhs.accept(this); + return needClosingParen; + } + + private void printUnsignedRightShift(Expression lhs, Expression rhs) { + // (type) (((unsigned type) lhs) >> rhs); + String type = getRightShiftType(lhs); + buffer.append("("); + buffer.append(type); + buffer.append(") (((unsigned "); + buffer.append(type); + buffer.append(") "); + lhs.accept(this); + buffer.append(") >> "); + rhs.accept(this); + buffer.append(")"); + } + + private String getRightShiftType(Expression node) { + ITypeBinding binding = node.resolveTypeBinding(); + AST ast = node.getAST(); + if (binding == null || ast.resolveWellKnownType("int").equals(binding)) { + return "int"; + } else if (ast.resolveWellKnownType("long").equals(binding)) { + return "long long"; + } else if (ast.resolveWellKnownType("byte").equals(binding)) { + return "char"; + } else if (ast.resolveWellKnownType("short").equals(binding)) { + return "short"; + } else if (ast.resolveWellKnownType("char").equals(binding)) { + return "unichar"; + } else { + throw new AssertionError("invalid right shift expression type: " + binding.getName()); + } + } + + @Override + public boolean visit(Block node) { + buffer.append("{\n"); + List stmts = node.statements(); + printStatements(stmts); + buffer.append("}\n"); + return false; + } + + private void printStatements(List statements) { + for (Iterator it = statements.iterator(); it.hasNext(); ) { + Statement s = (Statement) it.next(); + buffer.syncLineNumbers(s); + s.accept(this); + } + } + + /** + * Returns true if a node defines or is a sub-node of an assignment of a + * new instance to a instance or static field. This test is used when + * generating referencing counting code to see if autorelease and retain + * messages are necessary. + */ + private boolean isNewAssignment(ASTNode node) { + while (node != null) { + if (node instanceof Assignment) { + Assignment assign = (Assignment) node; + IVariableBinding var = Types.getVariableBinding(assign.getLeftHandSide()); + Expression rhs = assign.getRightHandSide(); + return var != null && var.isField() && + (rhs instanceof ClassInstanceCreation || rhs instanceof ArrayCreation); + } + node = node.getParent(); + } + return false; + } + + @Override + public boolean visit(BooleanLiteral node) { + buffer.append(node.booleanValue() ? "YES" : "NO"); + 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(CastExpression node) { + buffer.append("("); + buffer.append(NameTableCpp.javaRefToCpp(node.getType())); + buffer.append(") "); + node.getExpression().accept(this); + return false; + } + + @Override + public boolean visit(CatchClause node) { + buffer.append("@catch ("); + node.getException().accept(this); + buffer.append(") "); + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(CharacterLiteral node) { + int c = node.charValue(); + if (c >= 0x20 && c <= 0x7E) { // if ASCII + buffer.append(UnicodeUtils.escapeUnicodeSequences(node.getEscapedValue())); + } else { + buffer.append(String.format("0x%04x", c)); + } + return false; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(ClassInstanceCreation node) { + boolean addAutorelease = useReferenceCounting && !isNewAssignment(node); + buffer.append(addAutorelease ? "[[[" : "[["); + ITypeBinding type = Types.getTypeBinding(node.getType()); + ITypeBinding outerType = type.getDeclaringClass(); + buffer.append(NameTable.getFullName(type)); + buffer.append(" alloc] init"); + IMethodBinding method = Types.getMethodBinding(node); + List arguments = node.arguments(); + if (node.getExpression() != null && type.isMember() && arguments.size() > 0 && + !Types.getTypeBinding(arguments.get(0)).isEqualTo(outerType)) { + // This is calling an untranslated "Outer.new Inner()" method, + // so update its binding and arguments as if it had been translated. + GeneratedMethodBinding newBinding = new GeneratedMethodBinding(method); + newBinding.addParameter(0, outerType); + method = newBinding; + arguments = Lists.newArrayList(node.arguments()); + arguments.add(0, node.getExpression()); + } + printArguments(method, arguments); + buffer.append(']'); + if (addAutorelease) { + buffer.append(" autorelease]"); + } + return false; + } + + @Override + public boolean visit(ConditionalExpression node) { + boolean castNeeded = false; + boolean castPrinted = false; + ITypeBinding nodeType = Types.getTypeBinding(node); + ITypeBinding thenType = Types.getTypeBinding(node.getThenExpression()); + ITypeBinding elseType = Types.getTypeBinding(node.getElseExpression()); + + if (!thenType.equals(elseType) && + !(node.getThenExpression() instanceof NullLiteral) && + !(node.getElseExpression() instanceof NullLiteral)) { + // gcc fails to compile a conditional expression where the two clauses of + // the expression have differnt type. So cast the expressions to the type + // of the node, which is guaranteed to be a valid cast. + castNeeded = true; + } + + node.getExpression().accept(this); + + buffer.append(" ? "); + if (castNeeded) { + castPrinted = printCast(nodeType); + } + node.getThenExpression().accept(this); + if (castPrinted) { + buffer.append(')'); + } + + buffer.append(" : "); + if (castNeeded) { + castPrinted = printCast(nodeType); + } + node.getElseExpression().accept(this); + if (castPrinted) { + buffer.append(')'); + } + + return false; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(ConstructorInvocation node) { + buffer.append("[self init"); + printArguments(Types.getMethodBinding(node), node.arguments()); + buffer.append("]"); + return false; + } + + @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(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) { + buffer.append(";\n"); + return false; + } + + @Override + public boolean visit(EnhancedForStatement node) { + SingleVariableDeclaration var = node.getParameter(); + boolean emitAutoreleasePool = Types.hasAutoreleasePoolAnnotation(Types.getBinding(var)); + String varName = NameTable.getName(var.getName()); + if (NameTable.isReservedName(varName)) { + varName += "__"; + NameTable.rename(Types.getBinding(var.getName()), varName); + } + String arrayExpr = generate(node.getExpression(), fieldHiders, asFunction, + buffer.getCurrentLine()); + ITypeBinding arrayType = Types.getTypeBinding(node.getExpression()); + if (arrayType.isArray()) { + buffer.append("{\nint n__ = ["); + buffer.append(arrayExpr); + buffer.append(" count];\n"); + buffer.append("for (int i__ = 0; i__ < n__; i__++) {\n"); + if (emitAutoreleasePool) { + buffer.append("NSAutoreleasePool *pool__ = [[NSAutoreleasePool alloc] init];\n"); + } + buffer.append(NameTableCpp.javaRefToCpp(var.getType())); + buffer.append(' '); + buffer.append(varName); + buffer.append(" = ["); + buffer.append(arrayExpr); + buffer.append(' '); + if (arrayType.getComponentType().isPrimitive()) { + buffer.append(var.getType().toString()); + } else { + buffer.append("object"); + } + buffer.append("AtIndex:i__];\n"); + Statement body = node.getBody(); + if (body instanceof Block) { + // strip surrounding braces + printStatements(((Block) body).statements()); + } else { + body.accept(this); + } + if (emitAutoreleasePool) { + buffer.append("[pool__ release];\n"); + } + buffer.append("}\n}\n"); + } else { + // var must be an instance of an Iterable class. + String objcType = NameTableCpp.javaRefToCpp(var.getType()); + buffer.append("{\nid array__ = (id) "); + buffer.append(arrayExpr); + buffer.append(";\n"); + buffer.append("if (!array__) {\n"); + if (useReferenceCounting) { + buffer.append("@throw [[[JavaLangNullPointerException alloc] init] autorelease];\n}\n"); + } else { + buffer.append("@throw [[JavaLangNullPointerException alloc] init];\n}\n"); + } + buffer.append("id iter__ = [array__ iterator];\n"); + buffer.append("while ([iter__ hasNext]) {\n"); + if (emitAutoreleasePool) { + buffer.append("NSAutoreleasePool *pool__ = [[NSAutoreleasePool alloc] init];\n"); + } + buffer.append(objcType); + buffer.append(' '); + buffer.append(varName); + buffer.append(" = ("); + buffer.append(objcType); + buffer.append(") [iter__ next];\n"); + Statement body = node.getBody(); + if (body instanceof Block) { + // strip surrounding braces + printStatements(((Block) body).statements()); + } else { + body.accept(this); + } + if (emitAutoreleasePool) { + buffer.append("[pool__ release];\n"); + } + buffer.append("}\n}\n"); + } + return false; + } + + @Override + public boolean visit(ExpressionStatement node) { + node.getExpression().accept(this); + buffer.append(";\n"); + return false; + } + + @Override + public boolean visit(FieldAccess node) { + if (maybePrintArrayLength(node.getName().getIdentifier(), node.getExpression())) { + return false; + } + + Expression expr = node.getExpression(); + if (expr instanceof ArrayAccess) { + // Since arrays are untyped in Obj-C, add a cast of its element type. + ArrayAccess access = (ArrayAccess) expr; + ITypeBinding elementType = Types.getTypeBinding(access.getArray()).getElementType(); + buffer.append(String.format("((%s) ", NameTableCpp.javaRefToCpp(elementType))); + expr.accept(this); + buffer.append(')'); + } else { + printNilCheck(expr, true); + } + if (Options.inlineFieldAccess() && isProperty(node.getName())) { + buffer.append("->"); + } else { + buffer.append('.'); + } + node.getName().accept(this); + return false; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(ForStatement node) { + boolean emitAutoreleasePool = false; + buffer.append("for ("); + for (Iterator it = node.initializers().iterator(); it.hasNext(); ) { + Expression next = it.next(); + if (next instanceof VariableDeclarationExpression) { + List vars = + ((VariableDeclarationExpression) next).fragments(); + for (VariableDeclarationFragment fragment : vars) { + emitAutoreleasePool |= Types.hasAutoreleasePoolAnnotation(Types.getBinding(fragment)); + } + } + next.accept(this); + if (it.hasNext()) { + buffer.append(", "); + } + } + buffer.append("; "); + if (node.getExpression() != null) { + node.getExpression().accept(this); + } + buffer.append("; "); + for (Iterator it = node.updaters().iterator(); it.hasNext(); ) { + it.next().accept(this); + if (it.hasNext()) { + buffer.append(", "); + } + } + buffer.append(") "); + if (emitAutoreleasePool) { + buffer.append("{\nNSAutoreleasePool *pool__ = [[NSAutoreleasePool alloc] init];\n"); + } + node.getBody().accept(this); + if (emitAutoreleasePool) { + buffer.append("[pool__ release];\n}\n"); + } + 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; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(InfixExpression node) { + InfixExpression.Operator op = node.getOperator(); + ITypeBinding type = Types.getTypeBinding(node); + if (Types.isJavaStringType(type) && + op.equals(InfixExpression.Operator.PLUS)) { + printStringConcatenation(node.getLeftOperand(), node.getRightOperand(), + node.extendedOperands(), false); + } else if (op.equals(InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED)) { + printUnsignedRightShift(node.getLeftOperand(), node.getRightOperand()); + } else if (op.equals(InfixExpression.Operator.REMAINDER) && isFloatingPoint(node)) { + buffer.append(type.isEqualTo(node.getAST().resolveWellKnownType("float")) ? "fmodf" : "fmod"); + buffer.append('('); + node.getLeftOperand().accept(this); + buffer.append(", "); + node.getRightOperand().accept(this); + buffer.append(')'); + } else { + node.getLeftOperand().accept(this); + buffer.append(' '); + buffer.append(node.getOperator().toString()); + buffer.append(' '); + node.getRightOperand().accept(this); + final List extendedOperands = node.extendedOperands(); + if (extendedOperands.size() != 0) { + buffer.append(' '); + for (Iterator it = extendedOperands.iterator(); it.hasNext(); ) { + buffer.append(node.getOperator().toString()).append(' '); + it.next().accept(this); + } + } + } + return false; + } + + /** + * Converts a string concatenation expression into a NSString format string and a + * list of arguments for it, containing all the non-literal expressions. If the + * expression is all literals, then a string concatenation is printed. If not, + * then a NSString stringWithFormat: message is output. + */ + @SuppressWarnings("fallthrough") + private void printStringConcatenation(Expression leftOperand, Expression rightOperand, + List extendedOperands, boolean needRetainRhs) { + // Copy all operands into a single list. + List operands = Lists.newArrayList(leftOperand, rightOperand); + operands.addAll(extendedOperands); + + String format = "@\""; + List args = Lists.newArrayList(); + for (Expression operand : operands) { + if (operand instanceof BooleanLiteral + || operand instanceof CharacterLiteral + || operand instanceof NullLiteral) { + format += operand.toString(); + } else if (operand instanceof StringLiteral) { + StringLiteral literal = (StringLiteral) operand; + if (isValidCppString(literal)) { + String s = (((StringLiteral) operand).getEscapedValue()); + s = s.substring(1, s.length() - 1); // remove surrounding double-quotes + s = UnicodeUtils.escapeUnicodeSequences(s); + format += s.replace("%", "%%"); // escape % character + } else { + // Convert to NSString invocation when printing args. + format += "%@"; + args.add(operand); + } + } else if (operand instanceof NumberLiteral) { + format += ((NumberLiteral) operand).getToken(); + } else { + args.add(operand); + + // Append format specifier. + ITypeBinding operandType = Types.getTypeBinding(operand); + if (operandType.isPrimitive()) { + String type = operandType.getBinaryName(); + assert type.length() == 1; + switch (type.charAt(0)) { + case 'B': // byte + case 'I': // int + case 'S': // short + format += "%d"; + break; + case 'J': // long + format += "%qi"; + break; + case 'D': // double + case 'F': // float + format += "%f"; + break; + case 'C': // char + format += "%c"; + break; + case 'Z': // boolean + format += "%@"; + break; + default: + throw new AssertionError("unknown primitive type: " + type); + } + } else { + format += "%@"; + } + } + } + format += '"'; + + if (args.isEmpty()) { + buffer.append(format.replace("%%", "%")); // unescape % character + return; + } + + if (needRetainRhs) { + buffer.append("[[NSString alloc] initWithFormat:"); + } else { + buffer.append("[NSString stringWithFormat:"); + } + buffer.append(format); + buffer.append(", "); + for (Iterator iter = args.iterator(); iter.hasNext(); ) { + Expression arg = iter.next(); + if (Types.getTypeBinding(arg).isEqualTo(arg.getAST().resolveWellKnownType("boolean"))) { + buffer.append("[JavaLangBoolean toStringWithBOOL:"); + arg.accept(this); + buffer.append(']'); + } else if (arg instanceof StringLiteral) { + // Strings with all valid C99 characters were previously converted, + // so this literal needs to be defined with a char array. + buffer.append(buildStringFromChars(((StringLiteral) arg).getLiteralValue())); + } else { + arg.accept(this); + } + if (iter.hasNext()) { + buffer.append(", "); + } + } + buffer.append(']'); + } + + @Override + public boolean visit(InstanceofExpression node) { + ITypeBinding leftBinding = Types.getTypeBinding(node.getLeftOperand()); + ITypeBinding rightBinding = Types.getTypeBinding(node.getRightOperand()); + + if (rightBinding.isArray()) { + buffer.append("[[(IOSArray *) "); + node.getLeftOperand().accept(this); + buffer.append(" elementType] isEqual:["); + buffer.append(NameTable.getFullName(rightBinding.getElementType())); + buffer.append(" class]]"); + return false; + } + + buffer.append('['); + if (leftBinding.isInterface()) { + // Obj-C complains when a id is tested for a different + // protocol, so cast it to a generic id. + buffer.append("(id) "); + } + node.getLeftOperand().accept(this); + if (rightBinding.isInterface()) { + buffer.append(" conformsToProtocol: @protocol("); + node.getRightOperand().accept(this); + buffer.append(")"); + } else { + buffer.append(" isKindOfClass:["); + node.getRightOperand().accept(this); + buffer.append(" class]"); + } + buffer.append(']'); + return false; + } + + @Override + public boolean visit(LabeledStatement node) { + node.getLabel().accept(this); + buffer.append(": "); + node.getBody().accept(this); + return false; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(MethodInvocation node) { + invocations.push(node); + String methodName = NameTable.getName(node.getName()); + IMethodBinding binding = Types.getMethodBinding(node); + assert binding != null; + // Object receiving the message, or null if it's a method in this class. + Expression receiver = node.getExpression(); + ITypeBinding receiverType = receiver != null ? Types.getTypeBinding(receiver) : null; + + if (Types.isFunction(binding)) { + buffer.append(methodName); + buffer.append("("); + for (Iterator it = node.arguments().iterator(); it.hasNext(); ) { + it.next().accept(this); + if (it.hasNext()) { + buffer.append(", "); + } + } + buffer.append(")"); + } else { + boolean castAttempted = false; + boolean castReturnValue = false; + if (node.getParent() instanceof Expression || + node.getParent() instanceof ReturnStatement || + node.getParent() instanceof VariableDeclarationFragment) { + ITypeBinding actualType = binding.getMethodDeclaration().getReturnType(); + if (actualType.isArray()) { + actualType = Types.resolveArrayType(actualType.getComponentType()); + } + ITypeBinding expectedType; + if (node.getParent() instanceof VariableDeclarationFragment) { + expectedType = Types.getTypeBinding(node.getParent()); + } else { + expectedType = binding.getReturnType(); + } + if (expectedType.isArray()) { + expectedType = Types.resolveArrayType(expectedType.getComponentType()); + } + if (!actualType.isAssignmentCompatible(expectedType)) { + if (!actualType.isEqualTo(node.getAST().resolveWellKnownType("void"))) { + // Since type parameters aren't passed to Obj-C, add cast for it. + // However, this is only needed with nested invocations. + if (invocations.size() > 0) { + // avoid a casting again below, and know to print a closing ')' + // after the method invocation. + castReturnValue = printCast(expectedType); + castAttempted = true; + } + } + } + } + ITypeBinding typeBinding = binding.getDeclaringClass(); +// buffer.append('['); + + if (receiver != null) { + boolean castPrinted = false; + IMethodBinding methodReceiver = Types.getMethodBinding(receiver); + if (methodReceiver != null) { + if (methodReceiver.isConstructor()) { + // gcc sometimes fails to discern the constructor's type when + // chaining, so add a cast. + if (!castAttempted) { + castPrinted = printCast(typeBinding); + castAttempted = true; + } + } else { + ITypeBinding receiverReturnType = methodReceiver.getReturnType(); + if (receiverReturnType.isInterface()) { + // Add interface cast, so Obj-C knows the type node's receiver is. + if (!castAttempted) { + castPrinted = printCast(receiverReturnType); + castAttempted = true; + } + } + } + } else { + IVariableBinding var = Types.getVariableBinding(receiver); + if (var != null) { + if (Types.variableHasCast(var)) { + castPrinted = printCast(Types.getCastForVariable(var)); + } + } + } +// printNilCheck(receiver, !castPrinted); + if (castPrinted) { + buffer.append(')'); + } + } else { +// if ((binding.getModifiers() & Modifier.STATIC) > 0) { +// buffer.append(NameTable.getFullName(typeBinding)); +// } else { +// buffer.append("self"); +// } + } + buffer.append(' '); + if (binding instanceof IOSMethodBinding) { + buffer.append(binding.getName()); + } else { + buffer.append(methodName); + } + printArguments(binding, node.arguments()); +// buffer.append(']'); + if (castReturnValue) { + buffer.append(')'); + } + } + invocations.pop(); + return false; + } + + private void printInterfaceGetClass(MethodInvocation node, Expression receiver) { + buffer.append("[(id) "); + printNilCheck(receiver, true); + buffer.append(" getClass]"); + } + + /** + * Class.isAssignableFrom() can test protocols as well as classes, so which + * case needs to be detected and generated separately. + */ + private void printIsAssignableFromExpression(MethodInvocation node) { + assert !node.arguments().isEmpty(); + Expression firstExpression = node.getExpression(); + Expression secondExpression = (Expression) node.arguments().get(0); + buffer.append('['); + firstExpression.accept(this); + buffer.append(" isAssignableFrom:"); + secondExpression.accept(this); + buffer.append(']'); + } + + private boolean printCast(ITypeBinding type) { + if (type == null || type.isPrimitive() || type.isTypeVariable() || Types.isVoidType(type) || + Types.isJavaObjectType(type)) { + return false; + } + if (type.isCapture()) { + type = type.getWildcard(); + } + if (type.isWildcardType()) { + ITypeBinding bound = type.getBound(); + if (bound == null) { + return false; + } + type = bound; + } + buffer.append("(("); + if (type.isInterface()) { + buffer.append("id<"); + buffer.append(NameTable.getFullName(type)); + buffer.append('>'); + } else { + if (type.getName().equals("NSObject")) { + buffer.append("NSObject *"); + } else { + buffer.append(NameTableCpp.javaRefToCpp(type)); + } + } + buffer.append(") "); + return true; + } + + @Override + public boolean visit(NullLiteral node) { + buffer.append("nil"); + return false; + } + + @Override + public boolean visit(NumberLiteral node) { + String token = node.getToken(); + ITypeBinding binding = Types.getTypeBinding(node); + assert binding.isPrimitive(); + char kind = binding.getKey().charAt(0); // Primitive types have single-character keys. + + // Convert floating point literals to C format. No checking is + // necessary, since the format was verified by the parser. + if (kind == 'D' || kind == 'F') { + if (token.matches(FLOATING_POINT_SUFFIX_REGEX)) { + token = token.substring(0, token.length() - 1); // strip suffix + } + if (token.matches(HEX_LITERAL_REGEX)) { + token = Double.toString(Double.parseDouble(token)); + } else if (!token.matches(EXPONENTIAL_FLOATING_POINT_REGEX)) { + if (token.indexOf('.') == -1) { + token += ".0"; // C requires a fractional part, except in exponential form. + } + } + if (kind == 'F') { + token += 'f'; + } + } + else if (kind == 'J') { + if (token.equals("0x8000000000000000L") || token.equals("-9223372036854775808L")) { + // Convert min long literal to an expression + token = "-0x7fffffffffffffffLL - 1"; + } else { + // Convert Java long literals to long long for Obj-C + if (token.startsWith("0x")) { + buffer.append("(long long) "); // Ensure constant is treated as signed. + } + int pos = token.length() - 1; + int numLs = 0; + while (pos > 0 && token.charAt(pos) == 'L') { + numLs++; + pos--; + } + + if (numLs == 1) { + token += 'L'; + } + } + } else if (kind == 'I') { + if (token.startsWith("0x")) { + buffer.append("(int) "); // Ensure constant is treated as signed. + } + if (token.equals("0x80000000") || token.equals("-2147483648")) { + // Convert min int literal to an expression + token = "-0x7fffffff - 1"; + } + } + buffer.append(token); + return false; + } + + @Override + public boolean visit(ParenthesizedExpression node) { + buffer.append("("); + node.getExpression().accept(this); + buffer.append(")"); + return false; + } + + @Override + public boolean visit(PostfixExpression node) { + if (node.getOperand() instanceof ArrayAccess) { + PostfixExpression.Operator op = node.getOperator(); + if (op == PostfixExpression.Operator.INCREMENT || op == PostfixExpression.Operator.DECREMENT) { + String methodName = op == PostfixExpression.Operator.INCREMENT ? "postIncr" : "postDecr"; + printArrayIncrementOrDecrement((ArrayAccess) node.getOperand(), methodName); + return false; + } + } + node.getOperand().accept(this); + buffer.append(node.getOperator().toString()); + return false; + } + + @Override + public boolean visit(PrefixExpression node) { + if (node.getOperand() instanceof ArrayAccess) { + PrefixExpression.Operator op = node.getOperator(); + if (op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT) { + String methodName = op == PrefixExpression.Operator.INCREMENT ? "incr" : "decr"; + printArrayIncrementOrDecrement((ArrayAccess) node.getOperand(), methodName); + return false; + } + } + buffer.append(node.getOperator().toString()); + node.getOperand().accept(this); + return false; + } + + private void printArrayIncrementOrDecrement(ArrayAccess access, String methodName) { + buffer.append('['); + printNilCheck(access.getArray(), true); + buffer.append(' '); + buffer.append(methodName); + buffer.append(':'); + access.getIndex().accept(this); + buffer.append(']'); + } + + @Override + public boolean visit(PrimitiveType node) { + buffer.append(NameTable.primitiveTypeToObjC(node)); + return false; + } + + @Override + public boolean visit(QualifiedName node) { + IBinding binding = Types.getBinding(node); + if (binding instanceof IVariableBinding) { + IVariableBinding var = (IVariableBinding) binding; + if (Types.isPrimitiveConstant(var)) { + buffer.append(NameTable.getPrimitiveConstantName(var)); + return false; + } else if (Types.isStaticVariable(var)) { + printStaticVarReference(node); + return false; + } + + if (maybePrintArrayLength(var.getName(), node.getQualifier())) { + return false; + } + } + if (binding instanceof ITypeBinding) { + buffer.append(NameTable.getFullName((ITypeBinding) binding)); + return false; + } + printNilCheck(node.getQualifier(), true); + buffer.append('.'); + node.getName().accept(this); + return false; + } + + // Array.length is specially handled because it's a method that's + // syntactically a variable. + private boolean maybePrintArrayLength(String name, Expression qualifier) { + if (name.equals("length") && Types.getTypeBinding(qualifier).isArray()) { + buffer.append("(int) ["); // needs cast: count returns an unsigned value + if (qualifier instanceof ArrayAccess) { + String kind = getArrayAccessKind((ArrayAccess) qualifier); + buffer.append(String.format("(IOS%sArray *) ", kind)); + } + printNilCheck(qualifier, true); + buffer.append(" count]"); + return true; + } + return false; + } + + private void printStaticVarReference(ASTNode expression) { + IVariableBinding var = Types.getVariableBinding(expression); + AbstractTypeDeclaration owner = getOwningType(expression); + ITypeBinding owningType = owner != null ? + Types.getTypeBinding(owner).getTypeDeclaration() : null; + boolean isPublic = owningType != null ? useStaticPublicAccessor(expression, owningType) : true; + if (isPublic) { + buffer.append('['); + ITypeBinding declaringClass = var.getDeclaringClass(); + String receiver = NameTableCpp.javaTypeToCpp(declaringClass, true); + buffer.append(receiver); + buffer.append(' '); + } + String name = NameTable.getName(var); + if (isPublic) { + if (!var.isEnumConstant()) { + // use accessor name instead of var name + name = NameTable.getStaticAccessorName(var.getName()); + } + } else if (var.isEnumConstant()) { + buffer.append(NameTableCpp.javaTypeToCpp(var.getDeclaringClass(), false)); + buffer.append("_"); + } else if (!name.endsWith("_")) { + name = NameTable.getStaticVarQualifiedName(owningType, name); + } + buffer.append(name); + if (isPublic) { + buffer.append(']'); + } + } + + /** + * Returns the type declaration which the specified node is part of. + */ + private AbstractTypeDeclaration getOwningType(ASTNode node) { + ASTNode n = node; + while (n != null) { + if (n instanceof AbstractTypeDeclaration) { + return (AbstractTypeDeclaration) n; + } + n = n.getParent(); + } + return null; + } + + /** + * Returns the method which is the parent of the specified node. + */ + private MethodDeclaration getOwningMethod(ASTNode node) { + ASTNode n = node; + while (n != null) { + if (n instanceof MethodDeclaration) { + return (MethodDeclaration) n; + } + n = n.getParent(); + } + return null; + } + + /** + * Returns true if the caller should reference a static variable using its + * accessor methods. + */ + private boolean useStaticPublicAccessor(ASTNode expression, ITypeBinding owningType) { + MethodDeclaration method = getOwningMethod(expression); + if (method != null) { + // Functions should always use public accessor, to trigger the var's + // class loading if it hasn't happened yet. + if (Types.isFunction(Types.getMethodBinding(method))) { + return true; + } + } + IVariableBinding var = Types.getVariableBinding(expression); + return !owningType.isEqualTo(var.getDeclaringClass().getTypeDeclaration()); + } + + @Override + public boolean visit(QualifiedType node) { + ITypeBinding binding = node.resolveBinding(); + if (binding != null) { + buffer.append(NameTable.getFullName(binding)); + return false; + } + return true; + } + + @Override + public boolean visit(ReturnStatement node) { + buffer.append("return"); + Expression expr = node.getExpression(); + if (expr != null) { + buffer.append(' '); + boolean needsCast = false; + ITypeBinding expressionType = Types.getTypeBinding(expr); + IBinding binding = Types.getBinding(expr); + if (expr instanceof SuperMethodInvocation) { + needsCast = true; + } else if (expressionType.isParameterizedType()) { + // Add a cast if expr is a superclass field or method, as its declared + // type may be more general than expr's return type. + if (binding instanceof IVariableBinding && ((IVariableBinding) binding).isField()) { + IVariableBinding var = (IVariableBinding) binding; + ITypeBinding remoteC = var.getDeclaringClass(); + ITypeBinding localC = Types.getMethodBinding(getOwningMethod(node)).getDeclaringClass(); + needsCast = !localC.isEqualTo(remoteC) && var.getVariableDeclaration().getType().isTypeVariable(); + } else if (binding instanceof IMethodBinding) { + IMethodBinding method = (IMethodBinding) binding; + ITypeBinding remoteC = method.getDeclaringClass(); + ITypeBinding localC = Types.getMethodBinding(getOwningMethod(node)).getDeclaringClass(); + needsCast = !localC.isEqualTo(remoteC) && method.getMethodDeclaration().getReturnType().isTypeVariable(); + } + } + if (needsCast) { + buffer.append('('); + buffer.append(NameTableCpp.javaRefToCpp(expressionType)); + buffer.append(") "); + } + expr.accept(this); + } else if (Types.getMethodBinding(getOwningMethod(node)).isConstructor()) { + // A return statement without any expression is allowed in constructors. + buffer.append(" self"); + } + buffer.append(";\n"); + return false; + } + + @Override + public boolean visit(SimpleName node) { + IBinding binding = Types.getBinding(node); + if (binding instanceof IVariableBinding) { + IVariableBinding var = (IVariableBinding) binding; + if (Types.isPrimitiveConstant(var)) { + buffer.append(NameTable.getPrimitiveConstantName(var)); + } else if (Types.isStaticVariable(var)) { + printStaticVarReference(node); + } else { + String name = NameTable.getName(node); + if (Options.inlineFieldAccess() && isProperty(node)) { + buffer.append(NameTable.javaFieldToObjC(name)); + } else { + if (isProperty(node)) { + buffer.append("self."); + } + buffer.append(name); + if (!var.isField() && (fieldHiders.contains(var) || NameTable.isReservedName(name))) { + buffer.append("Arg"); + } + } + } + return false; + } + if (binding instanceof ITypeBinding) { + if (binding instanceof IOSTypeBinding) { + buffer.append(binding.getName()); + } else { + buffer.append(NameTableCpp.javaTypeToCpp(((ITypeBinding) binding), false)); + } + } else { + buffer.append(node.getIdentifier()); + } + return false; + } + + private boolean isProperty(SimpleName name) { + IVariableBinding var = Types.getVariableBinding(name); + if (!var.isField() || Modifier.isStatic(var.getModifiers())) { + return false; + } + int parentNodeType = name.getParent().getNodeType(); + if (parentNodeType == ASTNode.QUALIFIED_NAME && + name == ((QualifiedName) name.getParent()).getQualifier()) { + // This case is for arrays, with property.length references. + return true; + } + return parentNodeType != ASTNode.FIELD_ACCESS && parentNodeType != ASTNode.QUALIFIED_NAME; + } + + @Override + public boolean visit(SimpleType node) { + ITypeBinding binding = Types.getTypeBinding(node); + if (binding != null) { + String name = NameTable.getFullName(binding); + buffer.append(name); + return false; + } + return true; + } + + @Override + public boolean visit(SingleVariableDeclaration node) { + buffer.append(NameTableCpp.javaRefToCpp(node.getType())); + if (node.isVarargs()) { + buffer.append("..."); + } + if (buffer.charAt(buffer.length() - 1) != '*') { + buffer.append(" "); + } + node.getName().accept(this); + 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) { + if (isValidCppString(node)) { + buffer.append('@'); + buffer.append(UnicodeUtils.escapeUnicodeSequences(node.getEscapedValue())); + } else { + buffer.append(buildStringFromChars(node.getLiteralValue())); + } + return false; + } + + // Checks that there aren't any invalid characters or octal escape + // sequences, from a C99 perspective. + static boolean isValidCppString(StringLiteral node) { + return UnicodeUtils.hasValidCppCharacters(node.getLiteralValue()) + && !node.getEscapedValue().matches("\".*\\\\[2-3][0-9][0-9].*\""); + } + + @VisibleForTesting + static String buildStringFromChars(String s) { + int length = s.length(); + StringBuilder buffer = new StringBuilder(); + buffer.append( + "[NSString stringWithCharacters:(unichar[]) { "); + int i = 0; + while (i < length) { + char c = s.charAt(i); + buffer.append("(int) 0x"); + buffer.append(Integer.toHexString(c)); + if (++i < length) { + buffer.append(", "); + } + } + buffer.append(" } length:"); + String lengthString = Integer.toString(length); + buffer.append(lengthString); + buffer.append(']'); + return buffer.toString(); + } + + + @SuppressWarnings("unchecked") + @Override + public boolean visit(SuperConstructorInvocation node) { + buffer.append("[super init"); + printArguments(Types.getMethodBinding(node), node.arguments()); + buffer.append(']'); + return false; + } + + @Override + public boolean visit(SuperFieldAccess node) { + buffer.append("super."); + buffer.append(NameTable.getName(node.getName())); + return false; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(SuperMethodInvocation node) { + IMethodBinding binding = Types.getMethodBinding(node); + if (Modifier.isStatic(binding.getModifiers())) { + buffer.append("[[super class] "); + } else { + buffer.append("[super "); + } + buffer.append(NameTable.getName(binding)); + printArguments(binding, node.arguments()); + buffer.append(']'); + return false; + } + + @Override + public boolean visit(SwitchCase node) { + if (node.isDefault()) { + buffer.append(" default:"); + } else { + buffer.append(" case "); + Expression expr = node.getExpression(); + boolean isEnumConstant = Types.getTypeBinding(expr).isEnum(); + if (isEnumConstant) { + String bareTypeName = NameTable.getFullName(Types.getTypeBinding(expr)).replace("Enum", ""); + buffer.append(bareTypeName).append("_"); + } + if (isEnumConstant && expr instanceof SimpleName) { + buffer.append(((SimpleName) expr).getIdentifier()); + } else if (isEnumConstant && expr instanceof QualifiedName) { + buffer.append(((QualifiedName) expr).getName().getIdentifier()); + } else { + expr.accept(this); + } + buffer.append(":"); + } + return false; + } + + @SuppressWarnings("unchecked") + @Override + public boolean visit(SwitchStatement node) { + buffer.append("switch ("); + Expression expr = node.getExpression(); + ITypeBinding exprType = Types.getTypeBinding(expr); + if (exprType.isEnum()) { + buffer.append('['); + } + expr.accept(this); + if (exprType.isEnum()) { + buffer.append(" ordinal]"); + } + buffer.append(") "); + buffer.append("{\n"); + List stmts = node.statements(); // safe by definition + boolean needsClosingBrace = false; + int nStatements = stmts.size(); + for (int i = 0; i < nStatements; i++) { + Statement stmt = stmts.get(i); + buffer.syncLineNumbers(stmt); + if (stmt instanceof SwitchCase) { + if (needsClosingBrace) { + buffer.append("}\n"); + needsClosingBrace = false; + } + stmt.accept(this); + if (declaresLocalVar(stmts, i + 1)) { + buffer.append(" {\n"); + needsClosingBrace = true; + } else { + buffer.append('\n'); + } + } else { + stmt.accept(this); + } + } + if (!stmts.isEmpty() && stmts.get(nStatements - 1) instanceof SwitchCase) { + // Last switch case doesn't have an associated statement, so add + // an empty one. + buffer.append(";\n"); + } + if (needsClosingBrace) { + buffer.append("}\n"); + } + buffer.append("}\n"); + return false; + } + + // Scan statements until a SwitchCase statement, returning true if any + // return a local variable declaration. + private boolean declaresLocalVar(List stmts, int startIndex) { + int i = startIndex; + while (i < stmts.size()) { + Statement s = stmts.get(i); + if (s instanceof VariableDeclarationStatement) { + return true; + } + if (s instanceof SwitchCase) { + return false; + } + i++; + } + 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) { + buffer.append("@try "); + node.getBody().accept(this); + buffer.append(' '); + for (Iterator it = node.catchClauses().iterator(); it.hasNext(); ) { + CatchClause cc = (CatchClause) it.next(); + cc.accept(this); + } + if (node.getFinally() != null) { + buffer.append(" @finally "); + node.getFinally().accept(this); + } + return false; + } + + @Override + public boolean visit(TypeLiteral node) { + Type type = node.getType(); + ITypeBinding typeBinding = Types.getTypeBinding(type); + if (typeBinding != null && typeBinding.isInterface()) { + buffer.append("[IOSClass classWithProtocol:@protocol("); + type.accept(this); + buffer.append(")]"); + } else { + buffer.append("[IOSClass classWithClass:["); + type.accept(this); + buffer.append(" class]]"); + } + return false; + } + + @Override + public boolean visit(VariableDeclarationExpression node) { + buffer.append(NameTableCpp.javaRefToCpp(node.getType())); + buffer.append(" "); + for (Iterator it = node.fragments().iterator(); it.hasNext(); ) { + VariableDeclarationFragment f = (VariableDeclarationFragment) it.next(); + f.accept(this); + if (it.hasNext()) { + buffer.append(", "); + } + } + return false; + } + + @Override + public boolean visit(VariableDeclarationFragment node) { + node.getName().accept(this); + if (node.getInitializer() != null) { + buffer.append(" = "); + node.getInitializer().accept(this); + } + return false; + } + + @Override + public boolean visit(VariableDeclarationStatement node) { + @SuppressWarnings("unchecked") + List vars = node.fragments(); // safe by definition + assert !vars.isEmpty(); + ITypeBinding binding = Types.getTypeBinding(vars.get(0)); + String objcType = NameTableCpp.javaRefToCpp(binding); + boolean needsAsterisk = !binding.isPrimitive() && + !(objcType.equals(NameTable.ID_TYPE) || objcType.matches("id<.*>")); + if (needsAsterisk && objcType.endsWith(" *")) { + // Strip pointer from type, as it will be added when appending fragment. + // This is necessary to create "Foo *one, *two;" declarations. + objcType = objcType.substring(0, objcType.length() - 2); + } + buffer.append(objcType); + buffer.append(" "); + for (Iterator it = vars.iterator(); it.hasNext(); ) { + VariableDeclarationFragment f = it.next(); + if (needsAsterisk) { + buffer.append('*'); + } + 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"); + } +} diff --git a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/util/NameTableCpp.java b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/util/NameTableCpp.java index 87f9e0bdcb..164d755345 100644 --- a/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/util/NameTableCpp.java +++ b/build-scripts/net.osmand.translator/test/src/com/google/devtools/j2cpp/util/NameTableCpp.java @@ -38,12 +38,12 @@ public class NameTableCpp { /** * Return the Objective-C equivalent name for a Java primitive type. */ - public static String primitiveTypeToObjC(PrimitiveType type) { + public static String primitiveTypeToCpp(PrimitiveType type) { PrimitiveType.Code code = type.getPrimitiveTypeCode(); - return primitiveTypeToObjC(code.toString()); + return primitiveTypeToCpp(code.toString()); } - private static String primitiveTypeToObjC(String javaName) { + private static String primitiveTypeToCpp(String javaName) { if (javaName.equals("boolean")) { return "bool"; // defined in NSObject.h } @@ -70,12 +70,32 @@ public class NameTableCpp { return javaName; } + public static String basicTypeToCpp(String javaName) { +// TODO populate + if (javaName.equals("String")) { + return "string"; + } + if (javaName.equals("NSString *")) { + return "string"; + } + if (javaName.equals("JavaUtilList")) { + return "vector"; + } + if (javaName.equals("LongInt")) { + return "long long"; + } + if (javaName.equals("IOSCharArray *")) { + return "wchar_t []"; + } + return javaName; + } + /** - * Convert a Java type into an equivalent Objective-C type. + * Convert a Java type into an equivalent C++ type. */ - public static String javaTypeToObjC(Type type, boolean includeInterfaces) { + public static String javaTypeToCpp(Type type, boolean includeInterfaces) { if (type instanceof PrimitiveType) { - return primitiveTypeToObjC((PrimitiveType) type); + return primitiveTypeToCpp((PrimitiveType) type); } if (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getType(); // erase parameterized type @@ -88,10 +108,10 @@ public class NameTableCpp { } } ITypeBinding binding = Types.getTypeBinding(type); - return javaTypeToObjC(binding, includeInterfaces); + return javaTypeToCpp(binding, includeInterfaces); } - public static String javaTypeToObjC(ITypeBinding binding, boolean includeInterfaces) { + public static String javaTypeToCpp(ITypeBinding binding, boolean includeInterfaces) { if (binding.isInterface() && !includeInterfaces || binding == Types.resolveIOSType("id") || binding == Types.resolveIOSType("NSObject")) { return NameTable.ID_TYPE; @@ -106,15 +126,15 @@ public class NameTableCpp { return NameTable.getFullName(binding); } - public static String javaRefToObjC(Type type) { - return javaRefToObjC(Types.getTypeBinding(type)); + public static String javaRefToCpp(Type type) { + return javaRefToCpp(Types.getTypeBinding(type)); } - public static String javaRefToObjC(ITypeBinding type) { + public static String javaRefToCpp(ITypeBinding type) { if (type.isPrimitive()) { - return primitiveTypeToObjC(type.getName()); + return primitiveTypeToCpp(type.getName()); } - String typeName = javaTypeToObjC(type, false); + String typeName = javaTypeToCpp(type, false); if (typeName.equals(NameTable.ID_TYPE) || Types.isJavaVoidType(type)) { if (type.isInterface()) { return String.format("%s<%s>", NameTable.ID_TYPE, NameTable.getFullName(type)); @@ -136,7 +156,7 @@ public class NameTableCpp { public static String getFullName(ITypeBinding binding) { if (binding.isPrimitive()) { - return primitiveTypeToObjC(binding.getName()); + return primitiveTypeToCpp(binding.getName()); } binding = Types.mapType(binding.getErasure()); // Make sure type variables aren't included. String suffix = binding.isEnum() ? "Enum" : "";