Adds possibility to implement JSObject ant pass it as a callback to a

method of another JSObject
This commit is contained in:
Alexey Andreev 2014-02-07 23:30:31 +04:00
parent c9f78c5cdf
commit 48265c446f
14 changed files with 193 additions and 48 deletions

View File

@ -144,7 +144,7 @@ class DependencyGraphBuilder {
if (insn.getInstance() != null) { if (insn.getInstance() != null) {
nodes[insn.getInstance().getIndex()].connect(targetParams[0]); nodes[insn.getInstance().getIndex()].connect(targetParams[0]);
} }
if (targetGraph.getResultNode() != null) { if (targetGraph.getResultNode() != null && insn.getReceiver() != null) {
targetGraph.getResultNode().connect(nodes[insn.getReceiver().getIndex()]); targetGraph.getResultNode().connect(nodes[insn.getReceiver().getIndex()]);
} }
} }

View File

@ -21,6 +21,7 @@ import org.teavm.javascript.ast.*;
import org.teavm.javascript.ni.GeneratedBy; import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.InjectedBy; import org.teavm.javascript.ni.InjectedBy;
import org.teavm.javascript.ni.PreserveOriginalName;
import org.teavm.model.*; import org.teavm.model.*;
import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.ProgramUtils;
@ -121,7 +122,11 @@ public class Decompiler {
if (method.getAnnotations().get(InjectedBy.class.getName()) != null) { if (method.getAnnotations().get(InjectedBy.class.getName()) != null) {
continue; continue;
} }
clsNode.getMethods().add(decompile(method)); MethodNode methodNode = decompile(method);
clsNode.getMethods().add(methodNode);
if (method.getAnnotations().get(PreserveOriginalName.class.getName()) != null) {
methodNode.setOriginalNamePreserved(true);
}
} }
clsNode.getInterfaces().addAll(cls.getInterfaces()); clsNode.getInterfaces().addAll(cls.getInterfaces());
clsNode.getModifiers().addAll(mapModifiers(cls.getModifiers())); clsNode.getModifiers().addAll(mapModifiers(cls.getModifiers()));

View File

