/*
* 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;
/**
* Tests Java 8 default method support.
*
* @author Lukhnos Liu
*/
public class DefaultMethodsTest extends GenerationTest {
public void testDefaultMethodFunctionalization() throws IOException {
String source = "interface Foo { void f(); default void g() { f(); } }";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header, "- (void)f;");
assertTranslation(header, "- (void)g;");
assertTranslation(header, "void Foo_g(id<Foo> self);");
assertTranslatedLines(impl, "void Foo_g(id<Foo> self) {", "[self f];", "}");
}
public void testDefaultMethodFunctionalizationWithReflectionsStripped() throws IOException {
options.setStripReflection(true);
String source = "interface Foo { void f(); default void g() { f(); } }";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header, "- (void)f;");
assertTranslation(header, "- (void)g;");
assertTranslation(header, "void Foo_g(id<Foo> self);");
assertTranslatedLines(impl, "void Foo_g(id<Foo> self) {", "[self f];", "}");
}
public void testSuperDefaultMethodInvocation() throws IOException {
String translation = translateSourceFile(
"interface Foo { default int f(int y) { return y + 1; } }"
+ "class Bar implements Foo {"
+ " public Bar(int x) { int i = Foo.super.f(x); }"
+ " public int f(int y) { return Foo.super.f(y) + 1; }"
+ "}", "Test", "Test.m");
assertTranslation(translation, "jint i = Foo_fWithInt_(self, x);");
assertTranslation(translation, "return Foo_fWithInt_(self, y) + 1;");
}
public void testBasicDefaultMethodUsage() throws IOException {
String source = " interface A {"
+ " default void f() {}"
+ " default int g() { return 0; }"
+ " void p();"
+ " static void q() {}"
+ " default Object r(int x, A b) {"
+ " f(); p(); q();"
+ " return x + b.g();"
+ " }"
+ "}"
+ "class B implements A {"
+ " @Override public void p() {}"
+ "}";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header, "void A_f(id<A> self)");
assertTranslation(header, "jint A_g(id<A> self)");
assertTranslation(header, "void A_q()");
assertTranslation(header, "id A_rWithInt_withA_(id<A> self, jint x, id<A> b)");
// This is an illegal value for JVM's access_flags field and should never show up in metadata.
assertNotInTranslation(impl, "0x10001");
assertTranslatedLines(impl, "- (void)f {", "A_f(self);", "}");
assertTranslatedLines(impl, "- (jint)g {", "return A_g(self);", "}");
assertTranslatedLines(impl, "- (void)p {", "}");
assertTranslatedLines(impl, "- (id)rWithInt:(jint)arg0", "withA:(id<A>)arg1 {",
"return A_rWithInt_withA_(self, arg0, arg1);", "}");
}
public void testEnumSupport() throws Exception {
String source = "interface I { default void f() {} }"
+ "enum E implements I { FOO, BAR }";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertOccurrences(header, "- (void)f;", 1); // Declared in I but not E.
assertTranslatedLines(impl, "- (void)f {", "I_f(self);", "}");
}
public void testDefaultMethodWithLambda() throws IOException {
String source = "interface A {"
+ " String op(String a, String b);"
+ " default String underscorePrefix(String a) { return op(\"_\", a); }"
+ "}"
+ "interface Unrelated { default boolean unrelated() { return false; } }"
+ "class B {"
+ " void f() { g((x, y) -> x + y); }"
+ " String g(A a) { return a.underscorePrefix(\"foo\"); }"
+ " boolean other() { return ((A & Unrelated) (a,b) -> a).unrelated(); }"
+ "}";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header,
"NSString *A_underscorePrefixWithNSString_(id<A> self, NSString *a)");
assertTranslatedLines(impl,
"NSString *A_underscorePrefixWithNSString_(id<A> self, NSString *a) {",
" return [self opWithNSString:@\"_\" withNSString:a];",
"}");
// Make sure we base the non-capturing lambda on interface A's companion class that has the
// default method shim.
assertTranslatedLines(impl,
"- (NSString *)underscorePrefixWithNSString:(NSString *)arg0 {",
" return A_underscorePrefixWithNSString_(self, arg0);",
"}",
"",
"- (jboolean)unrelated {",
" return Unrelated_unrelated(self);",
"}");
}
public void testDefaultMethodsInInterfaceExtensions() throws IOException {
String source = "interface A { default void f() {} }"
+ "interface B extends A { default void f() {} }"
+ "class C implements B {}";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslatedLines(impl, "- (void)f {",
"B_f(self);",
"}");
}
public void testRedeclaringAbstractInterfaceMethods() throws IOException {
String source = "interface A { default void f() {} }"
+ "interface B extends A { void f(); }";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "void A_f(id<A> self)");
assertNotInTranslation(impl, "void B_f(id<A> self)");
}
public void testAbstractClassImplementations() throws IOException {
String source = "interface A { default void f() {} default void g() {} }"
+ "interface B extends A { default void f() {} void g(); }"
+ "abstract class C implements B {}";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "void A_f(id<A> self)");
assertTranslation(impl, "void A_g(id<A> self)");
assertTranslation(impl, "void B_f(id<B> self)");
assertNotInTranslation(impl, "void B_g(id<A> self)");
assertTranslatedLines(impl, "- (void)f {", "B_f(self);", "}");
assertNotInTranslation(impl, "A_g(self);");
assertNotInTranslation(impl, "B_g(self);");
}
public void testUniqueShimImplementation() throws IOException {
String source = "interface A { default void f() {} }"
+ "interface B extends A { default void g() {} }"
+ "class P implements A {}"
+ "class Q extends P implements B {}";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertOccurrences(header, "- (void)f;", 1); // Declared once by A.
assertOccurrences(impl, "A_f(self);", 1); // Called by -[P f].
assertOccurrences(impl, "B_g(self);", 1); // Called by -[Q g].
}
public void testConcreteMethodPrecedence() throws Exception {
String source = "interface A { default void f() {} }"
+ "class P { public void f() {} }"
+ "class Q extends P implements A {}";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertOccurrences(header, "- (void)f;", 2); // Declared once by A and another by P.
// f() is inherited from P so the default declaration in A is not used.
assertNotInTranslation(impl, "A_f(self);");
}
public void testAnonymousClass() throws IOException {
String source = "interface A {"
+ " default String f(String x) { return x + x; } "
+ "}"
+ "class B {"
+ " String h(String y) {"
+ " return new A() {}.f(y);"
+ " }"
+ "}";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslation(impl, "- (NSString *)fWithNSString:(NSString *)arg0 {");
assertTranslation(impl, "return A_fWithNSString_(self, arg0);");
}
public void testNestedAndInnerClasses() throws Exception {
String source = "class A {"
+ " interface P { default void f() {} }"
+ " static class B implements P {"
+ " class C implements P {}"
+ " }"
+ " class D implements P {}"
+ "}";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header, "void A_P_f(id<A_P> self);");
// This is called by the shims -[A_B f], -[A_B_C f], and -[A_D f].
assertOccurrences(impl, "A_P_f(self);", 3);
}
public void testFunctionizedMethodRenaming() throws Exception {
String source = "interface P {"
+ " default void f() {}"
+ " static Object f = new Object();"
+ "}";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header, "P_get_f_()");
assertTranslation(header, "P_f_");
assertTranslation(header, "void P_f(id<P> self)");
assertTranslation(impl, "id P_f_;");
assertTranslation(impl, "void P_f(id<P> self)");
}
public void testNoReconsideringSuperclasses() throws Exception {
String source = "interface P { default void f() {} }"
+ "class A implements P {}"
+ "class B extends A {}"
+ "class C extends B implements P {}";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertOccurrences(header, "- (void)f;", 1); // From P, but not A and C
assertOccurrences(impl, "P_f(self);", 1); // From -[A f], but not from B or C.
}
public void testInterfaceTraversalOrder() throws Exception {
String source = "interface A { default void f() {} }"
+ "interface B extends A { void f(); }"
+ "interface C extends B { default void f() {} }"
+ "interface D extends C { void f(); }"
+ "interface E extends D { default void f() {} }"
+ "interface F extends E { void f(); }"
+ "interface G extends F { default void f() {} }"
+ "class H implements G {}";
String impl = translateSourceFile(source, "Test", "Test.m");
// The shim should call the default method in the interface that's closest to H.
assertTranslatedLines(impl, "- (void)f {", "G_f(self);", "}");
}
public void testInterfaceTraversalOrderWithDuplicateImplements() throws Exception {
String source = "interface A { default void f() {} }"
+ "interface B extends A { }"
+ "interface C extends B { default void f() {} }"
+ "class D implements A, C {}";
String impl = translateSourceFile(source, "Test", "Test.m");
assertNotInTranslation(impl, "A_f(self);");
assertOccurrences(impl, "C_f(self);", 1); // -[D f]
}
public void testGenericDefaultMethods() throws Exception {
String source = "interface A<T> { default T f() { return null; } }"
+ "class P implements A<String> {}"
+ "class Q implements A<Integer> { public Integer f() { return 0; } }"
+ "abstract class R implements A<Long> { public abstract Long f(); }";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header, "- (id)f");
assertTranslation(header, "id A_f(id<A> self)");
assertTranslation(header, "- (JavaLangInteger *)f;"); // From Q
// From R; abstract method is still declared and will be implemented with throwing an exception.
assertTranslation(header, "- (JavaLangLong *)f;");
// From A<T>
assertTranslatedLines(impl, "id A_f(id<A> self) {", "return nil;", "}");
// From P
assertTranslatedLines(impl, "- (NSString *)f {", "return A_f(self);", "}");
// From Q
assertTranslatedLines(impl,
"- (JavaLangInteger *)f {",
"return JavaLangInteger_valueOfWithInt_(0);",
"}");
// From R
assertTranslatedLines(impl,
"- (JavaLangLong *)f {",
"// can't call an abstract method",
"[self doesNotRecognizeSelector:_cmd];",
"return 0;",
"}");
}
public void testDefaultMethodWithGenericTypeParameters() throws IOException {
String source = "interface A<T> {}"
+ "interface B { default void f(A<String> x) {} }"
+ "class P implements B {}"
+ "class Q implements B { @Override public void f(A<String> x) {} }";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
assertTranslation(header, "void B_fWithA_(id<B> self, id<A> x);");
assertOccurrences(header, "- (void)fWithA:(id<A>)x;", 2); // From B and Q
// From P
assertTranslatedLines(impl, "- (void)fWithA:(id<A>)arg0 {", "B_fWithA_(self, arg0);", "}");
}
public void testNarrowedReturnType() throws IOException {
String source = "interface A { default Object f() { return null; } }"
+ "class B implements A { public String f() { return \"\"; } }";
String header = translateSourceFile(source, "Test", "Test.h");
String impl = getTranslatedFile("Test.m");
// No shim should be generated as -[B f].
assertOccurrences(header, "- (id)f", 1);
assertTranslation(header, "- (NSString *)f");
// From the default method of A.
assertTranslatedLines(impl, "id A_f(id<A> self) {", "return nil;", "}");
// From @implementation B.
assertTranslatedLines(impl, "- (NSString *)f {", "return @\"\";", "}");
}
public void testAccessingOuterType() throws IOException {
String source = "interface A { default Class<?> type() { return getClass(); } }";
String impl = translateSourceFile(source, "Test", "Test.m");
assertTranslatedLines(impl,
"IOSClass *A_type(id<A> self) {", "return [self java_getClass];", "}");
}
public void testDefaultMethodWithMultipleSelectors() throws IOException {
addSourceFile("interface A <T> { void foo(T t); }", "A.java");
addSourceFile("interface B { void foo(String s); }", "B.java");
addSourceFile("interface C extends A<String>, B { default void foo(String s) {} }", "C.java");
addSourceFile("class D implements C {}", "D.java");
String headerC = translateSourceFile("C", "C.h");
String implD = translateSourceFile("D", "D.m");
assertTranslation(headerC, "- (void)fooWithId:(NSString *)s;");
assertTranslatedLines(implD,
"- (void)fooWithId:(NSString *)arg0 {", "C_fooWithNSString_(self, arg0);", "}");
assertTranslatedLines(implD,
"- (void)fooWithNSString:(NSString *)arg0 {", "[self fooWithId:arg0];", "}");
}
// Regression test simplified from java.util.stream.Node.
public void testNestedInterfaces() throws IOException {
String translation = translateSourceFile(
"interface Node<T> { "
+ "default Node<T> getChild(int i) {"
+ " throw new IndexOutOfBoundsException();"
+ "}"
+ "interface OfPrimitive<T, T_NODE extends OfPrimitive<T, T_NODE>> extends Node<T> {"
+ " default T_NODE getChild(int i) {"
+ " throw new IndexOutOfBoundsException();"
+ " }"
+ "}"
+ "interface OfInt extends OfPrimitive<Integer, OfInt> {}"
+ "static class OfIntImpl implements OfInt {}}", "Node", "Node.m");
assertTranslatedLines(translation,
"return ((id<Node_OfInt>) Node_OfPrimitive_getChildWithInt_(self, arg0));");
}
// Regression test simplified from java.util.stream.ReduceOps, where @interface for
// private interface wasn't generated.
public void testPrivateNestedInterfaceWithDefaultMethod() throws IOException {
addSourceFile("interface Sink<T> { default void test(long size) {}}", "Sink.java");
String translation = translateSourceFile("class Test {"
+ "private interface AccumulatingSink<T> extends Sink<T> {}}", "Test", "Test.m");
assertTranslatedLines(translation, "@interface Test_AccumulatingSink : NSObject");
}
public void testExtraSelectorsFromMultipleOverrides() throws IOException {
addSourceFile("interface I { int foo(String t); }", "I.java");
addSourceFile("class A<T> { int foo(T t) { return 0; } }", "A.java");
String translation = translateSourceFile(
"class B extends A<String> implements I { public int foo(String t) { return 7; } }",
"B", "B.h");
assertTranslation(translation, "- (jint)fooWithId:(NSString *)t;");
translation = getTranslatedFile("B.m");
assertTranslatedLines(translation,
"- (jint)fooWithId:(NSString *)t {",
" return 7;",
"}");
assertTranslatedLines(translation,
"- (jint)fooWithNSString:(NSString *)arg0 {",
" return [self fooWithId:arg0];",
"}");
}
public void testClassInheritsOverridingMethodsWithDifferentSelectors() throws IOException {
addSourceFile("class A extends B implements C<B> {}", "A.java");
addSourceFile("class B extends D { public void foo(B b) { } }", "B.java");
addSourceFile("interface C<T extends D> { public void foo(T d); }", "C.java");
addSourceFile("class D {}", "D.java");
String aImpl = translateSourceFile("A", "A.m");
assertTranslatedLines(aImpl,
"- (void)fooWithD:(B *)arg0 {",
" [self fooWithB:arg0];",
"}");
}
}