/*
* 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 java.io.IOException;
/**
* Unit tests for {@link MetadataWriter}.
*
* @author Keith Stanger
*/
public class MetadataWriterTest extends GenerationTest {
public void testMetadataHeaderGeneration() throws IOException {
String translation = translateSourceFile("package foo; class Test {}", "Test", "foo/Test.m");
assertTranslation(translation, "+ (const J2ObjcClassInfo *)__metadata");
assertTranslation(translation,
"static const J2ObjcClassInfo _FooTest = { \"Test\", \"foo\", NULL, methods, NULL, "
+ Integer.toString(MetadataWriter.METADATA_VERSION));
}
public void testConstructorsHaveNullJavaName() throws IOException {
String translation = translateSourceFile("class Test {}", "Test", "Test.m");
assertTranslatedLines(translation,
"static J2ObjcMethodInfo methods[] = {",
// The fourth field, "javaNameIdx", should be -1.
"{ NULL, NULL, 0x0, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "methods[0].selector = @selector(init)");
}
public void testMethodMetadata() throws IOException {
String translation = translateSourceFile(
// Separate methods are used so each only has one modifier.
"abstract class Test<T> { "
+ " Object test1() { return null; }" // package-private
+ " private char test2() { return 'a'; }"
+ " protected void test3() { }"
+ " final long test4() { return 0L; }"
+ " synchronized boolean test5() { return false; }"
+ " String test6(String s, Object... args) { return null; }"
+ " native void test7() /*-[ exit(0); ]-*/; "
+ " abstract void test8() throws InterruptedException, Error; "
+ " abstract T test9();"
+ " abstract void test10(int i, T t);"
+ " abstract <V,X> void test11(V one, X two, T three);"
+ "}",
"Test", "Test.m");
assertTranslation(translation, "{ NULL, \"LNSObject;\", 0x0, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"C\", 0x2, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"V\", 0x4, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"J\", 0x10, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"Z\", 0x20, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"LNSString;\", 0x80, 0, 1, -1, -1, -1, -1 }");
assertTranslation(translation, "{ NULL, \"V\", 0x100, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"V\", 0x400, -1, -1, 2, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"LNSObject;\", 0x400, -1, -1, -1, 3, -1, -1 },");
assertTranslation(translation, "{ NULL, \"V\", 0x400, 4, 5, -1, 6, -1, -1 },");
assertTranslation(translation, "{ NULL, \"V\", 0x400, 7, 8, -1, 9, -1, -1 },");
// Implicit default constructor
assertTranslation(translation, "methods[0].selector = @selector(init)");
assertTranslation(translation, "methods[1].selector = @selector(test1)");
assertTranslation(translation, "methods[2].selector = @selector(test2)");
assertTranslation(translation, "methods[3].selector = @selector(test3)");
assertTranslation(translation, "methods[4].selector = @selector(test4)");
assertTranslation(translation, "methods[5].selector = @selector(test5)");
assertTranslation(translation,
"methods[6].selector = @selector(test6WithNSString:withNSObjectArray:)");
assertTranslation(translation, "methods[7].selector = @selector(test7)");
assertTranslation(translation, "methods[8].selector = @selector(test8)");
assertTranslation(translation, "methods[9].selector = @selector(test9)");
assertTranslation(translation, "methods[10].selector = @selector(test10WithInt:withId:)");
assertTranslation(translation, "methods[11].selector = @selector(test11WithId:withId:withId:)");
assertTranslation(translation,
"static const void *ptrTable[] = { \"test6\", \"LNSString;[LNSObject;\", "
+ "\"LJavaLangInterruptedException;LJavaLangError;\", \"()TT;\", \"test10\", "
+ "\"ILNSObject;\", \"(ITT;)V\", \"test11\", \"LNSObject;LNSObject;LNSObject;\", "
+ "\"<V:Ljava/lang/Object;X:Ljava/lang/Object;>(TV;TX;TT;)V\", "
+ "\"<T:Ljava/lang/Object;>Ljava/lang/Object;\" };");
}
public void testFieldMetadata() throws IOException {
String translation = translateSourceFile(
"class Test<T extends Runnable> {"
+ "byte field1;"
+ "Object field2;"
+ "T field3;"
+ "}", "Test", "Test.m");
assertTranslatedLines(translation,
"static const J2ObjcFieldInfo fields[] = {",
" { \"field1_\", \"B\", .constantValue.asLong = 0, 0x0, -1, -1, -1, -1 },",
" { \"field2_\", \"LNSObject;\", .constantValue.asLong = 0, 0x0, -1, -1, -1, -1 },",
" { \"field3_\", \"LJavaLangRunnable;\", .constantValue.asLong = 0, 0x0, -1, -1, 0, -1 },",
"};");
assertTranslation(translation,
"static const void *ptrTable[] = { \"TT;\", "
+ "\"<T::Ljava/lang/Runnable;>Ljava/lang/Object;\" };");
}
public void testAnnotationMetadata() throws IOException {
String translation = translateSourceFile(
"import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @interface Test { "
+ " String foo() default \"bar\";"
+ " int num() default 5;"
+ "}",
"Test", "Test.m");
assertTranslation(translation, "{ NULL, \"LNSString;\", 0x401, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "{ NULL, \"I\", 0x401, -1, -1, -1, -1, -1, -1 },");
assertTranslation(translation, "methods[0].selector = @selector(foo)");
assertTranslation(translation, "methods[1].selector = @selector(num)");
}
public void testInnerClassesMetadata() throws IOException {
String translation = translateSourceFile(
" class A {"
+ "class B {"
+ " class InnerInner{}}"
+ "static class C {"
+ " Runnable test() {"
+ " return new Runnable() { public void run() {}};}}"
+ "interface D {}"
+ "@interface E {}"
+ "}"
, "A", "A.m");
assertTranslation(translation, "static const void *ptrTable[] = { \"LA_B;LA_C;LA_D;LA_E;\" };");
assertTranslation(translation,
"static const J2ObjcClassInfo _A = { \"A\", NULL, ptrTable, methods, NULL, 7, 0x0, 1, 0, "
+ "-1, 0, -1, -1, -1 };");
}
public void testEnclosingMethodAndConstructor() throws IOException {
String translation = translateSourceFile(
"class A { A(String s) { class B {}} void test(int i, long l) { class C { class D {}}}}",
"A", "A.m");
assertTranslatedLines(translation,
"static const void *ptrTable[] = { \"LA;\", \"initWithNSString:\" };",
"static const J2ObjcClassInfo _A_1B = { \"B\", NULL, ptrTable, methods, NULL, 7, 0x0, 1, "
+ "0, 0, -1, 1, -1, -1 };");
assertTranslatedLines(translation,
"static const void *ptrTable[] = { \"LA;\", \"LA_1C_D;\", \"testWithInt:withLong:\" };",
"static const J2ObjcClassInfo _A_1C = { \"C\", NULL, ptrTable, methods, NULL, 7, 0x0, 1, "
+ "0, 0, 1, 2, -1, -1 };");
// Verify D is not enclosed by test(), as it's enclosed by C.
assertTranslatedLines(translation,
"static const void *ptrTable[] = { \"LA_1C;\" };",
"static const J2ObjcClassInfo _A_1C_D = { \"D\", NULL, ptrTable, methods, NULL, 7, 0x0, 1, "
+ "0, 0, -1, -1, -1, -1 };");
}
public void testMethodAnnotationNoParameters() throws IOException {
String translation = translateSourceFile(
"import org.junit.*;"
+ "public class Test { @After void foo() {} }",
"Test", "Test.m");
assertTranslatedLines(translation,
"IOSObjectArray *Test__Annotations$0() {",
"return [IOSObjectArray arrayWithObjects:(id[]){ create_OrgJunitAfter() } "
+ "count:1 type:JavaLangAnnotationAnnotation_class_()];");
}
public void testMethodAnnotationWithParameter() throws IOException {
String translation = translateSourceFile(
"import org.junit.*;"
+ "public class Test { @After void foo(int i) {} }",
"Test", "Test.m");
assertTranslatedLines(translation,
"IOSObjectArray *Test__Annotations$0() {",
"return [IOSObjectArray arrayWithObjects:(id[]){ create_OrgJunitAfter() } "
+ "count:1 type:JavaLangAnnotationAnnotation_class_()];");
}
public void testConstructorAnnotationNoParameters() throws IOException {
String translation = translateSourceFile(
"public class Test { @Deprecated Test() {} }",
"Test", "Test.m");
assertTranslatedLines(translation,
"IOSObjectArray *Test__Annotations$0() {",
"return [IOSObjectArray arrayWithObjects:(id[]){ create_JavaLangDeprecated() } "
+ "count:1 type:JavaLangAnnotationAnnotation_class_()];");
}
public void testConstructorAnnotationWithParameter() throws IOException {
String translation = translateSourceFile(
"public class Test { @Deprecated Test(int i) {} }",
"Test", "Test.m");
assertTranslatedLines(translation,
"IOSObjectArray *Test__Annotations$0() {",
"return [IOSObjectArray arrayWithObjects:(id[]){ create_JavaLangDeprecated() } "
+ "count:1 type:JavaLangAnnotationAnnotation_class_()];");
}
public void testTypeAnnotationDefaultParameter() throws IOException {
String translation = translateSourceFile(
"import org.junit.*;"
+ "@Ignore public class Test { void test() {} }",
"Test", "Test.m");
assertTranslatedLines(translation,
"IOSObjectArray *Test__Annotations$0() {",
"return [IOSObjectArray arrayWithObjects:(id[]){ create_OrgJunitIgnore(@\"\") } "
+ "count:1 type:JavaLangAnnotationAnnotation_class_()];");
}
public void testTypeAnnotationWithParameter() throws IOException {
String translation = translateSourceFile(
"import org.junit.*;"
+ "@Ignore(\"some \\\"escaped\\n comment\") public class Test { void test() {} }",
"Test", "Test.m");
assertTranslatedLines(translation,
"IOSObjectArray *Test__Annotations$0() {",
"return [IOSObjectArray arrayWithObjects:(id[])"
+ "{ create_OrgJunitIgnore(@\"some \\\"escaped\\n comment\") } "
+ "count:1 type:JavaLangAnnotationAnnotation_class_()];");
}
// Verify that a class with an annotation with a reserved name property is
// created in the __annotations support method with that reserved name in the
// constructor.
public void testReservedWordAsAnnotationConstructorParameter() throws IOException {
String translation = translateSourceFile(
"package foo; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) "
+ "public @interface Bar { String namespace() default \"\"; } "
+ "@Bar(namespace=\"mynames\") class Test {}",
"Bar", "foo/Bar.m");
assertTranslatedLines(translation,
"IOSObjectArray *FooTest__Annotations$0() {",
"return [IOSObjectArray arrayWithObjects:(id[]){ create_FooBar(@\"mynames\") } "
+ "count:1 type:JavaLangAnnotationAnnotation_class_()];");
}
public void testOuterAndCaptureFieldsInMetadata() throws IOException {
String translation = translateSourceFile(
"class Test { int i; void test(int j) { class Inner { int foo() { return i + j; } } } }",
"Test", "Test.m");
assertTranslatedLines(translation,
"static const J2ObjcFieldInfo fields[] = {",
" { \"this$0_\", \"LTest;\", .constantValue.asLong = 0, 0x1012, -1, -1, -1, -1 },",
" { \"val$j_\", \"I\", .constantValue.asLong = 0, 0x1012, -1, -1, -1, -1 },",
"};");
}
}