@ -26,13 +26,38 @@ import org.teavm.model.instructions.*;
*/ */
class JavascriptNativeProcessor { class JavascriptNativeProcessor {
private ClassHolderSource classSource; private ClassHolderSource classSource;
private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>();
private Program program; private Program program;
private List<Instruction> replacement = new ArrayList<>(); private List<Instruction> replacement = new ArrayList<>();
private NativeJavascriptClassRepository nativeRepos;
public JavascriptNativeProcessor(ClassHolderSource classSource) { public JavascriptNativeProcessor(ClassHolderSource classSource) {
this.classSource = classSource; this.classSource = classSource;
knownJavaScriptClasses.put(JSObject.class.getName(), true); nativeRepos = new NativeJavascriptClassRepository(classSource);
}
public void processClass(ClassHolder cls) {
Set<MethodDescriptor> preservedMethods = new HashSet<>();
for (String iface : cls.getInterfaces()) {
if (nativeRepos.isJavaScriptClass(iface)) {
addPreservedMethods(iface, preservedMethods);
}
}
for (MethodHolder method : cls.getMethods()) {
if (preservedMethods.contains(method.getDescriptor()) &&
method.getAnnotations().get(PreserveOriginalName.class.getName()) == null) {
method.getAnnotations().add(new AnnotationHolder(PreserveOriginalName.class.getName()));
}
}
}
private void addPreservedMethods(String ifaceName, Set<MethodDescriptor> methods) {
ClassHolder iface = classSource.getClassHolder(ifaceName);
for (MethodHolder method : iface.getMethods()) {
methods.add(method.getDescriptor());
}
for (String superIfaceName : iface.getInterfaces()) {
addPreservedMethods(superIfaceName, methods);
}
} }
public void processProgram(Program program) { public void processProgram(Program program) {
@ -46,7 +71,7 @@ class JavascriptNativeProcessor {
continue; continue;
} }
InvokeInstruction invoke = (InvokeInstruction)insn; InvokeInstruction invoke = (InvokeInstruction)insn;
if (!isJavaScriptClass(invoke.getMethod().getClassName())) { if (!nativeRepos.isJavaScriptClass(invoke.getMethod().getClassName())) {
continue; continue;
} }
replacement.clear(); replacement.clear();
@ -86,7 +111,7 @@ class JavascriptNativeProcessor {
"a proper native JavaScript indexer declaration"); "a proper native JavaScript indexer declaration");
} }
} else { } else {
if (!isSupportedType(method.getResultType())) { if (method.getResultType() != ValueType.VOID && !isSupportedType(method.getResultType())) {
throw new RuntimeException("Method " + invoke.getMethod() + " is not " + throw new RuntimeException("Method " + invoke.getMethod() + " is not " +
"a proper native JavaScript method declaration"); "a proper native JavaScript method declaration");
} }
@ -258,28 +283,6 @@ class JavascriptNativeProcessor {
return result; return result;
} }
private boolean isJavaScriptClass(String className) {
Boolean known = knownJavaScriptClasses.get(className);
if (known == null) {
known = exploreIfJavaScriptClass(className);
knownJavaScriptClasses.put(className, known);
}
return known;
}
private boolean exploreIfJavaScriptClass(String className) {
ClassHolder cls = classSource.getClassHolder(className);
if (cls == null || !cls.getModifiers().contains(ElementModifier.INTERFACE)) {
return false;
}
for (String iface : cls.getInterfaces()) {
if (isJavaScriptClass(iface)) {
return true;
}
}
return false;
}
private MethodHolder getMethod(MethodReference ref) { private MethodHolder getMethod(MethodReference ref) {
ClassHolder cls = classSource.getClassHolder(ref.getClassName()); ClassHolder cls = classSource.getClassHolder(ref.getClassName());
MethodHolder method = cls.getMethod(ref.getDescriptor()); MethodHolder method = cls.getMethod(ref.getDescriptor());
@ -359,7 +362,7 @@ class JavascriptNativeProcessor {
return isSupportedType(((ValueType.Array)type).getItemType()); return isSupportedType(((ValueType.Array)type).getItemType());
} else if (type instanceof ValueType.Object) { } else if (type instanceof ValueType.Object) {
String typeName = ((ValueType.Object)type).getClassName(); String typeName = ((ValueType.Object)type).getClassName();
return typeName.equals("java.lang.String") || isJavaScriptClass(typeName); return typeName.equals("java.lang.String") || nativeRepos.isJavaScriptClass(typeName);
} else { } else {
return false; return false;
} }

View File

@ -24,6 +24,7 @@ import org.teavm.model.MethodHolder;
* @author Alexey Andreev * @author Alexey Andreev
*/ */
public class JavascriptProcessedClassSource implements ClassHolderSource { public class JavascriptProcessedClassSource implements ClassHolderSource {
private ThreadLocal<JavascriptNativeProcessor> processor = new ThreadLocal<>();
private ClassHolderSource innerSource; private ClassHolderSource innerSource;
public JavascriptProcessedClassSource(ClassHolderSource innerSource) { public JavascriptProcessedClassSource(ClassHolderSource innerSource) {
@ -40,11 +41,19 @@ public class JavascriptProcessedClassSource implements ClassHolderSource {
} }
private void transformClass(ClassHolder cls) { private void transformClass(ClassHolder cls) {
JavascriptNativeProcessor processor = new JavascriptNativeProcessor(innerSource); JavascriptNativeProcessor processor = getProcessor();
processor.processClass(cls);
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods()) {
if (method.getProgram() != null) { if (method.getProgram() != null) {
processor.processProgram(method.getProgram()); processor.processProgram(method.getProgram());
} }
} }
} }
private JavascriptNativeProcessor getProcessor() {
if (processor.get() == null) {
processor.set(new JavascriptNativeProcessor(innerSource));
}
return processor.get();
}
} }

View File

@ -0,0 +1,44 @@
package org.teavm.javascript;
import java.util.HashMap;
import java.util.Map;
import org.teavm.javascript.ni.JSObject;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
import org.teavm.model.ElementModifier;
/**
*
* @author Alexey Andreev
*/
class NativeJavascriptClassRepository {
private ClassHolderSource classSource;
private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>();
public NativeJavascriptClassRepository(ClassHolderSource classSource) {
this.classSource = classSource;
knownJavaScriptClasses.put(JSObject.class.getName(), true);
}
public boolean isJavaScriptClass(String className) {
Boolean known = knownJavaScriptClasses.get(className);
if (known == null) {
known = figureOutIfJavaScriptClass(className);
knownJavaScriptClasses.put(className, known);
}
return known;
}
private boolean figureOutIfJavaScriptClass(String className) {
ClassHolder cls = classSource.getClassHolder(className);
if (cls == null || !cls.getModifiers().contains(ElementModifier.INTERFACE)) {
return false;
}
for (String iface : cls.getInterfaces()) {
if (isJavaScriptClass(iface)) {
return true;
}
}
return false;
}
}

View File

