Skip to content

Commit

Permalink
Can convert function to interface of one default methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
tuchida authored and gbrail committed Jan 21, 2021
1 parent 0b57ab5 commit 1f45c8f
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 20 deletions.
51 changes: 31 additions & 20 deletions src/org/mozilla/javascript/InterfaceAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import java.util.HashSet;

/**
* Adapter to use JS function as implementation of Java interfaces with
* single method or multiple methods with the same signature.
Expand All @@ -35,31 +37,40 @@ static Object create(Context cx, Class<?> cl, ScriptableObject object)
adapter = (InterfaceAdapter)cache.getInterfaceAdapter(cl);
ContextFactory cf = cx.getFactory();
if (adapter == null) {
Method[] methods = cl.getMethods();
if ( object instanceof Callable) {
if (object instanceof Callable) {
Method[] methods = cl.getMethods();
// Check if interface can be implemented by a single function.
// We allow this if the interface has only one method or multiple
// methods with the same name (in which case they'd result in
// the same function to be invoked anyway).
int length = methods.length;
if (length == 0) {
throw Context.reportRuntimeErrorById(
"msg.no.empty.interface.conversion", cl.getName());
}
if (length > 1) {
String methodName = null;
for (Method method : methods) {
// there are multiple methods in the interface we inspect
// only abstract ones, they must all have the same name.
if (isFunctionalMethodCandidate(method)) {
if (methodName == null) {
methodName = method.getName();
} else if (!methodName.equals(method.getName())) {
throw Context.reportRuntimeErrorById(
"msg.no.function.interface.conversion",
cl.getName());
}
HashSet<String> functionalMethodNames = new HashSet<>();
HashSet<String> defaultMethodNames = new HashSet<>();
for (Method method : methods) {
// there are multiple methods in the interface we inspect
// only abstract ones, they must all have the same name.
if (isFunctionalMethodCandidate(method)) {
functionalMethodNames.add(method.getName());
if (functionalMethodNames.size() > 1) {
break;
}
} else {
defaultMethodNames.add(method.getName());
}
}

boolean canConvert =
(functionalMethodNames.size() == 1) ||
(functionalMethodNames.isEmpty() && defaultMethodNames.size() == 1);
// There is no abstract method or there are multiple methods.
if (!canConvert) {
if (functionalMethodNames.isEmpty() && defaultMethodNames.isEmpty()) {
throw Context.reportRuntimeErrorById(
"msg.no.empty.interface.conversion",
cl.getName());
} else {
throw Context.reportRuntimeErrorById(
"msg.no.function.interface.conversion",
cl.getName());
}
}
}
Expand Down
139 changes: 139 additions & 0 deletions testsrc/org/mozilla/javascript/tests/InterfaceAdapterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import java.util.List;

import org.junit.Test;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Wrapper;

import junit.framework.TestCase;

import static org.junit.Assert.*;

/*
* This testcase tests the support of converting javascript functions to
* functional interface adapters
Expand Down Expand Up @@ -105,4 +108,140 @@ public void testNativeFunctionAsComparator() {
testIt(js, Arrays.asList("bar", "foo"));
}

public interface EmptyInterface {
}

public static void receiveEmptyInterface(EmptyInterface i) {
}

@Test
public void testFunctionAsEmptyInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveEmptyInterface(a => a);\n"
+ "list";
assertThrows(EvaluatorException.class, () -> testIt(js, Arrays.asList("foo", "bar")));
}

public interface OneMethodInterface {
String a();
}

public static String receiveOneMethodInterface(OneMethodInterface i) {
return i.a();
}

@Test
public void testFunctionAsOneMethodInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveOneMethodInterface(() => 'ok');";
testIt(js, "ok");
}

public interface TwoMethodsInterface {
void a();
void b();
}

public static void receiveTwoMethodsInterface(TwoMethodsInterface i) {
}

@Test
public void testFunctionAsTwoMethodsInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveTwoMethodsInterface(a => a);\n"
+ "list";
assertThrows(EvaluatorException.class, () -> testIt(js, Arrays.asList("foo", "bar")));
}

public interface TwoMethodsWithExtendsInterface extends OneMethodInterface {
void b();
}

public static void receiveTwoMethodsWithExtendsInterface(TwoMethodsWithExtendsInterface i) {
}

@Test
public void testFunctionAsTwoMethodsWithExtendsInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveTwoMethodsWithExtendsInterface(a => a);\n"
+ "list";
assertThrows(EvaluatorException.class, () -> testIt(js, Arrays.asList("foo", "bar")));
}

public interface OneDefaultMethodInterface {
default String a() {
return "ng";
}
}

public static String receiveOneDefaultMethodInterface(OneDefaultMethodInterface i) {
return i.a();
}

@Test
public void testFunctionAsOneDefaultMethodInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveOneDefaultMethodInterface(() => 'ok');";
testIt(js, "ok");
}

public interface TwoDefaultMethodsInterface {
default void a() {}
default void b() {}
}

public static void receiveTwoDefaultMethodsInterface(TwoDefaultMethodsInterface i) {
}

@Test
public void testFunctionAsTwoDefaultMethodsInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveTwoDefaultMethodsInterface(a => a);\n"
+ "list";
assertThrows(EvaluatorException.class, () -> testIt(js, Arrays.asList("foo", "bar")));
}

public interface TwoSameNameDefaultMethodsInterface {
default String a(int i) {
return "ng";
}
default String a(String s) {
return "ng";
}
}

public static String receiveTwoSameNameDefaultMethodsInterface(TwoSameNameDefaultMethodsInterface i) {
return i.a(1);
}

@Test
public void testFunctionAsTwoSameNameDefaultMethodsInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveTwoSameNameDefaultMethodsInterface(i => 'ok,' + i);";
testIt(js, "ok,1");
}

public interface OneAbstructOneDefaultSameNameMethodInterface {
String a(int i);
default String a(String s) {
return "ng";
}
}

public static String receiveOneAbstructOneDefaultSameNameMethodInterface(OneAbstructOneDefaultSameNameMethodInterface i) {
return i.a(2);
}

@Test
public void testFunctionAsOneAbstructOneDefaultSameNameMethodInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveOneAbstructOneDefaultSameNameMethodInterface(i => 'ok,' + i);";
testIt(js, "ok,2");
}

public interface AddDefaultMethodWithExtendsInterface extends OneMethodInterface {
default void b() {}
}

public static String receiveAddDefaultMethodWithExtendsInterface(AddDefaultMethodWithExtendsInterface i) {
return i.a();
}

@Test
public void testFunctionAsAddDefaultMethodWithExtendsInterface() {
String js = "org.mozilla.javascript.tests.InterfaceAdapterTest.receiveAddDefaultMethodWithExtendsInterface(() => 'ok');";
testIt(js, "ok");
}
}

0 comments on commit 1f45c8f

Please sign in to comment.