/* * 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.util.HeaderMap; import java.io.File; import java.io.IOException; /** * Tests for {@link ObjectiveCHeaderGenerator}. * * @author Tom Ball */ public class ObjectiveCHeaderGeneratorTest extends GenerationTest { public void testInnerEnumWithPackage() throws IOException { String translation = translateSourceFile( "package mypackage;" + "public class Example { MyClass myclass = new MyClass(); }" + "enum Abcd { A, B, C; }" + "class MyClass {}", "Example", "mypackage/Example.h"); assertTranslation(translation, "@interface MypackageExample"); // enum declaration assertTranslation(translation, "typedef NS_ENUM(NSUInteger, MypackageAbcd_Enum) {"); assertTranslation(translation, "@interface MypackageAbcd"); assertTranslation(translation, "@interface MypackageMyClass"); assertTranslation(translation, "MypackageMyClass *myclass_;"); } public void testTypeNameTranslation() throws IOException { String translation = translateSourceFile( "public class Example {}", "Example", "Example.h"); assertTranslation(translation, "@interface Example "); } public void testUnicodeHeaderGuardTranslation() throws IOException { options.setSegmentedHeaders(false); // Non-letters should be replaced String translation = translateSourceFile( "public class ¢ents {}", "¢ents", "¢ents.h"); assertTranslation(translation, "#ifndef _ents_H"); // Unicode letters outside the Basic Latin range should not be replaced translation = translateSourceFile( "public class こんにちは {}", "こんにちは", "こんにちは.h"); assertTranslation(translation, "#ifndef こんにちは_H"); // Egyptian heiroglyph letters outside UCS-2, requiring two UTF-16 chars translation = translateSourceFile( "public class egyptian\uD80C\uDC00 {}", "egyptian\uD80C\uDC00", "egyptian\uD80C\uDC00.h"); assertTranslation(translation, "#ifndef Egyptian\uD80C\uDC00_H"); } public void testDeprecatedTypeNameTranslation() throws IOException { options.enableDeprecatedDeclarations(); String translation = translateSourceFile( "public @Deprecated class Example {}", "Example", "Example.h"); assertTranslation(translation, "__attribute__((deprecated))\n@interface Example "); } public void testDeprecatedTypeNameTranslationIsTurnedOff() throws IOException { String translation = translateSourceFile( "public @Deprecated class Example {}", "Example", "Example.h"); assertFalse(translation.contains("__attribute__((deprecated))")); } public void testFullyQualifiedDeprecatedTypeNameTranslation() throws IOException { options.enableDeprecatedDeclarations(); String translation = translateSourceFile( "public @java.lang.Deprecated class Example {}", "Example", "Example.h"); assertTranslation(translation, "__attribute__((deprecated))\n@interface Example "); } public void testPackageTypeNameTranslation() throws IOException { String translation = translateSourceFile( "package unit.test; public class Example {}", "Example", "unit/test/Example.h"); assertTranslation(translation, "@interface UnitTestExample "); } public void testPackageTypeNameTranslationWithInnerClass() throws IOException { String translation = translateSourceFile( "package unit.test; public class Example { class Inner {}}", "Example", "unit/test/Example.h"); assertTranslation(translation, "@interface UnitTestExample "); assertTranslation(translation, "Example_Inner"); assertTranslation(translation, "@interface UnitTestExample_Inner "); } public void testSuperclassTypeTranslation() throws IOException { String translation = translateSourceFile( "public class MyException extends Exception {}", "MyException", "MyException.h"); assertTranslation(translation, "@interface MyException : JavaLangException"); } public void testImplementsTypeTranslation() throws IOException { String translation = translateSourceFile( "import java.io.Serializable; public class Example implements Serializable {}", "Example", "Example.h"); assertTranslation(translation, "@interface Example : NSObject < JavaIoSerializable >"); } public void testImportTranslation() throws IOException { String translation = translateSourceFile( "public class MyException extends Exception { MyException(RuntimeException t) {super(t);}}", "MyException", "MyException.h"); assertTranslation(translation, "@class JavaLangRuntimeException;"); assertTranslation(translation, "#include \"java/lang/Exception.h\""); } public void testHeaderFileMapping() throws IOException { options.getHeaderMap().setMappingFiles("testMappings.j2objc"); addSourceFile("package unit.mapping.custom; public class Test { }", "unit/mapping/custom/Test.java"); loadHeaderMappings(); String translation = translateSourceFile( "import unit.mapping.custom.Test; " + "public class MyTest extends Test { MyTest() {}}", "MyTest", "MyTest.h"); assertTranslation(translation, "#include \"my/mapping/custom/Test.h\""); } public void testHeaderDefaultFileMapping() throws IOException { addSourceFile("package unit.mapping; public class Test { }", "unit/mapping/Test.java"); loadHeaderMappings(); String translation = translateSourceFile( "import unit.mapping.Test; " + "public class MyTest extends Test { MyTest() {}}", "MyTest", "MyTest.h"); assertTranslation(translation, "#include \"my/mapping/Test.h\""); } public void testNoHeaderMapping() throws IOException { // Should be able to turn off header mappings by passing empty list. options.getHeaderMap().setMappingFiles(""); addSourceFile("package unit.mapping; public class Test { }", "unit/mapping/Test.java"); loadHeaderMappings(); String translation = translateSourceFile( "import unit.mapping.Test; " + "public class MyTest extends Test { MyTest() {}}", "MyTest", "MyTest.h"); assertTranslation(translation, "#include \"unit/mapping/Test.h\""); } public void testOutputHeaderFileMapping() throws IOException { options.getHeaderMap().setMappingFiles("testMappings.j2objc"); options.getHeaderMap().setOutputStyle(HeaderMap.OutputStyleOption.SOURCE); addSourceFile("package unit.test; public class Dummy {}", "unit/test/Dummy.java"); addSourceFile( "package unit.test;" + "public class AnotherDummy extends Dummy { " + " public AnotherDummy() {}" + "}", "unit/test/AnotherDummy.java"); preprocessFiles("unit/test/Dummy.java", "unit/test/AnotherDummy.java"); loadHeaderMappings(); String translation = translateSourceFile(getTranslatedFile("unit/test/AnotherDummy.java"), "AnotherDummy", "AnotherDummy.h"); assertTranslation(translation, "#include \"unit/test/Dummy.h\""); HeaderMap headerMap = options.getHeaderMap(); assertEquals("unit/test/Dummy.h", headerMap.getMapped("unit.test.Dummy")); assertEquals("unit/test/AnotherDummy.h", headerMap.getMapped("unit.test.AnotherDummy")); assertEquals("my/mapping/custom/Test.h", headerMap.getMapped("unit.mapping.custom.Test")); assertEquals("my/mapping/custom/Test.h", headerMap.getMapped("unit.mapping.custom.AnotherTest")); } public void testOutputHeaderFileMappingWithMultipleClassesInOneHeader() throws IOException { options.getHeaderMap().setMappingFiles("testMappings.j2objc"); options.getHeaderMap().setOutputStyle(HeaderMap.OutputStyleOption.SOURCE); addSourceFile("package unit.mapping.custom; public class Test { }", "unit/mapping/custom/Test.java"); addSourceFile("package unit.mapping.custom; public class AnotherTest { }", "unit/mapping/custom/AnotherTest.java"); addSourceFile( "package unit.test;" + "import unit.mapping.custom.Test;" + "public class Dummy extends Test { " + " public Dummy() {}" + "}", "unit/test/Dummy.java"); addSourceFile( "package unit.test;" + "import unit.mapping.custom.AnotherTest;" + "public class AnotherDummy extends AnotherTest { " + " public AnotherDummy() {}" + "}", "unit/test/AnotherDummy.java"); preprocessFiles("unit/test/Dummy.java", "unit/test/AnotherDummy.java"); loadHeaderMappings(); String translationForDummy = translateSourceFile(getTranslatedFile("unit/test/Dummy.java"), "Dummy", "Dummy.h"); String translationForAnotherDummy = translateSourceFile( getTranslatedFile("unit/test/AnotherDummy.java"), "AnotherDummy", "AnotherDummy.h"); assertTranslation(translationForDummy, "#include \"my/mapping/custom/Test.h\""); assertTranslation(translationForAnotherDummy, "#include \"my/mapping/custom/Test.h\""); HeaderMap headerMap = options.getHeaderMap(); assertEquals("unit/test/Dummy.h", headerMap.getMapped("unit.test.Dummy")); assertEquals("unit/test/AnotherDummy.h", headerMap.getMapped("unit.test.AnotherDummy")); assertEquals("my/mapping/custom/Test.h", headerMap.getMapped("unit.mapping.custom.Test")); assertEquals("my/mapping/custom/Test.h", headerMap.getMapped("unit.mapping.custom.AnotherTest")); } public void testCombinedGeneration() throws IOException { options.setSegmentedHeaders(false); addSourceFile("package unit; public class Test {" + " public void Dummy() {}" + "}", "unit/Test.java"); addSourceFile("package unit; public class AnotherTest extends Test {" + " public void AnotherDummy() {}" + "}", "unit/AnotherTest.java"); String header = translateCombinedFiles( "unit/Foo", ".h", "unit/Test.java", "unit/AnotherTest.java"); assertTranslation(header, "#ifndef UnitFoo_H"); assertTranslation(header, "#define UnitFoo_H"); assertTranslation(header, "@interface UnitTest"); assertTranslation(header, "- (instancetype)init;"); assertTranslation(header, "- (void)Dummy;"); assertTranslation(header, "J2OBJC_EMPTY_STATIC_INIT(UnitTest)"); assertTranslation(header, "J2OBJC_TYPE_LITERAL_HEADER(UnitTest)"); assertTranslation(header, "@interface UnitAnotherTest : UnitTest"); assertTranslation(header, "- (void)AnotherDummy;"); assertTranslation(header, "J2OBJC_EMPTY_STATIC_INIT(UnitAnotherTest)"); assertTranslation(header, "J2OBJC_TYPE_LITERAL_HEADER(UnitAnotherTest)"); assertNotInTranslation(header, "@class UnitTest"); assertNotInTranslation(header, "@class UnitAnotherTest"); } public void testCombinedGenerationOrdering() throws IOException { addSourceFile("package unit; public class Test {" + " public void Dummy() {}" + "}", "unit/Test.java"); // Test that necessary forward declarations aren't eliminated. addSourceFile("package unit; public class TestDependent {" + " public Test Dummy() {" + " return null;" + " }" + " public AnotherTest AnotherDummy() {" + " return null;" + " }" + "}", "unit/TestDependent.java"); addSourceFile("package unit; public class AnotherTest extends Test {" + " public void AnotherDummy() {}" + "}", "unit/AnotherTest.java"); String header = translateCombinedFiles( "unit/Foo", ".h", "unit/TestDependent.java", "unit/AnotherTest.java", "unit/Test.java"); assert header.indexOf("@interface UnitTest") < header.indexOf("@interface UnitAnotherTest"); } public void testCombinedJarHeaderMapping() throws IOException { File outputHeaderMappingFile = new File(tempDir, "mappings.j2objc"); options.getHeaderMap().setOutputMappingFile(outputHeaderMappingFile); options.getHeaderMap().setOutputStyle(HeaderMap.OutputStyleOption.SOURCE); addSourceFile("package unit; public class Test { }", "unit/Test.java"); addSourceFile("package unit; public class AnotherTest extends Test { }", "unit/AnotherTest.java"); addSourceFile("package unit2;" + "import unit.Test;" + "public class AnotherTest extends Test { }", "unit2/AnotherTest.java"); addSourceFile("package unit2;" + "import unit.AnotherTest;" + "public class YetAnotherTest extends AnotherTest { }", "unit2/YetAnotherTest.java"); translateCombinedFiles("unit/Foo", ".h", "unit/Test.java", "unit/AnotherTest.java"); String header2 = translateCombinedFiles( "unit2/Foo", ".h", "unit2/AnotherTest.java", "unit2/YetAnotherTest.java"); HeaderMap headerMap = options.getHeaderMap(); assertEquals("unit/Foo.h", headerMap.getMapped("unit.Test")); assertEquals("unit/Foo.h", headerMap.getMapped("unit.AnotherTest")); assertTranslation(header2, "#include \"unit/Foo.h\""); assertEquals("unit2/Foo.h", headerMap.getMapped("unit2.AnotherTest")); assertEquals("unit2/Foo.h", headerMap.getMapped("unit2.YetAnotherTest")); } public void testForwardDeclarationTranslation() throws IOException { String translation = translateSourceFile( "public class MyException extends Exception { MyException(RuntimeException t) {super(t);}}", "MyException", "MyException.h"); assertTranslation(translation, "@class JavaLangRuntimeException;"); } public void testInstanceVariableTranslation() throws IOException { String translation = translateSourceFile( "public class Example { Exception testException; }", "Example", "Example.h"); assertTranslation(translation, "JavaLangException *testException_;"); } public void testInterfaceTranslation() throws IOException { String translation = translateSourceFile( "package unit.test; public interface Example {}", "Example", "unit/test/Example.h"); assertTranslation(translation, "@protocol UnitTestExample"); } public void testDeprecatedInterfaceTranslation() throws IOException { options.enableDeprecatedDeclarations(); String translation = translateSourceFile( "package unit.test; public @Deprecated interface Example {}", "Example", "unit/test/Example.h"); assertTranslation(translation, "__attribute__((deprecated))\n@protocol UnitTestExample"); } public void testInterfaceWithMethodTranslation() throws IOException { String translation = translateSourceFile( "package unit.test; public interface Example { Example getExample(); }", "Example", "unit/test/Example.h"); assertTranslation(translation, "(id<UnitTestExample>)getExample;"); } public void testInterfaceWithDeprecatedMethodTranslation() throws IOException { options.enableDeprecatedDeclarations(); String translation = translateSourceFile( "package unit.test; public interface Example { @Deprecated Example getExample(); }", "Example", "unit/test/Example.h"); assertTranslation(translation, "- (id<UnitTestExample>)getExample __attribute__((deprecated));"); } public void testSuperInterfaceTranslation() throws IOException { String translation = translateSourceFile( "package unit.test; public interface Example extends Bar {} interface Bar {}", "Example", "unit/test/Example.h"); assertTranslation(translation, "@protocol UnitTestExample < UnitTestBar, JavaObject >"); } public void testConstTranslation() throws IOException { String translation = translateSourceFile( "package unit.test; public class Example { public static final int FOO=1; }", "Example", "unit/test/Example.h"); assertTranslation(translation, "#define UnitTestExample_FOO 1"); assertFalse(translation.contains("initialize")); } public void testStaticVariableTranslation() throws IOException { String translation = translateSourceFile( "public class Example { public static java.util.Date today; }", "Example", "Example.h"); assertTranslatedLines(translation, "inline JavaUtilDate *Example_get_today();", "inline JavaUtilDate *Example_set_today(JavaUtilDate *value);", "/*! INTERNAL ONLY - Use accessor function from above. */", "FOUNDATION_EXPORT JavaUtilDate *Example_today;", "J2OBJC_STATIC_FIELD_OBJ(Example, today, JavaUtilDate *)"); assertFalse(translation.contains("initialize")); assertFalse(translation.contains("dealloc")); } public void testStaticVariableWithInitTranslation() throws IOException { String translation = translateSourceFile( "public class Example { public static java.util.Date today = new java.util.Date(); }", "Example", "Example.h"); assertTranslatedLines(translation, "inline JavaUtilDate *Example_get_today();", "inline JavaUtilDate *Example_set_today(JavaUtilDate *value);", "/*! INTERNAL ONLY - Use accessor function from above. */", "FOUNDATION_EXPORT JavaUtilDate *Example_today;", "J2OBJC_STATIC_FIELD_OBJ(Example, today, JavaUtilDate *)"); assertFalse(translation.contains("initialize;")); assertFalse(translation.contains("dealloc")); } public void testInitMessageTranslation() throws IOException { String translation = translateSourceFile( "public class Example { void init() {} void _init() {}}", "Example", "Example.h"); assertTranslation(translation, "- (void)init__ OBJC_METHOD_FAMILY_NONE;"); assertTranslation(translation, "- (void)_init OBJC_METHOD_FAMILY_NONE;"); } public void testInitializeMessageTranslation() throws IOException { String translation = translateSourceFile( "public class Example { void initialize() {} }", "Example", "Example.h"); assertTranslation(translation, "- (void)initialize__ OBJC_METHOD_FAMILY_NONE;"); } public void testToStringRenaming() throws IOException { String translation = translateSourceFile( "public class Example { public String toString() { return super.toString(); } }", "Example", "Example.h"); assertTranslation(translation, "- (NSString *)description;"); } public void testMultipleObjectDeclaration() throws IOException { String translation = translateSourceFile( "public class Example { String one, two, three; }", "Example", "Example.h"); if (options.isJDT()) { assertTranslation(translation, "NSString *one_, *two_, *three_;"); } else { assertTranslatedLines(translation, "NSString *one_;", "NSString *two_;", "NSString *three_;"); } } public void testMultiplePrimitiveDeclaration() throws IOException { String translation = translateSourceFile( "public class Example { int one, two, three; }", "Example", "Example.h"); if (options.isJDT()) { assertTranslation(translation, "int one_, two_, three_;"); } else { assertTranslatedLines(translation, "jint one_;", "jint two_;", "jint three_;"); } } public void testMultipleInterfaceDeclaration() throws IOException { String translation = translateSourceFile( "public class Example { Comparable one, two, three; }", "Example", "Example.h"); if (options.isJDT()) { assertTranslation(translation, "id<JavaLangComparable> one_, two_, three_;"); } else { assertTranslatedLines(translation, "id<JavaLangComparable> one_;", "id<JavaLangComparable> two_;", "id<JavaLangComparable> three_;"); } } public void testMultipleClassDeclaration() throws IOException { String translation = translateSourceFile( "public class Example { Class<?> one, two, three; }", "Example", "Example.h"); if (options.isJDT()) { assertTranslation(translation, "IOSClass *one_, *two_, *three_;"); } else { assertTranslatedLines(translation, "IOSClass *one_;", "IOSClass *two_;", "IOSClass *three_;"); } } public void testInnerClassDeclaration() throws IOException { String translation = translateSourceFile( "public class Example { class Inner {} }", "Example", "Example.h"); assertTranslation(translation, "@interface Example_Inner : NSObject"); assertNotInTranslation(translation, "Example *this"); assertTranslation(translation, "- (instancetype)initWithExample:(Example *)outer$;"); } public void testInnerClassDeclarationWithOuterReference() throws IOException { String translation = translateSourceFile( "public class Example { int i; class Inner { void test() { int j = i; } } }", "Example", "Example.h"); assertTranslation(translation, "@interface Example_Inner : NSObject"); assertTranslation(translation, "- (instancetype)initWithExample:(Example *)outer$;"); translation = getTranslatedFile("Example.m"); assertTranslation(translation, "Example *this$0_;"); } public void testEnum() throws IOException { String translation = translateSourceFile( "public enum Color { RED, WHITE, BLUE }", "Color", "Color.h"); assertTranslatedLines(translation, "typedef NS_ENUM(NSUInteger, Color_Enum) {", " Color_Enum_RED = 0,", " Color_Enum_WHITE = 1,", " Color_Enum_BLUE = 2,", "};"); assertTranslation(translation, "@interface Color : JavaLangEnum < NSCopying >"); assertTranslation(translation, "+ (IOSObjectArray *)values;"); assertTranslation(translation, "+ (Color *)valueOfWithNSString:(NSString *)name;"); assertTranslation(translation, "FOUNDATION_EXPORT Color *Color_values_[];"); assertTranslatedLines(translation, "inline Color *Color_get_RED();", "J2OBJC_ENUM_CONSTANT(Color, RED)"); assertTranslatedLines(translation, "inline Color *Color_get_WHITE();", "J2OBJC_ENUM_CONSTANT(Color, WHITE)"); assertTranslatedLines(translation, "inline Color *Color_get_BLUE();", "J2OBJC_ENUM_CONSTANT(Color, BLUE)"); } public void testEnumWithParameters() throws IOException { String translation = translateSourceFile( "public enum Color { RED(0xff0000), WHITE(0xffffff), BLUE(0x0000ff); " + "private int rgb; private Color(int rgb) { this.rgb = rgb; } " + "public int getRgb() { return rgb; }}", "Color", "Color.h"); assertTranslation(translation, "@interface Color : JavaLangEnum"); translation = getTranslatedFile("Color.m"); assertTranslation(translation, "int rgb_;"); assertTranslatedLines(translation, "void Color_initWithInt_withNSString_withInt_(" + "Color *self, jint rgb, NSString *__name, jint __ordinal) {"); } public void testEnumWithMultipleConstructors() throws IOException { String translation = translateSourceFile( "public enum Color { RED(0xff0000), WHITE(0xffffff, false), BLUE(0x0000ff); " + "private int rgb; private boolean primary;" + "private Color(int rgb, boolean primary) { this.rgb = rgb; this.primary = primary; } " + "private Color(int rgb) { this(rgb, true); } " + "public int getRgb() { return rgb; }" + "public boolean isPrimaryColor() { return primary; }}", "Color", "Color.h"); assertTranslation(translation, "@interface Color : JavaLangEnum"); translation = getTranslatedFile("Color.m"); assertTranslation(translation, "jboolean primary_;"); assertTranslation(translation, "Color_initWithInt_withBoolean_withNSString_withInt_(" + "self, rgb, true, __name, __ordinal);"); assertTranslatedLines(translation, "void Color_initWithInt_withBoolean_withNSString_withInt_(" + "Color *self, jint rgb, jboolean primary, NSString *__name, jint __ordinal) {", " JavaLangEnum_initWithNSString_withInt_(self, __name, __ordinal);", " self->rgb_ = rgb;", " self->primary_ = primary;", "}"); assertTranslatedLines(translation, "void Color_initWithInt_withNSString_withInt_(" + "Color *self, jint rgb, NSString *__name, jint __ordinal) {", " Color_initWithInt_withBoolean_withNSString_withInt_(" + "self, rgb, true, __name, __ordinal);", "}"); assertTranslation(translation, "Color_initWithInt_withBoolean_withNSString_withInt_(" + "e, (jint) 0xffffff, false, @\"WHITE\", 1);"); assertTranslation(translation, "Color_initWithInt_withNSString_withInt_(e, (jint) 0x0000ff, @\"BLUE\", 2);"); } public void testArrayFieldDeclaration() throws IOException { String translation = translateSourceFile( "public class Example { char[] before; char after[]; }", "Example", "Example.h"); assertTranslation(translation, "IOSCharArray *before_;"); assertTranslation(translation, "IOSCharArray *after_;"); } public void testForwardDeclarationOfInnerType() throws IOException { String translation = translateSourceFile( "public class Example { Foo foo; class Foo {} }", "Example", "Example.h"); // Test that Foo is forward declared because Example contains a field of // type Foo and Foo is declared after Example. assertTranslation(translation, "@class Example_Foo;"); } public void testAnnotationGeneration() throws IOException { String translation = translateSourceFile( "package foo; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) " + "public @interface Compatible { boolean fooable() default false; }", "Compatible", "foo/Compatible.h"); // Test that the annotation was declared as a protocol and a value class. assertTranslation(translation, "@protocol FooCompatible < JavaLangAnnotationAnnotation >"); assertTranslation(translation, "@interface FooCompatible : NSObject < FooCompatible >"); // Verify that the value is defined as a property instead of a method. assertTranslation(translation, "@public\n jboolean fooable_;"); assertTranslation(translation, "@property (readonly) jboolean fooable;"); // Check that constructor was created with the property as parameter. assertTranslation(translation, "FOUNDATION_EXPORT id<FooCompatible> create_FooCompatible(jboolean fooable);"); translation = getTranslatedFile("foo/Compatible.m"); // Verify default value accessor is generated for property. assertTranslation(translation, "+ (jboolean)fooableDefault {"); } public void testCharacterEdgeValues() throws IOException { String translation = translateSourceFile( "public class Test { " + " public static final char MIN = 0; " + " public static final char MAX = '\uffff'; " + "}", "Test", "Test.h"); assertTranslation(translation, "x00"); assertTranslation(translation, "0xffff"); } public void testEnumNaming() throws IOException { String translation = translateSourceFile( "public enum MyEnum { ONE, TWO, THREE }", "MyEnum", "MyEnum.h"); assertTranslation(translation, "typedef NS_ENUM(NSUInteger, MyEnum_Enum) {"); assertTranslation(translation, "@interface MyEnum : JavaLangEnum"); assertTranslation(translation, "FOUNDATION_EXPORT MyEnum *MyEnum_values_[];"); assertTranslation(translation, "inline MyEnum *MyEnum_get_ONE();"); } public void testNoImportForMappedTypes() throws IOException { String translation = translateSourceFile( "public class Test extends Object implements Cloneable { " + " public String toString() { return \"\"; }" + " public Class<?> myClass() { return getClass(); }}", "Test", "Test.h"); assertFalse(translation.contains("#include \"java/lang/Class.h\"")); assertFalse(translation.contains("#include \"java/lang/Cloneable.h\"")); assertFalse(translation.contains("#include \"java/lang/Object.h\"")); assertFalse(translation.contains("#include \"java/lang/String.h\"")); assertFalse(translation.contains("#include \"Class.h\"")); assertFalse(translation.contains("#include \"NSCopying.h\"")); assertFalse(translation.contains("#include \"NSObject.h\"")); assertFalse(translation.contains("#include \"NSString.h\"")); assertTranslation(translation, "NSCopying"); } // Verify that an empty Java enum doesn't define an empty C enum, // which is illegal. public void testEmptyEnum() throws IOException { String header = translateSourceFile("public class A { enum Foo {} }", "A", "A.h"); String impl = getTranslatedFile("A.m"); // Verify there's no C enum. assertFalse(header.contains("typedef enum {\n} A_Foo;")); // Verify there's still a Java enum type. assertTranslation(header, "@interface A_Foo : JavaLangEnum"); assertTranslation(impl, "@implementation A_Foo"); } public void testEnumWithInterfaces() throws IOException { String translation = translateSourceFile( "public class A { interface I {} " + "enum Foo implements I, Runnable, Cloneable { " + "A, B, C; public void run() {}}}", "A", "A.h"); assertTranslation(translation, "@interface A_Foo : JavaLangEnum < NSCopying, A_I, JavaLangRunnable >"); assertTranslation(translation, "#include \"java/lang/Runnable.h\""); } public void testExternalNativeMethod() throws IOException { String translation = translateSourceFile( "package foo; class Example { native void external(String s); " + " void test(String str) { external(str); }}", "Example", "foo/Example.h"); // Verify external() and test() are in main interface. assertTranslation(translation, "- (void)externalWithNSString:(NSString *)s;"); assertTranslation(translation, "- (void)testWithNSString:(NSString *)str;"); // Verify category method is invoked. translation = getTranslatedFile("foo/Example.m"); assertTranslation(translation, "@implementation FooExample\n"); assertTranslation(translation, "void FooExample_externalWithNSString_(FooExample *self, NSString *s);"); assertTranslatedLines(translation, "- (void)externalWithNSString:(NSString *)s {", " FooExample_externalWithNSString_(self, s);", "}"); assertTranslation(translation, "[self externalWithNSString:str];"); } public void testPropertiesOfTypeWeakOuter() throws IOException { String sourceContent = " import com.google.j2objc.annotations.Weak;" + "import com.google.j2objc.annotations.WeakOuter;" + "public class FooBar {" + " @Weak private Internal fieldBar;" + " private Internal fieldFoo;" + " @WeakOuter" + " private class Internal {" + " }" + "}"; String translation = translateSourceFile(sourceContent, "FooBar", "FooBar.m"); assertTranslatedLines(translation, "__unsafe_unretained FooBar_Internal *fieldBar_;", "FooBar_Internal *fieldFoo_;"); } public void testAddIgnoreDeprecationWarningsPragmaIfDeprecatedDeclarationsIsEnabled() throws IOException { options.enableDeprecatedDeclarations(); String sourceContent = "class Test {}"; String translation = translateSourceFile(sourceContent, "FooBar", "FooBar.h"); assertTranslation(translation, "#pragma clang diagnostic push"); assertTranslation(translation, "#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\""); assertTranslation(translation, "#pragma clang diagnostic pop"); } public void testDoNotAddIgnoreDeprecationWarningsPragmaIfDeprecatedDeclarationsIsDisabled() throws IOException { String sourceContent = "class Test {}"; String translation = translateSourceFile(sourceContent, "FooBar", "FooBar.h"); assertNotInTranslation(translation, "#pragma clang diagnostic push"); assertNotInTranslation(translation, "#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\""); assertNotInTranslation(translation, "#pragma clang diagnostic pop"); } public void testInnerAnnotationGeneration() throws IOException { String source = "import java.lang.annotation.*; public abstract class Test { " + "@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) " + "public @interface Initialize {}}"; String translation = translateSourceFile(source, "Test", "Test.h"); assertTranslation(translation, "@protocol Test_Initialize < JavaLangAnnotationAnnotation >"); assertTranslation(translation, "@interface Test_Initialize : NSObject < Test_Initialize >"); } public void testFieldSetterGeneration() throws IOException { String translation = translateSourceFile( "import com.google.j2objc.annotations.Weak;" + "class Test { Object o; @Weak String s; static Integer i; }", "Test", "Test.h"); assertTranslation(translation, "J2OBJC_FIELD_SETTER(Test, o_, id)"); // Make sure the @Weak and static fields don't generate setters. assertOccurrences(translation, "J2OBJC_FIELD_SETTER", 1); } public void testEnumWithNameAndOrdinalParameters() throws IOException { String translation = translateSourceFile( "public enum Test { FOO(\"foo\", 3), BAR(\"bar\", 5); " + "private String name; private int ordinal; " + "private Test(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }" + "public String getName() { return name; }}", "Test", "Test.h"); assertTranslation(translation, "@interface Test : JavaLangEnum"); translation = getTranslatedFile("Test.m"); assertTranslation(translation, "NSString *name_Test_;"); assertTranslation(translation, "int ordinal_Test_;"); assertTranslation(translation, "void Test_initWithNSString_withInt_withNSString_withInt_(" + "Test *self, NSString *name, jint ordinal, NSString *__name, jint __ordinal) {"); } public void testDeprecatedEnumType() throws IOException { options.enableDeprecatedDeclarations(); String translation = translateSourceFile( "@Deprecated public enum Test { A, B }", "Test", "Test.h"); assertTranslation(translation, "__attribute__((deprecated))\n@interface Test"); } public void testLongConstants() throws IOException { String translation = translateSourceFile( "class Test { static final long FOO = 123; }", "Test", "Test.h"); assertTranslation(translation, "123LL"); } public void testCustomWeakAnnotations() throws IOException { String translation = translateSourceFile( "class Test { @interface Weak {} @interface WeakOuter {}" + " void foo() {}" + " @WeakOuter public class Inner { void bar() { foo(); } }" + " @Weak public Object obj; }", "Test", "Test.h"); assertTranslation(translation, "__unsafe_unretained id obj_;"); translation = getTranslatedFile("Test.m"); assertTranslation(translation, "__unsafe_unretained Test *this$0_;"); } public void testReservedWordAsAnnotationPropertyName() throws IOException { String translation = translateSourceFile( "package foo; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) " + "public @interface Bar { String namespace() default \"\"; }", "Bar", "foo/Bar.h"); assertTranslation(translation, "@property (readonly) NSString *namespace__;"); assertTranslatedLines(translation, "@interface FooBar : NSObject < FooBar > {", "@public", "NSString *namespace___;", "}"); assertTranslation(translation, "FOUNDATION_EXPORT id<FooBar> create_FooBar(NSString *namespace__);"); translation = getTranslatedFile("foo/Bar.m"); assertTranslation(translation, "+ (NSString *)namespace__Default {"); } public void testMethodSorting() throws IOException { String translation = translateSourceFile("class A {" + "protected void gnu(String s, int i, Runnable r) {}" + "public A(int i) {}" + "private void zebra() {}" + "void yak() {}" + "A() {} }", "A", "A.h"); assertTranslatedLines(translation, "#pragma mark Public", "", "- (instancetype)initWithInt:(jint)i;", "", "#pragma mark Protected", "", "- (void)gnuWithNSString:(NSString *)s", "withInt:(jint)i", "withJavaLangRunnable:(id<JavaLangRunnable>)r;", "", "#pragma mark Package-Private", "", "- (instancetype)init;", "", "- (void)yak;"); assertNotInTranslation(translation, "zebra"); // No zebra() since it's private. } // Verify that when a class is referenced in the same source file, a header // isn't included for it. public void testPackagePrivateBaseClass() throws IOException { String translation = translateSourceFile( "package bar; public class Test extends Foo {} " + "abstract class Foo {}", "Test", "bar/Test.h"); assertNotInTranslation(translation, "#include \"Foo.h\""); } public void testNoForwardDeclarationWhenIncluded() throws IOException { options.setSegmentedHeaders(false); addSourceFile("class Foo { static class Bar { } }", "Foo.java"); String translation = translateSourceFile( "class Test extends Foo { Foo.Bar bar; }", "Test", "Test.h"); assertTranslation(translation, "#include \"Foo.h\""); // Forward declaration for Foo_Bar is not needed because we've included Foo.h. assertNotInTranslation(translation, "@class Foo_Bar"); } // Verify that the default constructor is disallowed if the class has a non-default // constructor. public void testDefaultConstructorDisallowed() throws IOException { options.setDisallowInheritedConstructors(true); String translation = translateSourceFile("class Test { Test(int i) {} }", "Test", "Test.h"); assertTranslation(translation, "- (instancetype)initWithInt:(jint)i;"); assertTranslation(translation, "- (instancetype)init NS_UNAVAILABLE;"); } // Verify that inherited constructors are disallowed. Exception has four constructors, // so a subclass that only implements one should have the other three disallowed. public void testConstructorsDisallowed() throws IOException { options.setDisallowInheritedConstructors(true); String translation = translateSourceFile( "class Test extends Exception { Test(String s) { super(s); } }", "Test", "Test.h"); assertTranslation(translation, "- (instancetype)initWithNSString:(NSString *)s;"); assertTranslation(translation, "- (instancetype)init NS_UNAVAILABLE;"); assertTranslation(translation, "- (instancetype)initWithNSException:(NSException *)arg0 NS_UNAVAILABLE;"); assertTranslatedLines(translation, "- (instancetype)initWithNSString:(NSString *)arg0", "withNSException:(NSException *)arg1 NS_UNAVAILABLE;"); assertTranslatedLines(translation, "- (instancetype)initWithNSString:(NSString *)arg0", "withNSException:(NSException *)arg1", "withBoolean:(jboolean)arg2", "withBoolean:(jboolean)arg3 NS_UNAVAILABLE;"); } public void testStaticInterfaceMethodDeclaredInCompanionClass() throws IOException { String source = "interface Foo { static void f() {} }" + "class Bar implements Foo { void g() { Foo.f(); } }"; String header = translateSourceFile(source, "Test", "Test.h"); String impl = getTranslatedFile("Test.m"); assertTranslation(header, "@protocol Foo < JavaObject >"); assertTranslatedSegments(header, "@interface Foo : NSObject", "+ (void)f;", "@end"); // Should only have one occurrence from the companion class. assertOccurrences(header, "+ (void)f;", 1); // The companion class of Foo still has the class method +[Foo f]. assertTranslatedLines(impl, "+ (void)f {", "Foo_f();", "}"); } }