mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
JS: allow passing Object to JS methods
This commit is contained in:
parent
059281a25c
commit
a1ed797d73
|
@ -171,8 +171,7 @@ class JSClassProcessor {
|
|||
callerMethod.getModifiers().add(ElementModifier.STATIC);
|
||||
Program program = ProgramUtils.copy(method.getProgram());
|
||||
program.createVariable();
|
||||
InstructionVariableMapper variableMapper = new InstructionVariableMapper(var ->
|
||||
program.variableAt(var.getIndex() + 1));
|
||||
var variableMapper = new InstructionVariableMapper(var -> program.variableAt(var.getIndex() + 1));
|
||||
for (int i = program.variableCount() - 1; i > 0; --i) {
|
||||
program.variableAt(i).setDebugName(program.variableAt(i - 1).getDebugName());
|
||||
program.variableAt(i).setLabel(program.variableAt(i - 1).getLabel());
|
||||
|
@ -241,12 +240,12 @@ class JSClassProcessor {
|
|||
processIsInstance((IsInstanceInstruction) insn);
|
||||
} else if (insn instanceof InvokeInstruction) {
|
||||
var invoke = (InvokeInstruction) insn;
|
||||
processInvokeArgs(invoke);
|
||||
|
||||
var method = getMethod(invoke.getMethod().getClassName(), invoke.getMethod().getDescriptor());
|
||||
if (method == null) {
|
||||
continue;
|
||||
}
|
||||
processInvokeArgs(invoke, method);
|
||||
var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
|
||||
replacement.clear();
|
||||
if (processInvocation(method, callLocation, invoke, methodToProcess)) {
|
||||
|
@ -272,8 +271,9 @@ class JSClassProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private void processInvokeArgs(InvokeInstruction invoke) {
|
||||
if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) {
|
||||
private void processInvokeArgs(InvokeInstruction invoke, MethodReader methodToInvoke) {
|
||||
if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())
|
||||
|| methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) {
|
||||
return;
|
||||
}
|
||||
Variable[] newArgs = null;
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
package org.teavm.jso.impl;
|
||||
|
||||
import java.util.Set;
|
||||
import org.teavm.dependency.AbstractDependencyListener;
|
||||
import org.teavm.dependency.DependencyAgent;
|
||||
import org.teavm.dependency.MethodDependency;
|
||||
|
@ -35,7 +34,7 @@ class JSDependencyListener extends AbstractDependencyListener {
|
|||
@Override
|
||||
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
||||
MethodReference ref = method.getReference();
|
||||
Set<MethodReference> callbackMethods = repository.callbackMethods.get(ref);
|
||||
var callbackMethods = repository.callbackMethods.get(ref);
|
||||
if (callbackMethods != null) {
|
||||
for (MethodReference callbackMethod : callbackMethods) {
|
||||
agent.linkMethod(callbackMethod).addLocation(new CallLocation(ref)).use();
|
||||
|
|
|
@ -55,14 +55,27 @@ public class JSOPlugin implements TeaVMPlugin {
|
|||
var wrapperGenerator = new JSWrapperGenerator();
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "directJavaToJs", Object.class, JSObject.class),
|
||||
wrapperGenerator);
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "directJsToJava", JSObject.class, Object.class),
|
||||
wrapperGenerator);
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, JSObject.class),
|
||||
wrapperGenerator);
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class),
|
||||
wrapperGenerator);
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "isJava", Object.class, boolean.class),
|
||||
wrapperGenerator);
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "isJava", JSObject.class, boolean.class),
|
||||
wrapperGenerator);
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "wrapperToJs", JSWrapper.class, JSObject.class),
|
||||
wrapperGenerator);
|
||||
jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
|
||||
wrapperGenerator);
|
||||
|
||||
host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
|
||||
wrapperGenerator);
|
||||
host.add(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, JSObject.class),
|
||||
wrapperGenerator);
|
||||
host.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class),
|
||||
wrapperGenerator);
|
||||
|
||||
TeaVMPluginUtil.handleNatives(host, JS.class);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class JSTypeHelper {
|
|||
private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>();
|
||||
private Map<String, Boolean> knownJavaScriptImplementations = new HashMap<>();
|
||||
|
||||
public JSTypeHelper(ClassReaderSource classSource) {
|
||||
JSTypeHelper(ClassReaderSource classSource) {
|
||||
this.classSource = classSource;
|
||||
knownJavaScriptClasses.put(JSObject.class.getName(), true);
|
||||
}
|
||||
|
@ -91,7 +91,8 @@ class JSTypeHelper {
|
|||
return isSupportedType(((ValueType.Array) type).getItemType());
|
||||
} else if (type instanceof ValueType.Object) {
|
||||
String typeName = ((ValueType.Object) type).getClassName();
|
||||
return typeName.equals("java.lang.String") || isJavaScriptClass(typeName);
|
||||
return typeName.equals("java.lang.String") || typeName.equals("java.lang.Object")
|
||||
|| isJavaScriptClass(typeName);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -107,6 +107,16 @@ class JSValueMarshaller {
|
|||
|
||||
if (type instanceof ValueType.Object) {
|
||||
String className = ((ValueType.Object) type).getClassName();
|
||||
if (className.equals("java.lang.Object")) {
|
||||
var unwrapNative = new InvokeInstruction();
|
||||
unwrapNative.setLocation(location);
|
||||
unwrapNative.setType(InvocationType.SPECIAL);
|
||||
unwrapNative.setMethod(new MethodReference(JSWrapper.class, "javaToJs", Object.class, JSObject.class));
|
||||
unwrapNative.setArguments(var);
|
||||
unwrapNative.setReceiver(program.createVariable());
|
||||
replacement.add(unwrapNative);
|
||||
return unwrapNative.getReceiver();
|
||||
}
|
||||
if (!className.equals("java.lang.String")) {
|
||||
return var;
|
||||
}
|
||||
|
@ -282,7 +292,16 @@ class JSValueMarshaller {
|
|||
}
|
||||
} else if (type instanceof ValueType.Object) {
|
||||
String className = ((ValueType.Object) type).getClassName();
|
||||
if (className.equals(JSObject.class.getName())) {
|
||||
if (className.equals(Object.class.getName())) {
|
||||
var wrapNative = new InvokeInstruction();
|
||||
wrapNative.setLocation(location.getSourceLocation());
|
||||
wrapNative.setType(InvocationType.SPECIAL);
|
||||
wrapNative.setMethod(new MethodReference(JSWrapper.class, "jsToJava", JSObject.class, Object.class));
|
||||
wrapNative.setArguments(var);
|
||||
wrapNative.setReceiver(program.createVariable());
|
||||
replacement.add(wrapNative);
|
||||
return wrapNative.getReceiver();
|
||||
} else if (className.equals(JSObject.class.getName())) {
|
||||
return var;
|
||||
} else if (className.equals("java.lang.String")) {
|
||||
return unwrap(var, "unwrapString", JSMethods.JS_OBJECT, stringType, location.getSourceLocation());
|
||||
|
|
|
@ -109,6 +109,15 @@ public final class JSWrapper {
|
|||
@NoSideEffects
|
||||
public static native JSObject directJavaToJs(Object obj);
|
||||
|
||||
@NoSideEffects
|
||||
public static native Object directJsToJava(JSObject obj);
|
||||
|
||||
@NoSideEffects
|
||||
public static native JSObject dependencyJavaToJs(Object obj);
|
||||
|
||||
@NoSideEffects
|
||||
public static native Object dependencyJsToJava(JSObject obj);
|
||||
|
||||
@NoSideEffects
|
||||
private static native JSObject wrapperToJs(JSWrapper obj);
|
||||
|
||||
|
@ -118,6 +127,9 @@ public final class JSWrapper {
|
|||
@NoSideEffects
|
||||
public static native boolean isJava(Object obj);
|
||||
|
||||
@NoSideEffects
|
||||
public static native boolean isJava(JSObject obj);
|
||||
|
||||
public static JSObject unwrap(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
|
@ -132,6 +144,20 @@ public final class JSWrapper {
|
|||
return isJava(o) ? unwrap(o) : directJavaToJs(o);
|
||||
}
|
||||
|
||||
public static JSObject javaToJs(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
return isJava(o) && o instanceof JSWrapper ? unwrap(o) : dependencyJavaToJs(o);
|
||||
}
|
||||
|
||||
public static Object jsToJava(JSObject o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
return !isJava(o) ? wrap(directJsToJava(o)) : dependencyJsToJava(o);
|
||||
}
|
||||
|
||||
public static boolean isJs(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
|
|
|
@ -19,15 +19,21 @@ import java.io.IOException;
|
|||
import org.teavm.backend.javascript.spi.Injector;
|
||||
import org.teavm.backend.javascript.spi.InjectorContext;
|
||||
import org.teavm.dependency.DependencyAgent;
|
||||
import org.teavm.dependency.DependencyNode;
|
||||
import org.teavm.dependency.DependencyPlugin;
|
||||
import org.teavm.dependency.MethodDependency;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public class JSWrapperGenerator implements Injector, DependencyPlugin {
|
||||
private DependencyNode externalClassesNode;
|
||||
|
||||
@Override
|
||||
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||
switch (methodRef.getName()) {
|
||||
case "directJavaToJs":
|
||||
case "directJsToJava":
|
||||
case "dependencyJavaToJs":
|
||||
case "dependencyJsToJava":
|
||||
case "wrapperToJs":
|
||||
case "jsToWrapper":
|
||||
context.writeExpr(context.getArgument(0));
|
||||
|
@ -41,8 +47,23 @@ public class JSWrapperGenerator implements Injector, DependencyPlugin {
|
|||
|
||||
@Override
|
||||
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
||||
if (method.getMethod().getName().equals("jsToWrapper")) {
|
||||
switch (method.getMethod().getName()) {
|
||||
case "jsToWrapper":
|
||||
method.getResult().propagate(agent.getType(JSWrapper.class.getName()));
|
||||
break;
|
||||
case "dependencyJavaToJs":
|
||||
method.getVariable(1).connect(getExternalClassesNode(agent));
|
||||
break;
|
||||
case "dependencyJsToJava":
|
||||
getExternalClassesNode(agent).connect(method.getResult());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private DependencyNode getExternalClassesNode(DependencyAgent agent) {
|
||||
if (externalClassesNode == null) {
|
||||
externalClassesNode = agent.createNode();
|
||||
}
|
||||
return externalClassesNode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,9 +229,39 @@ public class JSWrapperTest {
|
|||
assertEquals("org.teavm.jso.impl.JSWrapper", field1.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passJavaToJS() {
|
||||
var a = processObject(new A(23));
|
||||
assertTrue(a instanceof A);
|
||||
assertEquals(23, ((A) a).getX());
|
||||
|
||||
a = processObject(JSString.valueOf("qwe"));
|
||||
assertTrue(a instanceof JSString);
|
||||
assertEquals("qwe", ((JSString) a).stringValue());
|
||||
|
||||
a = processObject(JSNumber.valueOf(23));
|
||||
assertTrue(a instanceof JSString);
|
||||
assertEquals("number", ((JSString) a).stringValue());
|
||||
}
|
||||
|
||||
@JSBody(script = "return null;")
|
||||
private static native JSObject jsNull();
|
||||
|
||||
@JSBody(params = "o", script = "return o === null;")
|
||||
private static native boolean isNull(JSObject o);
|
||||
|
||||
@JSBody(params = "o", script = "return typeof o === 'number' ? 'number' : o;")
|
||||
private static native Object processObject(Object o);
|
||||
|
||||
static class A {
|
||||
private int x;
|
||||
|
||||
A(int x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
int getX() {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,15 +41,15 @@ public class JSOTest {
|
|||
assertNotNull(foundProblem);
|
||||
Object[] params = foundProblem.getParams();
|
||||
assertThat(params[0], is(new MethodReference(JSOTest.class, "jsBodyWithWrongParameter",
|
||||
Object.class, void.class)));
|
||||
A.class, void.class)));
|
||||
}
|
||||
|
||||
private static void callJSBodyWithWrongParameter() {
|
||||
jsBodyWithWrongParameter(23);
|
||||
jsBodyWithWrongParameter(new A());
|
||||
}
|
||||
|
||||
@JSBody(params = "param", script = "alert(param.toString());")
|
||||
private static native void jsBodyWithWrongParameter(Object param);
|
||||
private static native void jsBodyWithWrongParameter(A param);
|
||||
|
||||
@Test
|
||||
public void reportsAboutWrongNonStaticJSBody() {
|
||||
|
@ -69,7 +69,7 @@ public class JSOTest {
|
|||
new JSOTest().wrongNonStaticJSBody();
|
||||
}
|
||||
|
||||
@JSBody(params = {}, script = "alert(this.toString());")
|
||||
@JSBody(script = "alert(this.toString());")
|
||||
private native void wrongNonStaticJSBody();
|
||||
|
||||
@Test
|
||||
|
@ -83,7 +83,7 @@ public class JSOTest {
|
|||
assertNotNull(foundProblem);
|
||||
Object[] params = foundProblem.getParams();
|
||||
assertThat(params[0], is(new MethodReference(JSOTest.class, "jsBodyWithWrongReturningType", String.class,
|
||||
Object.class)));
|
||||
A.class)));
|
||||
}
|
||||
|
||||
private static void callJSBodyWithWrongReturningType() {
|
||||
|
@ -91,7 +91,7 @@ public class JSOTest {
|
|||
}
|
||||
|
||||
@JSBody(params = "value", script = "return value;")
|
||||
private static native Object jsBodyWithWrongReturningType(String value);
|
||||
private static native A jsBodyWithWrongReturningType(String value);
|
||||
|
||||
private List<Problem> build(String methodName) {
|
||||
TeaVM vm = new TeaVMBuilder(new JavaScriptTarget()).build();
|
||||
|
@ -101,4 +101,7 @@ public class JSOTest {
|
|||
vm.build(name -> new ByteArrayOutputStream(), "tmp");
|
||||
return vm.getProblemProvider().getSevereProblems();
|
||||
}
|
||||
|
||||
public static class A {
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user