Add support of JSBody

This commit is contained in:
konsoletyper 2015-01-24 16:40:10 +04:00
parent f138c837cf
commit b731687c3e
9 changed files with 290 additions and 24 deletions

View File

@ -16,6 +16,7 @@
package org.teavm.dom.browser; package org.teavm.dom.browser;
import org.teavm.dom.ajax.XMLHttpRequest; import org.teavm.dom.ajax.XMLHttpRequest;
import org.teavm.dom.events.EventTarget;
import org.teavm.dom.html.HTMLDocument; import org.teavm.dom.html.HTMLDocument;
import org.teavm.dom.json.JSON; import org.teavm.dom.json.JSON;
import org.teavm.dom.typedarrays.*; import org.teavm.dom.typedarrays.*;
@ -28,7 +29,7 @@ import org.teavm.jso.JSProperty;
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
public interface Window extends JSGlobal { public interface Window extends JSGlobal, EventTarget {
@JSProperty @JSProperty
HTMLDocument getDocument(); HTMLDocument getDocument();

View File

@ -18,8 +18,10 @@ package org.teavm.jso;
import java.util.Iterator; import java.util.Iterator;
import org.teavm.dependency.PluggableDependency; import org.teavm.dependency.PluggableDependency;
import org.teavm.javascript.ni.InjectedBy; import org.teavm.javascript.ni.InjectedBy;
import org.teavm.jso.plugin.JSNativeGenerator;
/** /**
* <p>Container of static methods to manipulate over {@link JSObject}s.</p>
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
@ -56,6 +58,10 @@ public final class JS {
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject getTypeName(JSObject obj); public static native JSObject getTypeName(JSObject obj);
/**
* Gets global JavaScript object, that is similar to the <code>window</code> object in the browser.
* @return global object.
*/
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject getGlobal(); public static native JSObject getGlobal();

View File

@ -0,0 +1,44 @@
/*
* Copyright 2015 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>Indicates that method is to have native JavaScript implementation.
* Method only can take and return primitive values and {@link JSObject}s.
* JSBody script can't call Java methods, but you can pass callbacks wrapped into {@link JSFunctor}.
* Note that unless method is static, it must belong to class that implements {@link JSObject}.
* If applied to non-native method, original Java body will be overwritten by JavaScript.</p>
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JSBody {
/**
* <p>How method parameters are named inside JavaScript implementation.</p>
*/
String[] params();
/**
* <p>JavaScript implementation.</p>
*/
String script();
}

View File

@ -37,17 +37,15 @@ public interface JSFunction extends JSObject {
JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c,
JSObject d); JSObject d);
JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e);
JSObject d, JSObject e);
JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f);
JSObject d, JSObject e, JSObject f);
JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject d, JSObject e, JSObject f, JSObject g); JSObject g);
JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject d, JSObject e, JSObject f, JSObject g, JSObject h); JSObject g, JSObject h);
JSObject apply(JSObject thisArg, JSObject[] arguments); JSObject apply(JSObject thisArg, JSObject[] arguments);
} }

View File

