/*
* Copyright 2012 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.util;
import com.google.devtools.j2objc.GenerationTest;
import com.google.devtools.j2objc.ast.AbstractTypeDeclaration;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.TreeVisitor;
import java.io.IOException;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
/**
* Unit tests for {@link NameTable}.
*
* @author Tom Ball
*/
public class NameTableTest extends GenerationTest {
// Verify class name without package is unchanged.
public void testGetFullNameNoPackage() {
String source = "public class SomeClass {}";
CompilationUnit unit = translateType("SomeClass", source);
NameTable nameTable = unit.getEnv().nameTable();
AbstractTypeDeclaration decl = unit.getTypes().get(0);
assertEquals("SomeClass", nameTable.getFullName(decl.getTypeElement()));
}
// Verify class name with package is camel-cased.
public void testGetFullNameWithPackage() {
String source = "package foo.bar; public class SomeClass {}";
CompilationUnit unit = translateType("SomeClass", source);
NameTable nameTable = unit.getEnv().nameTable();
AbstractTypeDeclaration decl = unit.getTypes().get(0);
assertEquals("FooBarSomeClass", nameTable.getFullName(decl.getTypeElement()));
}
// Verify inner class name with package is camel-cased.
public void testGetFullNameWithInnerClass() {
String source = "package foo.bar; public class SomeClass { static class Inner {}}";
CompilationUnit unit = translateType("SomeClass", source);
NameTable nameTable = unit.getEnv().nameTable();
AbstractTypeDeclaration decl = unit.getTypes().get(1);
assertEquals("FooBarSomeClass_Inner", nameTable.getFullName(decl.getTypeElement()));
}
// Verify the name of an inner class of an enum.
public void testGetFullNameEnumWithInnerClasses() {
String source = "package foo.bar; "
+ "public enum SomeClass { A; static class Inner {} static enum Inner2 { B; }}";
CompilationUnit unit = translateType("SomeClass", source);
NameTable nameTable = unit.getEnv().nameTable();
AbstractTypeDeclaration decl = unit.getTypes().get(1);
// Outer type should not have "Enum" added to name.
assertEquals("FooBarSomeClass_Inner", nameTable.getFullName(decl.getTypeElement()));
// Inner enum should have "Enum" added to name.
decl = unit.getTypes().get(2);
assertEquals("FooBarSomeClass_Inner2", nameTable.getFullName(decl.getTypeElement()));
}
// Verify local class name.
public void testGetFullNameWithLocalClass() {
String source = "package foo.bar; class SomeClass { void test() { "
// Put each Foo in a separate scope, so leading index number changes.
// This matches JVM naming, once '$' is substituted for the '_' characters.
+ "{ class Foo {}} { class Foo {}}}}";
CompilationUnit unit = translateType("SomeClass", source);
NameTable nameTable = unit.getEnv().nameTable();
AbstractTypeDeclaration decl = unit.getTypes().get(1);
assertEquals("FooBarSomeClass_1Foo", nameTable.getFullName(decl.getTypeElement()));
decl = unit.getTypes().get(2);
assertEquals("FooBarSomeClass_2Foo", nameTable.getFullName(decl.getTypeElement()));
}
public void testTypeVariableWithTypeVariableBounds() {
String source = "class A<T> { <E extends T> void foo(E e) {} }";
CompilationUnit unit = translateType("A", source);
NameTable nameTable = unit.getEnv().nameTable();
final ExecutableElement[] methodElement = new ExecutableElement[1];
unit.accept(new TreeVisitor() {
@Override public void endVisit(MethodDeclaration node) {
ExecutableElement element = node.getExecutableElement();
if (ElementUtil.getName(element).equals("foo")) {
methodElement[0] = element;
}
}
});
assertNotNull(methodElement[0]);
TypeMirror paramType = methodElement[0].getParameters().get(0).asType();
assertEquals("id", nameTable.getObjCType(paramType));
}
public void testPrimitiveArrayParameterName() throws IOException {
String translation = translateSourceFile("public class A { "
+ "void foo(int[] value1) {}"
+ "void foo(Integer[] value2) {}"
+ "void foo(String[] value3) {}}", "A", "A.h");
assertTranslation(translation, "- (void)fooWithIntArray:(IOSIntArray *)value1");
assertTranslation(translation,
"- (void)fooWithJavaLangIntegerArray:(IOSObjectArray *)value2");
assertTranslation(translation,
"- (void)fooWithNSStringArray:(IOSObjectArray *)value3");
}
public void testMultiDimArrayName() throws IOException {
String translation = translateSourceFile("public class A { "
+ "void foo(int[] values) {}"
+ "void foo(int[][] values) {}"
+ "void foo(int[][][] values) {}}", "A", "A.h");
assertTranslation(translation, "- (void)fooWithIntArray:(IOSIntArray *)values");
assertTranslation(translation, "- (void)fooWithIntArray2:(IOSObjectArray *)values");
assertTranslation(translation, "- (void)fooWithIntArray3:(IOSObjectArray *)values");
}
public void testRenameClassAnnotation() throws IOException {
addSourceFile("package foo; "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"TestName\") "
+ "public class A { public static void test() {} "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"TheInner\") "
+ "public static class C { public static void test2() {} } }", "foo/A.java");
addSourceFile(
"public class B { void test() { foo.A.test(); foo.A.C.test2(); }}", "B.java");
String translation = translateSourceFile("foo.A", "foo/A.h");
assertTranslation(translation, "@interface TestName : NSObject");
assertTranslation(translation, "@interface TheInner : NSObject");
translation = translateSourceFile("B", "B.m");
assertTranslatedLines(translation, "TestName_test();", "TheInner_test2();");
}
public void testRenameMapping() throws IOException {
options.getMappings().addClass("foo.bar.A", "Test2Name");
addSourceFile("package foo.bar; public class A { static void test() {}}", "foo/bar/A.java");
addSourceFile("package foo.bar; public class B { void test() { A.test(); }}", "foo/bar/B.java");
String translation = translateSourceFile("foo.bar.A", "foo/bar/A.h");
assertTranslation(translation, "@interface Test2Name : NSObject");
translation = translateSourceFile("foo.bar.B", "foo/bar/B.m");
assertTranslation(translation, "Test2Name_test();");
}
public void testRenameMethodAnnotation() throws IOException {
String objcName = "test:offset:";
addSourceFile("public class A { "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"" + objcName + "\") "
+ "void test(String s, int n) {}}", "A.java");
String translation = translateSourceFile("A", "A.h");
assertTranslatedLines(translation,
"- (void)test:(NSString *)s",
"offset:(jint)n;");
assertNotInTranslation(translation, "testWithNSString:");
translation = getTranslatedFile("A.m");
assertTranslatedLines(translation,
"- (void)test:(NSString *)s",
"offset:(jint)n {");
assertNotInTranslation(translation, "testWithNSString:");
// Test invocation of renamed method.
translation = translateSourceFile(
"class Test { void test(A a) { a.test(\"foo\", 4); } }", "Test", "Test.m");
assertTranslation(translation, "[((A *) nil_chk(a)) test:@\"foo\" offset:4];");
}
public void testRenameStaticMethod() throws IOException {
String translation = translateSourceFile("public class Test { "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"foo\") "
+ "static void test(String s, int n) {}}", "Test", "Test.h");
assertTranslatedLines(translation,
"+ (void)fooWithNSString:(NSString *)s",
" withInt:(jint)n;");
assertTranslation(translation, "FOUNDATION_EXPORT void Test_foo(NSString *s, jint n);");
translation = getTranslatedFile("Test.m");
assertTranslatedLines(translation,
"+ (void)fooWithNSString:(NSString *)s",
" withInt:(jint)n {",
" Test_foo(s, n);",
"}");
assertTranslatedLines(translation,
"t_foo(NSString *s, jint n) {",
" Test_initialize();",
"}");
}
public void testRenameConstructorAnnotation() throws IOException {
String objcName = "init:offset:";
addSourceFile("public class A { "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"" + objcName + "\") "
+ "A(String s, int n) {}}", "A.java");
String translation = translateSourceFile("A", "A.h");
assertTranslatedLines(translation,
"- (instancetype)init:(NSString *)s",
"offset:(jint)n;");
assertNotInTranslation(translation, "testWithNSString");
translation = getTranslatedFile("A.m");
assertTranslatedLines(translation,
"- (instancetype)init:(NSString *)s",
"offset:(jint)n {");
assertNotInTranslation(translation, "testWithNSString");
// Test invocation of renamed constructor.
translation = translateSourceFile(
"class Test { A test() { return new A(\"foo\", 5); } }", "Test", "Test.m");
assertTranslation(translation, "return create_A_init_offset_(@\"foo\", 5);");
}
public void testSuperMethodNotNamedWarning() throws IOException {
translateSourceFile("class A { void test(String s, int n) {}"
+ "static class B extends A { "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"test:(NSString *)s offset:(int)n\")"
+ "@Override void test(String s, int n) {}}}", "A", "A.m");
assertWarningCount(1);
}
public void testMethodConflictingName() throws IOException {
translateSourceFile("class A { "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"foo:bar:\")"
+ "void test(String s, int n) {}"
+ "static class B extends A { "
+ "@com.google.j2objc.annotations.ObjectiveCName(\"test:offset:\")"
+ "@Override void test(String s, int n) {}}}", "A", "A.m");
assertWarningCount(1);
}
// Verify enum constant names are not modified, even if they use a reserved word.
// This is necessary for compatibility with proto compiler output.
public void testGetReservedEnumConstantName() throws IOException {
String translation = translateSourceFile("enum E { HUGE }", "E", "E.h");
assertTranslation(translation, "HUGE");
assertNotInTranslation(translation, "HUGE_");
}
public void testRenamePackageAnnotation() throws IOException {
addSourcesToSourcepaths();
addSourceFile("@com.google.j2objc.annotations.ObjectiveCName(\"FB\") "
+ "package foo.bar;", "foo/bar/package-info.java");
addSourceFile("package foo.bar; public class Test {}", "foo/bar/Test.java");
String translation = translateSourceFile("foo.bar.Test", "foo/bar/Test.h");
assertTranslation(translation, "@interface FBTest : NSObject");
assertTranslation(translation, "J2OBJC_EMPTY_STATIC_INIT(FBTest)");
assertTranslation(translation, "@compatibility_alias FooBarTest FBTest;");
translation = getTranslatedFile("foo/bar/Test.m");
assertTranslation(translation, "#include \"foo/bar/Test.h\""); // should be full path.
assertTranslation(translation, "@implementation FBTest");
assertTranslation(translation, "J2ObjcClassInfo _FBTest = { \"Test\", \"foo.bar\", ");
}
public void testRenamePackageAnnotationEnum() throws IOException {
addSourcesToSourcepaths();
addSourceFile("@com.google.j2objc.annotations.ObjectiveCName(\"FB\") "
+ "package foo.bar;", "foo/bar/package-info.java");
addSourceFile("package foo.bar; public enum Test { FOO, BAR }", "foo/bar/Test.java");
String translation = translateSourceFile("foo.bar.Test", "foo/bar/Test.h");
assertTranslatedLines(translation,
"typedef NS_ENUM(NSUInteger, FBTest_Enum) {",
" FBTest_Enum_FOO = 0,",
" FBTest_Enum_BAR = 1,",
"};");
assertTranslation(translation, "@interface FBTest : JavaLangEnum");
assertTranslation(translation, "FBTest_values()");
assertTranslation(translation, "+ (FBTest *)valueOfWithNSString:(NSString *)name;");
assertTranslation(translation, "FBTest *FBTest_valueOfWithNSString_");
assertTranslation(translation, "J2OBJC_STATIC_INIT(FBTest)");
assertTranslation(translation, "@compatibility_alias FooBarTest FBTest;");
translation = getTranslatedFile("foo/bar/Test.m");
assertTranslation(translation, "#include \"foo/bar/Test.h\""); // should be full path.
assertTranslation(translation, "@implementation FBTest");
assertTranslation(translation, "J2ObjcClassInfo _FBTest = { \"Test\", \"foo.bar\", ");
// Make sure package-info class doesn't use prefix for its own type name.
translation = translateSourceFile("foo.bar.package-info", "foo/bar/package-info.m");
assertTranslation(translation, "@interface FooBarpackage_info");
assertTranslation(translation, "@implementation FooBarpackage_info");
assertNotInTranslation(translation, "FBpackage_info");
}
}