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.ValueEmitter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexey Andreev
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BootstrapMethodSubstitutor {
|
||||
ValueEmitter substitute(DynamicCallSite callSite, ProgramEmitter pe);
|
||||
|
|
|
@ -51,6 +51,12 @@ class DependencyClassSource implements ClassHolderSource {
|
|||
if (innerSource.get(cls.getName()) != null || generatedClasses.containsKey(cls.getName())) {
|
||||
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);
|
||||
for (MethodHolder method : cls.getMethods()) {
|
||||
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.emit.ValueEmitter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexey Andreev
|
||||
*/
|
||||
public class DynamicCallSite {
|
||||
private MethodDescriptor calledMethod;
|
||||
private ValueEmitter instance;
|
||||
|
|
|
@ -145,8 +145,8 @@ public class Program implements ProgramReader {
|
|||
|
||||
@Override
|
||||
public Variable variableAt(int index) {
|
||||
if (index < 0) {
|
||||
throw new IllegalArgumentException("Index " + index + " is negative");
|
||||
if (index < 0 || index >= variables.size()) {
|
||||
throw new IllegalArgumentException("Index " + index + " is out of range");
|
||||
}
|
||||
return variables.get(index);
|
||||
}
|
||||
|
|
|
@ -20,12 +20,7 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexey Andreev
|
||||
*/
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@Target(ElementType.TYPE)
|
||||
@interface FunctorImpl {
|
||||
String value();
|
||||
}
|
||||
|
|
|
@ -16,29 +16,24 @@
|
|||
package org.teavm.jso.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
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.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.MethodReader;
|
||||
import org.teavm.vm.BuildTarget;
|
||||
import org.teavm.vm.spi.RendererListener;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexey Andreev
|
||||
*/
|
||||
class JSAliasRenderer implements RendererListener {
|
||||
private static String variableChars = "abcdefghijklmnopqrstuvwxyz";
|
||||
private JSDependencyListener dependencyListener;
|
||||
private SourceWriter writer;
|
||||
private ClassReaderSource classSource;
|
||||
|
||||
public JSAliasRenderer(JSDependencyListener dependencyListener) {
|
||||
this.dependencyListener = dependencyListener;
|
||||
}
|
||||
private ListableClassReaderSource classSource;
|
||||
|
||||
@Override
|
||||
public void begin(RenderingManager context, BuildTarget buildTarget) throws IOException {
|
||||
|
@ -48,25 +43,32 @@ class JSAliasRenderer implements RendererListener {
|
|||
|
||||
@Override
|
||||
public void complete() throws IOException {
|
||||
if (!dependencyListener.isAnyAliasExists()) {
|
||||
if (!hasClassesToExpose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
writer.append("(function()").ws().append("{").softNewLine().indent();
|
||||
writer.append("var c;").softNewLine();
|
||||
for (Map.Entry<String, ExposedClass> entry : dependencyListener.getExposedClasses().entrySet()) {
|
||||
ExposedClass cls = entry.getValue();
|
||||
ClassReader classReader = classSource.get(entry.getKey());
|
||||
if (classReader == null || cls.methods.isEmpty()) {
|
||||
for (String className : classSource.getClassNames()) {
|
||||
ClassReader classReader = classSource.get(className);
|
||||
Map<MethodDescriptor, String> methods = new HashMap<>();
|
||||
for (MethodReader method : classReader.getMethods()) {
|
||||
String methodAlias = getPublicAlias(method);
|
||||
if (methodAlias != null) {
|
||||
methods.put(method.getDescriptor(), methodAlias);
|
||||
}
|
||||
}
|
||||
if (methods.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
if (first) {
|
||||
writer.append("c").ws().append("=").ws().appendClass(entry.getKey()).append(".prototype;")
|
||||
writer.append("c").ws().append("=").ws().appendClass(className).append(".prototype;")
|
||||
.softNewLine();
|
||||
first = false;
|
||||
}
|
||||
|
@ -78,13 +80,33 @@ class JSAliasRenderer implements RendererListener {
|
|||
writer.ws().append("=").ws().append("c.").appendMethod(aliasEntry.getKey()).append(";").softNewLine();
|
||||
}
|
||||
|
||||
if (cls.functorField != null) {
|
||||
writeFunctor(cls);
|
||||
FieldReader functorField = getFunctorField(classReader);
|
||||
if (functorField != null) {
|
||||
writeFunctor(classReader, functorField.getReference());
|
||||
}
|
||||
}
|
||||
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) {
|
||||
switch (id) {
|
||||
case "with":
|
||||
|
@ -104,28 +126,31 @@ class JSAliasRenderer implements RendererListener {
|
|||
}
|
||||
}
|
||||
|
||||
private void writeFunctor(ExposedClass cls) throws IOException {
|
||||
String alias = cls.methods.get(cls.functorMethod);
|
||||
private void writeFunctor(ClassReader cls, FieldReference functorField) throws IOException {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
writer.append("c.jso$functor$").append(alias).ws().append("=").ws().append("function()").ws().append("{")
|
||||
.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();
|
||||
writer.append("var self").ws().append('=').ws().append("this;").softNewLine();
|
||||
|
||||
writer.append("this.").appendField(cls.functorField).ws().append('=').ws().append("function(");
|
||||
appendArguments(cls.functorMethod.parameterCount());
|
||||
writer.append("this.").appendField(functorField).ws().append('=').ws().append("function(");
|
||||
appendArguments(functorMethod.parameterCount());
|
||||
writer.append(")").ws().append('{').indent().softNewLine();
|
||||
writer.append("return self.").appendMethod(cls.functorMethod).append('(');
|
||||
appendArguments(cls.functorMethod.parameterCount());
|
||||
writer.append("return self.").appendMethod(functorMethod).append('(');
|
||||
appendArguments(functorMethod.parameterCount());
|
||||
writer.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();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import org.mozilla.javascript.CompilerEnvirons;
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.ast.AstNode;
|
||||
|
@ -43,8 +42,6 @@ import org.teavm.jso.JSIndexer;
|
|||
import org.teavm.jso.JSMethod;
|
||||
import org.teavm.jso.JSObject;
|
||||
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.AnnotationContainerReader;
|
||||
import org.teavm.model.AnnotationHolder;
|
||||
|
@ -56,7 +53,6 @@ import org.teavm.model.ClassHolder;
|
|||
import org.teavm.model.ClassReader;
|
||||
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;
|
||||
|
@ -67,8 +63,6 @@ import org.teavm.model.TextLocation;
|
|||
import org.teavm.model.ValueType;
|
||||
import org.teavm.model.Variable;
|
||||
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.InvocationType;
|
||||
import org.teavm.model.instructions.InvokeInstruction;
|
||||
|
@ -86,13 +80,15 @@ class JSClassProcessor {
|
|||
private final JSTypeHelper typeHelper;
|
||||
private final Diagnostics diagnostics;
|
||||
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.typeHelper = typeHelper;
|
||||
this.repository = repository;
|
||||
this.diagnostics = diagnostics;
|
||||
typeHelper = new JSTypeHelper(classSource);
|
||||
javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, repository, classSource, diagnostics);
|
||||
}
|
||||
|
||||
|
@ -100,15 +96,7 @@ class JSClassProcessor {
|
|||
return classSource;
|
||||
}
|
||||
|
||||
public boolean isNative(String className) {
|
||||
return typeHelper.isJavaScriptClass(className);
|
||||
}
|
||||
|
||||
public boolean isNativeImplementation(String className) {
|
||||
return typeHelper.isJavaScriptImplementation(className);
|
||||
}
|
||||
|
||||
public MethodReference isFunctor(String className) {
|
||||
MethodReference isFunctor(String className) {
|
||||
if (!typeHelper.isJavaScriptImplementation(className)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -135,7 +123,7 @@ class JSClassProcessor {
|
|||
});
|
||||
}
|
||||
|
||||
public void processClass(ClassHolder cls) {
|
||||
void processClass(ClassHolder cls) {
|
||||
Set<MethodDescriptor> preservedMethods = new HashSet<>();
|
||||
for (String iface : cls.getInterfaces()) {
|
||||
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])) {
|
||||
if (method.hasModifier(ElementModifier.STATIC)) {
|
||||
continue;
|
||||
|
@ -185,10 +173,10 @@ class JSClassProcessor {
|
|||
|
||||
private MethodReader getOverriddenMethod(MethodReader finalMethod) {
|
||||
MethodReference ref = finalMethod.getReference();
|
||||
if (!overridenMethodCache.containsKey(ref)) {
|
||||
overridenMethodCache.put(ref, findOverriddenMethod(finalMethod.getOwnerName(), finalMethod));
|
||||
if (!overriddenMethodCache.containsKey(ref)) {
|
||||
overriddenMethodCache.put(ref, findOverriddenMethod(finalMethod.getOwnerName(), finalMethod));
|
||||
}
|
||||
return overridenMethodCache.get(ref);
|
||||
return overriddenMethodCache.get(ref);
|
||||
}
|
||||
|
||||
private MethodReader findOverriddenMethod(String className, MethodReader finalMethod) {
|
||||
|
@ -203,22 +191,7 @@ class JSClassProcessor {
|
|||
.orElse(null);
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
public void makeSync(ClassHolder cls) {
|
||||
void makeSync(ClassHolder cls) {
|
||||
Set<MethodDescriptor> methods = new HashSet<>();
|
||||
findInheritedMethods(cls, methods, new HashSet<>());
|
||||
for (MethodHolder method : cls.getMethods()) {
|
||||
|
@ -233,14 +206,14 @@ class JSClassProcessor {
|
|||
if (!visited.add(cls.getName())) {
|
||||
return;
|
||||
}
|
||||
if (isNative(cls.getName())) {
|
||||
if (typeHelper.isJavaScriptClass(cls.getName())) {
|
||||
for (MethodReader method : cls.getMethods()) {
|
||||
if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.FINAL)
|
||||
&& method.getLevel() != AccessLevel.PRIVATE) {
|
||||
methods.add(method.getDescriptor());
|
||||
}
|
||||
}
|
||||
} else if (isNativeImplementation(cls.getName())) {
|
||||
} else if (typeHelper.isJavaScriptImplementation(cls.getName())) {
|
||||
if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) {
|
||||
ClassReader parentCls = classSource.get(cls.getParent());
|
||||
if (parentCls != null) {
|
||||
|
@ -266,8 +239,13 @@ class JSClassProcessor {
|
|||
return staticSignature;
|
||||
}
|
||||
|
||||
private void setCurrentProgram(Program program) {
|
||||
this.program = program;
|
||||
marshaller = new JSValueMarshaller(diagnostics, typeHelper, program, replacement);
|
||||
}
|
||||
|
||||
void processProgram(MethodHolder methodToProcess) {
|
||||
program = methodToProcess.getProgram();
|
||||
setCurrentProgram(methodToProcess.getProgram());
|
||||
for (int i = 0; i < program.basicBlockCount(); ++i) {
|
||||
BasicBlock block = program.basicBlockAt(i);
|
||||
for (Instruction insn : block) {
|
||||
|
@ -366,7 +344,7 @@ class JSClassProcessor {
|
|||
}
|
||||
replacement.add(newInvoke);
|
||||
if (result != null) {
|
||||
result = unwrap(callLocation, result, method.getResultType());
|
||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||
}
|
||||
|
||||
|
@ -387,7 +365,7 @@ class JSClassProcessor {
|
|||
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
|
||||
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
|
||||
if (result != null) {
|
||||
result = unwrap(callLocation, result, method.getResultType());
|
||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||
}
|
||||
return true;
|
||||
|
@ -416,18 +394,18 @@ class JSClassProcessor {
|
|||
private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
|
||||
if (isProperGetIndexer(method.getDescriptor())) {
|
||||
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());
|
||||
if (result != null) {
|
||||
result = unwrap(callLocation, result, method.getResultType());
|
||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||
copyVar(result, invoke.getReceiver(), invoke.getLocation());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
Variable value = wrap(invoke.getArguments().get(1), method.parameterType(1),
|
||||
Variable value = marshaller.wrap(invoke.getArguments().get(1), method.parameterType(1),
|
||||
invoke.getLocation(), false);
|
||||
addIndexerSet(invoke.getInstance(), index, value, invoke.getLocation());
|
||||
return true;
|
||||
|
@ -501,7 +479,7 @@ class JSClassProcessor {
|
|||
}
|
||||
replacement.add(newInvoke);
|
||||
if (result != null) {
|
||||
result = unwrap(callLocation, result, method.getResultType());
|
||||
result = marshaller.unwrap(callLocation, result, method.getResultType());
|
||||
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])) {
|
||||
MethodReference methodRef = method.getReference();
|
||||
if (method.getAnnotations().get(JSBody.class.getName()) == null) {
|
||||
|
@ -637,7 +615,7 @@ class JSClassProcessor {
|
|||
callerMethod.getModifiers().add(ElementModifier.STATIC);
|
||||
CallLocation location = new CallLocation(callback);
|
||||
|
||||
program = new Program();
|
||||
setCurrentProgram(new Program());
|
||||
for (int i = 0; i <= callback.parameterCount(); ++i) {
|
||||
program.createVariable();
|
||||
}
|
||||
|
@ -649,11 +627,12 @@ class JSClassProcessor {
|
|||
insn.setMethod(calleeRef);
|
||||
replacement.clear();
|
||||
if (!callee.hasModifier(ElementModifier.STATIC)) {
|
||||
insn.setInstance(unwrap(location, program.variableAt(paramIndex++),
|
||||
insn.setInstance(marshaller.unwrap(location, program.variableAt(paramIndex++),
|
||||
ValueType.object(calleeRef.getClassName())));
|
||||
}
|
||||
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) {
|
||||
insn.setReceiver(program.createVariable());
|
||||
|
@ -664,7 +643,7 @@ class JSClassProcessor {
|
|||
ExitInstruction exit = new ExitInstruction();
|
||||
if (insn.getReceiver() != null) {
|
||||
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.add(exit);
|
||||
|
@ -732,7 +711,7 @@ class JSClassProcessor {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -745,240 +724,6 @@ class JSClassProcessor {
|
|||
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) {
|
||||
if (type instanceof ValueType.Object) {
|
||||
String className = ((ValueType.Object) type).getClassName();
|
||||
|
@ -987,7 +732,7 @@ class JSClassProcessor {
|
|||
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) {
|
||||
|
@ -1020,128 +765,6 @@ class JSClassProcessor {
|
|||
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) {
|
||||
ClassReader cls = classSource.get(ref.getClassName());
|
||||
if (cls == null) {
|
||||
|
|
|
@ -15,48 +15,23 @@
|
|||
*/
|
||||
package org.teavm.jso.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.teavm.dependency.AbstractDependencyListener;
|
||||
import org.teavm.dependency.DependencyAgent;
|
||||
import org.teavm.dependency.MethodDependency;
|
||||
import org.teavm.jso.JSMethod;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.model.AnnotationReader;
|
||||
import org.teavm.model.AnnotationValue;
|
||||
import org.teavm.model.CallLocation;
|
||||
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.MethodReference;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexey Andreev
|
||||
*/
|
||||
class JSDependencyListener extends AbstractDependencyListener {
|
||||
private Map<String, ExposedClass> exposedClasses = new HashMap<>();
|
||||
private ClassReaderSource classSource;
|
||||
private DependencyAgent agent;
|
||||
private JSBodyRepository repository;
|
||||
private boolean anyAliasExists;
|
||||
|
||||
public JSDependencyListener(JSBodyRepository repository) {
|
||||
JSDependencyListener(JSBodyRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void started(DependencyAgent agent) {
|
||||
this.agent = agent;
|
||||
classSource = agent.getClassSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
|
||||
MethodReference ref = method.getReference();
|
||||
|
@ -70,108 +45,14 @@ class JSDependencyListener extends AbstractDependencyListener {
|
|||
|
||||
@Override
|
||||
public void classReached(DependencyAgent agent, String className, CallLocation location) {
|
||||
getExposedClass(className);
|
||||
}
|
||||
|
||||
boolean isAnyAliasExists() {
|
||||
return anyAliasExists;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
ClassReader cls = agent.getClassSource().get(className);
|
||||
for (MethodReader method : cls.getMethods()) {
|
||||
AnnotationReader exposeAnnot = method.getAnnotations().get(JSMethodToExpose.class.getName());
|
||||
if (exposeAnnot != null) {
|
||||
MethodDependency methodDep = agent.linkMethod(method.getReference(), null);
|
||||
methodDep.getVariable(0).propagate(agent.getType(className));
|
||||
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.add(new JSObjectClassTransformer(repository));
|
||||
JSDependencyListener dependencyListener = new JSDependencyListener(repository);
|
||||
JSAliasRenderer aliasRenderer = new JSAliasRenderer(dependencyListener);
|
||||
JSAliasRenderer aliasRenderer = new JSAliasRenderer();
|
||||
host.add(dependencyListener);
|
||||
host.getExtension(TeaVMJavaScriptHost.class).add(aliasRenderer);
|
||||
}
|
||||
|
|
|
@ -15,44 +15,245 @@
|
|||
*/
|
||||
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.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.ClassHolderTransformer;
|
||||
import org.teavm.model.ClassReader;
|
||||
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.MethodReader;
|
||||
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 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
|
||||
this.innerSource = 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);
|
||||
if (processor.isNative(cls.getName())) {
|
||||
if (typeHelper.isJavaScriptClass(cls.getName())) {
|
||||
processor.processMemberMethods(cls);
|
||||
}
|
||||
if (processor.isNativeImplementation(cls.getName())) {
|
||||
if (typeHelper.isJavaScriptImplementation(cls.getName())) {
|
||||
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])) {
|
||||
if (method.getProgram() != null) {
|
||||
processor.processProgram(method);
|
||||
}
|
||||
}
|
||||
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