From b731687c3e3cd7ed1087ccb2152fc7684f29605d Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sat, 24 Jan 2015 16:40:10 +0400 Subject: [PATCH] Add support of JSBody --- .../java/org/teavm/dom/browser/Window.java | 3 +- teavm-jso/src/main/java/org/teavm/jso/JS.java | 6 ++ .../src/main/java/org/teavm/jso/JSBody.java | 44 ++++++++ .../main/java/org/teavm/jso/JSFunction.java | 14 ++- .../org/teavm/jso/plugin/JSBodyGenerator.java | 88 +++++++++++++++ .../java/org/teavm/jso/plugin/JSBodyImpl.java | 35 ++++++ .../jso/{ => plugin}/JSNativeGenerator.java | 3 +- .../jso/plugin/JSObjectClassTransformer.java | 21 ++-- .../jso/plugin/JavascriptNativeProcessor.java | 100 +++++++++++++++++- 9 files changed, 290 insertions(+), 24 deletions(-) create mode 100644 teavm-jso/src/main/java/org/teavm/jso/JSBody.java create mode 100644 teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java create mode 100644 teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java rename teavm-jso/src/main/java/org/teavm/jso/{ => plugin}/JSNativeGenerator.java (99%) diff --git a/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java b/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java index a877b3955..0e0243eb1 100644 --- a/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java +++ b/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java @@ -16,6 +16,7 @@ package org.teavm.dom.browser; import org.teavm.dom.ajax.XMLHttpRequest; +import org.teavm.dom.events.EventTarget; import org.teavm.dom.html.HTMLDocument; import org.teavm.dom.json.JSON; import org.teavm.dom.typedarrays.*; @@ -28,7 +29,7 @@ import org.teavm.jso.JSProperty; * * @author Alexey Andreev */ -public interface Window extends JSGlobal { +public interface Window extends JSGlobal, EventTarget { @JSProperty HTMLDocument getDocument(); diff --git a/teavm-jso/src/main/java/org/teavm/jso/JS.java b/teavm-jso/src/main/java/org/teavm/jso/JS.java index 6de56e908..d1096a78b 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/JS.java +++ b/teavm-jso/src/main/java/org/teavm/jso/JS.java @@ -18,8 +18,10 @@ package org.teavm.jso; import java.util.Iterator; import org.teavm.dependency.PluggableDependency; import org.teavm.javascript.ni.InjectedBy; +import org.teavm.jso.plugin.JSNativeGenerator; /** + *

Container of static methods to manipulate over {@link JSObject}s.

* * @author Alexey Andreev */ @@ -56,6 +58,10 @@ public final class JS { @InjectedBy(JSNativeGenerator.class) public static native JSObject getTypeName(JSObject obj); + /** + * Gets global JavaScript object, that is similar to the window object in the browser. + * @return global object. + */ @InjectedBy(JSNativeGenerator.class) public static native JSObject getGlobal(); diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSBody.java b/teavm-jso/src/main/java/org/teavm/jso/JSBody.java new file mode 100644 index 000000000..83ab3b79c --- /dev/null +++ b/teavm-jso/src/main/java/org/teavm/jso/JSBody.java @@ -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; + +/** + *

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.

+ * + * @author Alexey Andreev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface JSBody { + /** + *

How method parameters are named inside JavaScript implementation.

+ */ + String[] params(); + + /** + *

JavaScript implementation.

+ */ + String script(); +} diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java b/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java index 9f0a228e8..5374025a1 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java +++ b/teavm-jso/src/main/java/org/teavm/jso/JSFunction.java @@ -37,17 +37,15 @@ public interface JSFunction extends JSObject { JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d); - JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, - JSObject d, JSObject e); + JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e); - JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, - JSObject d, JSObject e, JSObject f); + JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f); - JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, - JSObject d, JSObject e, JSObject f, JSObject g); + JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, + JSObject g); - JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, - JSObject d, JSObject e, JSObject f, JSObject g, JSObject h); + JSObject call(JSObject thisArg, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, + JSObject g, JSObject h); JSObject apply(JSObject thisArg, JSObject[] arguments); } diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java new file mode 100644 index 000000000..a671c6ca6 --- /dev/null +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyGenerator.java @@ -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 + */ +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 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(); + } +} diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java new file mode 100644 index 000000000..c2fa25419 --- /dev/null +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java @@ -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 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@interface JSBodyImpl { + String[] params(); + + String script(); + + boolean isStatic(); +} diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java similarity index 99% rename from teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java rename to teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java index ef4f9fb16..856aa0dc9 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSNativeGenerator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.jso; +package org.teavm.jso.plugin; import java.io.IOException; 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.ni.Injector; import org.teavm.javascript.ni.InjectorContext; +import org.teavm.jso.JS; import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; import org.teavm.model.MethodReader; diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java index 083491b45..738fa639d 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JSObjectClassTransformer.java @@ -16,31 +16,28 @@ package org.teavm.jso.plugin; import org.teavm.diagnostics.Diagnostics; +import org.teavm.jso.JSBody; import org.teavm.model.*; /** * * @author Alexey Andreev */ -class JSObjectClassTransformer implements ClassHolderTransformer { - private ThreadLocal processor = new ThreadLocal<>(); +public class JSObjectClassTransformer implements ClassHolderTransformer { + private JavascriptNativeProcessor processor; @Override public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { - JavascriptNativeProcessor processor = getProcessor(innerSource); + processor = new JavascriptNativeProcessor(innerSource); processor.setDiagnostics(diagnostics); processor.processClass(cls); - for (MethodHolder method : cls.getMethods()) { - if (method.getProgram() != null) { + for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { + 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); } } } - - private JavascriptNativeProcessor getProcessor(ClassReaderSource innerSource) { - if (processor.get() == null) { - processor.set(new JavascriptNativeProcessor(innerSource)); - } - return processor.get(); - } } diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java index 6c43ad759..8bb924573 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java @@ -17,6 +17,7 @@ package org.teavm.jso.plugin; import java.util.*; import org.teavm.diagnostics.Diagnostics; +import org.teavm.javascript.ni.GeneratedBy; import org.teavm.javascript.ni.PreserveOriginalName; import org.teavm.jso.*; import org.teavm.model.*; @@ -32,6 +33,7 @@ class JavascriptNativeProcessor { private List replacement = new ArrayList<>(); private NativeJavascriptClassRepository nativeRepos; private Diagnostics diagnostics; + private int methodIndexGenerator; public JavascriptNativeProcessor(ClassReaderSource 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 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, InstructionLocation location) { Variable nameVar = addStringWrap(addString(propertyName, location), location); @@ -373,8 +470,7 @@ class JavascriptNativeProcessor { } Variable result = program.createVariable(); InvokeInstruction insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class.getName(), "wrap", type, - ValueType.object(JSObject.class.getName()))); + insn.setMethod(new MethodReference(JS.class.getName(), "wrap", type, ValueType.parse(JSObject.class))); insn.getArguments().add(var); insn.setReceiver(result); insn.setType(InvocationType.SPECIAL);