@ -290,15 +290,8 @@ public class Renderer implements ExprVisitor, StatementVisitor {
if (ref.getDescriptor().getName().equals("<init>")) { if (ref.getDescriptor().getName().equals("<init>")) {
renderInitializer(method); renderInitializer(method);
} }
int startParam = 0; writer.appendClass(ref.getClassName()).append(".prototype.").appendMethod(ref)
if (method.getModifiers().contains(NodeModifier.STATIC)) { .ws().append("=").ws().append("function(");
startParam = 1;
}
writer.appendClass(ref.getClassName()).append('.');
if (startParam == 0) {
writer.append("prototype.");
}
writer.appendMethod(ref).ws().append("=").ws().append("function(");
for (int i = 1; i <= ref.parameterCount(); ++i) { for (int i = 1; i <= ref.parameterCount(); ++i) {
if (i > 1) { if (i > 1) {
writer.append(", "); writer.append(", ");
@ -307,17 +300,17 @@ public class Renderer implements ExprVisitor, StatementVisitor {
} }
writer.append(")").ws().append("{").softNewLine().indent(); writer.append(")").ws().append("{").softNewLine().indent();
writer.append("return ").appendMethodBody(ref).append("("); writer.append("return ").appendMethodBody(ref).append("(");
if (startParam == 0) { writer.append("this");
writer.append("this");
}
for (int i = 1; i <= ref.parameterCount(); ++i) { for (int i = 1; i <= ref.parameterCount(); ++i) {
if (i > 1 || startParam == 0) { writer.append(",").ws().append(variableName(i));
writer.append(",").ws();
}
writer.append(variableName(i));
} }
writer.append(");").softNewLine(); writer.append(");").softNewLine();
writer.outdent().append("}").newLine(); writer.outdent().append("}").newLine();
if (method.isOriginalNamePreserved()) {
writer.appendClass(ref.getClassName()).append(".prototype.").append(ref.getName()).ws().append("=")
.ws().appendClass(ref.getClassName()).append(".prototype.").appendMethod(ref)
.append(';').newLine();
}
} catch (NamingException e) { } catch (NamingException e) {
throw new RenderingException("Error rendering method " + method.getReference() + ". " + throw new RenderingException("Error rendering method " + method.getReference() + ". " +
"See cause for details", e); "See cause for details", e);

View File

@ -26,6 +26,7 @@ import org.teavm.model.MethodReference;
public abstract class MethodNode { public abstract class MethodNode {
private MethodReference reference; private MethodReference reference;
private Set<NodeModifier> modifiers = EnumSet.noneOf(NodeModifier.class); private Set<NodeModifier> modifiers = EnumSet.noneOf(NodeModifier.class);
private boolean originalNamePreserved;
public MethodNode(MethodReference reference) { public MethodNode(MethodReference reference) {
this.reference = reference; this.reference = reference;
@ -40,5 +41,13 @@ public abstract class MethodNode {
return modifiers; return modifiers;
} }
public boolean isOriginalNamePreserved() {
return originalNamePreserved;
}
public void setOriginalNamePreserved(boolean originalNamePreserved) {
this.originalNamePreserved = originalNamePreserved;
}
public abstract void acceptVisitor(MethodNodeVisitor visitor); public abstract void acceptVisitor(MethodNodeVisitor visitor);
} }

View File

@ -16,6 +16,7 @@
package org.teavm.javascript.ni; package org.teavm.javascript.ni;
import java.util.Iterator; import java.util.Iterator;
import org.teavm.dependency.PluggableDependency;
/** /**
* *
@ -66,6 +67,9 @@ public final class JS {
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(double num); public static native JSObject wrap(double num);
@InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(boolean num);
public static <T extends JSObject> JSArray<T> wrap(T[] array) { public static <T extends JSObject> JSArray<T> wrap(T[] array) {
JSArray<T> result = createArray(array.length); JSArray<T> result = createArray(array.length);
for (int i = 0; i < array.length; ++i) { for (int i = 0; i < array.length; ++i) {
@ -120,34 +124,43 @@ public final class JS {
public static native boolean isUndefined(JSObject obj); public static native boolean isUndefined(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method); public static native JSObject invoke(JSObject instance, JSObject method);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c); public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d); JSObject d);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e); JSObject d, JSObject e);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f); JSObject d, JSObject e, JSObject f);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f, JSObject g); JSObject d, JSObject e, JSObject f, JSObject g);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c,
JSObject d, JSObject e, JSObject f, JSObject g, JSObject h); JSObject d, JSObject e, JSObject f, JSObject g, JSObject h);

View File

@ -17,17 +17,23 @@ package org.teavm.javascript.ni;
import java.io.IOException; import java.io.IOException;
import org.teavm.codegen.SourceWriter; import org.teavm.codegen.SourceWriter;
import org.teavm.dependency.DependencyChecker;
import org.teavm.dependency.DependencyConsumer;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodGraph;
import org.teavm.javascript.ast.ConstantExpr; import org.teavm.javascript.ast.ConstantExpr;
import org.teavm.javascript.ast.Expr; import org.teavm.javascript.ast.Expr;
import org.teavm.javascript.ast.InvocationExpr; import org.teavm.javascript.ast.InvocationExpr;
import org.teavm.model.ClassHolder;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
/** /**
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
public class JSNativeGenerator implements Generator, Injector { public class JSNativeGenerator implements Generator, Injector, DependencyPlugin {
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef)
throws IOException { throws IOException {
@ -87,6 +93,27 @@ public class JSNativeGenerator implements Generator, Injector {
} }
} }
@Override
public void methodAchieved(final DependencyChecker checker, MethodReference method) {
MethodGraph graph = checker.attachMethodGraph(method);
for (int i = 0; i < method.parameterCount(); ++i) {
graph.getVariableNode(i).addConsumer(new DependencyConsumer() {
@Override public void consume(String type) {
achieveFunctorMethods(checker, type);
}
});
}
}
private void achieveFunctorMethods(DependencyChecker checker, String type) {
ClassHolder cls = checker.getClassSource().getClassHolder(type);
if (cls != null) {
for (MethodHolder method : cls.getMethods()) {
checker.attachMethodGraph(method.getReference());
}
}
}
private void generateWrapString(GeneratorContext context, SourceWriter writer) throws IOException { private void generateWrapString(GeneratorContext context, SourceWriter writer) throws IOException {
FieldReference charsField = new FieldReference("java.lang.String", "characters"); FieldReference charsField = new FieldReference("java.lang.String", "characters");
writer.append("var result = \"\";").softNewLine(); writer.append("var result = \"\";").softNewLine();

View File

@ -0,0 +1,15 @@
package org.teavm.javascript.ni;
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)
public @interface PreserveOriginalName {
}

View File

@ -39,6 +39,9 @@ public class Parser {
ssaProducer.transformToSSA(program, method.getParameterTypes()); ssaProducer.transformToSSA(program, method.getParameterTypes());
method.setProgram(program); method.setProgram(program);
parseAnnotations(method.getAnnotations(), node); parseAnnotations(method.getAnnotations(), node);
while (program.variableCount() <= method.parameterCount()) {
program.createVariable();
}
return method; return method;
} }

View File

@ -62,6 +62,9 @@ public class SSATransformer {
} }
private void applySignature() { private void applySignature() {
if (program.variableCount() == 0) {
return;
}
int index = 0; int index = 0;
variableMap[index] = program.variableAt(index); variableMap[index] = program.variableAt(index);
++index; ++index;

View File

@ -16,12 +16,13 @@
package org.teavm.dom.events; package org.teavm.dom.events;
import org.teavm.javascript.ni.JSFunctor; import org.teavm.javascript.ni.JSFunctor;
import org.teavm.javascript.ni.JSObject;
/** /**
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
@JSFunctor @JSFunctor
public interface EventListener { public interface EventListener extends JSObject {
void handleEvent(Event evt); void handleEvent(Event evt);
} }

View File

@ -18,6 +18,9 @@ package org.teavm.samples;
import org.teavm.dom.core.Document; import org.teavm.dom.core.Document;
import org.teavm.dom.core.Element; import org.teavm.dom.core.Element;
import org.teavm.dom.core.Window; import org.teavm.dom.core.Window;
import org.teavm.dom.events.Event;
import org.teavm.dom.events.EventListener;
import org.teavm.dom.events.EventTarget;
import org.teavm.javascript.ni.JS; import org.teavm.javascript.ni.JS;
@ -34,7 +37,24 @@ public class HelloWorld {
window = (Window)JS.getGlobal(); window = (Window)JS.getGlobal();
document = window.getDocument(); document = window.getDocument();
body = document.getDocumentElement().getElementsByTagName("body").item(0); body = document.getDocumentElement().getElementsByTagName("body").item(0);
createButton();
}
private static void createButton() {
Element elem = document.createElement("div");
body.appendChild(elem);
final Element button = document.createElement("button");
button.appendChild(document.createTextNode("Click me!"));
elem.appendChild(button);
((EventTarget)button).addEventListener("click", new EventListener() {
@Override public void handleEvent(Event evt) {
button.getParentNode().removeChild(button);
printHelloWorld();
}
}, false);
}
private static void printHelloWorld() {
println("Hello, world!"); println("Hello, world!");
println("Here is the Fibonacci sequence:"); println("Here is the Fibonacci sequence:");
long a = 0; long a = 0;