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

View File

@ -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;
/**
* <p>Container of static methods to manipulate over {@link JSObject}s.</p>
*
* @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 <code>window</code> object in the browser.
* @return global object.
*/
@InjectedBy(JSNativeGenerator.class)
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 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);
}

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

View File

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

View File

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