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.ValueEmitter;
/**
*
* @author Alexey Andreev
*/
@FunctionalInterface
public interface BootstrapMethodSubstitutor {
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())) {
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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)) {
ClassReader cls = agent.getClassSource().get(className);
for (MethodReader method : cls.getMethods()) {
if (method.getName().equals("<init>")) {
continue;
}
if (exposedCls.inheritedMethods.containsKey(method.getDescriptor())
|| exposedCls.methods.containsKey(method.getDescriptor())) {
AnnotationReader exposeAnnot = method.getAnnotations().get(JSMethodToExpose.class.getName());
if (exposeAnnot != null) {
MethodDependency methodDep = agent.linkMethod(method.getReference(), null);
methodDep.getVariable(0).propagate(agent.getType(name));
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);
}
}

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

View File

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

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