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:
Alexey Andreev 2017-06-06 21:34:24 +03:00
parent d324847fe6
commit 45ba247265
13 changed files with 806 additions and 595 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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);
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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) {

View File

@ -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);
} }
} }

View File

@ -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();
}

View File

@ -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);
} }

View File

@ -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<>();
} }
} }

View 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;
}
}

View 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 + "]";
}
}
}