mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
When Java class gets exported to JS, generate bridges for its
methods so that these bridges perform type conversion between Java and JS
This commit is contained in:
parent
d324847fe6
commit
45ba247265
|
@ -18,10 +18,6 @@ package org.teavm.dependency;
|
||||||
import org.teavm.model.emit.ProgramEmitter;
|
import org.teavm.model.emit.ProgramEmitter;
|
||||||
import org.teavm.model.emit.ValueEmitter;
|
import org.teavm.model.emit.ValueEmitter;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Alexey Andreev
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface BootstrapMethodSubstitutor {
|
public interface BootstrapMethodSubstitutor {
|
||||||
ValueEmitter substitute(DynamicCallSite callSite, ProgramEmitter pe);
|
ValueEmitter substitute(DynamicCallSite callSite, ProgramEmitter pe);
|
||||||
|
|
|
@ -51,6 +51,12 @@ class DependencyClassSource implements ClassHolderSource {
|
||||||
if (innerSource.get(cls.getName()) != null || generatedClasses.containsKey(cls.getName())) {
|
if (innerSource.get(cls.getName()) != null || generatedClasses.containsKey(cls.getName())) {
|
||||||
throw new IllegalArgumentException("Class " + cls.getName() + " is already defined");
|
throw new IllegalArgumentException("Class " + cls.getName() + " is already defined");
|
||||||
}
|
}
|
||||||
|
if (!transformers.isEmpty()) {
|
||||||
|
for (ClassHolderTransformer transformer : transformers) {
|
||||||
|
transformer.transformClass(cls, innerSource, diagnostics);
|
||||||
|
}
|
||||||
|
cls = ModelUtils.copyClass(cls);
|
||||||
|
}
|
||||||
generatedClasses.put(cls.getName(), cls);
|
generatedClasses.put(cls.getName(), cls);
|
||||||
for (MethodHolder method : cls.getMethods()) {
|
for (MethodHolder method : cls.getMethods()) {
|
||||||
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
|
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
|
||||||
|
|
|
@ -23,10 +23,6 @@ import org.teavm.model.MethodHandle;
|
||||||
import org.teavm.model.RuntimeConstant;
|
import org.teavm.model.RuntimeConstant;
|
||||||
import org.teavm.model.emit.ValueEmitter;
|
import org.teavm.model.emit.ValueEmitter;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Alexey Andreev
|
|
||||||
*/
|
|
||||||
public class DynamicCallSite {
|
public class DynamicCallSite {
|
||||||
private MethodDescriptor calledMethod;
|
private MethodDescriptor calledMethod;
|
||||||
private ValueEmitter instance;
|
private ValueEmitter instance;
|
||||||
|
|
|
@ -145,8 +145,8 @@ public class Program implements ProgramReader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Variable variableAt(int index) {
|
public Variable variableAt(int index) {
|
||||||
if (index < 0) {
|
if (index < 0 || index >= variables.size()) {
|
||||||
throw new IllegalArgumentException("Index " + index + " is negative");
|
throw new IllegalArgumentException("Index " + index + " is out of range");
|
||||||
}
|
}
|
||||||
return variables.get(index);
|
return variables.get(index);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Alexey Andreev
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@interface FunctorImpl {
|
@interface FunctorImpl {
|
||||||
String value();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,29 +16,24 @@
|
||||||
package org.teavm.jso.impl;
|
package org.teavm.jso.impl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||||
import org.teavm.backend.javascript.rendering.RenderingManager;
|
import org.teavm.backend.javascript.rendering.RenderingManager;
|
||||||
import org.teavm.jso.impl.JSDependencyListener.ExposedClass;
|
import org.teavm.model.AnnotationReader;
|
||||||
import org.teavm.model.ClassReader;
|
import org.teavm.model.ClassReader;
|
||||||
import org.teavm.model.ClassReaderSource;
|
import org.teavm.model.FieldReader;
|
||||||
|
import org.teavm.model.FieldReference;
|
||||||
|
import org.teavm.model.ListableClassReaderSource;
|
||||||
import org.teavm.model.MethodDescriptor;
|
import org.teavm.model.MethodDescriptor;
|
||||||
|
import org.teavm.model.MethodReader;
|
||||||
import org.teavm.vm.BuildTarget;
|
import org.teavm.vm.BuildTarget;
|
||||||
import org.teavm.vm.spi.RendererListener;
|
import org.teavm.vm.spi.RendererListener;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Alexey Andreev
|
|
||||||
*/
|
|
||||||
class JSAliasRenderer implements RendererListener {
|
class JSAliasRenderer implements RendererListener {
|
||||||
private static String variableChars = "abcdefghijklmnopqrstuvwxyz";
|
private static String variableChars = "abcdefghijklmnopqrstuvwxyz";
|
||||||
private JSDependencyListener dependencyListener;
|
|
||||||
private SourceWriter writer;
|
private SourceWriter writer;
|
||||||
private ClassReaderSource classSource;
|
private ListableClassReaderSource classSource;
|
||||||
|
|
||||||
public JSAliasRenderer(JSDependencyListener dependencyListener) {
|
|
||||||
this.dependencyListener = dependencyListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void begin(RenderingManager context, BuildTarget buildTarget) throws IOException {
|
public void begin(RenderingManager context, BuildTarget buildTarget) throws IOException {
|
||||||
|
@ -48,25 +43,32 @@ class JSAliasRenderer implements RendererListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void complete() throws IOException {
|
public void complete() throws IOException {
|
||||||
if (!dependencyListener.isAnyAliasExists()) {
|
if (!hasClassesToExpose()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.append("(function()").ws().append("{").softNewLine().indent();
|
writer.append("(function()").ws().append("{").softNewLine().indent();
|
||||||
writer.append("var c;").softNewLine();
|
writer.append("var c;").softNewLine();
|
||||||
for (Map.Entry<String, ExposedClass> entry : dependencyListener.getExposedClasses().entrySet()) {
|
for (String className : classSource.getClassNames()) {
|
||||||
ExposedClass cls = entry.getValue();
|
ClassReader classReader = classSource.get(className);
|
||||||
ClassReader classReader = classSource.get(entry.getKey());
|
Map<MethodDescriptor, String> methods = new HashMap<>();
|
||||||
if (classReader == null || cls.methods.isEmpty()) {
|
for (MethodReader method : classReader.getMethods()) {
|
||||||
|
String methodAlias = getPublicAlias(method);
|
||||||
|
if (methodAlias != null) {
|
||||||
|
methods.put(method.getDescriptor(), methodAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (methods.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (Map.Entry<MethodDescriptor, String> aliasEntry : cls.methods.entrySet()) {
|
for (Map.Entry<MethodDescriptor, String> aliasEntry : methods.entrySet()) {
|
||||||
if (classReader.getMethod(aliasEntry.getKey()) == null) {
|
if (classReader.getMethod(aliasEntry.getKey()) == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (first) {
|
if (first) {
|
||||||
writer.append("c").ws().append("=").ws().appendClass(entry.getKey()).append(".prototype;")
|
writer.append("c").ws().append("=").ws().appendClass(className).append(".prototype;")
|
||||||
.softNewLine();
|
.softNewLine();
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
@ -78,13 +80,33 @@ class JSAliasRenderer implements RendererListener {
|
||||||
writer.ws().append("=").ws().append("c.").appendMethod(aliasEntry.getKey()).append(";").softNewLine();
|
writer.ws().append("=").ws().append("c.").appendMethod(aliasEntry.getKey()).append(";").softNewLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cls.functorField != null) {
|
FieldReader functorField = getFunctorField(classReader);
|
||||||
writeFunctor(cls);
|
if (functorField != null) {
|
||||||
|
writeFunctor(classReader, functorField.getReference());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.outdent().append("})();").newLine();
|
writer.outdent().append("})();").newLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasClassesToExpose() {
|
||||||
|
for (String className : classSource.getClassNames()) {
|
||||||
|
ClassReader cls = classSource.get(className);
|
||||||
|
if (cls.getMethods().stream().anyMatch(method -> getPublicAlias(method) != null)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPublicAlias(MethodReader method) {
|
||||||
|
AnnotationReader annot = method.getAnnotations().get(JSMethodToExpose.class.getName());
|
||||||
|
return annot != null ? annot.getValue("name").getString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FieldReader getFunctorField(ClassReader cls) {
|
||||||
|
return cls.getField("$$jso_functor$$");
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isKeyword(String id) {
|
private boolean isKeyword(String id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case "with":
|
case "with":
|
||||||
|
@ -104,28 +126,31 @@ class JSAliasRenderer implements RendererListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeFunctor(ExposedClass cls) throws IOException {
|
private void writeFunctor(ClassReader cls, FieldReference functorField) throws IOException {
|
||||||
String alias = cls.methods.get(cls.functorMethod);
|
AnnotationReader implAnnot = cls.getAnnotations().get(FunctorImpl.class.getName());
|
||||||
|
MethodDescriptor functorMethod = MethodDescriptor.parse(implAnnot.getValue("value").getString());
|
||||||
|
String alias = cls.getMethod(functorMethod).getAnnotations()
|
||||||
|
.get(JSMethodToExpose.class.getName()).getValue("name").getString();
|
||||||
if (alias == null) {
|
if (alias == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.append("c.jso$functor$").append(alias).ws().append("=").ws().append("function()").ws().append("{")
|
writer.append("c.jso$functor$").append(alias).ws().append("=").ws().append("function()").ws().append("{")
|
||||||
.indent().softNewLine();
|
.indent().softNewLine();
|
||||||
writer.append("if").ws().append("(!this.").appendField(cls.functorField).append(")").ws().append("{")
|
writer.append("if").ws().append("(!this.").appendField(functorField).append(")").ws().append("{")
|
||||||
.indent().softNewLine();
|
.indent().softNewLine();
|
||||||
writer.append("var self").ws().append('=').ws().append("this;").softNewLine();
|
writer.append("var self").ws().append('=').ws().append("this;").softNewLine();
|
||||||
|
|
||||||
writer.append("this.").appendField(cls.functorField).ws().append('=').ws().append("function(");
|
writer.append("this.").appendField(functorField).ws().append('=').ws().append("function(");
|
||||||
appendArguments(cls.functorMethod.parameterCount());
|
appendArguments(functorMethod.parameterCount());
|
||||||
writer.append(")").ws().append('{').indent().softNewLine();
|
writer.append(")").ws().append('{').indent().softNewLine();
|
||||||
writer.append("return self.").appendMethod(cls.functorMethod).append('(');
|
writer.append("return self.").appendMethod(functorMethod).append('(');
|
||||||
appendArguments(cls.functorMethod.parameterCount());
|
appendArguments(functorMethod.parameterCount());
|
||||||
writer.append(");").softNewLine();
|
writer.append(");").softNewLine();
|
||||||
writer.outdent().append("};").softNewLine();
|
writer.outdent().append("};").softNewLine();
|
||||||
|
|
||||||
writer.outdent().append("}").softNewLine();
|
writer.outdent().append("}").softNewLine();
|
||||||
writer.append("return this.").appendField(cls.functorField).append(';').softNewLine();
|
writer.append("return this.").appendField(functorField).append(';').softNewLine();
|
||||||
writer.outdent().append("};").softNewLine();
|
writer.outdent().append("};").softNewLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
|
||||||
import org.mozilla.javascript.CompilerEnvirons;
|
import org.mozilla.javascript.CompilerEnvirons;
|
||||||
import org.mozilla.javascript.Context;
|
import org.mozilla.javascript.Context;
|
||||||
import org.mozilla.javascript.ast.AstNode;
|
import org.mozilla.javascript.ast.AstNode;
|
||||||
|
@ -43,8 +42,6 @@ import org.teavm.jso.JSIndexer;
|
||||||
import org.teavm.jso.JSMethod;
|
import org.teavm.jso.JSMethod;
|
||||||
import org.teavm.jso.JSObject;
|
import org.teavm.jso.JSObject;
|
||||||
import org.teavm.jso.JSProperty;
|
import org.teavm.jso.JSProperty;
|
||||||
import org.teavm.jso.core.JSArray;
|
|
||||||
import org.teavm.jso.core.JSArrayReader;
|
|
||||||
import org.teavm.model.AccessLevel;
|
import org.teavm.model.AccessLevel;
|
||||||
import org.teavm.model.AnnotationContainerReader;
|
import org.teavm.model.AnnotationContainerReader;
|
||||||
import org.teavm.model.AnnotationHolder;
|
import org.teavm.model.AnnotationHolder;
|
||||||
|
@ -56,7 +53,6 @@ import org.teavm.model.ClassHolder;
|
||||||
import org.teavm.model.ClassReader;
|
import org.teavm.model.ClassReader;
|
||||||
import org.teavm.model.ClassReaderSource;
|
import org.teavm.model.ClassReaderSource;
|
||||||
import org.teavm.model.ElementModifier;
|
import org.teavm.model.ElementModifier;
|
||||||
import org.teavm.model.FieldHolder;
|
|
||||||
import org.teavm.model.Instruction;
|
import org.teavm.model.Instruction;
|
||||||
import org.teavm.model.MethodDescriptor;
|
import org.teavm.model.MethodDescriptor;
|
||||||
import org.teavm.model.MethodHolder;
|
import org.teavm.model.MethodHolder;
|
||||||
|
@ -67,8 +63,6 @@ import org.teavm.model.TextLocation;
|
||||||
import org.teavm.model.ValueType;
|
import org.teavm.model.ValueType;
|
||||||
import org.teavm.model.Variable;
|
import org.teavm.model.Variable;
|
||||||
import org.teavm.model.instructions.AssignInstruction;
|
import org.teavm.model.instructions.AssignInstruction;
|
||||||
import org.teavm.model.instructions.CastInstruction;
|
|
||||||
import org.teavm.model.instructions.ClassConstantInstruction;
|
|
||||||
import org.teavm.model.instructions.ExitInstruction;
|
import org.teavm.model.instructions.ExitInstruction;
|
||||||
import org.teavm.model.instructions.InvocationType;
|
import org.teavm.model.instructions.InvocationType;
|
||||||
import org.teavm.model.instructions.InvokeInstruction;
|
import org.teavm.model.instructions.InvokeInstruction;
|
||||||
|
@ -86,13 +80,15 @@ class JSClassProcessor {
|
||||||
private final JSTypeHelper typeHelper;
|
private final JSTypeHelper typeHelper;
|
||||||
private final Diagnostics diagnostics;
|
private final Diagnostics diagnostics;
|
||||||
private int methodIndexGenerator;
|
private int methodIndexGenerator;
|
||||||
private final Map<MethodReference, MethodReader> overridenMethodCache = new HashMap<>();
|
private final Map<MethodReference, MethodReader> overriddenMethodCache = new HashMap<>();
|
||||||
|
private JSValueMarshaller marshaller;
|
||||||
|
|
||||||
public JSClassProcessor(ClassReaderSource classSource, JSBodyRepository repository, Diagnostics diagnostics) {
|
JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository,
|
||||||
|
Diagnostics diagnostics) {
|
||||||
this.classSource = classSource;
|
this.classSource = classSource;
|
||||||
|
this.typeHelper = typeHelper;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
typeHelper = new JSTypeHelper(classSource);
|
|
||||||
javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, repository, classSource, diagnostics);
|
javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, repository, classSource, diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,15 +96,7 @@ class JSClassProcessor {
|
||||||
return classSource;
|
return classSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNative(String className) {
|
MethodReference isFunctor(String className) {
|
||||||
return typeHelper.isJavaScriptClass(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNativeImplementation(String className) {
|
|
||||||
return typeHelper.isJavaScriptImplementation(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodReference isFunctor(String className) {
|
|
||||||
if (!typeHelper.isJavaScriptImplementation(className)) {
|
if (!typeHelper.isJavaScriptImplementation(className)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -135,7 +123,7 @@ class JSClassProcessor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processClass(ClassHolder cls) {
|
void processClass(ClassHolder cls) {
|
||||||
Set<MethodDescriptor> preservedMethods = new HashSet<>();
|
Set<MethodDescriptor> preservedMethods = new HashSet<>();
|
||||||
for (String iface : cls.getInterfaces()) {
|
for (String iface : cls.getInterfaces()) {
|
||||||
if (typeHelper.isJavaScriptClass(iface)) {
|
if (typeHelper.isJavaScriptClass(iface)) {
|
||||||
|
@ -154,7 +142,7 @@ class JSClassProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processMemberMethods(ClassHolder cls) {
|
void processMemberMethods(ClassHolder cls) {
|
||||||
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
||||||
if (method.hasModifier(ElementModifier.STATIC)) {
|
if (method.hasModifier(ElementModifier.STATIC)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -185,10 +173,10 @@ class JSClassProcessor {
|
||||||
|
|
||||||
private MethodReader getOverriddenMethod(MethodReader finalMethod) {
|
private MethodReader getOverriddenMethod(MethodReader finalMethod) {
|
||||||
MethodReference ref = finalMethod.getReference();
|
MethodReference ref = finalMethod.getReference();
|
||||||
if (!overridenMethodCache.containsKey(ref)) {
|
if (!overriddenMethodCache.containsKey(ref)) {
|
||||||
overridenMethodCache.put(ref, findOverriddenMethod(finalMethod.getOwnerName(), finalMethod));
|
overriddenMethodCache.put(ref, findOverriddenMethod(finalMethod.getOwnerName(), finalMethod));
|
||||||
}
|
}
|
||||||
return overridenMethodCache.get(ref);
|
return overriddenMethodCache.get(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodReader findOverriddenMethod(String className, MethodReader finalMethod) {
|
private MethodReader findOverriddenMethod(String className, MethodReader finalMethod) {
|
||||||
|
@ -203,22 +191,7 @@ class JSClassProcessor {
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addFunctorField(ClassHolder cls, MethodReference method) {
|
void makeSync(ClassHolder cls) {
|
||||||
if (cls.getAnnotations().get(FunctorImpl.class.getName()) != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FieldHolder field = new FieldHolder("$$jso_functor$$");
|
|
||||||
field.setLevel(AccessLevel.PUBLIC);
|
|
||||||
field.setType(ValueType.parse(JSObject.class));
|
|
||||||
cls.addField(field);
|
|
||||||
|
|
||||||
AnnotationHolder annot = new AnnotationHolder(FunctorImpl.class.getName());
|
|
||||||
annot.getValues().put("value", new AnnotationValue(method.getDescriptor().toString()));
|
|
||||||
cls.getAnnotations().add(annot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void makeSync(ClassHolder cls) {
|
|
||||||
Set<MethodDescriptor> methods = new HashSet<>();
|
Set<MethodDescriptor> methods = new HashSet<>();
|
||||||
findInheritedMethods(cls, methods, new HashSet<>());
|
findInheritedMethods(cls, methods, new HashSet<>());
|
||||||
for (MethodHolder method : cls.getMethods()) {
|
for (MethodHolder method : cls.getMethods()) {
|
||||||
|
@ -233,14 +206,14 @@ class JSClassProcessor {
|
||||||
if (!visited.add(cls.getName())) {
|
if (!visited.add(cls.getName())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isNative(cls.getName())) {
|
if (typeHelper.isJavaScriptClass(cls.getName())) {
|
||||||
for (MethodReader method : cls.getMethods()) {
|
for (MethodReader method : cls.getMethods()) {
|
||||||
if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.FINAL)
|
if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.FINAL)
|
||||||
&& method.getLevel() != AccessLevel.PRIVATE) {
|
&& method.getLevel() != AccessLevel.PRIVATE) {
|
||||||
methods.add(method.getDescriptor());
|
methods.add(method.getDescriptor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isNativeImplementation(cls.getName())) {
|
} else if (typeHelper.isJavaScriptImplementation(cls.getName())) {
|
||||||
if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) {
|
if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) {
|
||||||
ClassReader parentCls = classSource.get(cls.getParent());
|
ClassReader parentCls = classSource.get(cls.getParent());
|
||||||
if (parentCls != null) {
|
if (parentCls != null) {
|
||||||
|
@ -266,8 +239,13 @@ class JSClassProcessor {
|
||||||
return staticSignature;
|
return staticSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setCurrentProgram(Program program) {
|
||||||
|
this.program = program;
|
||||||
|
marshaller = new JSValueMarshaller(diagnostics, typeHelper, program, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
void processProgram(MethodHolder methodToProcess) {
|
void processProgram(MethodHolder methodToProcess) {
|
||||||
program = methodToProcess.getProgram();
|
setCurrentProgram(methodToProcess.getProgram());
|
||||||
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
||||||
BasicBlock block = program.basicBlockAt(i);
|
BasicBlock block = program.basicBlockAt(i);
|
||||||
for (Instruction insn : block) {
|
for (Instruction insn : block) {
|
||||||
|
@ -366,7 +344,7 @@ class JSClassProcessor {
|
||||||
}
|
}
|
||||||
replacement.add(newInvoke);
|
replacement.add(newInvoke);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,7 +365,7 @@ class JSClassProcessor {
|
||||||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
|
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -416,18 +394,18 @@ class JSClassProcessor {
|
||||||
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
||||||
if (isProperGetIndexer(method.getDescriptor())) {
|
if (isProperGetIndexer(method.getDescriptor())) {
|
||||||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||||
addIndexerGet(invoke.getInstance(), wrap(invoke.getArguments().get(0),
|
addIndexerGet(invoke.getInstance(), marshaller.wrap(invoke.getArguments().get(0),
|
||||||
method.parameterType(0), invoke.getLocation(), false), result, invoke.getLocation());
|
method.parameterType(0), invoke.getLocation(), false), result, invoke.getLocation());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (isProperSetIndexer(method.getDescriptor())) {
|
if (isProperSetIndexer(method.getDescriptor())) {
|
||||||
Variable index = wrap(invoke.getArguments().get(0), method.parameterType(0),
|
Variable index = marshaller.wrap(invoke.getArguments().get(0), method.parameterType(0),
|
||||||
invoke.getLocation(), false);
|
invoke.getLocation(), false);
|
||||||
Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1),
|
Variable value = marshaller.wrap(invoke.getArguments().get(1), method.parameterType(1),
|
||||||
invoke.getLocation(), false);
|
invoke.getLocation(), false);
|
||||||
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
|
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
|
||||||
return true;
|
return true;
|
||||||
|
@ -501,7 +479,7 @@ class JSClassProcessor {
|
||||||
}
|
}
|
||||||
replacement.add(newInvoke);
|
replacement.add(newInvoke);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
result = unwrap(callLocation, result, method.getResultType());
|
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,7 +575,7 @@ class JSClassProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createJSMethods(ClassHolder cls) {
|
void createJSMethods(ClassHolder cls) {
|
||||||
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
||||||
MethodReference methodRef = method.getReference();
|
MethodReference methodRef = method.getReference();
|
||||||
if (method.getAnnotations().get(JSBody.class.getName()) == null) {
|
if (method.getAnnotations().get(JSBody.class.getName()) == null) {
|
||||||
|
@ -637,7 +615,7 @@ class JSClassProcessor {
|
||||||
callerMethod.getModifiers().add(ElementModifier.STATIC);
|
callerMethod.getModifiers().add(ElementModifier.STATIC);
|
||||||
CallLocation location = new CallLocation(callback);
|
CallLocation location = new CallLocation(callback);
|
||||||
|
|
||||||
program = new Program();
|
setCurrentProgram(new Program());
|
||||||
for (int i = 0; i <= callback.parameterCount(); ++i) {
|
for (int i = 0; i <= callback.parameterCount(); ++i) {
|
||||||
program.createVariable();
|
program.createVariable();
|
||||||
}
|
}
|
||||||
|
@ -649,11 +627,12 @@ class JSClassProcessor {
|
||||||
insn.setMethod(calleeRef);
|
insn.setMethod(calleeRef);
|
||||||
replacement.clear();
|
replacement.clear();
|
||||||
if (!callee.hasModifier(ElementModifier.STATIC)) {
|
if (!callee.hasModifier(ElementModifier.STATIC)) {
|
||||||
insn.setInstance(unwrap(location, program.variableAt(paramIndex++),
|
insn.setInstance(marshaller.unwrap(location, program.variableAt(paramIndex++),
|
||||||
ValueType.object(calleeRef.getClassName())));
|
ValueType.object(calleeRef.getClassName())));
|
||||||
}
|
}
|
||||||
for (int i = 0; i < callee.parameterCount(); ++i) {
|
for (int i = 0; i < callee.parameterCount(); ++i) {
|
||||||
insn.getArguments().add(unwrap(location, program.variableAt(paramIndex++), callee.parameterType(i)));
|
insn.getArguments().add(marshaller.unwrap(location, program.variableAt(paramIndex++),
|
||||||
|
callee.parameterType(i)));
|
||||||
}
|
}
|
||||||
if (callee.getResultType() != ValueType.VOID) {
|
if (callee.getResultType() != ValueType.VOID) {
|
||||||
insn.setReceiver(program.createVariable());
|
insn.setReceiver(program.createVariable());
|
||||||
|
@ -664,7 +643,7 @@ class JSClassProcessor {
|
||||||
ExitInstruction exit = new ExitInstruction();
|
ExitInstruction exit = new ExitInstruction();
|
||||||
if (insn.getReceiver() != null) {
|
if (insn.getReceiver() != null) {
|
||||||
replacement.clear();
|
replacement.clear();
|
||||||
exit.setValueToReturn(wrap(insn.getReceiver(), callee.getResultType(), null, false));
|
exit.setValueToReturn(marshaller.wrap(insn.getReceiver(), callee.getResultType(), null, false));
|
||||||
block.addAll(replacement);
|
block.addAll(replacement);
|
||||||
}
|
}
|
||||||
block.add(exit);
|
block.add(exit);
|
||||||
|
@ -732,7 +711,7 @@ class JSClassProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Variable addStringWrap(Variable var, TextLocation location) {
|
private Variable addStringWrap(Variable var, TextLocation location) {
|
||||||
return wrap(var, ValueType.object("java.lang.String"), location, false);
|
return marshaller.wrap(var, ValueType.object("java.lang.String"), location, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Variable addString(String str, TextLocation location) {
|
private Variable addString(String str, TextLocation location) {
|
||||||
|
@ -745,240 +724,6 @@ class JSClassProcessor {
|
||||||
return var;
|
return var;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Variable unwrap(CallLocation location, Variable var, ValueType type) {
|
|
||||||
if (type instanceof ValueType.Primitive) {
|
|
||||||
switch (((ValueType.Primitive) type).getKind()) {
|
|
||||||
case BOOLEAN:
|
|
||||||
return unwrap(var, "unwrapBoolean", ValueType.parse(JSObject.class), ValueType.BOOLEAN,
|
|
||||||
location.getSourceLocation());
|
|
||||||
case BYTE:
|
|
||||||
return unwrap(var, "unwrapByte", ValueType.parse(JSObject.class), ValueType.BYTE,
|
|
||||||
location.getSourceLocation());
|
|
||||||
case SHORT:
|
|
||||||
return unwrap(var, "unwrapShort", ValueType.parse(JSObject.class), ValueType.SHORT,
|
|
||||||
location.getSourceLocation());
|
|
||||||
case INTEGER:
|
|
||||||
return unwrap(var, "unwrapInt", ValueType.parse(JSObject.class), ValueType.INTEGER,
|
|
||||||
location.getSourceLocation());
|
|
||||||
case CHARACTER:
|
|
||||||
return unwrap(var, "unwrapCharacter", ValueType.parse(JSObject.class), ValueType.CHARACTER,
|
|
||||||
location.getSourceLocation());
|
|
||||||
case DOUBLE:
|
|
||||||
return unwrap(var, "unwrapDouble", ValueType.parse(JSObject.class), ValueType.DOUBLE,
|
|
||||||
location.getSourceLocation());
|
|
||||||
case FLOAT:
|
|
||||||
return unwrap(var, "unwrapFloat", ValueType.parse(JSObject.class), ValueType.FLOAT,
|
|
||||||
location.getSourceLocation());
|
|
||||||
case LONG:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (type instanceof ValueType.Object) {
|
|
||||||
String className = ((ValueType.Object) type).getClassName();
|
|
||||||
if (className.equals(JSObject.class.getName())) {
|
|
||||||
return var;
|
|
||||||
} else if (className.equals("java.lang.String")) {
|
|
||||||
return unwrap(var, "unwrapString", ValueType.parse(JSObject.class), ValueType.parse(String.class),
|
|
||||||
location.getSourceLocation());
|
|
||||||
} else if (isNative(className)) {
|
|
||||||
Variable result = program.createVariable();
|
|
||||||
CastInstruction castInsn = new CastInstruction();
|
|
||||||
castInsn.setReceiver(result);
|
|
||||||
castInsn.setValue(var);
|
|
||||||
castInsn.setTargetType(type);
|
|
||||||
castInsn.setLocation(location.getSourceLocation());
|
|
||||||
replacement.add(castInsn);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} else if (type instanceof ValueType.Array) {
|
|
||||||
return unwrapArray(location, var, (ValueType.Array) type);
|
|
||||||
}
|
|
||||||
diagnostics.error(location, "Unsupported type: {{t0}}", type);
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable unwrapArray(CallLocation location, Variable var, ValueType.Array type) {
|
|
||||||
ValueType itemType = type;
|
|
||||||
int degree = 0;
|
|
||||||
while (itemType instanceof ValueType.Array) {
|
|
||||||
++degree;
|
|
||||||
itemType = ((ValueType.Array) itemType).getItemType();
|
|
||||||
}
|
|
||||||
|
|
||||||
CastInstruction castInsn = new CastInstruction();
|
|
||||||
castInsn.setValue(var);
|
|
||||||
castInsn.setTargetType(ValueType.parse(JSArrayReader.class));
|
|
||||||
var = program.createVariable();
|
|
||||||
castInsn.setReceiver(var);
|
|
||||||
castInsn.setLocation(location.getSourceLocation());
|
|
||||||
replacement.add(castInsn);
|
|
||||||
|
|
||||||
var = degree == 1
|
|
||||||
? unwrapSingleDimensionArray(location, var, itemType)
|
|
||||||
: unwrapMultiDimensionArray(location, var, itemType, degree);
|
|
||||||
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable unwrapSingleDimensionArray(CallLocation location, Variable var, ValueType type) {
|
|
||||||
Variable result = program.createVariable();
|
|
||||||
|
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(singleDimensionArrayUnwrapper(type));
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
|
|
||||||
if (insn.getMethod().parameterCount() == 2) {
|
|
||||||
Variable cls = program.createVariable();
|
|
||||||
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
|
||||||
clsInsn.setConstant(type);
|
|
||||||
clsInsn.setLocation(location.getSourceLocation());
|
|
||||||
clsInsn.setReceiver(cls);
|
|
||||||
replacement.add(clsInsn);
|
|
||||||
insn.getArguments().add(cls);
|
|
||||||
}
|
|
||||||
|
|
||||||
insn.getArguments().add(var);
|
|
||||||
insn.setReceiver(result);
|
|
||||||
replacement.add(insn);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable unwrapMultiDimensionArray(CallLocation location, Variable var, ValueType type, int degree) {
|
|
||||||
Variable function = program.createVariable();
|
|
||||||
|
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(multipleDimensionArrayUnwrapper(type));
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
|
|
||||||
if (insn.getMethod().parameterCount() == 1) {
|
|
||||||
Variable cls = program.createVariable();
|
|
||||||
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
|
||||||
clsInsn.setConstant(type);
|
|
||||||
clsInsn.setLocation(location.getSourceLocation());
|
|
||||||
clsInsn.setReceiver(cls);
|
|
||||||
replacement.add(clsInsn);
|
|
||||||
insn.getArguments().add(cls);
|
|
||||||
}
|
|
||||||
|
|
||||||
insn.setReceiver(function);
|
|
||||||
replacement.add(insn);
|
|
||||||
|
|
||||||
while (--degree > 1) {
|
|
||||||
type = ValueType.arrayOf(type);
|
|
||||||
Variable cls = program.createVariable();
|
|
||||||
|
|
||||||
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
|
||||||
clsInsn.setConstant(type);
|
|
||||||
clsInsn.setLocation(location.getSourceLocation());
|
|
||||||
clsInsn.setReceiver(cls);
|
|
||||||
replacement.add(clsInsn);
|
|
||||||
|
|
||||||
insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(new MethodReference(JS.class, "arrayUnmapper", Class.class, Function.class,
|
|
||||||
Function.class));
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.getArguments().add(cls);
|
|
||||||
insn.getArguments().add(function);
|
|
||||||
function = program.createVariable();
|
|
||||||
insn.setReceiver(function);
|
|
||||||
replacement.add(insn);
|
|
||||||
}
|
|
||||||
|
|
||||||
Variable cls = program.createVariable();
|
|
||||||
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
|
||||||
clsInsn.setConstant(ValueType.arrayOf(type));
|
|
||||||
clsInsn.setLocation(location.getSourceLocation());
|
|
||||||
clsInsn.setReceiver(cls);
|
|
||||||
replacement.add(clsInsn);
|
|
||||||
|
|
||||||
insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(new MethodReference(JS.class, "unmapArray", Class.class, JSArrayReader.class, Function.class,
|
|
||||||
Object[].class));
|
|
||||||
insn.getArguments().add(cls);
|
|
||||||
insn.getArguments().add(var);
|
|
||||||
insn.getArguments().add(function);
|
|
||||||
insn.setReceiver(var);
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.setLocation(location.getSourceLocation());
|
|
||||||
replacement.add(insn);
|
|
||||||
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodReference singleDimensionArrayUnwrapper(ValueType itemType) {
|
|
||||||
if (itemType instanceof ValueType.Primitive) {
|
|
||||||
switch (((ValueType.Primitive) itemType).getKind()) {
|
|
||||||
case BOOLEAN:
|
|
||||||
return new MethodReference(JS.class, "unwrapBooleanArray", JSArrayReader.class, boolean[].class);
|
|
||||||
case BYTE:
|
|
||||||
return new MethodReference(JS.class, "unwrapByteArray", JSArrayReader.class, byte[].class);
|
|
||||||
case SHORT:
|
|
||||||
return new MethodReference(JS.class, "unwrapShortArray", JSArrayReader.class, short[].class);
|
|
||||||
case CHARACTER:
|
|
||||||
return new MethodReference(JS.class, "unwrapCharArray", JSArrayReader.class, char[].class);
|
|
||||||
case INTEGER:
|
|
||||||
return new MethodReference(JS.class, "unwrapIntArray", JSArrayReader.class, int[].class);
|
|
||||||
case FLOAT:
|
|
||||||
return new MethodReference(JS.class, "unwrapFloatArray", JSArrayReader.class, float[].class);
|
|
||||||
case DOUBLE:
|
|
||||||
return new MethodReference(JS.class, "unwrapDoubleArray", JSArrayReader.class, double[].class);
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (itemType.isObject(String.class)) {
|
|
||||||
return new MethodReference(JS.class, "unwrapStringArray", JSArrayReader.class, String[].class);
|
|
||||||
}
|
|
||||||
return new MethodReference(JS.class, "unwrapArray", Class.class, JSArrayReader.class, JSObject[].class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodReference multipleDimensionArrayUnwrapper(ValueType itemType) {
|
|
||||||
if (itemType instanceof ValueType.Primitive) {
|
|
||||||
switch (((ValueType.Primitive) itemType).getKind()) {
|
|
||||||
case BOOLEAN:
|
|
||||||
return new MethodReference(JS.class, "booleanArrayUnwrapper", Function.class);
|
|
||||||
case BYTE:
|
|
||||||
return new MethodReference(JS.class, "byteArrayUnwrapper", Function.class);
|
|
||||||
case SHORT:
|
|
||||||
return new MethodReference(JS.class, "shortArrayUnwrapper", Function.class);
|
|
||||||
case CHARACTER:
|
|
||||||
return new MethodReference(JS.class, "charArrayUnwrapper", Function.class);
|
|
||||||
case INTEGER:
|
|
||||||
return new MethodReference(JS.class, "intArrayUnwrapper", Function.class);
|
|
||||||
case FLOAT:
|
|
||||||
return new MethodReference(JS.class, "floatArrayUnwrapper", Function.class);
|
|
||||||
case DOUBLE:
|
|
||||||
return new MethodReference(JS.class, "doubleArrayUnwrapper", Function.class);
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (itemType.isObject(String.class)) {
|
|
||||||
return new MethodReference(JS.class, "stringArrayUnwrapper", Function.class);
|
|
||||||
}
|
|
||||||
return new MethodReference(JS.class, "arrayUnwrapper", Class.class, Function.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable unwrap(Variable var, String methodName, ValueType argType, ValueType resultType,
|
|
||||||
TextLocation location) {
|
|
||||||
if (!argType.isObject(JSObject.class.getName())) {
|
|
||||||
Variable castValue = program.createVariable();
|
|
||||||
CastInstruction castInsn = new CastInstruction();
|
|
||||||
castInsn.setValue(var);
|
|
||||||
castInsn.setReceiver(castValue);
|
|
||||||
castInsn.setLocation(location);
|
|
||||||
castInsn.setTargetType(argType);
|
|
||||||
replacement.add(castInsn);
|
|
||||||
var = castValue;
|
|
||||||
}
|
|
||||||
Variable result = program.createVariable();
|
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(new MethodReference(JS.class.getName(), methodName, argType, resultType));
|
|
||||||
insn.getArguments().add(var);
|
|
||||||
insn.setReceiver(result);
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.setLocation(location);
|
|
||||||
replacement.add(insn);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Variable wrapArgument(CallLocation location, Variable var, ValueType type, boolean byRef) {
|
private Variable wrapArgument(CallLocation location, Variable var, ValueType type, boolean byRef) {
|
||||||
if (type instanceof ValueType.Object) {
|
if (type instanceof ValueType.Object) {
|
||||||
String className = ((ValueType.Object) type).getClassName();
|
String className = ((ValueType.Object) type).getClassName();
|
||||||
|
@ -987,7 +732,7 @@ class JSClassProcessor {
|
||||||
return wrapFunctor(location, var, cls);
|
return wrapFunctor(location, var, cls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return wrap(var, type, location.getSourceLocation(), byRef);
|
return marshaller.wrap(var, type, location.getSourceLocation(), byRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isProperFunctor(ClassReader type) {
|
private boolean isProperFunctor(ClassReader type) {
|
||||||
|
@ -1020,128 +765,6 @@ class JSClassProcessor {
|
||||||
return functor;
|
return functor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Variable wrap(Variable var, ValueType type, TextLocation location, boolean byRef) {
|
|
||||||
if (byRef) {
|
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(new MethodReference(JS.class, "arrayData", Object.class, JSObject.class));
|
|
||||||
insn.setReceiver(program.createVariable());
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.getArguments().add(var);
|
|
||||||
replacement.add(insn);
|
|
||||||
return insn.getReceiver();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type instanceof ValueType.Object) {
|
|
||||||
String className = ((ValueType.Object) type).getClassName();
|
|
||||||
if (!className.equals("java.lang.String")) {
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Variable result = program.createVariable();
|
|
||||||
|
|
||||||
ValueType itemType = type;
|
|
||||||
int degree = 0;
|
|
||||||
while (itemType instanceof ValueType.Array) {
|
|
||||||
itemType = ((ValueType.Array) itemType).getItemType();
|
|
||||||
++degree;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (degree <= 1) {
|
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(new MethodReference(JS.class.getName(), "wrap", getWrappedType(type),
|
|
||||||
getWrapperType(type)));
|
|
||||||
insn.getArguments().add(var);
|
|
||||||
insn.setReceiver(result);
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.setLocation(location);
|
|
||||||
replacement.add(insn);
|
|
||||||
} else {
|
|
||||||
Variable function = program.createVariable();
|
|
||||||
|
|
||||||
InvokeInstruction insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(getWrapperFunction(itemType));
|
|
||||||
insn.setReceiver(function);
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.setLocation(location);
|
|
||||||
replacement.add(insn);
|
|
||||||
|
|
||||||
while (--degree > 1) {
|
|
||||||
insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(new MethodReference(JS.class, "arrayMapper", Function.class, Function.class));
|
|
||||||
insn.getArguments().add(function);
|
|
||||||
function = program.createVariable();
|
|
||||||
insn.setReceiver(function);
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.setLocation(location);
|
|
||||||
replacement.add(insn);
|
|
||||||
}
|
|
||||||
|
|
||||||
insn = new InvokeInstruction();
|
|
||||||
insn.setMethod(new MethodReference(JS.class.getName(), "map", getWrappedType(type),
|
|
||||||
ValueType.parse(Function.class), getWrapperType(type)));
|
|
||||||
insn.getArguments().add(var);
|
|
||||||
insn.getArguments().add(function);
|
|
||||||
insn.setReceiver(result);
|
|
||||||
insn.setType(InvocationType.SPECIAL);
|
|
||||||
insn.setLocation(location);
|
|
||||||
replacement.add(insn);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodReference getWrapperFunction(ValueType type) {
|
|
||||||
if (type instanceof ValueType.Primitive) {
|
|
||||||
switch (((ValueType.Primitive) type).getKind()) {
|
|
||||||
case BOOLEAN:
|
|
||||||
return new MethodReference(JS.class, "booleanArrayWrapper", Function.class);
|
|
||||||
case BYTE:
|
|
||||||
return new MethodReference(JS.class, "byteArrayWrapper", Function.class);
|
|
||||||
case SHORT:
|
|
||||||
return new MethodReference(JS.class, "shortArrayWrapper", Function.class);
|
|
||||||
case CHARACTER:
|
|
||||||
return new MethodReference(JS.class, "charArrayWrapper", Function.class);
|
|
||||||
case INTEGER:
|
|
||||||
return new MethodReference(JS.class, "intArrayWrapper", Function.class);
|
|
||||||
case FLOAT:
|
|
||||||
return new MethodReference(JS.class, "floatArrayWrapper", Function.class);
|
|
||||||
case DOUBLE:
|
|
||||||
return new MethodReference(JS.class, "doubleArrayWrapper", Function.class);
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (type.isObject(String.class)) {
|
|
||||||
return new MethodReference(JS.class, "stringArrayWrapper", Function.class);
|
|
||||||
}
|
|
||||||
return new MethodReference(JS.class, "arrayWrapper", Function.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueType getWrappedType(ValueType type) {
|
|
||||||
if (type instanceof ValueType.Array) {
|
|
||||||
ValueType itemType = ((ValueType.Array) type).getItemType();
|
|
||||||
if (itemType instanceof ValueType.Array) {
|
|
||||||
return ValueType.parse(Object[].class);
|
|
||||||
} else {
|
|
||||||
return ValueType.arrayOf(getWrappedType(itemType));
|
|
||||||
}
|
|
||||||
} else if (type instanceof ValueType.Object) {
|
|
||||||
if (type.isObject(String.class)) {
|
|
||||||
return type;
|
|
||||||
} else {
|
|
||||||
return ValueType.parse(JSObject.class);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueType getWrapperType(ValueType type) {
|
|
||||||
if (type instanceof ValueType.Array) {
|
|
||||||
return ValueType.parse(JSArray.class);
|
|
||||||
} else {
|
|
||||||
return ValueType.parse(JSObject.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodReader getMethod(MethodReference ref) {
|
private MethodReader getMethod(MethodReference ref) {
|
||||||
ClassReader cls = classSource.get(ref.getClassName());
|
ClassReader cls = classSource.get(ref.getClassName());
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
|
|
|
@ -15,48 +15,23 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.jso.impl;
|
package org.teavm.jso.impl;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.teavm.dependency.AbstractDependencyListener;
|
import org.teavm.dependency.AbstractDependencyListener;
|
||||||
import org.teavm.dependency.DependencyAgent;
|
import org.teavm.dependency.DependencyAgent;
|
||||||
import org.teavm.dependency.MethodDependency;
|
import org.teavm.dependency.MethodDependency;
|
||||||
import org.teavm.jso.JSMethod;
|
|
||||||
import org.teavm.jso.JSObject;
|
|
||||||
import org.teavm.model.AnnotationReader;
|
import org.teavm.model.AnnotationReader;
|
||||||
import org.teavm.model.AnnotationValue;
|
|
||||||
import org.teavm.model.CallLocation;
|
import org.teavm.model.CallLocation;
|
||||||
import org.teavm.model.ClassReader;
|
import org.teavm.model.ClassReader;
|
||||||
import org.teavm.model.ClassReaderSource;
|
|
||||||
import org.teavm.model.ElementModifier;
|
|
||||||
import org.teavm.model.FieldReader;
|
|
||||||
import org.teavm.model.FieldReference;
|
|
||||||
import org.teavm.model.MethodDescriptor;
|
|
||||||
import org.teavm.model.MethodReader;
|
import org.teavm.model.MethodReader;
|
||||||
import org.teavm.model.MethodReference;
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Alexey Andreev
|
|
||||||
*/
|
|
||||||
class JSDependencyListener extends AbstractDependencyListener {
|
class JSDependencyListener extends AbstractDependencyListener {
|
||||||
private Map<String, ExposedClass> exposedClasses = new HashMap<>();
|
|
||||||
private ClassReaderSource classSource;
|
|
||||||
private DependencyAgent agent;
|
|
||||||
private JSBodyRepository repository;
|
private JSBodyRepository repository;
|
||||||
private boolean anyAliasExists;
|
|
||||||
|
|
||||||
public JSDependencyListener(JSBodyRepository repository) {
|
JSDependencyListener(JSBodyRepository repository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void started(DependencyAgent agent) {
|
|
||||||
this.agent = agent;
|
|
||||||
classSource = agent.getClassSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
|
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
|
||||||
MethodReference ref = method.getReference();
|
MethodReference ref = method.getReference();
|
||||||
|
@ -70,108 +45,14 @@ class JSDependencyListener extends AbstractDependencyListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void classReached(DependencyAgent agent, String className, CallLocation location) {
|
public void classReached(DependencyAgent agent, String className, CallLocation location) {
|
||||||
getExposedClass(className);
|
ClassReader cls = agent.getClassSource().get(className);
|
||||||
}
|
for (MethodReader method : cls.getMethods()) {
|
||||||
|
AnnotationReader exposeAnnot = method.getAnnotations().get(JSMethodToExpose.class.getName());
|
||||||
boolean isAnyAliasExists() {
|
if (exposeAnnot != null) {
|
||||||
return anyAliasExists;
|
MethodDependency methodDep = agent.linkMethod(method.getReference(), null);
|
||||||
}
|
methodDep.getVariable(0).propagate(agent.getType(className));
|
||||||
|
methodDep.use();
|
||||||
Map<String, ExposedClass> getExposedClasses() {
|
|
||||||
return exposedClasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ExposedClass {
|
|
||||||
Map<MethodDescriptor, String> inheritedMethods = new HashMap<>();
|
|
||||||
Map<MethodDescriptor, String> methods = new HashMap<>();
|
|
||||||
Set<String> implementedInterfaces = new HashSet<>();
|
|
||||||
FieldReference functorField;
|
|
||||||
MethodDescriptor functorMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExposedClass getExposedClass(String name) {
|
|
||||||
return exposedClasses.computeIfAbsent(name, this::createExposedClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExposedClass createExposedClass(String name) {
|
|
||||||
ClassReader cls = classSource.get(name);
|
|
||||||
ExposedClass exposedCls = new ExposedClass();
|
|
||||||
if (cls == null || cls.hasModifier(ElementModifier.INTERFACE)) {
|
|
||||||
return exposedCls;
|
|
||||||
}
|
|
||||||
if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) {
|
|
||||||
ExposedClass parent = getExposedClass(cls.getParent());
|
|
||||||
exposedCls.inheritedMethods.putAll(parent.inheritedMethods);
|
|
||||||
exposedCls.inheritedMethods.putAll(parent.methods);
|
|
||||||
exposedCls.implementedInterfaces.addAll(parent.implementedInterfaces);
|
|
||||||
}
|
|
||||||
addInterfaces(exposedCls, cls);
|
|
||||||
if (!cls.hasModifier(ElementModifier.ABSTRACT)) {
|
|
||||||
for (MethodReader method : cls.getMethods()) {
|
|
||||||
if (method.getName().equals("<init>")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (exposedCls.inheritedMethods.containsKey(method.getDescriptor())
|
|
||||||
|| exposedCls.methods.containsKey(method.getDescriptor())) {
|
|
||||||
MethodDependency methodDep = agent.linkMethod(method.getReference(), null);
|
|
||||||
methodDep.getVariable(0).propagate(agent.getType(name));
|
|
||||||
methodDep.use();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (exposedCls.functorField == null) {
|
|
||||||
FieldReader functorField = cls.getField("$$jso_functor$$");
|
|
||||||
if (functorField != null) {
|
|
||||||
exposedCls.functorField = functorField.getReference();
|
|
||||||
AnnotationReader annot = cls.getAnnotations().get(FunctorImpl.class.getName());
|
|
||||||
exposedCls.functorMethod = MethodDescriptor.parse(annot.getValue("value").getString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exposedCls;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean addInterfaces(ExposedClass exposedCls, ClassReader cls) {
|
|
||||||
boolean added = false;
|
|
||||||
for (String ifaceName : cls.getInterfaces()) {
|
|
||||||
if (exposedCls.implementedInterfaces.contains(ifaceName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ClassReader iface = classSource.get(ifaceName);
|
|
||||||
if (iface == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (addInterface(exposedCls, iface)) {
|
|
||||||
added = true;
|
|
||||||
for (MethodReader method : iface.getMethods()) {
|
|
||||||
if (method.hasModifier(ElementModifier.STATIC)
|
|
||||||
|| (method.getProgram() != null && method.getProgram().basicBlockCount() > 0)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!exposedCls.inheritedMethods.containsKey(method.getDescriptor())) {
|
|
||||||
String name = method.getName();
|
|
||||||
AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
|
|
||||||
if (methodAnnot != null) {
|
|
||||||
AnnotationValue nameVal = methodAnnot.getValue("value");
|
|
||||||
if (nameVal != null) {
|
|
||||||
String nameStr = nameVal.getString();
|
|
||||||
if (!nameStr.isEmpty()) {
|
|
||||||
name = nameStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exposedCls.methods.put(method.getDescriptor(), name);
|
|
||||||
anyAliasExists = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean addInterface(ExposedClass exposedCls, ClassReader cls) {
|
|
||||||
if (cls.getName().equals(JSObject.class.getName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return addInterfaces(exposedCls, cls);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* 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 org.teavm.jso.impl;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@interface JSMethodToExpose {
|
||||||
|
String name();
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ public class JSOPlugin implements TeaVMPlugin {
|
||||||
host.registerService(JSBodyRepository.class, repository);
|
host.registerService(JSBodyRepository.class, repository);
|
||||||
host.add(new JSObjectClassTransformer(repository));
|
host.add(new JSObjectClassTransformer(repository));
|
||||||
JSDependencyListener dependencyListener = new JSDependencyListener(repository);
|
JSDependencyListener dependencyListener = new JSDependencyListener(repository);
|
||||||
JSAliasRenderer aliasRenderer = new JSAliasRenderer(dependencyListener);
|
JSAliasRenderer aliasRenderer = new JSAliasRenderer();
|
||||||
host.add(dependencyListener);
|
host.add(dependencyListener);
|
||||||
host.getExtension(TeaVMJavaScriptHost.class).add(aliasRenderer);
|
host.getExtension(TeaVMJavaScriptHost.class).add(aliasRenderer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,44 +15,245 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.jso.impl;
|
package org.teavm.jso.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import org.teavm.diagnostics.Diagnostics;
|
import org.teavm.diagnostics.Diagnostics;
|
||||||
|
import org.teavm.jso.JSMethod;
|
||||||
|
import org.teavm.jso.JSObject;
|
||||||
|
import org.teavm.model.AccessLevel;
|
||||||
|
import org.teavm.model.AnnotationHolder;
|
||||||
|
import org.teavm.model.AnnotationReader;
|
||||||
|
import org.teavm.model.AnnotationValue;
|
||||||
|
import org.teavm.model.BasicBlock;
|
||||||
|
import org.teavm.model.CallLocation;
|
||||||
import org.teavm.model.ClassHolder;
|
import org.teavm.model.ClassHolder;
|
||||||
import org.teavm.model.ClassHolderTransformer;
|
import org.teavm.model.ClassHolderTransformer;
|
||||||
|
import org.teavm.model.ClassReader;
|
||||||
import org.teavm.model.ClassReaderSource;
|
import org.teavm.model.ClassReaderSource;
|
||||||
|
import org.teavm.model.ElementModifier;
|
||||||
|
import org.teavm.model.FieldHolder;
|
||||||
|
import org.teavm.model.Instruction;
|
||||||
|
import org.teavm.model.MethodDescriptor;
|
||||||
import org.teavm.model.MethodHolder;
|
import org.teavm.model.MethodHolder;
|
||||||
|
import org.teavm.model.MethodReader;
|
||||||
import org.teavm.model.MethodReference;
|
import org.teavm.model.MethodReference;
|
||||||
|
import org.teavm.model.Program;
|
||||||
|
import org.teavm.model.ValueType;
|
||||||
|
import org.teavm.model.Variable;
|
||||||
|
import org.teavm.model.instructions.ExitInstruction;
|
||||||
|
import org.teavm.model.instructions.InvocationType;
|
||||||
|
import org.teavm.model.instructions.InvokeInstruction;
|
||||||
|
|
||||||
public class JSObjectClassTransformer implements ClassHolderTransformer {
|
class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
private JSClassProcessor processor;
|
private JSClassProcessor processor;
|
||||||
private JSBodyRepository repository;
|
private JSBodyRepository repository;
|
||||||
|
private JSTypeHelper typeHelper;
|
||||||
|
private ClassReaderSource innerSource;
|
||||||
|
private Map<String, ExposedClass> exposedClasses = new HashMap<>();
|
||||||
|
|
||||||
public JSObjectClassTransformer(JSBodyRepository repository) {
|
JSObjectClassTransformer(JSBodyRepository repository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
|
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
|
||||||
|
this.innerSource = innerSource;
|
||||||
if (processor == null || processor.getClassSource() != innerSource) {
|
if (processor == null || processor.getClassSource() != innerSource) {
|
||||||
processor = new JSClassProcessor(innerSource, repository, diagnostics);
|
typeHelper = new JSTypeHelper(innerSource);
|
||||||
|
processor = new JSClassProcessor(innerSource, typeHelper, repository, diagnostics);
|
||||||
}
|
}
|
||||||
processor.processClass(cls);
|
processor.processClass(cls);
|
||||||
if (processor.isNative(cls.getName())) {
|
if (typeHelper.isJavaScriptClass(cls.getName())) {
|
||||||
processor.processMemberMethods(cls);
|
processor.processMemberMethods(cls);
|
||||||
}
|
}
|
||||||
if (processor.isNativeImplementation(cls.getName())) {
|
if (typeHelper.isJavaScriptImplementation(cls.getName())) {
|
||||||
processor.makeSync(cls);
|
processor.makeSync(cls);
|
||||||
}
|
}
|
||||||
MethodReference functorMethod = processor.isFunctor(cls.getName());
|
|
||||||
if (functorMethod != null) {
|
|
||||||
if (processor.isFunctor(cls.getParent()) == null) {
|
|
||||||
processor.addFunctorField(cls, functorMethod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
|
||||||
if (method.getProgram() != null) {
|
if (method.getProgram() != null) {
|
||||||
processor.processProgram(method);
|
processor.processProgram(method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processor.createJSMethods(cls);
|
processor.createJSMethods(cls);
|
||||||
|
|
||||||
|
MethodReference functorMethod = processor.isFunctor(cls.getName());
|
||||||
|
if (functorMethod != null) {
|
||||||
|
if (processor.isFunctor(cls.getParent()) != null) {
|
||||||
|
functorMethod = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassReader originalClass = innerSource.get(cls.getName());
|
||||||
|
ExposedClass exposedClass;
|
||||||
|
if (originalClass != null) {
|
||||||
|
exposedClass = getExposedClass(cls.getName());
|
||||||
|
} else {
|
||||||
|
exposedClass = new ExposedClass();
|
||||||
|
createExposedClass(cls, exposedClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
exposeMethods(cls, exposedClass, diagnostics, functorMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exposeMethods(ClassHolder classHolder, ExposedClass classToExpose, Diagnostics diagnostics,
|
||||||
|
MethodReference functorMethod) {
|
||||||
|
int index = 0;
|
||||||
|
for (MethodDescriptor method : classToExpose.methods.keySet()) {
|
||||||
|
MethodReference methodRef = new MethodReference(classHolder.getName(), method);
|
||||||
|
CallLocation callLocation = new CallLocation(methodRef);
|
||||||
|
|
||||||
|
ValueType[] exportedMethodSignature = Arrays.stream(method.getSignature())
|
||||||
|
.map(type -> ValueType.object(JSObject.class.getName()))
|
||||||
|
.toArray(ValueType[]::new);
|
||||||
|
MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
|
||||||
|
exportedMethodSignature);
|
||||||
|
MethodHolder exportedMethod = new MethodHolder(exportedMethodDesc);
|
||||||
|
Program program = new Program();
|
||||||
|
exportedMethod.setProgram(program);
|
||||||
|
program.createVariable();
|
||||||
|
|
||||||
|
BasicBlock basicBlock = program.createBasicBlock();
|
||||||
|
List<Instruction> marshallInstructions = new ArrayList<>();
|
||||||
|
JSValueMarshaller marshaller = new JSValueMarshaller(diagnostics, typeHelper, program,
|
||||||
|
marshallInstructions);
|
||||||
|
|
||||||
|
List<Variable> variablesToPass = new ArrayList<>();
|
||||||
|
for (int i = 0; i < method.parameterCount(); ++i) {
|
||||||
|
variablesToPass.add(program.createVariable());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < method.parameterCount(); ++i) {
|
||||||
|
Variable var = marshaller.unwrap(callLocation, variablesToPass.get(i), method.parameterType(i));
|
||||||
|
variablesToPass.set(i, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
basicBlock.addAll(marshallInstructions);
|
||||||
|
marshallInstructions.clear();
|
||||||
|
|
||||||
|
InvokeInstruction invocation = new InvokeInstruction();
|
||||||
|
invocation.setType(InvocationType.VIRTUAL);
|
||||||
|
invocation.setInstance(program.variableAt(0));
|
||||||
|
invocation.setMethod(methodRef);
|
||||||
|
invocation.getArguments().addAll(variablesToPass);
|
||||||
|
basicBlock.add(invocation);
|
||||||
|
|
||||||
|
ExitInstruction exit = new ExitInstruction();
|
||||||
|
if (method.getResultType() != ValueType.VOID) {
|
||||||
|
invocation.setReceiver(program.createVariable());
|
||||||
|
exit.setValueToReturn(marshaller.wrap(invocation.getReceiver(), method.getResultType(),
|
||||||
|
null, false));
|
||||||
|
basicBlock.addAll(marshallInstructions);
|
||||||
|
marshallInstructions.clear();
|
||||||
|
}
|
||||||
|
basicBlock.add(exit);
|
||||||
|
|
||||||
|
classHolder.addMethod(exportedMethod);
|
||||||
|
|
||||||
|
String publicAlias = classToExpose.methods.get(method);
|
||||||
|
AnnotationHolder annot = new AnnotationHolder(JSMethodToExpose.class.getName());
|
||||||
|
annot.getValues().put("name", new AnnotationValue(publicAlias));
|
||||||
|
exportedMethod.getAnnotations().add(annot);
|
||||||
|
|
||||||
|
if (methodRef.equals(functorMethod)) {
|
||||||
|
addFunctorField(classHolder, exportedMethod.getReference());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExposedClass getExposedClass(String name) {
|
||||||
|
return exposedClasses.computeIfAbsent(name, this::createExposedClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExposedClass createExposedClass(String name) {
|
||||||
|
ClassReader cls = innerSource.get(name);
|
||||||
|
ExposedClass exposedCls = new ExposedClass();
|
||||||
|
if (cls != null) {
|
||||||
|
createExposedClass(cls, exposedCls);
|
||||||
|
}
|
||||||
|
return exposedCls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createExposedClass(ClassReader cls, ExposedClass exposedCls) {
|
||||||
|
if (cls.hasModifier(ElementModifier.INTERFACE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) {
|
||||||
|
ExposedClass parent = getExposedClass(cls.getParent());
|
||||||
|
exposedCls.inheritedMethods.putAll(parent.inheritedMethods);
|
||||||
|
exposedCls.inheritedMethods.putAll(parent.methods);
|
||||||
|
exposedCls.implementedInterfaces.addAll(parent.implementedInterfaces);
|
||||||
|
}
|
||||||
|
addInterfaces(exposedCls, cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addInterfaces(ExposedClass exposedCls, ClassReader cls) {
|
||||||
|
boolean added = false;
|
||||||
|
for (String ifaceName : cls.getInterfaces()) {
|
||||||
|
if (exposedCls.implementedInterfaces.contains(ifaceName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ClassReader iface = innerSource.get(ifaceName);
|
||||||
|
if (iface == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (addInterface(exposedCls, iface)) {
|
||||||
|
added = true;
|
||||||
|
for (MethodReader method : iface.getMethods()) {
|
||||||
|
if (method.hasModifier(ElementModifier.STATIC)
|
||||||
|
|| (method.getProgram() != null && method.getProgram().basicBlockCount() > 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!exposedCls.inheritedMethods.containsKey(method.getDescriptor())) {
|
||||||
|
String name = method.getName();
|
||||||
|
AnnotationReader methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
|
||||||
|
if (methodAnnot != null) {
|
||||||
|
AnnotationValue nameVal = methodAnnot.getValue("value");
|
||||||
|
if (nameVal != null) {
|
||||||
|
String nameStr = nameVal.getString();
|
||||||
|
if (!nameStr.isEmpty()) {
|
||||||
|
name = nameStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exposedCls.methods.put(method.getDescriptor(), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addInterface(ExposedClass exposedCls, ClassReader cls) {
|
||||||
|
if (cls.getName().equals(JSObject.class.getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return addInterfaces(exposedCls, cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFunctorField(ClassHolder cls, MethodReference method) {
|
||||||
|
if (cls.getAnnotations().get(FunctorImpl.class.getName()) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldHolder field = new FieldHolder("$$jso_functor$$");
|
||||||
|
field.setLevel(AccessLevel.PUBLIC);
|
||||||
|
field.setType(ValueType.parse(JSObject.class));
|
||||||
|
cls.addField(field);
|
||||||
|
|
||||||
|
AnnotationHolder annot = new AnnotationHolder(FunctorImpl.class.getName());
|
||||||
|
annot.getValues().put("value", new AnnotationValue(method.getDescriptor().toString()));
|
||||||
|
cls.getAnnotations().add(annot);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ExposedClass {
|
||||||
|
Map<MethodDescriptor, String> inheritedMethods = new HashMap<>();
|
||||||
|
Map<MethodDescriptor, String> methods = new HashMap<>();
|
||||||
|
Set<String> implementedInterfaces = new HashSet<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
405
jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java
Normal file
405
jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* 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 org.teavm.jso.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.teavm.diagnostics.Diagnostics;
|
||||||
|
import org.teavm.jso.JSObject;
|
||||||
|
import org.teavm.jso.core.JSArray;
|
||||||
|
import org.teavm.jso.core.JSArrayReader;
|
||||||
|
import org.teavm.model.CallLocation;
|
||||||
|
import org.teavm.model.Instruction;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
import org.teavm.model.Program;
|
||||||
|
import org.teavm.model.TextLocation;
|
||||||
|
import org.teavm.model.ValueType;
|
||||||
|
import org.teavm.model.Variable;
|
||||||
|
import org.teavm.model.instructions.CastInstruction;
|
||||||
|
import org.teavm.model.instructions.ClassConstantInstruction;
|
||||||
|
import org.teavm.model.instructions.InvocationType;
|
||||||
|
import org.teavm.model.instructions.InvokeInstruction;
|
||||||
|
|
||||||
|
class JSValueMarshaller {
|
||||||
|
private Diagnostics diagnostics;
|
||||||
|
private JSTypeHelper typeHelper;
|
||||||
|
private Program program;
|
||||||
|
private List<Instruction> replacement;
|
||||||
|
|
||||||
|
JSValueMarshaller(Diagnostics diagnostics, JSTypeHelper typeHelper, Program program,
|
||||||
|
List<Instruction> replacement) {
|
||||||
|
this.diagnostics = diagnostics;
|
||||||
|
this.typeHelper = typeHelper;
|
||||||
|
this.program = program;
|
||||||
|
this.replacement = replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variable wrap(Variable var, ValueType type, TextLocation location, boolean byRef) {
|
||||||
|
if (byRef) {
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(new MethodReference(JS.class, "arrayData", Object.class, JSObject.class));
|
||||||
|
insn.setReceiver(program.createVariable());
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
replacement.add(insn);
|
||||||
|
return insn.getReceiver();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type instanceof ValueType.Object) {
|
||||||
|
String className = ((ValueType.Object) type).getClassName();
|
||||||
|
if (!className.equals("java.lang.String")) {
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Variable result = program.createVariable();
|
||||||
|
|
||||||
|
ValueType itemType = type;
|
||||||
|
int degree = 0;
|
||||||
|
while (itemType instanceof ValueType.Array) {
|
||||||
|
itemType = ((ValueType.Array) itemType).getItemType();
|
||||||
|
++degree;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (degree <= 1) {
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(new MethodReference(JS.class.getName(), "wrap", getWrappedType(type),
|
||||||
|
getWrapperType(type)));
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
insn.setReceiver(result);
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setLocation(location);
|
||||||
|
replacement.add(insn);
|
||||||
|
} else {
|
||||||
|
Variable function = program.createVariable();
|
||||||
|
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(getWrapperFunction(itemType));
|
||||||
|
insn.setReceiver(function);
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setLocation(location);
|
||||||
|
replacement.add(insn);
|
||||||
|
|
||||||
|
while (--degree > 1) {
|
||||||
|
insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(new MethodReference(JS.class, "arrayMapper", Function.class, Function.class));
|
||||||
|
insn.getArguments().add(function);
|
||||||
|
function = program.createVariable();
|
||||||
|
insn.setReceiver(function);
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setLocation(location);
|
||||||
|
replacement.add(insn);
|
||||||
|
}
|
||||||
|
|
||||||
|
insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(new MethodReference(JS.class.getName(), "map", getWrappedType(type),
|
||||||
|
ValueType.parse(Function.class), getWrapperType(type)));
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
insn.getArguments().add(function);
|
||||||
|
insn.setReceiver(result);
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setLocation(location);
|
||||||
|
replacement.add(insn);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueType getWrappedType(ValueType type) {
|
||||||
|
if (type instanceof ValueType.Array) {
|
||||||
|
ValueType itemType = ((ValueType.Array) type).getItemType();
|
||||||
|
if (itemType instanceof ValueType.Array) {
|
||||||
|
return ValueType.parse(Object[].class);
|
||||||
|
} else {
|
||||||
|
return ValueType.arrayOf(getWrappedType(itemType));
|
||||||
|
}
|
||||||
|
} else if (type instanceof ValueType.Object) {
|
||||||
|
if (type.isObject(String.class)) {
|
||||||
|
return type;
|
||||||
|
} else {
|
||||||
|
return ValueType.parse(JSObject.class);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueType getWrapperType(ValueType type) {
|
||||||
|
if (type instanceof ValueType.Array) {
|
||||||
|
return ValueType.parse(JSArray.class);
|
||||||
|
} else {
|
||||||
|
return ValueType.parse(JSObject.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodReference getWrapperFunction(ValueType type) {
|
||||||
|
if (type instanceof ValueType.Primitive) {
|
||||||
|
switch (((ValueType.Primitive) type).getKind()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return new MethodReference(JS.class, "booleanArrayWrapper", Function.class);
|
||||||
|
case BYTE:
|
||||||
|
return new MethodReference(JS.class, "byteArrayWrapper", Function.class);
|
||||||
|
case SHORT:
|
||||||
|
return new MethodReference(JS.class, "shortArrayWrapper", Function.class);
|
||||||
|
case CHARACTER:
|
||||||
|
return new MethodReference(JS.class, "charArrayWrapper", Function.class);
|
||||||
|
case INTEGER:
|
||||||
|
return new MethodReference(JS.class, "intArrayWrapper", Function.class);
|
||||||
|
case FLOAT:
|
||||||
|
return new MethodReference(JS.class, "floatArrayWrapper", Function.class);
|
||||||
|
case DOUBLE:
|
||||||
|
return new MethodReference(JS.class, "doubleArrayWrapper", Function.class);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (type.isObject(String.class)) {
|
||||||
|
return new MethodReference(JS.class, "stringArrayWrapper", Function.class);
|
||||||
|
}
|
||||||
|
return new MethodReference(JS.class, "arrayWrapper", Function.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable unwrap(CallLocation location, Variable var, ValueType type) {
|
||||||
|
if (type instanceof ValueType.Primitive) {
|
||||||
|
switch (((ValueType.Primitive) type).getKind()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return unwrap(var, "unwrapBoolean", ValueType.parse(JSObject.class), ValueType.BOOLEAN,
|
||||||
|
location.getSourceLocation());
|
||||||
|
case BYTE:
|
||||||
|
return unwrap(var, "unwrapByte", ValueType.parse(JSObject.class), ValueType.BYTE,
|
||||||
|
location.getSourceLocation());
|
||||||
|
case SHORT:
|
||||||
|
return unwrap(var, "unwrapShort", ValueType.parse(JSObject.class), ValueType.SHORT,
|
||||||
|
location.getSourceLocation());
|
||||||
|
case INTEGER:
|
||||||
|
return unwrap(var, "unwrapInt", ValueType.parse(JSObject.class), ValueType.INTEGER,
|
||||||
|
location.getSourceLocation());
|
||||||
|
case CHARACTER:
|
||||||
|
return unwrap(var, "unwrapCharacter", ValueType.parse(JSObject.class), ValueType.CHARACTER,
|
||||||
|
location.getSourceLocation());
|
||||||
|
case DOUBLE:
|
||||||
|
return unwrap(var, "unwrapDouble", ValueType.parse(JSObject.class), ValueType.DOUBLE,
|
||||||
|
location.getSourceLocation());
|
||||||
|
case FLOAT:
|
||||||
|
return unwrap(var, "unwrapFloat", ValueType.parse(JSObject.class), ValueType.FLOAT,
|
||||||
|
location.getSourceLocation());
|
||||||
|
case LONG:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (type instanceof ValueType.Object) {
|
||||||
|
String className = ((ValueType.Object) type).getClassName();
|
||||||
|
if (className.equals(JSObject.class.getName())) {
|
||||||
|
return var;
|
||||||
|
} else if (className.equals("java.lang.String")) {
|
||||||
|
return unwrap(var, "unwrapString", ValueType.parse(JSObject.class), ValueType.parse(String.class),
|
||||||
|
location.getSourceLocation());
|
||||||
|
} else if (typeHelper.isJavaScriptClass(className)) {
|
||||||
|
Variable result = program.createVariable();
|
||||||
|
CastInstruction castInsn = new CastInstruction();
|
||||||
|
castInsn.setReceiver(result);
|
||||||
|
castInsn.setValue(var);
|
||||||
|
castInsn.setTargetType(type);
|
||||||
|
castInsn.setLocation(location.getSourceLocation());
|
||||||
|
replacement.add(castInsn);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else if (type instanceof ValueType.Array) {
|
||||||
|
return unwrapArray(location, var, (ValueType.Array) type);
|
||||||
|
}
|
||||||
|
diagnostics.error(location, "Unsupported type: {{t0}}", type);
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable unwrapArray(CallLocation location, Variable var, ValueType.Array type) {
|
||||||
|
ValueType itemType = type;
|
||||||
|
int degree = 0;
|
||||||
|
while (itemType instanceof ValueType.Array) {
|
||||||
|
++degree;
|
||||||
|
itemType = ((ValueType.Array) itemType).getItemType();
|
||||||
|
}
|
||||||
|
|
||||||
|
CastInstruction castInsn = new CastInstruction();
|
||||||
|
castInsn.setValue(var);
|
||||||
|
castInsn.setTargetType(ValueType.parse(JSArrayReader.class));
|
||||||
|
var = program.createVariable();
|
||||||
|
castInsn.setReceiver(var);
|
||||||
|
castInsn.setLocation(location.getSourceLocation());
|
||||||
|
replacement.add(castInsn);
|
||||||
|
|
||||||
|
var = degree == 1
|
||||||
|
? unwrapSingleDimensionArray(location, var, itemType)
|
||||||
|
: unwrapMultiDimensionArray(location, var, itemType, degree);
|
||||||
|
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable unwrapSingleDimensionArray(CallLocation location, Variable var, ValueType type) {
|
||||||
|
Variable result = program.createVariable();
|
||||||
|
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(singleDimensionArrayUnwrapper(type));
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
|
||||||
|
if (insn.getMethod().parameterCount() == 2) {
|
||||||
|
Variable cls = program.createVariable();
|
||||||
|
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
||||||
|
clsInsn.setConstant(type);
|
||||||
|
clsInsn.setLocation(location.getSourceLocation());
|
||||||
|
clsInsn.setReceiver(cls);
|
||||||
|
replacement.add(clsInsn);
|
||||||
|
insn.getArguments().add(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
insn.setReceiver(result);
|
||||||
|
replacement.add(insn);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable unwrapMultiDimensionArray(CallLocation location, Variable var, ValueType type, int degree) {
|
||||||
|
Variable function = program.createVariable();
|
||||||
|
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(multipleDimensionArrayUnwrapper(type));
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
|
||||||
|
if (insn.getMethod().parameterCount() == 1) {
|
||||||
|
Variable cls = program.createVariable();
|
||||||
|
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
||||||
|
clsInsn.setConstant(type);
|
||||||
|
clsInsn.setLocation(location.getSourceLocation());
|
||||||
|
clsInsn.setReceiver(cls);
|
||||||
|
replacement.add(clsInsn);
|
||||||
|
insn.getArguments().add(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
insn.setReceiver(function);
|
||||||
|
replacement.add(insn);
|
||||||
|
|
||||||
|
while (--degree > 1) {
|
||||||
|
type = ValueType.arrayOf(type);
|
||||||
|
Variable cls = program.createVariable();
|
||||||
|
|
||||||
|
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
||||||
|
clsInsn.setConstant(type);
|
||||||
|
clsInsn.setLocation(location.getSourceLocation());
|
||||||
|
clsInsn.setReceiver(cls);
|
||||||
|
replacement.add(clsInsn);
|
||||||
|
|
||||||
|
insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(new MethodReference(JS.class, "arrayUnmapper", Class.class, Function.class,
|
||||||
|
Function.class));
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.getArguments().add(cls);
|
||||||
|
insn.getArguments().add(function);
|
||||||
|
function = program.createVariable();
|
||||||
|
insn.setReceiver(function);
|
||||||
|
replacement.add(insn);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable cls = program.createVariable();
|
||||||
|
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
|
||||||
|
clsInsn.setConstant(ValueType.arrayOf(type));
|
||||||
|
clsInsn.setLocation(location.getSourceLocation());
|
||||||
|
clsInsn.setReceiver(cls);
|
||||||
|
replacement.add(clsInsn);
|
||||||
|
|
||||||
|
insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(new MethodReference(JS.class, "unmapArray", Class.class, JSArrayReader.class, Function.class,
|
||||||
|
Object[].class));
|
||||||
|
insn.getArguments().add(cls);
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
insn.getArguments().add(function);
|
||||||
|
insn.setReceiver(var);
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setLocation(location.getSourceLocation());
|
||||||
|
replacement.add(insn);
|
||||||
|
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodReference singleDimensionArrayUnwrapper(ValueType itemType) {
|
||||||
|
if (itemType instanceof ValueType.Primitive) {
|
||||||
|
switch (((ValueType.Primitive) itemType).getKind()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return new MethodReference(JS.class, "unwrapBooleanArray", JSArrayReader.class, boolean[].class);
|
||||||
|
case BYTE:
|
||||||
|
return new MethodReference(JS.class, "unwrapByteArray", JSArrayReader.class, byte[].class);
|
||||||
|
case SHORT:
|
||||||
|
return new MethodReference(JS.class, "unwrapShortArray", JSArrayReader.class, short[].class);
|
||||||
|
case CHARACTER:
|
||||||
|
return new MethodReference(JS.class, "unwrapCharArray", JSArrayReader.class, char[].class);
|
||||||
|
case INTEGER:
|
||||||
|
return new MethodReference(JS.class, "unwrapIntArray", JSArrayReader.class, int[].class);
|
||||||
|
case FLOAT:
|
||||||
|
return new MethodReference(JS.class, "unwrapFloatArray", JSArrayReader.class, float[].class);
|
||||||
|
case DOUBLE:
|
||||||
|
return new MethodReference(JS.class, "unwrapDoubleArray", JSArrayReader.class, double[].class);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (itemType.isObject(String.class)) {
|
||||||
|
return new MethodReference(JS.class, "unwrapStringArray", JSArrayReader.class, String[].class);
|
||||||
|
}
|
||||||
|
return new MethodReference(JS.class, "unwrapArray", Class.class, JSArrayReader.class, JSObject[].class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodReference multipleDimensionArrayUnwrapper(ValueType itemType) {
|
||||||
|
if (itemType instanceof ValueType.Primitive) {
|
||||||
|
switch (((ValueType.Primitive) itemType).getKind()) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return new MethodReference(JS.class, "booleanArrayUnwrapper", Function.class);
|
||||||
|
case BYTE:
|
||||||
|
return new MethodReference(JS.class, "byteArrayUnwrapper", Function.class);
|
||||||
|
case SHORT:
|
||||||
|
return new MethodReference(JS.class, "shortArrayUnwrapper", Function.class);
|
||||||
|
case CHARACTER:
|
||||||
|
return new MethodReference(JS.class, "charArrayUnwrapper", Function.class);
|
||||||
|
case INTEGER:
|
||||||
|
return new MethodReference(JS.class, "intArrayUnwrapper", Function.class);
|
||||||
|
case FLOAT:
|
||||||
|
return new MethodReference(JS.class, "floatArrayUnwrapper", Function.class);
|
||||||
|
case DOUBLE:
|
||||||
|
return new MethodReference(JS.class, "doubleArrayUnwrapper", Function.class);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (itemType.isObject(String.class)) {
|
||||||
|
return new MethodReference(JS.class, "stringArrayUnwrapper", Function.class);
|
||||||
|
}
|
||||||
|
return new MethodReference(JS.class, "arrayUnwrapper", Class.class, Function.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Variable unwrap(Variable var, String methodName, ValueType argType, ValueType resultType,
|
||||||
|
TextLocation location) {
|
||||||
|
if (!argType.isObject(JSObject.class.getName())) {
|
||||||
|
Variable castValue = program.createVariable();
|
||||||
|
CastInstruction castInsn = new CastInstruction();
|
||||||
|
castInsn.setValue(var);
|
||||||
|
castInsn.setReceiver(castValue);
|
||||||
|
castInsn.setLocation(location);
|
||||||
|
castInsn.setTargetType(argType);
|
||||||
|
replacement.add(castInsn);
|
||||||
|
var = castValue;
|
||||||
|
}
|
||||||
|
Variable result = program.createVariable();
|
||||||
|
InvokeInstruction insn = new InvokeInstruction();
|
||||||
|
insn.setMethod(new MethodReference(JS.class.getName(), methodName, argType, resultType));
|
||||||
|
insn.getArguments().add(var);
|
||||||
|
insn.setReceiver(result);
|
||||||
|
insn.setType(InvocationType.SPECIAL);
|
||||||
|
insn.setLocation(location);
|
||||||
|
replacement.add(insn);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
56
tests/src/test/java/org/teavm/jso/test/ExportClass.java
Normal file
56
tests/src/test/java/org/teavm/jso/test/ExportClass.java
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* 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 org.teavm.jso.test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.teavm.jso.JSBody;
|
||||||
|
import org.teavm.jso.JSObject;
|
||||||
|
import org.teavm.junit.SkipJVM;
|
||||||
|
import org.teavm.junit.TeaVMTestRunner;
|
||||||
|
|
||||||
|
@RunWith(TeaVMTestRunner.class)
|
||||||
|
@SkipJVM
|
||||||
|
public class ExportClass {
|
||||||
|
@Test
|
||||||
|
public void simpleClassExported() {
|
||||||
|
assertEquals("(OK)", callIFromJs(new SimpleClass()));
|
||||||
|
assertEquals("[OK]", callIFromJs(new DerivedSimpleClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JSBody(params = "a", script = "return a.foo('OK');")
|
||||||
|
private static native String callIFromJs(I a);
|
||||||
|
|
||||||
|
interface I extends JSObject {
|
||||||
|
String foo(String a);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimpleClass implements I {
|
||||||
|
@Override
|
||||||
|
public String foo(String a) {
|
||||||
|
return "(" + a + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DerivedSimpleClass implements I {
|
||||||
|
@Override
|
||||||
|
public String foo(String a) {
|
||||||
|
return "[" + a + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user