/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.j2objc.translate;
import com.google.devtools.j2objc.GenerationTest;
import com.google.devtools.j2objc.ast.Block;
import com.google.devtools.j2objc.ast.EmptyStatement;
import com.google.devtools.j2objc.ast.ForStatement;
import com.google.devtools.j2objc.ast.IfStatement;
import com.google.devtools.j2objc.ast.LabeledStatement;
import com.google.devtools.j2objc.ast.Statement;
import java.io.IOException;
import java.util.List;
/**
* Unit tests for {@link Rewriter}.
*
* @author Tom Ball
*/
public class RewriterTest extends GenerationTest {
public void testContinueAndBreakUsingSameLabel() throws IOException {
String translation = translateSourceFile(
"class Test { void test() { "
+ "int i = 0; outer: for (; i < 10; i++) { "
+ "for (int j = 0; j < 10; j++) { "
+ "int n = i + j; "
+ "if (n == 5) continue outer; "
+ "else break outer; } } } }", "Test", "Test.m");
assertTranslatedLines(translation,
"jint i = 0;",
"for (; i < 10; i++) {",
"{",
"for (jint j = 0; j < 10; j++) {",
"jint n = i + j;",
"if (n == 5) goto continue_outer;",
"else goto break_outer;",
"}",
"}",
"continue_outer: ;",
"}",
"break_outer: ;");
}
public void testLabeledContinue() throws IOException {
List<Statement> stmts = translateStatements(
"int i = 0; outer: for (; i < 10; i++) { "
+ "for (int j = 0; j < 10; j++) { continue outer; }}");
assertEquals(2, stmts.size());
Statement s = stmts.get(1);
assertTrue(s instanceof ForStatement); // not LabeledStatement
ForStatement fs = (ForStatement) s;
Statement forStmt = fs.getBody();
assertTrue(forStmt instanceof Block);
stmts = ((Block) forStmt).getStatements();
assertEquals(2, stmts.size());
Statement lastStmt = stmts.get(1);
assertTrue(lastStmt instanceof LabeledStatement);
assertTrue(((LabeledStatement) lastStmt).getBody() instanceof EmptyStatement);
}
public void testLabeledBreak() throws IOException {
List<Statement> stmts = translateStatements(
"int i = 0; outer: for (; i < 10; i++) { "
+ "for (int j = 0; j < 10; j++) { break outer; }}");
assertEquals(3, stmts.size());
Statement s = stmts.get(1);
assertTrue(s instanceof ForStatement); // not LabeledStatement
ForStatement fs = (ForStatement) s;
Statement forStmt = fs.getBody();
assertTrue(forStmt instanceof Block);
assertEquals(1, ((Block) forStmt).getStatements().size());
Statement lastStmt = stmts.get(2);
assertTrue(lastStmt instanceof LabeledStatement);
assertTrue(((LabeledStatement) lastStmt).getBody() instanceof EmptyStatement);
}
public void testLabeledBreakWithNonBlockParent() throws IOException {
List<Statement> stmts = translateStatements(
"int i = 0; if (i == 0) outer: for (; i < 10; i++) { "
+ "for (int j = 0; j < 10; j++) { break outer; }}");
assertEquals(2, stmts.size());
Statement s = stmts.get(1);
assertTrue(s instanceof IfStatement);
s = ((IfStatement) s).getThenStatement();
assertTrue(s instanceof Block);
stmts = ((Block) s).getStatements();
assertEquals(2, stmts.size());
s = stmts.get(0);
assertTrue(s instanceof ForStatement); // not LabeledStatement
ForStatement fs = (ForStatement) s;
Statement forStmt = fs.getBody();
assertTrue(forStmt instanceof Block);
assertEquals(1, ((Block) forStmt).getStatements().size());
Statement labelStmt = stmts.get(1);
assertTrue(labelStmt instanceof LabeledStatement);
assertTrue(((LabeledStatement) labelStmt).getBody() instanceof EmptyStatement);
}
public void testLabeledBreakOnSwitchStatement() throws IOException {
String translation = translateSourceFile(
"class Test { void main(int i) { outer: switch(i) { case 1: break outer; } } }",
"Test", "Test.m");
assertTranslatedLines(translation,
"switch (i) {",
"case 1:",
"goto break_outer;",
"}",
"break_outer: ;");
}
/**
* Verify that array initializers are rewritten as method calls.
*/
public void testArrayInitializerRewrite() throws IOException {
String translation = translateSourceFile(
"public class Test { void test() { int[] a = { 1, 2, 3 }; char b[] = { '4', '5' }; } }",
"Test", "Test.m");
assertTranslatedLines(translation,
"IOSIntArray *a = [IOSIntArray arrayWithInts:(jint[]){ 1, 2, 3 } count:3];",
"IOSCharArray *b = [IOSCharArray arrayWithChars:(jchar[]){ '4', '5' } count:2];");
}
/**
* Verify that static array initializers are rewritten as method calls.
*/
public void testStaticArrayInitializerRewrite() throws IOException {
String translation = translateSourceFile(
"public class Test { static int[] a = { 1, 2, 3 }; static char b[] = { '4', '5' }; }",
"Test", "Test.m");
assertTranslatedLines(translation,
"+ (void)initialize {",
"if (self == [Test class]) {",
"JreStrongAssignAndConsume(&Test_a, "
+ "[IOSIntArray newArrayWithInts:(jint[]){ 1, 2, 3 } count:3]);",
"JreStrongAssignAndConsume(&Test_b, "
+ "[IOSCharArray newArrayWithChars:(jchar[]){ '4', '5' } count:2]);");
}
public void testNonStaticMultiDimArrayInitializer() throws IOException {
String translation = translateSourceFile(
"class Test { int[][] a = { { 1, 2, 3 } }; }", "Test", "Test.m");
assertTranslation(translation,
"[IOSObjectArray newArrayWithObjects:(id[]){"
+ " [IOSIntArray arrayWithInts:(jint[]){ 1, 2, 3 } count:3] } count:1"
+ " type:IOSClass_intArray(1)]");
}
public void testArrayCreationInConstructorInvocation() throws IOException {
String translation = translateSourceFile(
"class Test { Test(int[] i) {} Test() { this(new int[] {}); } }", "Test", "Test.m");
assertTranslation(translation,
"Test_initWithIntArray_(self, [IOSIntArray arrayWithInts:(jint[]){ } count:0]);");
}
public void testInterfaceFieldsAreStaticFinal() throws IOException {
String source = "interface Test { String foo = \"bar\"; }";
String translation = translateSourceFile(source, "Test", "Test.h");
assertTranslation(translation, "J2OBJC_STATIC_FIELD_OBJ_FINAL(Test, foo, NSString *)");
}
// Regression test: the wrong method name used for "f.group()" translation.
public void testPrintlnWithMethodInvocation() throws IOException {
String source = "public class A { "
+ "String group() { return \"foo\"; } "
+ "void test() { A a = new A(); System.out.println(a.group()); }}";
String translation = translateSourceFile(source, "A", "A.m");
assertTranslation(translation, "printlnWithNSString:[a group]];");
}
public void testStaticArrayInitializerMove() throws IOException {
String source = "class Test { static final double[] EVERY_SIXTEENTH_FACTORIAL = "
+ "{ 0x1.0p0, 0x1.30777758p44, 0x1.956ad0aae33a4p117, 0x1.ee69a78d72cb6p202, "
+ "0x1.fe478ee34844ap295, 0x1.c619094edabffp394, 0x1.3638dd7bd6347p498, "
+ "0x1.7cac197cfe503p605, 0x1.1e5dfc140e1e5p716, 0x1.8ce85fadb707ep829, "
+ "0x1.95d5f3d928edep945 }; }";
String translation = translateSourceFile(source, "Test", "Test.m");
assertTranslation(translation, "{ 1.0, 2.0922789888E13, 2.631308369336935E35, "
+ "1.2413915592536073E61, 1.2688693218588417E89, 7.156945704626381E118, "
+ "9.916779348709496E149, 1.974506857221074E182, 3.856204823625804E215, "
+ "5.5502938327393044E249, 4.7147236359920616E284 }");
}
public void testTypeCheckInCompareToMethod() throws IOException {
String translation = translateSourceFile(
"class Test implements Comparable<Test> { int i; "
+ " public int compareTo(Test t) { return i - t.i; } }", "Test", "Test.m");
assertTranslatedLines(translation,
"- (jint)compareToWithId:(Test *)t {",
"cast_chk(t, [Test class]);");
}
public void testAdditionWithinStringConcatenation() throws IOException {
String translation = translateSourceFile(
"class Test { void test() { String s = 1 + 2.3f + \"foo\"; } }", "Test", "Test.m");
assertTranslation(translation, "NSString *s = JreStrcat(\"F$\", 1 + 2.3f, @\"foo\");");
}
public void testMethodCollisionWithSuperclassField() throws IOException {
addSourceFile("class A { protected int i; }", "A.java");
String translation = translateSourceFile(
"class B extends A { int i() { return i; } }", "B", "B.m");
assertTranslation(translation, "return i_;");
}
public void testMultipleLabelsWithSameName() throws IOException {
String translation = translateSourceFile(
"public class Test { void test() { "
+ " outer: for (int r = 0; r < 10; r++) {"
+ " for (int s = 0; s < 10; s++) {"
+ " break outer; }}"
+ " outer: for (int t = 0; t < 10; t++) {"
+ " for (int u = 0; u < 10; u++) {"
+ " break outer; }}"
+ " outer: for (int v = 0; v < 10; v++) {"
+ " for (int w = 0; w < 10; w++) {"
+ " break outer;"
+ "}}}}", "Test", "Test.m");
assertTranslation(translation, "break_outer:");
assertTranslation(translation, "goto break_outer;");
assertTranslation(translation, "break_outer_2:");
assertTranslation(translation, "goto break_outer_2;");
assertTranslation(translation, "break_outer_3:");
assertTranslation(translation, "goto break_outer_3;");
}
public void testExtraDimensionsInFieldDeclaration() throws IOException {
String translation = translateSourceFile(
"class Test { int i1, i2[], i3[][], i4[][][], i5[][], i6; }", "Test", "Test.h");
if (options.isJDT()) {
assertTranslatedLines(translation,
"jint i1_, i6_;",
"IOSIntArray *i2_;",
"IOSObjectArray *i3_, *i5_;",
"IOSObjectArray *i4_;");
} else {
assertTranslatedLines(translation,
"jint i1_;",
"IOSIntArray *i2_;",
"IOSObjectArray *i3_;",
"IOSObjectArray *i4_;",
"IOSObjectArray *i5_;",
"jint i6_;");
}
}
public void testExtraDimensionsInVariableDeclarationStatement() throws IOException {
String translation = translateSourceFile(
"class Test { void test() { char c1[][], c2[], c3, c4, c5[][]; } }", "Test", "Test.m");
if (options.isJDT()) {
assertTranslatedLines(translation,
"IOSObjectArray *c1, *c5;",
"IOSCharArray *c2;",
"jchar c3, c4;");
} else {
assertTranslatedLines(translation,
"IOSObjectArray *c1;",
"IOSCharArray *c2;",
"jchar c3;",
"jchar c4;",
"IOSObjectArray *c5;");
}
}
// Objective-C requires that && tests be surrounded by parens when mixed with || tests.
public void testLogicalPrecedence() throws IOException {
String translation = translateSourceFile(
"class Test { "
+ "boolean test1(boolean a, boolean b) {"
+ " return a && b; }"
+ "boolean test2(boolean c, boolean d) {"
+ " return c || d; }"
+ "boolean test3(boolean e, boolean f, boolean g, boolean h, boolean i) { "
+ " return e && f || g && h || i; }"
+ "boolean test4(boolean j, boolean k, boolean l, boolean m, boolean n) {"
+ " return j || k || l && m && n; }}",
"Test", "Test.m");
assertTranslation(translation, "return a && b;");
assertTranslation(translation, "return c || d;");
assertTranslatedLines(translation, "return (e && f) || (g && h) || i;");
assertTranslatedLines(translation, "return j || k || (l && m && n);");
translation = translateSourceFile(
"class Test { int i; @Override public boolean equals(Object object) { "
+ "return (object == this) || (object instanceof Test) && (i == ((Test) object).i); } }",
"Test", "Test.m");
assertTranslatedLines(translation, "(object == self) || "
+ "(([object isKindOfClass:[Test class]]) && (i_ == ((Test *) nil_chk(((Test *) "
+ "cast_chk(object, [Test class]))))->i_));");
}
// Objective-C requires that bit-wise and tests be surrounded by parens when mixed with or tests.
public void testBitPrecedence() throws IOException {
String translation = translateSourceFile(
"class Test { "
+ "int test1(int a, int b) {"
+ " return a & b; }"
+ "int test2(int c, int d) {"
+ " return c | d; }"
+ "int test3(int e, int f, int g, int h, int i) { "
+ " return e & f | g & h | i; }"
+ "int test4(int j, int k, int l, int m, int n) {"
+ " return j | k | l & m & n; }}",
"Test", "Test.m");
assertTranslation(translation, "return a & b;");
assertTranslation(translation, "return c | d;");
assertTranslatedLines(translation, "return (e & f) | (g & h) | i;");
assertTranslatedLines(translation, "return j | k | (l & m & n);");
}
// C compiler requires that tests using & or | as boolean test have parentheses around
// infix operands.
public void testLowerPrecedence() throws IOException {
String translation = translateSourceFile(
"class Test { "
+ "boolean test1(int o, int p, int q) {"
+ " return o < 0 | (o == 0 & p > q); } "
+ "boolean test2(int r) {"
+ " return r < 0 & !isPowerOfTwo(r); } "
+ "boolean isPowerOfTwo(int i) { return false; }}",
"Test", "Test.m");
assertTranslatedLines(translation, "return (o < 0) | ((o == 0) & (p > q));");
assertTranslatedLines(translation, "return (r < 0) & ![self isPowerOfTwoWithInt:r];");
}
public void testInitializeRenamed() throws IOException {
String translation = translateSourceFile(
"class Test { "
+ " public static void initialize() {}}",
"Test", "Test.m");
assertTranslation(translation, "+ (void)initialize__ {");
}
}