/* * 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.Options.MemoryManagementOption; import java.io.IOException; /** * Tests for {@link Functionizer}. * * @author Tom Ball */ public class FunctionizerTest extends GenerationTest { public void testPrivateInstanceMethodNoArgs() throws IOException { String translation = translateSourceFile( "class A { String test(String msg) { return str(); } " + " private String str() { return toString(); }}", "A", "A.h"); String functionHeader = "NSString *A_str(A *self)"; assertNotInTranslation(translation, functionHeader); translation = getTranslatedFile("A.m"); assertTranslation(translation, "static " + functionHeader + ";"); assertTranslation(translation, functionHeader + " {"); assertTranslation(translation, "return A_str(self);"); assertTranslation(translation, "return [self description];"); } // Verify one function calls another with the instance parameter. public void testPrivateToPrivate() throws IOException { String translation = translateSourceFile( "class A { private String test(String msg) { return str(); } " + " private String str() { return toString(); }}", "A", "A.m"); assertTranslation(translation, "return A_str(self);"); assertTranslation(translation, "return [self description];"); } public void testPrivateInstanceMethod() throws IOException { String translation = translateSourceFile( "class A { String test(String msg) { return str(msg, getClass()); } " + " private String str(String msg, Class<?> cls) { return msg + cls; }}", "A", "A.h"); String functionHeader = "NSString *A_strWithNSString_withIOSClass_(A *self, NSString *msg, IOSClass *cls)"; assertNotInTranslation(translation, functionHeader); translation = getTranslatedFile("A.m"); assertTranslation(translation, "static " + functionHeader + ";"); assertTranslatedLines(translation, functionHeader + " {", "return JreStrcat(\"$@\", msg, cls);"); assertTranslation(translation, "return A_strWithNSString_withIOSClass_(self, msg, [self java_getClass]);"); } // Verify non-private instance method is generated normally. public void testNonPrivateInstanceMethod() throws IOException { String translation = translateSourceFile( "class A { String test(String msg) { return str(msg, getClass()); } " + " String str(String msg, Class<?> cls) { return msg + cls; }}", "A", "A.m"); assertTranslatedLines(translation, "- (NSString *)strWithNSString:(NSString *)msg", "withIOSClass:(IOSClass *)cls {"); assertTranslation(translation, "return [self strWithNSString:msg withIOSClass:[self java_getClass]];"); } // Verify instance field access in function. public void testFieldAccessInFunction() throws IOException { String translation = translateSourceFile( "class A { String hello = \"hello\";" + " String test() { return str(); } " + " private String str() { return hello; }}", "A", "A.m"); assertTranslatedLines(translation, "- (NSString *)test {", "return A_str(self);"); assertTranslatedLines(translation, "NSString *A_str(A *self) {", "return self->hello_;"); } // Verify super field access in function. public void testSuperFieldAccessInFunction() throws IOException { String translation = translateSourceFile( "class A { String hello = \"hello\";" + " static class B extends A { void use() { str(); }" + " private String str() { super.hello = \"hi\"; return super.hello; }}}", "A", "A.m"); assertTranslatedLines(translation, "NSString *A_B_str(A_B *self) {", "JreStrongAssign(&self->hello_, @\"hi\");", "return self->hello_;"); } // Verify there isn't any super method invocations in functions. public void testSuperMethodInvocationInFunction() throws IOException { String translation = translateSourceFile( "class A { " + " private String hello() { return \"hello\"; } " + " public String shout() { return \"HELLO\"; } " + " void use() { hello(); } " + " static class B extends A { " + " private String test1() { return super.hello(); } " + " private String test2() { return super.shout(); }" + " void use() { test1(); test2(); }}}", "A", "A.m"); assertTranslatedLines(translation, "- (NSString *)test1 {", "return [super hello];"); assertTranslatedLines(translation, "- (NSString *)test2 {", "return [super shout];"); } // Verify functions can call other functions, correctly passing the instance variable. // Also tests that overloaded functions work. public void testFunctionCallingFunction() throws IOException { String translation = translateSourceFile( "class A { String hello = \"hello\";" + " String test() { return str(0); } " + " private String str(int i) { return str(); }" + " private String str() { return hello; } }", "A", "A.m"); translation = getTranslatedFile("A.m"); assertTranslatedLines(translation, "- (NSString *)test {", "return A_strWithInt_(self, 0);"); assertTranslatedLines(translation, "NSString *A_strWithInt_(A *self, jint i) {", "return A_str(self);"); assertTranslatedLines(translation, "NSString *A_str(A *self) {", "return self->hello_;"); } // Verify this expressions are changed to self parameters in functions. public void testThisParameters() throws IOException { String translation = translateSourceFile( "class A { private void test(java.util.List list) { list.add(this); }}", "A", "A.m"); assertTranslatedLines(translation, "[((id<JavaUtilList>) nil_chk(list)) addWithId:self];"); } // Verify that a call to a private method in an outer class is converted correctly. public void testOuterCall() throws IOException { String translation = translateSourceFile( "class A { int outerN = str(); private int str() { return 0; }" + " class B { " + " private int test1() { return str(); } " + " private int test2() { return A.this.str(); }" + " private int test3() { return A.this.outerN; }}}", "A", "A.m"); assertTranslatedLines(translation, "int A_str(A *self) {", "return 0;"); assertTranslatedLines(translation, "- (jint)test1 {", "return A_str(this$0_);"); assertTranslatedLines(translation, "- (jint)test2 {", "return A_str(this$0_);"); assertTranslatedLines(translation, "- (jint)test3 {", "return this$0_->outerN_;"); } // Verify that a call to a private method in an outer class is converted correctly. public void testInnerOuterCall() throws IOException { String translation = translateSourceFile( "class A { private int str() { return 0; }" + " class B { " + " private int test() { return str(); }}" + " class C { " + " private void test(B b) { b.test(); }}}", "A", "A.m"); assertTranslatedLines(translation, "- (void)testWithA_B:(A_B *)b {", "A_B_test(nil_chk(b));"); } // Verify annotation parameters are ignored. public void testAnnotationParameters() throws IOException { String translation = translateSourceFile( "import java.lang.annotation.*; @Target({ElementType.METHOD}) public @interface Test {}", "Test", "Test.m"); assertNotInTranslation(translation, "self"); } // Verify function declaration is in .m file, not the header. public void testPrivateStaticMethod() throws IOException { String translation = translateSourceFile( "class A { String test(String msg) { return str(msg, getClass()); } " + " private static String str(String msg, Class<?> cls) { return msg + cls; }}", "A", "A.h"); String functionHeader = "NSString *A_strWithNSString_withIOSClass_(NSString *msg, IOSClass *cls)"; assertNotInTranslation(translation, functionHeader + ';'); translation = getTranslatedFile("A.m"); // Check new function. assertTranslatedLines(translation, functionHeader + " {", "A_initialize();", "return JreStrcat(\"$@\", msg, cls);"); // Check wrapper. assertTranslatedLines(translation, "+ (NSString *)strWithNSString:(NSString *)msg", "withIOSClass:(IOSClass *)cls {", "return A_strWithNSString_withIOSClass_(msg, cls);"); // Check invocation. assertTranslatedLines(translation, "- (NSString *)testWithNSString:(NSString *)msg {", "return A_strWithNSString_withIOSClass_(msg, [self java_getClass]);"); } // Verify function declaration is in the header. public void testStaticMethod() throws IOException { String translation = translateSourceFile( "class A { String test(String msg) { return str(msg, getClass()); } " + " static String str(String msg, Class<?> cls) { return msg + cls; }}", "A", "A.h"); String functionHeader = "NSString *A_strWithNSString_withIOSClass_(NSString *msg, IOSClass *cls)"; assertTranslation(translation, functionHeader + ';'); translation = getTranslatedFile("A.m"); // Check new function. assertTranslatedLines(translation, functionHeader + " {", "A_initialize();", "return JreStrcat(\"$@\", msg, cls);"); // Check wrapper. assertTranslatedLines(translation, "+ (NSString *)strWithNSString:(NSString *)msg", "withIOSClass:(IOSClass *)cls {", "return A_strWithNSString_withIOSClass_(msg, cls);"); // Check invocation. assertTranslatedLines(translation, "- (NSString *)testWithNSString:(NSString *)msg {", "return A_strWithNSString_withIOSClass_(msg, [self java_getClass]);"); } public void testFunctionParameter() throws IOException { String translation = translateSourceFile( "class A { private String test(String msg) { return echo(str(msg)); } " + " private String echo(String msg) { return msg; } " + " private String str(String msg) { return msg; }}", "A", "A.m"); assertTranslatedLines(translation, "A_echoWithNSString_(self, A_strWithNSString_(self, msg))"); } public void testStaticVarargsMethod() throws IOException { String translation = translateSourceFile( "class A { String test(String msg) { return strchars('a', 'b', 'c'); } " + " private static String strchars(char... args) { return String.valueOf(args); }}", "A", "A.h"); String functionHeader = "NSString *A_strcharsWithCharArray_(IOSCharArray *args)"; assertNotInTranslation(translation, functionHeader + ';'); translation = getTranslatedFile("A.m"); assertTranslation(translation, functionHeader + " {"); assertTranslation(translation, "return A_strcharsWithCharArray_(" + "[IOSCharArray arrayWithChars:(jchar[]){ 'a', 'b', 'c' } count:3]);"); } public void testSynchronizedFunction() throws IOException { String translation = translateSourceFile( "class A { void test() { str(); } " + "private synchronized String str() { return toString(); }}", "A", "A.m"); assertTranslation(translation, "@synchronized(self)"); assertOccurrences(translation, "@synchronized", 1); translation = translateSourceFile( "class A { void test() { str(); } " + " private String str() { synchronized(this) { return toString(); }}}", "A", "A.m"); assertTranslation(translation, "@synchronized(self)"); translation = translateSourceFile( "class A { void test() { str(); } " + " private static synchronized String str() { return \"abc\"; }}", "A", "A.m"); assertTranslation(translation, "@synchronized(A_class_())"); assertOccurrences(translation, "@synchronized", 1); translation = translateSourceFile( "class A { void test() { str(); } " + " private String str() { synchronized(this.getClass()) { return \"abc\"; }}}", "A", "A.m"); assertTranslation(translation, "@synchronized([self java_getClass])"); translation = translateSourceFile( "class A { void test() { str(); } " + " private static String str() { synchronized(A.class) { return \"abc\"; }}}", "A", "A.m"); assertTranslation(translation, "@synchronized(A_class_())"); } public void testSetter() throws IOException { String translation = translateSourceFile( "class A { Object o; private void setO(Object o) { this.o = o; }}", "A", "A.m"); assertTranslation(translation, "JreStrongAssign(&self->o_, o)"); } public void testClassInitializerCalledFromFunction() throws IOException { String translation = translateSourceFile( "class A { static Object o = new Object(); " + " private static Object foo() { return o; }" + " void test() { A.foo(); }" + " private void test2() {}" + " void use() { test2(); }}", "A", "A.m"); // Verify static class function calls class init. assertTranslatedLines(translation, "id A_foo() {", "A_initialize();", "return A_o;", "}"); // Verify class method doesn't call class init. assertTranslatedLines(translation, "- (void)test {", "A_foo();", "}"); // Verify non-static class function doesn't call class init. assertTranslatedLines(translation, "void A_test2(A *self) {", "}"); } public void testClassInitializerCalledFromEnumFunctions() throws IOException { String translation = translateSourceFile( "enum A { A, B; static Object o = new Object(); " + " private static Object foo() { return o; }" + " void test() { A.foo(); }" + " private void test2() {}" + " void use() { test2(); }}", "A", "A.m"); // Verify valueOf function calls class init. assertTranslatedLines(translation, "A *A_valueOfWithNSString_(NSString *name) {", "A_initialize();", "for (int i = 0; i < 2; i++) {"); // Verify static class function calls class init. assertTranslatedLines(translation, "id A_foo() {", "A_initialize();", "return A_o;", "}"); // Verify class method doesn't call class init. assertTranslatedLines(translation, "- (void)test {", "A_foo();", "}"); // Verify non-static class function doesn't call class init. assertTranslatedLines(translation, "void A_test2(A *self) {", "}"); } public void testPrivateNativeMethod() throws IOException { String translation = translateSourceFile( "class A { Object o; void use() { setO(null); } " + " private native void setO(Object o) /*-[ self->o_ = o; ]-*/; }", "A", "A.m"); assertTranslation(translation, "static void A_setOWithId_(A *self, id o);"); assertTranslatedLines(translation, "void A_setOWithId_(A *self, id o) {", "self->o_ = o;", "}"); assertTranslatedLines(translation, "- (void)setOWithId:(id)o {", "A_setOWithId_(self, o);", "}"); } public void testGenericMethod() throws IOException { String translation = translateSourceFile( "class Test { private static <T> void foo(T t) {} static void bar() { foo(\"test\"); } }", "Test", "Test.m"); assertTranslation(translation, "Test_fooWithId_(@\"test\");"); } public void testProtectedMethodInPrivateClass() throws IOException { String translation = translateSourceFile( "class Test { private static class A { protected void foo() {} void bar() { foo(); } } " + "private static class B extends A { protected void foo() {} } }", "Test", "Test.m"); assertNotInTranslation(translation, "Test_A_foo"); } public void testPrivateMethodCalledFromAnonymousEnum() throws IOException { String translation = translateSourceFile( "enum Test { A { void bar() { foo(); } }; private static void foo() {} }", "Test", "Test.m"); assertTranslatedLines(translation, "- (void)bar {", "Test_foo();"); assertTranslation(translation, "static void Test_foo();"); assertTranslation(translation, "void Test_foo() {"); } public void testNativeMethodsWithoutOcni() throws IOException { String translation = translateSourceFile( "class Test { public native void foo(); public native static void bar(); }", "Test", "Test.h"); // Public declaration for "foo" instance method, within "NativeMethods" category. assertTranslation(translation, "- (void)foo;"); // Public declaration for "bar". both the class method and c-function. assertTranslation(translation, "+ (void)bar;"); assertTranslation(translation, "FOUNDATION_EXPORT void Test_bar();"); translation = getTranslatedFile("Test.m"); // Implementation for "foo" is functionized. assertTranslation(translation, "void Test_foo(Test *self);"); assertTranslatedLines(translation, "- (void)foo {", "Test_foo(self);", "}"); // class method wrapper for "bar". assertTranslatedLines(translation, "+ (void)bar {", "Test_bar();", "}"); // JNI external function declarations assertTranslation(translation, "JNIEXPORT void Java_Test_foo(JNIEnv *_env_, jobject self);"); assertTranslation(translation, "JNIEXPORT void Java_Test_bar(JNIEnv *_env_, jclass _cls_);"); // JNI wrapper functions assertTranslatedLines(translation, "void Test_foo(Test *self) {", "Java_Test_foo(&J2ObjC_JNIEnv, self);", "}"); assertTranslatedLines(translation, "void Test_bar() {", "Java_Test_bar(&J2ObjC_JNIEnv, Test_class_());", "}"); } public void testOverloadedNativeMethodsWithoutOcni() throws IOException { String translation = translateSourceFile( "class Test { public native void foo(int i); public native static void foo(String s); }", "Test", "Test.h"); // Public declaration for "foo" instance method, within "NativeMethods" category. assertTranslation(translation, "- (void)fooWithInt:(jint)i;"); // Public declaration for "bar". both the class method and c-function. assertTranslation(translation, "+ (void)fooWithNSString:(NSString *)s;"); assertTranslation(translation, "FOUNDATION_EXPORT void Test_fooWithNSString_(NSString *s);"); translation = getTranslatedFile("Test.m"); // Implementation for "foo" is functionized. assertTranslation(translation, "void Test_fooWithInt_(Test *self, jint i);"); assertTranslatedLines(translation, "- (void)fooWithInt:(jint)i {", "Test_fooWithInt_(self, i);", "}"); // class method wrapper for "bar". assertTranslatedLines(translation, "+ (void)fooWithNSString:(NSString *)s {", "Test_fooWithNSString_(s);", "}"); // JNI external function declarations assertTranslation(translation, "JNIEXPORT void Java_Test_foo__I(JNIEnv *_env_, jobject self, jint i);"); assertTranslation(translation, "JNIEXPORT void Java_Test_foo__Ljava_lang_String_2(" + "JNIEnv *_env_, jclass _cls_, jstring s);"); // JNI wrapper functions assertTranslatedLines(translation, "void Test_fooWithInt_(Test *self, jint i) {", "Java_Test_foo__I(&J2ObjC_JNIEnv, self, i);", "}"); assertTranslatedLines(translation, "void Test_fooWithNSString_(NSString *s) {", "Java_Test_foo__Ljava_lang_String_2(&J2ObjC_JNIEnv, Test_class_(), s);", "}"); } // Verify that static methods called via a super invokation are correctly // functionized. public void testStaticSuperInvocation() throws IOException { String translation = translateSourceFile( "public class A { static class Base { static void test() {} } " + "static class Foo extends Base { void test2() { super.test(); } }}", "A", "A.m"); assertTranslatedLines(translation, "- (void)test2 {", " A_Base_test();", "}"); } public void testSuperInvocationFromConstructor() throws IOException { String translation = translateSourceFile( "class Test { Test() { super.toString(); } }", "Test", "Test.m"); assertTranslation(translation, "Test_super$_description(self, @selector(description));"); } public String innerTestFunctionizedConstructors() throws IOException { String translation = translateSourceFile( "class Test { int i; " + "Test() { this(0); } " + "private Test(int i) { this.i = i; } }", "Test", "Test.h"); // Functionized constructor. assertTranslation(translation, "FOUNDATION_EXPORT void Test_init(Test *self);"); // Retaining allocating constructor. assertTranslation(translation, "FOUNDATION_EXPORT Test *new_Test_init() NS_RETURNS_RETAINED;"); // Releasing allocating constructor. assertTranslation(translation, "FOUNDATION_EXPORT Test *create_Test_init();"); translation = getTranslatedFile("Test.m"); // Declarations for the private constructor. assertTranslation(translation, "__attribute__((unused)) static void Test_initWithInt_(Test *self, jint i);"); assertTranslation(translation, "__attribute__((unused)) static Test *new_Test_initWithInt_(jint i) NS_RETURNS_RETAINED;"); assertTranslation(translation, "__attribute__((unused)) static Test *create_Test_initWithInt_(jint i);"); // Implementations. assertTranslatedLines(translation, "void Test_init(Test *self) {", " Test_initWithInt_(self, 0);", "}"); assertTranslatedLines(translation, "Test *new_Test_init() {", " J2OBJC_NEW_IMPL(Test, init)", "}"); assertTranslatedLines(translation, "void Test_initWithInt_(Test *self, jint i) {", " NSObject_init(self);", " self->i_ = i;", "}"); assertTranslatedLines(translation, "Test *new_Test_initWithInt_(jint i) {", " J2OBJC_NEW_IMPL(Test, initWithInt_, i)", "}"); assertTranslatedLines(translation, "Test *create_Test_init() {", " J2OBJC_CREATE_IMPL(Test, init)", "}"); assertTranslatedLines(translation, "Test *create_Test_initWithInt_(jint i) {", " J2OBJC_CREATE_IMPL(Test, initWithInt_, i)", "}"); return translation; } public void testFunctionizedConstructors() throws IOException { innerTestFunctionizedConstructors(); } public void testFunctionizedConstructorsARC() throws IOException { options.setMemoryManagementOption(MemoryManagementOption.ARC); innerTestFunctionizedConstructors(); } public void testNoAllocatingConstructorsForAbstractClass() throws IOException { String translation = translateSourceFile("abstract class Test {}", "Test", "Test.h"); assertTranslation(translation, "FOUNDATION_EXPORT void Test_init(Test *self);"); assertNotInTranslation(translation, "new_Test_init"); assertNotInTranslation(translation, "create_Test_init"); translation = getTranslatedFile("Test.m"); assertNotInTranslation(translation, "new_Test_init"); assertNotInTranslation(translation, "create_Test_init"); } }