mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Add support of JSBody
This commit is contained in:
parent
f138c837cf
commit
b731687c3e
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
44
teavm-jso/src/main/java/org/teavm/jso/JSBody.java
Normal file
44
teavm-jso/src/main/java/org/teavm/jso/JSBody.java
Normal 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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
35
teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java
Normal file
35
teavm-jso/src/main/java/org/teavm/jso/plugin/JSBodyImpl.java
Normal 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();
|
||||
}
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user