/* * 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.devtools.j2objc.GenerationTest; import com.google.devtools.j2objc.Options.MemoryManagementOption; import com.google.devtools.j2objc.ast.Statement; import java.io.IOException; import java.util.List; /** * Tests for {@link StatementGenerator}. * * @author Tom Ball */ public class StatementGeneratorTest extends GenerationTest { // Verify that return statements output correctly for reserved words. public void testReturnReservedWord() throws IOException { String translation = translateSourceFile( "public class Test { static final String BOOL = \"bool\"; String test() { return BOOL; }}", "Test", "Test.m"); assertTranslation(translation, "return Test_BOOL;"); } // Verify that both a class and interface type invoke getClass() correctly. public void testGetClass() throws IOException { String translation = translateSourceFile( "import java.util.*; public class A {" + " void test(ArrayList one, List two) { " + " Class<?> classOne = one.getClass();" + " Class<?> classTwo = two.getClass(); }}", "A", "A.m"); assertTranslation(translation, "[((JavaUtilArrayList *) nil_chk(one)) java_getClass]"); assertTranslation(translation, "[((id<JavaUtilList>) nil_chk(two)) java_getClass]"); } public void testEnumConstantReferences() throws IOException { String translation = translateSourceFile( "public class A { static enum B { ONE, TWO; " + "public static B doSomething(boolean b) { return b ? ONE : TWO; }}}", "A", "A.m"); assertTranslation(translation, "return b ? JreEnum(A_B, ONE) : JreEnum(A_B, TWO);"); } public void testInnerClassFQN() throws IOException { String translation = translateSourceFile( "package com.example.foo; " + "public class Foo { static class Inner { public static void doSomething() {} }}" + "class Bar { public static void mumber() { Foo.Inner.doSomething(); }}", "Foo", "com/example/foo/Foo.m"); assertTranslation(translation, "ComExampleFooFoo_Inner_doSomething();"); } public void testLocalVariableTranslation() throws IOException { String source = "Exception e;"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("JavaLangException *e;", result); } public void testClassCreationTranslation() throws IOException { String source = "new Exception(\"test\");"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("create_JavaLangException_initWithNSString_(@\"test\");", result); } public void testParameterTranslation() throws IOException { String source = "Throwable cause = new Throwable(); new Exception(cause);"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals("create_JavaLangException_initWithNSException_(cause);", result); } public void testCastTranslation() throws IOException { String source = "Object o = new Object(); Throwable t = (Throwable) o; int[] i = (int[]) o;"; List<Statement> stmts = translateStatements(source); assertEquals(3, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals("NSException *t = " + "(NSException *) cast_chk(o, [NSException class]);", result); result = generateStatement(stmts.get(2)); assertEquals("IOSIntArray *i = " + "(IOSIntArray *) cast_chk(o, [IOSIntArray class]);", result); } public void testInterfaceCastTranslation() throws IOException { String source = "java.util.Collection al = new java.util.ArrayList(); " + "java.util.List l = (java.util.List) al;"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals("id<JavaUtilList> l = " + "(id<JavaUtilList>) cast_check(al, JavaUtilList_class_());", result); } public void testCatchTranslation() throws IOException { String source = "try { ; } catch (Exception e) {}"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("@try {\n;\n}\n @catch (JavaLangException *e) {\n}", result); } public void testInstanceOfTranslation() throws IOException { String source = "Exception e = new Exception(); if (e instanceof Throwable) {}"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals("if ([e isKindOfClass:[NSException class]]) {\n}", result); } public void testFullyQualifiedTypeTranslation() throws IOException { String source = "java.lang.Exception e = null;"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("JavaLangException *e = nil;", result); } public void testToStringRenaming() throws IOException { String source = "Object o = new Object(); o.toString(); toString();"; List<Statement> stmts = translateStatements(source); assertEquals(3, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals("[o description];", result); result = generateStatement(stmts.get(2)); assertEquals("[self description];", result); } public void testSuperToStringRenaming() throws IOException { String translation = translateSourceFile( "public class Example { public String toString() { return super.toString(); } }", "Example", "Example.m"); assertTranslation(translation, "return [super description];"); } public void testAccessPublicConstant() throws IOException { String translation = translateSourceFile( "public class Example { public static final int FOO=1; int foo; { foo = FOO; } }", "Example", "Example.m"); assertTranslation(translation, "foo_ = Example_FOO;"); } public void testAccessPublicConstant2() throws IOException { String translation = translateSourceFile( "public class Example { public static final int FOO=1;" + "int test() { int foo = FOO; return foo;} }", "Example", "Example.m"); assertTranslation(translation, "foo = Example_FOO;"); } public void testAccessPrivateConstant() throws IOException { String translation = translateSourceFile( "public class Example { private static final int FOO=1;" + "int test() { int foo = FOO; return foo;} }", "Example", "Example.m"); assertTranslation(translation, "foo = Example_FOO;"); } public void testAccessExternalConstant() throws IOException { String translation = translateSourceFile( "public class Example { static class Bar { public static final int FOO=1; } " + "int foo; { foo = Bar.FOO; } }", "Example", "Example.m"); assertTranslation(translation, "foo_ = Example_Bar_FOO;"); assertFalse(translation.contains("int Example_Bar_FOO_ = 1;")); translation = getTranslatedFile("Example.h"); assertTranslation(translation, "#define Example_Bar_FOO 1"); } public void testAccessExternalStringConstant() throws IOException { String translation = translateSourceFile( "public class Example { static class Bar { public static final String FOO=\"Mumble\"; } " + "String foo; { foo = Bar.FOO; } }", "Example", "Example.m"); assertTranslation(translation, "JreStrongAssign(&self->foo_, Example_Bar_FOO)"); assertTranslation(translation, "NSString *Example_Bar_FOO = @\"Mumble\";"); translation = getTranslatedFile("Example.h"); assertTranslation(translation, "FOUNDATION_EXPORT NSString *Example_Bar_FOO;"); assertTranslation(translation, "J2OBJC_STATIC_FIELD_OBJ_FINAL(Example_Bar, FOO, NSString *)"); } public void testMultipleVariableDeclarations() throws IOException { String source = "String one, two;"; List<Statement> stmts = translateStatements(source); if (options.isJDT()) { // TODO(tball): remove test and this JDT block when javac conversion is complete. String result = generateStatement(stmts.get(0)); assertEquals("NSString *one, *two;", result); } else { assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("NSString *one;", result); result = generateStatement(stmts.get(1)); assertEquals("NSString *two;", result); } } public void testObjectDeclaration() throws IOException { List<Statement> stmts = translateStatements("Object o;"); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("id o;", result); } public void testStaticBooleanFields() throws IOException { String translation = translateSourceFile( "public class Example { Boolean b1 = Boolean.TRUE; Boolean b2 = Boolean.FALSE; }", "Example", "Example.m"); assertTranslation(translation, "JreStrongAssign(&self->b1_, JreLoadStatic(JavaLangBoolean, TRUE))"); assertTranslation(translation, "JreStrongAssign(&self->b2_, JreLoadStatic(JavaLangBoolean, FALSE))"); } public void testStringConcatenation() throws IOException { String translation = translateSourceFile( "public class Example<K,V> { void test() { String s = \"hello, \" + \"world\"; }}", "Example", "Example.m"); assertTranslation(translation, "NSString *s = @\"hello, world\""); } public void testStringConcatenation2() throws IOException { String source = "class A { " + "private static final String A = \"bob\"; " + "private static final char SPACE = ' '; " + "private static final double ANSWER = 22.0 / 2; " + "private static final boolean B = false; " + "private static final String C = " + "\"hello \" + A + ' ' + 3 + SPACE + true + ' ' + ANSWER + ' ' + B; }"; String translation = translateSourceFile(source, "A", "A.m"); assertTranslation(translation, "\"hello bob 3 true 11.0 false\""); } public void testStringConcatenationTypes() throws IOException { String translation = translateSourceFile( "public class Example<K,V> { Object obj; boolean b; char c; double d; float f; int i; " + "long l; short s; String str; public String toString() { " + "return \"obj=\" + obj + \" b=\" + b + \" c=\" + c + \" d=\" + d + \" f=\" + f" + " + \" i=\" + i + \" l=\" + l + \" s=\" + s; }}", "Example", "Example.m"); assertTranslation(translation, "return JreStrcat(\"$@$Z$C$D$F$I$J$S\", @\"obj=\", obj_, @\" b=\", b_, @\" c=\", c_," + " @\" d=\", d_, @\" f=\", f_, @\" i=\", i_, @\" l=\", l_, @\" s=\", s_);"); } public void testStringConcatenationWithLiterals() throws IOException { String translation = translateSourceFile( "public class Example<K,V> { public String toString() { " + "return \"literals: \" + true + \", \" + 'c' + \", \" + 1.0d + \", \" + 3.14 + \", \"" + " + 42 + \", \" + 123L + \", \" + 1; }}", "Example", "Example.m"); assertTranslation(translation, "return @\"literals: true, c, 1.0, 3.14, 42, 123, 1\";"); } public void testStringConcatenationEscaping() throws IOException { String translation = translateSourceFile( "public class Example<K,V> { String s = \"hello, \" + 50 + \"% of the world\\n\"; }", "Example", "Example.m"); assertTranslation(translation, "JreStrongAssign(&self->s_, @\"hello, 50% of the world\\n\");"); } public void testStringConcatenationMethodInvocation() throws IOException { String translation = translateSourceFile( "public class Test { " + " String getStr() { return \"str\"; } " + " int getInt() { return 42; } " + " void test() { " + " String a = \"foo\" + getStr() + \"bar\" + getInt() + \"baz\"; } }", "Test", "Test.m"); assertTranslation(translation, "JreStrcat(\"$$$I$\", @\"foo\", [self getStr], @\"bar\", [self getInt], @\"baz\")"); } public void testVarargsMethodInvocation() throws IOException { String translation = translateSourceFile("public class Example { " + "public void call() { foo(null); bar(\"\", null, null); }" + "void foo(Object ... args) { }" + "void bar(String firstArg, Object ... varArgs) { }}", "Example", "Example.m"); assertTranslation(translation, "[self fooWithNSObjectArray:"); assertTranslation(translation, "[IOSObjectArray arrayWithObjects:(id[]){ nil, nil } count:2 type:NSObject_class_()]"); assertTranslation(translation, "[self barWithNSString:"); assertTranslation(translation, "withNSObjectArray:"); } public void testVarargsMethodInvocationSingleArg() throws IOException { String translation = translateSourceFile("public class Example { " + "public void call() { foo(1); }" + "void foo(Object ... args) { }}", "Example", "Example.m"); assertTranslation(translation, "[self fooWithNSObjectArray:" + "[IOSObjectArray arrayWithObjects:(id[]){ JavaLangInteger_valueOfWithInt_(1) } count:1 " + "type:NSObject_class_()]];"); } public void testVarargsMethodInvocationPrimitiveArgs() throws IOException { String translation = translateSourceFile( "class Test { void call() { foo(1); } void foo(int... i) {} }", "Test", "Test.m"); assertTranslation(translation, "[self fooWithIntArray:[IOSIntArray arrayWithInts:(jint[]){ 1 } count:1]];"); } public void testStaticInnerSubclassAccessingOuterStaticVar() throws IOException { String translation = translateSourceFile( "public class Test { public static final Object FOO = new Object(); " + "static class Inner { Object test() { return FOO; }}}", "Test", "Test.m"); assertTranslation(translation, "return JreLoadStatic(Test, FOO);"); } public void testReservedIdentifierReference() throws IOException { String translation = translateSourceFile( "public class Test { public int test(int id) { return id; }}", "Test", "Test.m"); assertTranslation(translation, "- (jint)testWithInt:(jint)id_"); assertTranslation(translation, "return id_;"); } public void testReservedTypeQualifierReference() throws IOException { String translation = translateSourceFile( "public class Test { public int test(int in, int out) { return in + out; }}", "Test", "Test.m"); assertTranslation(translation, "- (jint)testWithInt:(jint)inArg"); assertTranslation(translation, "return inArg + outArg;"); } public void testFieldAccess() throws IOException { String translation = translateSourceFile( "import com.google.j2objc.annotations.Weak;" + "public class Test { " + " Object i;" + " @Weak Object j;" + " Test(Object otherI, Object otherJ) {" + " i = otherI;" + " j = otherJ;" + " }" + "}", "Test", "Test.m"); assertTranslation(translation, "JreStrongAssign(&self->i_, otherI);"); assertTranslation(translation, "j_ = otherJ;"); assertTranslation(translation, "RELEASE_(i_);"); } public void testInnerInnerClassFieldAccess() throws IOException { String translation = translateSourceFile( "public class Test { static class One {} static class Two extends Test { " + "Integer i; Two(Integer i) { this.i = i; } int getI() { return i.intValue(); }}}", "Test", "Test.m"); assertTranslation(translation, "- (instancetype)initWithJavaLangInteger:(JavaLangInteger *)i {"); assertTranslation(translation, "return [((JavaLangInteger *) nil_chk(i_)) intValue];"); } public void testInnerClassSuperConstructor() throws IOException { String translation = translateSourceFile( "public class Test { static class One { int i; One(int i) { this.i = i; }} " + "static class Two extends One { Two(int i) { super(i); }}}", "Test", "Test.m"); assertTranslation(translation, "- (instancetype)initWithInt:(jint)i"); assertTranslatedLines(translation, "void Test_Two_initWithInt_(Test_Two *self, jint i) {", " Test_One_initWithInt_(self, i);", "}"); } public void testStaticInnerClassSuperFieldAccess() throws IOException { String translation = translateSourceFile( "public class Test { protected int foo; " + "static class One extends Test { int i; One() { i = foo; } int test() { return i; }}}", "Test", "Test.m"); assertTranslation(translation, "- (instancetype)init {"); assertTranslation(translation, "self->i_ = self->foo_;"); assertTranslation(translation, "return i_;"); } public void testMethodInvocationOfReturnedInterface() throws IOException { String translation = translateSourceFile( "import java.util.*; public class Test <K,V> { " + "Iterator<Map.Entry<K,V>> iterator; " + "K test() { return iterator.next().getKey(); }}", "Test", "Test.m"); assertTranslation(translation, "return [((id<JavaUtilMap_Entry>) " + "nil_chk([((id<JavaUtilIterator>) nil_chk(iterator_)) next])) getKey];"); } public void testAnonymousClassInInnerStatic() throws IOException { String translation = translateSourceFile( "import java.util.*; public class Test { " + "static <T> Enumeration<T> enumeration(Collection<T> collection) {" + "final Collection<T> c = collection; " + "return new Enumeration<T>() { " + "Iterator<T> it = c.iterator(); " + "public boolean hasMoreElements() { return it.hasNext(); } " + "public T nextElement() { return it.next(); } }; }}", "Test", "Test.m"); assertTranslation(translation, "return [((id<JavaUtilIterator>) nil_chk(it_)) hasNext];"); assertTranslation(translation, "return [((id<JavaUtilIterator>) nil_chk(it_)) next];"); assertFalse(translation.contains("Test *this$0;")); } public void testGenericMethodWithAnonymousReturn() throws IOException { String translation = translateSourceFile( "import java.util.*; public class Test { " + "public static <T> Enumeration<T> enumeration(final Collection<T> collection) {" + "return new Enumeration<T>() {" + " Iterator<T> it = collection.iterator();" + " public boolean hasMoreElements() { return it.hasNext(); }" + " public T nextElement() { return it.next(); }}; }}", "Test", "Test.m"); assertTranslation(translation, "return create_Test_1_initWithJavaUtilCollection_(collection);"); assertTranslation(translation, "- (instancetype)initWithJavaUtilCollection:(id<JavaUtilCollection>)capture$0;"); assertTranslation(translation, "__attribute__((unused)) static Test_1 *new_Test_1_initWithJavaUtilCollection_(" + "id<JavaUtilCollection> capture$0) NS_RETURNS_RETAINED;"); } public void testEnumInEqualsTest() throws IOException { String translation = translateSourceFile( "public class Test { enum TicTacToe { X, Y } " + "boolean isX(TicTacToe ttt) { return ttt == TicTacToe.X; } }", "Test", "Test.m"); assertTranslation(translation, "return ttt == JreLoadEnum(Test_TicTacToe, X);"); } public void testArrayLocalVariable() throws IOException { String source = "char[] array = new char[1];"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("IOSCharArray *array = [IOSCharArray arrayWithLength:1];", result); source = "char array[] = new char[1];"; stmts = translateStatements(source); assertEquals(1, stmts.size()); result = generateStatement(stmts.get(0)); assertEquals("IOSCharArray *array = [IOSCharArray arrayWithLength:1];", result); } public void testArrayParameterLengthUse() throws IOException { String translation = translateSourceFile( "public class Test { void test(char[] foo, char bar[]) { " + "sync(foo.length, bar.length); } void sync(int a, int b) {} }", "Test", "Test.m"); assertTranslation(translation, "[self syncWithInt:((IOSCharArray *) nil_chk(foo))->size_ withInt:" + "((IOSCharArray *) nil_chk(bar))->size_];"); } public void testLongLiteral() throws IOException { String translation = translateSourceFile("public class Test { " + "public static void testLong() { long l1 = 1L; }}", "Test", "Test.m"); assertTranslation(translation, "jlong l1 = 1LL"); } public void testStringLiteralEscaping() throws IOException { String source = "String s = \"\\u1234\\u2345\\n\";"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("NSString *s = @\"\\u1234\\u2345\\n\";", result); } public void testStringLiteralEscapingInStringConcatenation() throws IOException { String source = "String s = \"\\u1234\" + \"Hi\" + \"\\u2345\\n\";"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("NSString *s = @\"\\u1234Hi\\u2345\\n\";", result); } /** * Verify that Unicode escape sequences that aren't legal C++ Unicode are * converted to C hexadecimal escape sequences. This works because all * illegal sequences are less than 0xA0. */ public void testStringLiteralWithInvalidCppUnicode() throws IOException { String source = "String s = \"\\u0093\\u0048\\u0069\\u0094\\n\";"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("NSString *s = @\"\\xc2\\x93Hi\\xc2\\x94\\n\";", result); } public void testArrayInitializer() { String source = "int[] a = { 1, 2, 3 }; char b[] = { '4', '5' };"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("IOSIntArray *a = [IOSIntArray arrayWithInts:(jint[]){ 1, 2, 3 } count:3];", result); result = generateStatement(stmts.get(1)); assertEquals("IOSCharArray *b = " + "[IOSCharArray arrayWithChars:(jchar[]){ '4', '5' } count:2];", result); } /** * Verify that static array initializers are rewritten as method calls. */ public void testStaticArrayInitializer() throws IOException { String translation = translateSourceFile( "public class Test { static int[] a = { 1, 2, 3 }; static char b[] = { '4', '5' }; }", "Test", "Test.m"); assertTranslation(translation, "JreStrongAssignAndConsume(&Test_a, " + "[IOSIntArray newArrayWithInts:(jint[]){ 1, 2, 3 } count:3]);"); assertTranslation(translation, "JreStrongAssignAndConsume(&Test_b, " + "[IOSCharArray newArrayWithChars:(jchar[]){ '4', '5' } count:2]);"); } public void testLocalArrayCreation() throws IOException { String translation = translateSourceFile( "public class Example { char[] test() { int high = 0xD800, low = 0xDC00; " + "return new char[] { (char) high, (char) low }; } }", "Example", "Example.m"); assertTranslation(translation, "return [IOSCharArray " + "arrayWithChars:(jchar[]){ (jchar) high, (jchar) low } count:2];"); } // Regression test: "case:" was output instead of "case". public void testSwitchCaseStatement() throws IOException { String source = "int c = 1; " + "switch (c) { case 1: c = 0; break; default: break; }"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertTrue(result.contains("case 1:")); } public void testEnhancedForStatement() throws IOException { String source = "String[] strings = {\"test1\", \"test2\"};" + "for (String string : strings) { }"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertTranslatedLines(result, "{", "IOSObjectArray *a__ = strings;", "NSString * const *b__ = a__->buffer_;", "NSString * const *e__ = b__ + a__->size_;", "while (b__ < e__) {", "NSString *string = *b__++;", "}", "}"); } public void testEnhancedForStatementInSwitchStatement() throws IOException { String source = "int test = 5; int[] myInts = new int[10]; " + "switch (test) { case 0: break; default: " + "for (int i : myInts) {} break; }"; List<Statement> stmts = translateStatements(source); assertEquals(3, stmts.size()); String result = generateStatement(stmts.get(2)); assertTranslatedLines(result, "switch (test) {", "case 0:", "break;", "default:", "{", "IOSIntArray *a__ = myInts;", "jint const *b__ = a__->buffer_;", "jint const *e__ = b__ + a__->size_;", "while (b__ < e__) {", "jint i = *b__++;", "}", "}", "break;", "}"); } public void testSwitchStatementWithExpression() throws IOException { String translation = translateSourceFile("public class Example { " + "static enum Test { ONE, TWO } " + "Test foo() { return Test.ONE; } " + "void bar() { switch (foo()) { case ONE: break; case TWO: break; }}}", "Example", "Example.m"); assertTranslation(translation, "switch ([[self foo] ordinal])"); } public void testClassVariable() throws IOException { String source = "Class<?> myClass = getClass();" + "Class<?> mySuperClass = myClass.getSuperclass();" + "Class<?> enumClass = Enum.class;"; List<Statement> stmts = translateStatements(source); assertEquals(3, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("IOSClass *myClass = [self java_getClass];", result); result = generateStatement(stmts.get(1)); assertEquals("IOSClass *mySuperClass = [myClass getSuperclass];", result); result = generateStatement(stmts.get(2)); assertEquals("IOSClass *enumClass = JavaLangEnum_class_();", result); } public void testInnerClassCreation() throws IOException { String translation = translateSourceFile( "public class A { int x; class Inner { int y; Inner(int i) { y = i + x; }}" + "public Inner test() { return this.new Inner(3); }}", "A", "A.m"); assertTranslation(translation, "return create_A_Inner_initWithA_withInt_(self, 3);"); } public void testNewFieldNotRetained() throws IOException { String translation = translateSourceFile( "import java.util.*; public class A { Map map; A() { map = new HashMap(); }}", "A", "A.m"); assertTranslation(translation, "JreStrongAssignAndConsume(&self->map_, new_JavaUtilHashMap_init())"); } public void testStringAddOperator() throws IOException { String translation = translateSourceFile( "import java.util.*; public class A { String myString;" + " A() { myString = \"Foo\"; myString += \"Bar\"; }}", "A", "A.m"); assertTranslation(translation, "JreStrAppendStrong(&self->myString_, \"$\", @\"Bar\");"); } public void testInterfaceStaticVarReference() throws IOException { String translation = translateSourceFile( "public class Test { " + " public interface I { " + " void foo(); public static final int FOO = 1; } " + " public class Bar implements I { public void foo() { int i = I.FOO; } } }", "Test", "Test.m"); assertTranslation(translation, "int i = Test_I_FOO;"); } public void testMethodWithPrimitiveArrayParameter() throws IOException { String translation = translateSourceFile( "public class Test { " + " public void foo(char[] chars) { } }", "Test", "Test.m"); assertTranslation(translation, "fooWithCharArray:"); } public void testMethodWithGenericArrayParameter() throws IOException { String translation = translateSourceFile( "public class Test<T> { " + " T[] tArray; " + " public void foo(T[] ts) { } " + " public void bar(Test<? extends T>[] tLists) { } " + " public void foo() { foo(tArray); } " + " public class Inner<S extends Test<T>> { " + " public void baz(S[] ss) { } } }", "Test", "Test.m"); assertTranslation(translation, "- (void)fooWithNSObjectArray:"); assertTranslation(translation, "- (void)barWithTestArray:"); assertTranslation(translation, "- (void)foo {"); assertTranslation(translation, "[self fooWithNSObjectArray:"); assertTranslation(translation, "- (void)bazWithTestArray:"); } public void testGenericMethodWithfGenericArrayParameter() throws IOException { String translation = translateSourceFile( "public class Test { " + " public <T> void foo(T[] ts) { } " + " public void foo() { foo(new String[1]); } }", "Test", "Test.m"); assertTranslation(translation, "[self fooWithNSObjectArray:"); } public void testJreDoubleNegativeInfinity() throws IOException { String translation = translateSourceFile( "public class Test { " + " public void foo() { Double d = Double.NEGATIVE_INFINITY; } }", "Test", "Test.m"); assertTranslation(translation, "JavaLangDouble_valueOfWithDouble_(JavaLangDouble_NEGATIVE_INFINITY)"); } public void testInvokeMethodInConcreteImplOfGenericInterface() throws IOException { String translation = translateSourceFile( "public class Test { " + " public interface Foo<T> { void foo(T t); } " + " public class FooImpl implements Foo<Test> { " + " public void foo(Test t) { } " + " public void bar() { foo(new Test()); } } }", "Test", "Test.m"); assertTranslation(translation, "[self fooWithId:"); } public void testNewStringWithArrayInAnonymousClass() throws IOException { String translation = translateSourceFile( "public class Test { " + " public Runnable foo() { " + " String s1 = new String(new char[10]); " + " return new Runnable() { " + " public void run() { String s = new String(new char[10]); } }; } }", "Test", "Test.m"); assertTranslation(translation, "s = [NSString java_stringWith"); } public void testMostNegativeIntegers() throws IOException { String translation = translateSourceFile( "public class Test { " + " int min_int = 0x80000000; " + " long min_long = 0x8000000000000000L; }", "Test", "Test.m"); assertTranslation(translation, "-0x7fffffff - 1"); assertTranslation(translation, "-0x7fffffffffffffffLL - 1"); } public void testInnerNewStatement() throws IOException { String translation = translateSourceFile( "class A { class B {} static B test() { return new A().new B(); }}", "A", "A.m"); assertTranslation(translation, "create_A_B_initWithA_(create_A_init())"); } public void testSuperFieldAccess() throws IOException { String translation = translateSourceFile( "public class A { int i; class B extends A { int i; int test() { return super.i + i; }}}", "A", "A.m"); assertTranslation(translation, "return i_ + i_B_;"); } public void testStaticConstants() throws IOException { String source = "float f = Float.NaN; double d = Double.POSITIVE_INFINITY;"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(0)).trim(); assertEquals(result, "jfloat f = JavaLangFloat_NaN;"); result = generateStatement(stmts.get(1)).trim(); assertEquals(result, "jdouble d = JavaLangDouble_POSITIVE_INFINITY;"); } public void testInstanceStaticConstants() throws IOException { String translation = translateSourceFile( "public class Test { Foo f; void test() { int i = f.DEFAULT; Object lock = f.LOCK; }} " + "class Foo { public static final int DEFAULT = 1; " + "public static final Object LOCK = null; }", "Test", "Test.m"); assertTranslation(translation, "int i = Foo_DEFAULT;"); assertTranslation(translation, "id lock = JreLoadStatic(Foo, LOCK);"); } public void testCastGenericReturnType() throws IOException { String translation = translateSourceFile( "class Test { " + " static class A<E extends A> { E other; public E getOther() { return other; } } " + " static class B extends A<B> { B other = getOther(); } }", "Test", "Test.h"); // Test_B's "other" needs a trailing underscore, since there is an "other" // field in its superclass. assertTranslation(translation, "Test_B *other_B_;"); translation = getTranslatedFile("Test.m"); assertTranslation(translation, "JreStrongAssign(&self->other_B_, [self getOther])"); } public void testArrayInstanceOfTranslation() throws IOException { String source = "Object args = new String[0]; if (args instanceof String[]) {}"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals( "if ([IOSClass_arrayType(NSString_class_(), 1) isInstance:args]) {\n}", result); } public void testInterfaceArrayInstanceOfTranslation() throws IOException { String source = "Object args = new Readable[0]; if (args instanceof Readable[]) {}"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals( "if ([IOSClass_arrayType(JavaLangReadable_class_(), 1) isInstance:args]) {\n}", result); } public void testPrimitiveArrayInstanceOfTranslation() throws IOException { String source = "Object args = new int[0]; if (args instanceof int[]) {}"; List<Statement> stmts = translateStatements(source); assertEquals(2, stmts.size()); String result = generateStatement(stmts.get(1)); assertEquals("if ([args isKindOfClass:[IOSIntArray class]]) {\n}", result); } public void testObjectArrayInitializer() throws IOException { String source = "String[] a = { \"one\", \"two\", \"three\" };"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("IOSObjectArray *a = [IOSObjectArray " + "arrayWithObjects:(id[]){ @\"one\", @\"two\", @\"three\" } " + "count:3 type:NSString_class_()];", result); source = "Comparable[] a = { \"one\", \"two\", \"three\" };"; stmts = translateStatements(source); assertEquals(1, stmts.size()); result = generateStatement(stmts.get(0)); assertEquals("IOSObjectArray *a = [IOSObjectArray " + "arrayWithObjects:(id[]){ @\"one\", @\"two\", @\"three\" } " + "count:3 type:JavaLangComparable_class_()];", result); } public void testArrayPlusAssign() throws IOException { String source = "int[] array = new int[] { 1, 2, 3 }; int offset = 1; array[offset] += 23;"; List<Statement> stmts = translateStatements(source); assertEquals(3, stmts.size()); String result = generateStatement(stmts.get(2)); assertEquals("*IOSIntArray_GetRef(array, offset) += 23;", result); } public void testRegisterVariableName() throws IOException { String source = "int register = 42;"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String result = generateStatement(stmts.get(0)); assertEquals("jint register_ = 42;", result); } public void testStaticVariableSetterReference() throws IOException { String translation = translateSourceFile( "public class Example { public static java.util.Date today; }" + "class Test { void test(java.util.Date now) { Example.today = now; }}", "Example", "Example.m"); assertTranslation(translation, "JreStrongAssign(JreLoadStaticRef(Example, today), now);"); } // b/5872533: reserved method name not renamed correctly in super invocation. public void testSuperReservedName() throws IOException { addSourceFile("public class A { A() {} public void init(int a) { }}", "A.java"); addSourceFile( "public class B extends A { B() {} public void init(int b) { super.init(b); }}", "B.java"); String translation = translateSourceFile("A", "A.h"); assertTranslation(translation, "- (instancetype)init;"); assertTranslation(translation, "- (void)init__WithInt:(jint)a"); translation = translateSourceFile("B", "B.m"); assertTranslation(translation, "A_init(self);"); assertTranslation(translation, "[super init__WithInt:b];"); } // b/5872757: verify multi-dimensional array has cast before each // secondary reference. public void testMultiDimArrayCast() throws IOException { String translation = translateSourceFile( "public class Test {" + " static String[][] a = new String[1][1];" + " public static void main(String[] args) { " + " a[0][0] = \"42\"; System.out.println(a[0].length); }}", "Test", "Test.m"); assertTranslation(translation, "IOSObjectArray_Set(nil_chk(IOSObjectArray_Get(nil_chk(Test_a), 0)), 0, @\"42\");"); assertTranslation(translation, "((IOSObjectArray *) nil_chk(IOSObjectArray_Get(Test_a, 0)))->size_"); } public void testMultiDimArray() throws IOException { String source = "int[][] a = new int[][] { null, { 0, 2 }, { 2, 2 }};"; List<Statement> stmts = translateStatements(source); assertEquals(1, stmts.size()); String translation = generateStatement(stmts.get(0)); assertTranslation(translation, "IOSObjectArray *a = [IOSObjectArray arrayWithObjects:(id[]){ nil, " + "[IOSIntArray arrayWithInts:(jint[]){ 0, 2 } count:2], " + "[IOSIntArray arrayWithInts:(jint[]){ 2, 2 } count:2] } count:3 " + "type:IOSClass_intArray(1)];"); } public void testObjectMultiDimArray() throws IOException { String source = "class Test { Integer i = new Integer(1); Integer j = new Integer(2);" + "void test() { Integer[][] a = new Integer[][] { null, { i, j }, { j, i }}; }}"; String translation = translateSourceFile(source, "Test", "Test.m"); assertTranslation(translation, "IOSObjectArray *a = [IOSObjectArray arrayWithObjects:(id[]){ nil, " + "[IOSObjectArray arrayWithObjects:(id[]){ i_, j_ } count:2 " + "type:JavaLangInteger_class_()], " + "[IOSObjectArray arrayWithObjects:(id[]){ j_, i_ } count:2 " + "type:JavaLangInteger_class_()] } count:3 " + "type:IOSClass_arrayType(JavaLangInteger_class_(), 1)];"); } public void testVarargsMethodInvocationZeroLengthArray() throws IOException { String translation = translateSourceFile( "public class Example { " + " public void call() { foo(new Object[0]); bar(new Object[0]); } " + " public void foo(Object ... args) { } " + " public void bar(Object[] ... args) { } }", "Example", "Example.h"); assertTranslation(translation, "- (void)fooWithNSObjectArray:(IOSObjectArray *)args"); assertTranslation(translation, "- (void)barWithNSObjectArray2:(IOSObjectArray *)args"); translation = getTranslatedFile("Example.m"); // Should be equivalent to foo(new Object[0]). assertTranslation(translation, "[self fooWithNSObjectArray:[IOSObjectArray arrayWithLength:0 type:NSObject_class_()]]"); // Should be equivalent to bar(new Object[] { new Object[0] }). assertTranslation(translation, "[self barWithNSObjectArray2:[IOSObjectArray arrayWithObjects:" + "(id[]){ [IOSObjectArray arrayWithLength:0 type:NSObject_class_()] } count:1 " + "type:IOSClass_arrayType(NSObject_class_(), 1)]];"); } public void testVarargsIOSMethodInvocation() throws IOException { String translation = translateSourceFile( "import java.lang.reflect.Constructor; public class Test { " + " public void test() throws Exception { " + " Constructor c1 = Test.class.getConstructor();" + " Constructor c2 = Test.class.getConstructor(String.class);" + " Constructor c3 = Test.class.getConstructor(String.class, Byte.TYPE);" + " Class[] types = new Class[] { Object.class, Exception.class };" + " Constructor c4 = Test.class.getConstructor(types); }}", "Test", "Test.m"); assertTranslation(translation, "c1 = [Test_class_() getConstructor:" + "[IOSObjectArray arrayWithLength:0 type:IOSClass_class_()]];"); assertTranslation(translation, "c2 = [Test_class_() getConstructor:[IOSObjectArray " + "arrayWithObjects:(id[]){ NSString_class_() } count:1 type:IOSClass_class_()]];"); assertTranslation(translation, "c3 = [Test_class_() getConstructor:[IOSObjectArray arrayWithObjects:" + "(id[]){ NSString_class_(), JreLoadStatic(JavaLangByte, TYPE) } count:2 " + "type:IOSClass_class_()]];"); // Array contents should be expanded. assertTranslation(translation, "c4 = [Test_class_() getConstructor:types];"); } public void testGetVarargsWithLeadingParameter() throws IOException { String translation = translateSourceFile( "public class Test {" + " void test() throws Exception { " + " getClass().getMethod(\"equals\", Object.class); }}", "Test", "Test.m"); assertTranslation(translation, "[[self java_getClass] getMethod:@\"equals\" parameterTypes:[IOSObjectArray " + "arrayWithObjects:(id[]){ NSObject_class_() } count:1 type:IOSClass_class_()]];"); } public void testGetVarargsWithLeadingParameterNoArgs() throws IOException { String translation = translateSourceFile( "public class Test {" + " void test() throws Exception { " + " getClass().getMethod(\"hashCode\", new Class[0]); }}", "Test", "Test.m"); assertTranslation(translation, "[[self java_getClass] getMethod:@\"hashCode\" parameterTypes:[IOSObjectArray " + "arrayWithLength:0 type:IOSClass_class_()]];"); } public void testTypeVariableWithBoundCast() throws IOException { String translation = translateSourceFile( "import java.util.ArrayList; public class Test {" + " public static class Foo<T extends Foo.Bar> {" + " public static class Bar { } " + " public T foo() { return null; } } " + " public static class BarD extends Foo.Bar { } " + " public void bar(Foo<BarD> f) { BarD b = f.foo(); } }", "Test", "Test.m"); assertTranslation(translation, "[((Test_Foo *) nil_chk(f)) foo]"); } // b/5934474: verify that static variables are always referenced by // their accessors in functions, since their class may not have loaded. public void testFunctionReferencesStaticVariable() throws IOException { String translation = translateSourceFile( "public class HelloWorld {" + " static String staticString = \"hello world\";" + " public static void main(String[] args) {" + " System.out.println(staticString);" + " }}", "HelloWorld", "HelloWorld.m"); assertTranslation(translation, "printlnWithNSString:HelloWorld_staticString];"); } public void testThisCallInEnumConstructor() throws IOException { String translation = translateSourceFile( "public enum Test {" + " A, B(1);" + " private int i;" + " private Test(int i) { this.i = i; } " + " private Test() { this(0); }}", "Test", "Test.m"); assertTranslation(translation, "Test_initWithInt_withNSString_withInt_(self, 0, __name, __ordinal);"); } public void testThisCallInInnerConstructor() throws IOException { String translation = translateSourceFile( "public class Test {" + " class Inner {" + " public Inner() { }" + " public Inner(int foo) { this(); int i = foo; }}}", "Test", "Test.m"); assertTranslation(translation, "Test_Inner_initWithTest_(self, outer$);"); } // Verify that an external string can be used in string concatenation, // for a parameter to a translated method. public void testConcatPublicStaticString() throws IOException { String translation = translateSourceFile( "class B { public static final String separator = \"/\"; } " + "public class A { String prefix(Object o) { return new String(o + B.separator); }}", "A", "A.m"); assertTranslation(translation, "[NSString stringWithString:JreStrcat(\"@$\", o, B_separator)]"); } public void testStringConcatWithBoolean() throws IOException { String translation = translateSourceFile( "public class A { String test(boolean b) { return \"foo: \" + b; }}", "A", "A.m"); assertTranslation(translation, "return JreStrcat(\"$Z\", @\"foo: \", b);"); } public void testStringConcatWithChar() throws IOException { String translation = translateSourceFile( "public class A { String test(char c) { return \"foo: \" + c; }}", "A", "A.m"); assertTranslation(translation, "return JreStrcat(\"$C\", @\"foo: \", c);"); } // Verify that double quote character constants are concatenated correctly. public void testConcatDoubleQuoteChar() throws IOException { String translation = translateSourceFile( "public class Test { " + "static final char QUOTE = '\"'; static final String TEST = QUOTE + \"\"; }", "Test", "Test.m"); assertTranslation(translation, "Test_TEST = @\"\\\"\";"); } // Verify that return statements in constructors return self. public void testConstructorReturn() throws IOException { String translation = translateSourceFile( "public class A { public A() { return; }}", "A", "A.m"); assertTranslation(translation, "return self;"); } public void testNonAsciiOctalEscapeInString() throws IOException { String translation = translateSourceFile( "public class A { String s1 = \"\\177\"; String s2 = \"\\200\"; String s3 = \"\\377\"; }", "A", "A.m"); assertTranslation(translation, "@\"\\x7f\""); assertTranslation(translation, "@\"\\xc2\\x80\""); assertTranslation(translation, "@\"\\u00ff\""); } public void testCharLiteralsAreEscaped() throws IOException { String translation = translateSourceFile( "public class A {" + "public static final char APOSTROPHE = '\\''; " + "public static final char BACKSLASH = '\\\\'; " + "void foo(char c) {} void test() { foo('\\''); foo('\\\\'); }}", "A", "A.h"); assertTranslation(translation, "#define A_APOSTROPHE '\\''"); assertTranslation(translation, "#define A_BACKSLASH '\\\\'"); translation = getTranslatedFile("A.m"); assertTranslation(translation, "fooWithChar:'\\'']"); assertTranslation(translation, "fooWithChar:'\\\\']"); } public void testStaticVarAccessFromInnerClass() throws IOException { String translation = translateSourceFile("public class Test { public static String foo; " + "interface Assigner { void assign(String s); } static { " + "new Assigner() { public void assign(String s) { foo = s; }}; }}", "Test", "Test.m"); assertTranslation(translation, "JreStrongAssign(JreLoadStaticRef(Test, foo), s);"); } public void testNoAutoreleasePoolForStatement() throws IOException { String translation = translateSourceFile( "public class Test {" + " public void foo() {" + " for (int i = 0; i < 10; i++) {" + " }" + " }" + "}", "Test", "Test.m"); assertTranslation(translation, " for (jint i = 0; i < 10; i++) {\n }"); } public void testAutoreleasePoolForStatement() throws IOException { String translation = translateSourceFile( "import com.google.j2objc.annotations.AutoreleasePool;" + "public class Test {" + " public void foo() {" + " for (@AutoreleasePool int i = 0; i < 10; i++) {" + " }" + " }" + "}", "Test", "Test.m"); assertTranslation(translation, " for (jint i = 0; i < 10; i++) {\n" + " @autoreleasepool {\n }\n" + " }"); } public void testAutoreleasePoolEnhancedForStatement() throws IOException { String translation = translateSourceFile( "import com.google.j2objc.annotations.AutoreleasePool;" + "public class Test {" + " public void foo(String[] strings) {" + " for (@AutoreleasePool String s : strings) {" + " }" + " }" + "}", "Test", "Test.m"); assertTranslation(translation, "@autoreleasepool"); } public void testARCAutoreleasePoolForStatement() throws IOException { options.setMemoryManagementOption(MemoryManagementOption.ARC); String translation = translateSourceFile( "import com.google.j2objc.annotations.AutoreleasePool;" + "public class Test {" + " public void foo() {" + " for (@AutoreleasePool int i = 0; i < 10; i++) {" + " }" + " }" + "}", "Test", "Test.m"); assertTranslation(translation, " for (jint i = 0; i < 10; i++) {\n" + " @autoreleasepool {\n" + " }\n" + " }"); } public void testARCAutoreleasePoolEnhancedForStatement() throws IOException { options.setMemoryManagementOption(MemoryManagementOption.ARC); String translation = translateSourceFile( "import com.google.j2objc.annotations.AutoreleasePool;" + "public class Test {" + " public void foo(String[] strings) {" + " for (@AutoreleasePool String s : strings) {" + " }" + " }" + "}", "Test", "Test.m"); assertTranslation(translation, "@autoreleasepool {"); } public void testShiftAssignArrayElement() throws IOException { String translation = translateSourceFile( "public class Test { void test(int[] array) { " + "int i = 2;" + "array[0] >>>= 2; " + "array[i - 1] >>>= 3; " + "array[1] >>= 4;" + "array[2] <<= 5;}}", "Test", "Test.m"); assertTranslation(translation, "JreURShiftAssignInt(IOSIntArray_GetRef(nil_chk(array), 0), 2)"); assertTranslation(translation, "JreURShiftAssignInt(IOSIntArray_GetRef(array, i - 1), 3)"); assertTranslation(translation, "JreRShiftAssignInt(IOSIntArray_GetRef(array, 1), 4)"); assertTranslation(translation, "JreLShiftAssignInt(IOSIntArray_GetRef(array, 2), 5)"); } public void testAssertWithoutDescription() throws IOException { String translation = translateSourceFile( "public class Test { void test() {\n" + "int a = 5;\nint b = 6;\nassert a < b;\n}\n}\n", "Test", "Test.m"); assertTranslation(translation, "JreAssert((a < b), (@\"Test.java:4 condition failed: assert a < b;\"))"); } public void testAssertWithDescription() throws IOException { String translation = translateSourceFile( "public class Test { void test() { " + "int a = 5; int b = 6; assert a < b : \"a should be lower than b\";}}", "Test", "Test.m"); assertTranslation(translation, "JreAssert((a < b), (@\"a should be lower than b\"))"); } public void testAssertWithDynamicDescription() throws IOException { String translation = translateSourceFile( "public class Test { void test() { " + "int a = 5; int b = 6; assert a < b : a + \" should be lower than \" + b;}}", "Test", "Test.m"); assertTranslation(translation, "JreAssert((a < b), (JreStrcat(\"I$I\", a, @\" should be lower than \", b)));"); } // Verify that a Unicode escape sequence is preserved with string // concatenation. public void testUnicodeStringConcat() throws IOException { String translation = translateSourceFile( "class Test { static final String NAME = \"\\u4e2d\\u56fd\";" + " static final String CAPTION = \"China's name is \";" + " static final String TEST = CAPTION + NAME; }", "Test", "Test.m"); assertTranslation(translation, "Test_TEST = @\"China's name is \\u4e2d\\u56fd\""); } public void testPartialArrayCreation2D() throws IOException { String translation = translateSourceFile( "class Test { void foo() { char[][] c = new char[3][]; } }", "Test", "Test.m"); assertTranslation(translation, "#include \"IOSObjectArray.h\""); assertTranslation(translation, "#include \"IOSClass.h\""); assertTranslation(translation, "IOSObjectArray *c = [IOSObjectArray arrayWithLength:3 type:IOSClass_charArray(1)]"); } public void testPartialArrayCreation3D() throws IOException { String translation = translateSourceFile( "class Test { void foo() { char[][][] c = new char[3][][]; } }", "Test", "Test.m"); assertTranslation(translation, "#include \"IOSObjectArray.h\""); assertTranslation(translation, "IOSObjectArray *c = [IOSObjectArray arrayWithLength:3 type:IOSClass_charArray(2)]"); } public void testUnsignedRightShift() throws IOException { String translation = translateSourceFile( "public class Test { void test(int a, long b, char c, byte d, short e) { " + "long r; r = a >>> 1; r = b >>> 2; r = c >>> 3; r = d >>> 4; r = e >>> 5; }}", "Test", "Test.m"); assertTranslation(translation, "r = JreURShift32(a, 1);"); assertTranslation(translation, "r = JreURShift64(b, 2);"); assertTranslation(translation, "r = JreURShift32(c, 3);"); assertTranslation(translation, "r = JreURShift32(d, 4);"); assertTranslation(translation, "r = JreURShift32(e, 5);"); } public void testUnsignedRightShiftAssign() throws IOException { String translation = translateSourceFile( "public class Test { void test(int a, long b, char c, byte d, short e) { " + "a >>>= 1; b >>>= 2; c >>>= 3; d >>>= 4; e >>>= 5; }}", "Test", "Test.m"); assertTranslation(translation, "JreURShiftAssignInt(&a, 1);"); assertTranslation(translation, "JreURShiftAssignLong(&b, 2);"); assertTranslation(translation, "JreURShiftAssignChar(&c, 3);"); assertTranslation(translation, "JreURShiftAssignByte(&d, 4);"); assertTranslation(translation, "JreURShiftAssignShort(&e, 5);"); } public void testUnsignedShiftRightAssignCharArray() throws IOException { String translation = translateSourceFile( "public class Test { void test(char[] array) { " + "array[0] >>>= 2; }}", "Test", "Test.m"); assertTranslation(translation, "JreURShiftAssignChar(IOSCharArray_GetRef(nil_chk(array), 0), 2)"); } public void testDoubleQuoteConcatenation() throws IOException { String translation = translateSourceFile( "public class Test { String test(String s) { return '\"' + s + '\"'; }}", "Test", "Test.m"); assertTranslation(translation, "return JreStrcat(\"C$C\", '\"', s, '\"');"); } public void testIntConcatenation() throws IOException { String translation = translateSourceFile( "public class Test { void check(boolean expr, String fmt, Object... args) {} " + "void test(int i, int j) { check(true, \"%d-%d\", i, j); }}", "Test", "Test.m"); assertTranslation(translation, "[self checkWithBoolean:true withNSString:@\"%d-%d\" " + "withNSObjectArray:[IOSObjectArray arrayWithObjects:(id[]){ " + "JavaLangInteger_valueOfWithInt_(i), JavaLangInteger_valueOfWithInt_(j) } count:2 " + "type:NSObject_class_()]];"); } // Verify that a string == comparison is converted to compare invocation. public void testStringComparison() throws IOException { String translation = translateSourceFile( "public class Test { void check(String s, Object o) { " + "boolean b1 = s == null; boolean b2 = \"foo\" == s; boolean b3 = o == \"bar\"; " + "boolean b4 = \"baz\" != s; boolean b5 = null != \"abc\"; }}", "Test", "Test.m"); // Assert that non-string compare isn't converted. assertTranslation(translation, "jboolean b1 = s == nil;"); // Assert string equate is converted, assertTranslation(translation, "jboolean b2 = [@\"foo\" isEqual:s];"); // Order is reversed when literal is on the right. assertTranslation(translation, "jboolean b3 = [@\"bar\" isEqual:o];"); // Not equals is converted. assertTranslation(translation, "jboolean b4 = ![@\"baz\" isEqual:s];"); // Comparing null with string literal. assertTranslation(translation, "jboolean b5 = ![@\"abc\" isEqual:nil];"); } public void testBinaryLiterals() throws IOException { String translation = translateSourceFile( "public class A { " + " byte aByte = (byte)0b00100001; short aShort = (short)0b1010000101000101;" + " int anInt1 = 0b10100001010001011010000101000101; " + " int anInt2 = 0b101; int anInt3 = 0B101;" // b can be lower or upper case. + " long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L; }", "A", "A.m"); assertTranslation(translation, "aByte_ = (jbyte) 0b00100001;"); assertTranslation(translation, "aShort_ = (jshort) 0b1010000101000101;"); assertTranslation(translation, "anInt1_ = 0b10100001010001011010000101000101;"); assertTranslation(translation, "anInt2_ = 0b101;"); assertTranslation(translation, "anInt3_ = 0B101;"); assertTranslation(translation, "aLong_ = 0b1010000101000101101000010100010110100001010001011010000101000101LL;"); } public void testUnderscoresInNumericLiterals() throws IOException { String translation = translateSourceFile( "public class A { " + " long creditCardNumber = 1234_5678_9012_3456L; " + " long socialSecurityNumber = 999_99_9999L; " + " float pi = 3.14_15F; " + " long hexBytes = 0xFF_EC_DE_5E; " + " long hexWords = 0xCAFE_BABE; " + " long maxLong = 0x7fff_ffff_ffff_ffffL; " + " byte nybbles = 0b0010_0101; " + " long bytes = 0b11010010_01101001_10010100_10010010; }", "A", "A.m"); assertTranslation(translation, "creditCardNumber_ = 1234567890123456LL;"); assertTranslation(translation, "socialSecurityNumber_ = 999999999LL;"); assertTranslation(translation, "pi_ = 3.1415f;"); assertTranslation(translation, "hexBytes_ = (jint) 0xFFECDE5E;"); assertTranslation(translation, "hexWords_ = (jint) 0xCAFEBABE;"); assertTranslation(translation, "maxLong_ = (jlong) 0x7fffffffffffffffLL;"); assertTranslation(translation, "nybbles_ = 0b00100101;"); assertTranslation(translation, "bytes_ = 0b11010010011010011001010010010010;"); } // Verify that the null literal is concatenated as "null" in strings. public void testNullConcatenation() throws IOException { String translation = translateSourceFile( "public class Test { String test(String s) { return \"the nil value is \" + null; }}", "Test", "Test.m"); assertTranslation(translation, "return JreStrcat(\"$@\", @\"the nil value is \", nil);"); } public void testTypeVariableWithBoundsIsCast() throws IOException { String translation = translateSourceFile( "class Test<E> { interface A<T> { void foo(); } class B { int foo() { return 1; } } " + "<T extends A<? super E>> void test(T t) { t.foo(); } " + "<T extends B> void test2(T t) { t.foo(); } }", "Test", "Test.m"); assertTranslation(translation, "[((id<Test_A>) nil_chk(t)) foo];"); assertTranslation(translation, "[((Test_B *) nil_chk(t)) foo];"); } public void testTypeVariableWithMultipleBounds() throws IOException { String translation = translateSourceFile( "class Test<T extends String & Runnable & Cloneable> { T t; }", "Test", "Test.h"); assertTranslation(translation, "NSString<JavaLangRunnable, NSCopying> *t"); } public void testMultiCatch() throws IOException { String translation = translateSourceFile( "import java.util.*; public class Test { " + " static class FirstException extends Exception {} " + " static class SecondException extends Exception {} " + " public void rethrowException(String exceptionName) " + " throws FirstException, SecondException { " + " try { " + " if (exceptionName.equals(\"First\")) { throw new FirstException(); } " + " else { throw new SecondException(); }" + " } catch (FirstException|SecondException e) { throw e; }}}", "Test", "Test.m"); assertTranslation(translation, "@catch (Test_FirstException *e) {\n @throw nil_chk(e);\n }"); assertTranslation(translation, "@catch (Test_SecondException *e) {\n @throw nil_chk(e);\n }"); assertNotInTranslation(translation, "@catch (JavaLangException *e) {\n @throw nil_chk(e);\n }"); } public void testDifferentTypesInConditionalExpression() throws IOException { String translation = translateSourceFile( "class Test { String test(Runnable r) { return \"foo\" + (r != null ? r : \"bar\"); } }", "Test", "Test.m"); assertTranslation(translation, "(r != nil ? r : (id) @\"bar\")"); } // Verify that when a method invocation returns an object that is ignored, // it is cast to (void) to avoid a clang warning when compiling with ARC. public void testVoidedUnusedInvocationReturn() throws IOException { options.setMemoryManagementOption(MemoryManagementOption.ARC); String translation = translateSourceFile( "class Test { void test() {" + " StringBuilder sb = new StringBuilder();" + " sb.append(\"hello, world\");" + " new Throwable(); }}", "Test", "Test.m"); assertTranslation(translation, "(void) [sb appendWithNSString:@\"hello, world\"];"); assertTranslation(translation, "(void) new_NSException_init();"); } // Verify minimal try-with-resources translation. public void testTryWithResourceNoCatchOrFinally() throws IOException { String translation = translateSourceFile( "import java.io.*; public class Test { String test(String path) throws IOException { " + " try (BufferedReader br = new BufferedReader(new FileReader(path))) {" + " return br.readLine(); } }}", "Test", "Test.m"); assertTranslatedLines(translation, "JavaIoBufferedReader *br = create_JavaIoBufferedReader_initWithJavaIoReader_(" + "create_JavaIoFileReader_initWithNSString_(path));", "NSException *__primaryException1 = nil;", "@try {", " return [br readLine];", "}", "@catch (NSException *e) {", " __primaryException1 = e;", " @throw e;", "}", "@finally {", " if (br != nil) {", " if (__primaryException1 != nil) {", " @try {", " [br close];", " } @catch (NSException *e) {", " [__primaryException1 addSuppressedWithNSException:e];", " }", " } else {", " [br close];", " }", " }", "}"); } // Verify try-with-resources translation with multiple resources. public void testTryWithMultipleResourceNoCatchOrFinally() throws IOException { String translation = translateSourceFile( "import java.io.*; public class Test { String test(String path) throws IOException { " + " try (BufferedReader br = new BufferedReader(new FileReader(path));" + " BufferedReader br2 = new BufferedReader(new FileReader(path))) {" + " return br.readLine(); } }}", "Test", "Test.m"); assertTranslatedLines(translation, "JavaIoBufferedReader *br = create_JavaIoBufferedReader_initWithJavaIoReader_(" + "create_JavaIoFileReader_initWithNSString_(path));", "NSException *__primaryException2 = nil;", "@try {", " JavaIoBufferedReader *br2 = create_JavaIoBufferedReader_initWithJavaIoReader_(" + "create_JavaIoFileReader_initWithNSString_(path));", " NSException *__primaryException1 = nil;", " @try {", " return [br readLine];", " }", " @catch (NSException *e) {", " __primaryException1 = e;", " @throw e;", " }", " @finally {", " if (br2 != nil) {", " if (__primaryException1 != nil) {", " @try {", " [br2 close];", " } @catch (NSException *e) {", " [__primaryException1 addSuppressedWithNSException:e];", " }", " } else {", " [br2 close];", " }", " }", " }", "}", "@catch (NSException *e) {", " __primaryException2 = e;", " @throw e;", "}", "@finally {", " if (br != nil) {", " if (__primaryException2 != nil) {", " @try {", " [br close];", " } @catch (NSException *e) {", " [__primaryException2 addSuppressedWithNSException:e];", " }", " } else {", " [br close];", " }", " }", "}"); } // Verify try-with-resources translation is inside of try block with catch clause outside. public void testTryWithResourceAndCatch() throws IOException { String translation = translateSourceFile( "import java.io.*; public class Test { String test(String path) throws IOException { " + " try (BufferedReader br = new BufferedReader(new FileReader(path))) {" + " return br.readLine(); " + " } catch (IOException e) {" + " System.out.println(e);" + " throw e;" + " } }}", "Test", "Test.m"); assertTranslatedLines(translation, "@try {", " JavaIoBufferedReader *br = create_JavaIoBufferedReader_initWithJavaIoReader_(" + "create_JavaIoFileReader_initWithNSString_(path));", " NSException *__primaryException1 = nil;", " @try {", " return [br readLine];", " }", " @catch (NSException *e) {", " __primaryException1 = e;", " @throw e;", " }", " @finally {", " if (br != nil) {", " if (__primaryException1 != nil) {", " @try {", " [br close];", " } @catch (NSException *e) {", " [__primaryException1 addSuppressedWithNSException:e];", " }", " } else {", " [br close];", " }", " }", " }", "}", "@catch (JavaIoIOException *e) {", " [((JavaIoPrintStream *) nil_chk(JreLoadStatic(JavaLangSystem, out))) printlnWithId:e];", " @throw nil_chk(e);", "}"); } // Verify that multiple resources are closed in reverse order from opening. public void testTryMultiResourcesNoCatchOrFinally() throws IOException { String translation = translateSourceFile( "import java.io.*; public class Test { " + "static class Resource implements AutoCloseable { " + " public void close() throws Exception {}} " + "void test() throws Exception { " + " try (Resource r1 = new Resource();" + " Resource r2 = new Resource();" + " Resource r3 = new Resource()) {" + " }}}", "Test", "Test.m"); assertTranslatedSegments(translation, "Test_Resource *r1", "Test_Resource *r2", "Test_Resource *r3"); assertTranslatedSegments(translation, "[r3 close]", "[r2 close]", "[r1 close]"); } public void testGenericResultIsCastForChainedMethodCall() throws IOException { String translation = translateSourceFile( "abstract class Test<T extends Test.Foo> { " + "abstract T getObj(); static class Foo { void foo() { } } " + "static void test(Test<Foo> t) { t.getObj().foo(); } }", "Test", "Test.m"); assertTranslation(translation, "[((Test_Foo *) nil_chk([((Test *) nil_chk(t)) getObj])) foo]"); } public void testCastResultWhenInterfaceDeclaresMoreGenericType() throws IOException { String translation = translateSourceFile( "class Test { " + "interface I1 { A foo(); } " + "interface I2 { B foo(); } " + "static class A { } static class B extends A { } " + "static abstract class C { abstract B foo(); } " + "static abstract class D extends C implements I1, I2 { } " + "B test(D d) { return d.foo(); } }", "Test", "Test.h"); // Check that protocols are declared in the same order. assertTranslation(translation, "@interface Test_D : Test_C < Test_I1, Test_I2 >"); // A "foo" declaration is added to class "D" to override the less specific // return type inherited from "I1". assertOccurrences(translation, "- (Test_B *)foo;", 3); translation = getTranslatedFile("Test.m"); // Check that the result of d.foo() is not cast. assertTranslation(translation, "return [((Test_D *) nil_chk(d)) foo];"); } public void testStaticMethodCalledOnObject() throws IOException { String translation = translateSourceFile( "class Test { static void foo() {} void test(Test t) { t.foo(); } }", "Test", "Test.m"); assertTranslation(translation, "Test_foo();"); } public void testAnnotationVariableDeclaration() throws IOException { String translation = translateSourceFile( "public class Test { void test() { " + "Deprecated annotation = null; }}", "Test", "Test.m"); assertTranslation(translation, "id<JavaLangDeprecated> annotation = "); } public void testAnnotationTypeLiteral() throws IOException { String translation = translateSourceFile( "@Deprecated public class Test { " + " Deprecated deprecated() { " + " return Test.class.getAnnotation(Deprecated.class); }}", "Test", "Test.m"); assertTranslation(translation, "JavaLangDeprecated_class_()"); } public void testEnumThisCallWithNoArguments() throws IOException { String translation = translateSourceFile( "enum Test { A, B; Test() {} Test(int i) { this(); } }", "Test", "Test.m"); assertTranslation(translation, "JavaLangEnum_initWithNSString_withInt_(self, __name, __ordinal);"); // Called from the "this()" call. assertOccurrences(translation, "Test_initWithNSString_withInt_(self, __name, __ordinal);", 1); } public void testForStatementWithMultipleInitializers() throws IOException { String translation = translateSourceFile( "class Test { void test() { " + "for (String s1 = null, s2 = null;;) {}}}", "Test", "Test.m"); // C requires that each var have its own pointer. assertTranslation(translation, "for (NSString *s1 = nil, *s2 = nil; ; )"); } // Verify that constant variables are directly referenced when expression is "self". public void testSelfStaticVarAccess() throws IOException { String translation = translateSourceFile( "public class Test { enum Type { TYPE_BOOL; } Type test() { return Type.TYPE_BOOL; }}", "Test", "Test.m"); assertTranslation(translation, "return JreLoadEnum(Test_Type, TYPE_BOOL);"); } public void testMakeQuotedStringHang() throws IOException { // Test hangs if bug makeQuotedString() isn't fixed. translateSourceFile( "public class Test { void test(String s) { assert !\"null\\foo\\nbar\".equals(s); }}", "Test", "Test.m"); } // Verify that the type of superclass field's type variable is cast properly. public void testSuperTypeVariable() throws IOException { addSourceFile("import java.util.List; class TestList <T extends List> { " + " protected final T testField; TestList(T field) { testField = field; }}", "TestList.java"); addSourceFile("import java.util.ArrayList; class TestArrayList extends TestList<ArrayList> { " + " TestArrayList(ArrayList list) { super(list); }}", "TestArrayList.java"); String translation = translateSourceFile( "import java.util.ArrayList; class Test extends TestArrayList { " + " Test(ArrayList list) { super(list); } " + " private class Inner {" + " void test() { testField.ensureCapacity(42); }}}", "Test", "Test.m"); assertTranslation(translation, "[((JavaUtilArrayList *) nil_chk(this$0_->testField_)) ensureCapacityWithInt:42];"); } public void testNoTrigraphs() throws IOException { String translation = translateSourceFile( // C trigraph list from http://en.wikipedia.org/wiki/Digraphs_and_trigraphs#C. "class Test { static final String S1 = \"??=??/??'??(??)??!??<??>??-\"; " // S2 has char sequences that start with ?? but aren't trigraphs. + " static final String S2 = \"??@??$??%??&??*??A??z??1??.\"; }", "Test", "Test.m"); assertTranslation(translation, "S1 = @\"?\" \"?=?\" \"?/?\" \"?'?\" \"?(?\" \"?)?\" \"?!?\" \"?<?\" \"?>?\" \"?-\";"); assertTranslation(translation, "S2 = @\"??@??$??%??&??*??A??z??1??.\";"); } // Verify that casting from a floating point primitive to an integral primitive // uses the right cast macro. public void testFloatingPointCasts() throws IOException { String translation = translateSourceFile( "public class Test { " + " byte testByte(float f) { return (byte) f; }" + " char testChar(float f) { return (char) f; }" + " short testShort(float f) { return (short) f; }" + " int testInt(float f) { return (int) f; }" + " long testLong(float f) { return (long) f; }" + " byte testByte(double d) { return (byte) d; }" + " char testChar(double d) { return (char) d; }" + " short testShort(double d) { return (short) d; }" + " int testInt(double d) { return (int) d; }" + " long testLong(double d) { return (long) d; }}", "Test", "Test.m"); // Verify referenced return value is cast. assertTranslatedLines(translation, "- (jbyte)testByteWithFloat:(jfloat)f {", "return (jbyte) JreFpToInt(f);"); assertTranslatedLines(translation, "- (jchar)testCharWithFloat:(jfloat)f {", "return JreFpToChar(f);"); assertTranslatedLines(translation, "- (jshort)testShortWithFloat:(jfloat)f {", "return (jshort) JreFpToInt(f);"); assertTranslatedLines(translation, "- (jint)testIntWithFloat:(jfloat)f {", "return JreFpToInt(f);"); assertTranslatedLines(translation, "- (jlong)testLongWithFloat:(jfloat)f {", "return JreFpToLong(f);"); assertTranslatedLines(translation, "- (jbyte)testByteWithDouble:(jdouble)d {", "return (jbyte) JreFpToInt(d);"); assertTranslatedLines(translation, "- (jchar)testCharWithDouble:(jdouble)d {", "return JreFpToChar(d);"); assertTranslatedLines(translation, "- (jshort)testShortWithDouble:(jdouble)d {", "return (jshort) JreFpToInt(d);"); assertTranslatedLines(translation, "- (jint)testIntWithDouble:(jdouble)d {", "return JreFpToInt(d);"); assertTranslatedLines(translation, "- (jlong)testLongWithDouble:(jdouble)d {", "return JreFpToLong(d);"); } // Verify that string constants used in switch statements can be generated after functionizing. public void testFunctionalizedStringStringStatement() throws IOException { String source = "class A { " + "private static final String STR = \"\"; " + "private void f(String s) { switch(s) { case STR: return; } } " + "public void g() { f(\"\"); } }"; // Assertion was thrown in StatementGenerator.getStringConstant(), due to the QualifiedName // node not having a constant value. translateSourceFile(source, "A", "A.m"); } public void testSuppressedUnusedVariable() throws IOException { String translation = translateSourceFile( "class Test {" + "void test() { " + "@SuppressWarnings(\"unused\") int foo; }}", "Test", "Test.m"); assertTranslation(translation, "__unused jint foo;"); } public void testSuppressedUnusedVariableFromMethod() throws IOException { String translation = translateSourceFile( "class Test {" + "@SuppressWarnings(\"unused\") void test() { " + "int foo; }}", "Test", "Test.m"); assertTranslation(translation, "__unused jint foo;"); } public void testSuppressedUnusedVariableFromClass() throws IOException { String translation = translateSourceFile( "@SuppressWarnings(\"unused\") class Test {" + "void test() { " + " int foo; }}", "Test", "Test.m"); assertTranslation(translation, "__unused jint foo;"); } // Verify that empty statements line offset to owning statement is preserved. public void testEmptyStatementFormatting() throws IOException { String translation = translateSourceFile( "class Test {\n" + " void foo(int a, int b) {\n" + " if (a < b) ;\n" // Empty statement on same line as if statement. + " }\n" + " void bar(int c, int d) {\n" + " if (c < d)\n" + " ;\n" // Empty statement on different line than if statement. + " }}", "Test", "Test.m"); assertTranslation(translation, "if (a < b) ;"); assertTranslatedLines(translation, "if (c < d)", ";"); } }