@ -0,0 +1,88 @@
/*
* Copyright 2015 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.plugin;
import java.io.IOException;
import java.util.List;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.model.*;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class JSBodyGenerator implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
MethodReader method = cls.getMethod(methodRef.getDescriptor());
AnnotationReader annot = method.getAnnotations().get(JSBodyImpl.class.getName());
boolean isStatic = annot.getValue("isStatic").getBoolean();
List<AnnotationValue> paramNames = annot.getValue("params").getList();
int bodyParamCount = method.parameterCount();
int offset = isStatic ? 1 : 0;
writer.append("if (!").appendMethodBody(methodRef).append(".$native)").ws().append('{').indent().newLine();
writer.appendMethodBody(methodRef).append(".$native").ws().append('=').ws().append("function(");
int count = method.parameterCount() + (isStatic ? 0 : 1);
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append('_').append(context.getParameterName(i + offset));
}
writer.append(')').ws().append('{').softNewLine().indent();
writer.append("return (function(");
for (int i = 0; i < bodyParamCount; ++i) {
if (i > 0) {
writer.append(',').ws();
}
String name = paramNames.get(i).getString();
writer.append(name);
}
writer.append(')').ws().append('{').softNewLine().indent();
writer.append(annot.getValue("script").getString()).softNewLine();
writer.outdent().append("})");
if (!isStatic) {
writer.append(".call");
}
writer.append('(');
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append('_').append(context.getParameterName(i + offset));
}
writer.append(");").softNewLine();
writer.outdent().append("};").softNewLine();
writer.appendMethodBody(methodRef).ws().append('=').ws().appendMethodBody(methodRef).append(".$native;")
.softNewLine();
writer.outdent().append("}").softNewLine();
writer.appendMethodBody(methodRef).append('(');
for (int i = 0; i < count; ++i) {
if (i > 0) {
writer.append(',').ws();
}
writer.append(context.getParameterName(i + offset));
}
writer.append(");").softNewLine();
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2015 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.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface JSBodyImpl {
String[] params();
String script();
boolean isStatic();
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.jso; package org.teavm.jso.plugin;
import java.io.IOException; import java.io.IOException;
import org.teavm.codegen.SourceWriter; import org.teavm.codegen.SourceWriter;
@ -23,6 +23,7 @@ import org.teavm.javascript.ast.Expr;
import org.teavm.javascript.ast.InvocationExpr; import org.teavm.javascript.ast.InvocationExpr;
import org.teavm.javascript.ni.Injector; import org.teavm.javascript.ni.Injector;
import org.teavm.javascript.ni.InjectorContext; import org.teavm.javascript.ni.InjectorContext;
import org.teavm.jso.JS;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;

View File

@ -16,31 +16,28 @@
package org.teavm.jso.plugin; package org.teavm.jso.plugin;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.jso.JSBody;
import org.teavm.model.*; import org.teavm.model.*;
/** /**
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
class JSObjectClassTransformer implements ClassHolderTransformer { public class JSObjectClassTransformer implements ClassHolderTransformer {
private ThreadLocal<JavascriptNativeProcessor> processor = new ThreadLocal<>(); private JavascriptNativeProcessor processor;
@Override @Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
JavascriptNativeProcessor processor = getProcessor(innerSource); processor = new JavascriptNativeProcessor(innerSource);
processor.setDiagnostics(diagnostics); processor.setDiagnostics(diagnostics);
processor.processClass(cls); processor.processClass(cls);
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
if (method.getProgram() != null) { if (method.getAnnotations().get(JSBody.class.getName()) != null) {
processor.processJSBody(cls, method);
} else if (method.getProgram() != null &&
method.getAnnotations().get(JSBodyImpl.class.getName()) == null) {
processor.processProgram(method); processor.processProgram(method);
} }
} }
} }
private JavascriptNativeProcessor getProcessor(ClassReaderSource innerSource) {
if (processor.get() == null) {
processor.set(new JavascriptNativeProcessor(innerSource));
}
return processor.get();
}
} }

View File

@ -17,6 +17,7 @@ package org.teavm.jso.plugin;
import java.util.*; import java.util.*;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.javascript.ni.PreserveOriginalName; import org.teavm.javascript.ni.PreserveOriginalName;
import org.teavm.jso.*; import org.teavm.jso.*;
import org.teavm.model.*; import org.teavm.model.*;
@ -32,6 +33,7 @@ class JavascriptNativeProcessor {
private List<Instruction> replacement = new ArrayList<>(); private List<Instruction> replacement = new ArrayList<>();
private NativeJavascriptClassRepository nativeRepos; private NativeJavascriptClassRepository nativeRepos;
private Diagnostics diagnostics; private Diagnostics diagnostics;
private int methodIndexGenerator;
public JavascriptNativeProcessor(ClassReaderSource classSource) { public JavascriptNativeProcessor(ClassReaderSource classSource) {
this.classSource = classSource; this.classSource = classSource;
@ -209,6 +211,101 @@ class JavascriptNativeProcessor {
} }
} }
public void processJSBody(ClassHolder cls, MethodHolder methodToProcess) {
CallLocation location = new CallLocation(methodToProcess.getReference());
boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC);
// validate parameter names
AnnotationHolder bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
int jsParamCount = bodyAnnot.getValue("params").getList().size();
if (methodToProcess.parameterCount() != jsParamCount) {
diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount() +
" parameters, but annotation specifies " + jsParamCount, methodToProcess);
return;
}
// remove annotation and make non-native
methodToProcess.getAnnotations().remove(JSBody.class.getName());
methodToProcess.getModifiers().remove(ElementModifier.NATIVE);
// generate parameter types for original method and validate
int paramCount = methodToProcess.parameterCount();
if (!isStatic) {
++paramCount;
}
ValueType[] paramTypes = new ValueType[paramCount];
int offset = 0;
if (!isStatic) {
ValueType paramType = ValueType.object(cls.getName());
paramTypes[offset++] = paramType;
if (!isSupportedType(paramType)) {
diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}",
methodToProcess.getReference(), cls.getName());
}
}
if (methodToProcess.getResultType() != ValueType.VOID && !isSupportedType(methodToProcess.getResultType())) {
diagnostics.error(location, "JSBody method {{m0}} returns unsupported type {{t1}}",
methodToProcess.getReference(), methodToProcess.getResultType());
}
// generate parameter types for proxy method
for (int i = 0; i < methodToProcess.parameterCount(); ++i) {
paramTypes[offset++] = methodToProcess.parameterType(i);
}
ValueType[] proxyParamTypes = new ValueType[paramCount + 1];
for (int i = 0; i < paramCount; ++i) {
proxyParamTypes[i] = ValueType.parse(JSObject.class);
}
proxyParamTypes[paramCount] = methodToProcess.getResultType() == ValueType.VOID ? ValueType.VOID :
ValueType.parse(JSObject.class);
// create proxy method
MethodHolder proxyMethod = new MethodHolder("$js_body$_" + methodIndexGenerator++, proxyParamTypes);
proxyMethod.getModifiers().add(ElementModifier.NATIVE);
proxyMethod.getModifiers().add(ElementModifier.STATIC);
AnnotationHolder genBodyAnnot = new AnnotationHolder(JSBodyImpl.class.getName());
genBodyAnnot.getValues().put("script", bodyAnnot.getValue("script"));
genBodyAnnot.getValues().put("params", bodyAnnot.getValue("params"));
genBodyAnnot.getValues().put("isStatic", new AnnotationValue(isStatic));
AnnotationHolder generatorAnnot = new AnnotationHolder(GeneratedBy.class.getName());
generatorAnnot.getValues().put("value", new AnnotationValue(ValueType.parse(JSBodyGenerator.class)));
proxyMethod.getAnnotations().add(genBodyAnnot);
proxyMethod.getAnnotations().add(generatorAnnot);
cls.addMethod(proxyMethod);
// create program that invokes proxy method
program = new Program();
BasicBlock block = program.createBasicBlock();
List<Variable> params = new ArrayList<>();
for (int i = 0; i < paramCount; ++i) {
params.add(program.createVariable());
}
methodToProcess.setProgram(program);
// Generate invoke instruction
replacement.clear();
InvokeInstruction invoke = new InvokeInstruction();
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(proxyMethod.getReference());
for (int i = 0; i < paramCount; ++i) {
Variable var = program.createVariable();
invoke.getArguments().add(wrapArgument(location, var, paramTypes[i]));
}
block.getInstructions().addAll(replacement);
block.getInstructions().add(invoke);
// Generate return
ExitInstruction exit = new ExitInstruction();
if (methodToProcess.getResultType() != ValueType.VOID) {
replacement.clear();
Variable result = program.createVariable();
invoke.setReceiver(result);
exit.setValueToReturn(unwrap(location, result, methodToProcess.getResultType()));
block.getInstructions().addAll(replacement);
}
block.getInstructions().add(exit);
}
private void addPropertyGet(String propertyName, Variable instance, Variable receiver, private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
InstructionLocation location) { InstructionLocation location) {
Variable nameVar = addStringWrap(addString(propertyName, location), location); Variable nameVar = addStringWrap(addString(propertyName, location), location);
@ -373,8 +470,7 @@ class JavascriptNativeProcessor {
} }
Variable result = program.createVariable(); Variable result = program.createVariable();
InvokeInstruction insn = new InvokeInstruction(); InvokeInstruction insn = new InvokeInstruction();
insn.setMethod(new MethodReference(JS.class.getName(), "wrap", type, insn.setMethod(new MethodReference(JS.class.getName(), "wrap", type, ValueType.parse(JSObject.class)));
ValueType.object(JSObject.class.getName())));
insn.getArguments().add(var); insn.getArguments().add(var);
insn.setReceiver(result); insn.setReceiver(result);
insn.setType(InvocationType.SPECIAL); insn.setType(InvocationType.SPECIAL);