From c9f78c5cdf0ecee1b037e6459814d78ccee47881 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Fri, 7 Feb 2014 00:44:06 +0400 Subject: [PATCH] Introduces concept of an *injector*. Uses injectors to make generated JavaScript code neat. --- .../java/org/teavm/javascript/Decompiler.java | 4 + .../teavm/javascript/JavascriptBuilder.java | 2 +- .../java/org/teavm/javascript/Renderer.java | 99 ++++++++++++- .../org/teavm/javascript/ni/InjectedBy.java | 31 ++++ .../org/teavm/javascript/ni/Injector.java | 27 ++++ .../teavm/javascript/ni/InjectorContext.java | 41 ++++++ .../main/java/org/teavm/javascript/ni/JS.java | 46 +++--- .../javascript/ni/JSNativeGenerator.java | 134 ++++++++++++------ 8 files changed, 319 insertions(+), 65 deletions(-) create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ni/InjectedBy.java create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ni/Injector.java create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java diff --git a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java index a0d07089e..aa3120576 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -20,6 +20,7 @@ import org.teavm.common.*; import org.teavm.javascript.ast.*; import org.teavm.javascript.ni.GeneratedBy; import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.InjectedBy; import org.teavm.model.*; import org.teavm.model.util.ProgramUtils; @@ -117,6 +118,9 @@ public class Decompiler { if (method.getModifiers().contains(ElementModifier.ABSTRACT)) { continue; } + if (method.getAnnotations().get(InjectedBy.class.getName()) != null) { + continue; + } clsNode.getMethods().add(decompile(method)); } clsNode.getInterfaces().addAll(cls.getInterfaces()); diff --git a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java index 69a2aff99..93ab5f182 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java +++ b/teavm-core/src/main/java/org/teavm/javascript/JavascriptBuilder.java @@ -96,7 +96,6 @@ public class JavascriptBuilder { SourceWriterBuilder builder = new SourceWriterBuilder(naming); builder.setMinified(minifying); SourceWriter sourceWriter = builder.build(writer); - Renderer renderer = new Renderer(sourceWriter, classSource); dependencyChecker.attachMethodGraph(new MethodReference("java.lang.Class", new MethodDescriptor("createNew", ValueType.object("java.lang.Class")))); dependencyChecker.attachMethodGraph(new MethodReference("java.lang.String", new MethodDescriptor("", @@ -104,6 +103,7 @@ public class JavascriptBuilder { executor.complete(); ListableClassHolderSource classSet = dependencyChecker.cutUnachievableClasses(); Decompiler decompiler = new Decompiler(classSet, classLoader, executor); + Renderer renderer = new Renderer(sourceWriter, classSet, classLoader); ClassSetOptimizer optimizer = new ClassSetOptimizer(executor); optimizer.optimizeAll(classSet); executor.complete(); diff --git a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java index 56069f761..6766736f7 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -16,13 +16,20 @@ package org.teavm.javascript; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.teavm.codegen.NamingException; import org.teavm.codegen.NamingStrategy; import org.teavm.codegen.SourceWriter; import org.teavm.javascript.ast.*; import org.teavm.javascript.ni.GeneratorContext; +import org.teavm.javascript.ni.InjectedBy; +import org.teavm.javascript.ni.Injector; +import org.teavm.javascript.ni.InjectorContext; import org.teavm.model.*; /** @@ -34,12 +41,23 @@ public class Renderer implements ExprVisitor, StatementVisitor { private NamingStrategy naming; private SourceWriter writer; private ClassHolderSource classSource; + private ClassLoader classLoader; private boolean minifying; + private Map injectorMap = new HashMap<>(); - public Renderer(SourceWriter writer, ClassHolderSource classSource) { + private static class InjectorHolder { + public final Injector injector; + + public InjectorHolder(Injector injector) { + this.injector = injector; + } + } + + public Renderer(SourceWriter writer, ClassHolderSource classSource, ClassLoader classLoader) { this.naming = writer.getNaming(); this.writer = writer; this.classSource = classSource; + this.classLoader = classLoader; } public SourceWriter getWriter() { @@ -193,6 +211,7 @@ public class Renderer implements ExprVisitor, StatementVisitor { } writer.outdent().append("}").newLine(); for (MethodNode method : cls.getMethods()) { + cls.getMethods(); if (!method.getModifiers().contains(NodeModifier.STATIC)) { renderDeclaration(method); } @@ -947,6 +966,11 @@ public class Renderer implements ExprVisitor, StatementVisitor { @Override public void visit(InvocationExpr expr) { try { + Injector injector = getInjector(expr.getMethod()); + if (injector != null) { + injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod()); + return; + } String className = naming.getNameFor(expr.getMethod().getClassName()); String name = naming.getNameFor(expr.getMethod()); String fullName = naming.getFullNameFor(expr.getMethod()); @@ -1124,4 +1148,77 @@ public class Renderer implements ExprVisitor, StatementVisitor { throw new RenderingException("IO error occured", e); } } + + private Injector getInjector(MethodReference ref) { + InjectorHolder holder = injectorMap.get(ref); + if (holder == null) { + MethodHolder method = classSource.getClassHolder(ref.getClassName()).getMethod(ref.getDescriptor()); + AnnotationHolder injectedByAnnot = method.getAnnotations().get(InjectedBy.class.getName()); + if (injectedByAnnot != null) { + ValueType type = injectedByAnnot.getValues().get("value").getJavaClass(); + holder = new InjectorHolder(instantiateInjector(((ValueType.Object)type).getClassName())); + } else { + holder = new InjectorHolder(null); + } + injectorMap.put(ref, holder); + } + return holder.injector; + } + + private Injector instantiateInjector(String type) { + try { + Class cls = Class.forName(type, true, classLoader).asSubclass(Injector.class); + Constructor cons = cls.getConstructor(); + return cons.newInstance(); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Illegal injector: " + type, e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Default constructor was not found in the " + type + " injector", e); + } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException("Error instantiating injector " + type, e); + } + } + + private class InjectorContextImpl implements InjectorContext { + private List arguments; + + public InjectorContextImpl(List arguments) { + this.arguments = arguments; + } + + @Override + public Expr getArgument(int index) { + return arguments.get(index); + } + + @Override + public boolean isMinifying() { + return minifying; + } + + @Override + public SourceWriter getWriter() { + return writer; + } + + @Override + public void writeEscaped(String str) throws IOException { + writer.append(escapeString(str)); + } + + @Override + public void writeType(ValueType type) throws IOException { + writer.append(typeToClsString(naming, type)); + } + + @Override + public void writeExpr(Expr expr) throws IOException { + expr.acceptVisitor(Renderer.this); + } + + @Override + public int argumentCount() { + return arguments.size(); + } + } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/InjectedBy.java b/teavm-core/src/main/java/org/teavm/javascript/ni/InjectedBy.java new file mode 100644 index 000000000..0398b043b --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/InjectedBy.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 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.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 InjectedBy { + Class value(); +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/Injector.java b/teavm-core/src/main/java/org/teavm/javascript/ni/Injector.java new file mode 100644 index 000000000..d67c483fd --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/Injector.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 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.javascript.ni; + +import java.io.IOException; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public interface Injector { + void generate(InjectorContext context, MethodReference methodRef) throws IOException; +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java b/teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java new file mode 100644 index 000000000..95cf19776 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 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.javascript.ni; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.ast.Expr; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +public interface InjectorContext { + Expr getArgument(int index); + + int argumentCount(); + + boolean isMinifying(); + + SourceWriter getWriter(); + + void writeEscaped(String str) throws IOException; + + void writeType(ValueType type) throws IOException; + + void writeExpr(Expr expr) throws IOException; +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/JS.java b/teavm-core/src/main/java/org/teavm/javascript/ni/JS.java index 40b1e983d..84a43546f 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ni/JS.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/JS.java @@ -45,25 +45,25 @@ public final class JS { public static native JSArray createArray(int size); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject getTypeName(JSObject obj); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject getGlobal(); @GeneratedBy(JSNativeGenerator.class) public static native JSObject wrap(String str); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject wrap(char c); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject wrap(int num); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject wrap(float num); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject wrap(double num); public static JSArray wrap(T[] array) { @@ -90,7 +90,7 @@ public final class JS { return result; } - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native boolean unwrapBoolean(JSObject obj); public static byte unwrapByte(JSObject obj) { @@ -101,53 +101,53 @@ public final class JS { return (short)unwrapInt(obj); } - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native int unwrapInt(JSObject obj); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native float unwrapFloat(JSObject obj); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native double unwrapDouble(JSObject obj); @GeneratedBy(JSNativeGenerator.class) public static native String unwrapString(JSObject obj); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native char unwrapCharacter(JSObject obj); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native boolean isUndefined(JSObject obj); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject invoke(JSObject instance, JSObject method, JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f, JSObject g); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) 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); @@ -170,9 +170,9 @@ public final class JS { }; } - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject get(JSObject instance, JSObject index); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native void set(JSObject instance, JSObject index, JSObject obj); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/JSNativeGenerator.java b/teavm-core/src/main/java/org/teavm/javascript/ni/JSNativeGenerator.java index 7275f5739..5fb8584ad 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ni/JSNativeGenerator.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/JSNativeGenerator.java @@ -17,6 +17,9 @@ package org.teavm.javascript.ni; import java.io.IOException; import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.ast.ConstantExpr; +import org.teavm.javascript.ast.Expr; +import org.teavm.javascript.ast.InvocationExpr; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; @@ -24,47 +27,62 @@ import org.teavm.model.MethodReference; * * @author Alexey Andreev */ -public class JSNativeGenerator implements Generator { +public class JSNativeGenerator implements Generator, Injector { @Override public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + if (methodRef.getName().equals("wrap")) { + generateWrapString(context, writer); + } else if (methodRef.getName().equals("unwrapString")) { + writer.append("return $rt_str(").append(context.getParameterName(1)).append(");") + .softNewLine(); + } + } + + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + SourceWriter writer = context.getWriter(); switch (methodRef.getName()) { - case "getTypeName": - writer.append("return typeof ").append(context.getParameterName(1)).append(";").softNewLine(); - break; case "getGlobal": - writer.append("return window;").softNewLine(); - break; - case "wrap": - if (methodRef.getParameterTypes()[0].isObject("java.lang.String")) { - generateWrapString(context, writer); - } else { - writer.append("return ").append(context.getParameterName(1)).append(";").softNewLine(); - } - break; - case "get": - writer.append("return ").append(context.getParameterName(1)).append("[") - .append(context.getParameterName(2)).append("];").softNewLine(); - break; - case "set": - writer.append(context.getParameterName(1)).append("[").append(context.getParameterName(2)) - .append("] = ").append(context.getParameterName(3)).softNewLine(); - break; - case "invoke": - generateInvoke(context, writer, methodRef.parameterCount() - 2); + writer.append("window"); break; case "isUndefined": - writer.append("return ").append(context.getParameterName(1)).append(" === undefined;"); + writer.append("("); + context.writeExpr(context.getArgument(0)); + writer.ws().append("===").ws().append("undefined)"); break; - default: - if (methodRef.getName().startsWith("unwrap")) { - if (methodRef.getDescriptor().getResultType().isObject("java.lang.String")) { - writer.append("return $rt_str(").append(context.getParameterName(1)).append(");") - .softNewLine(); - } else { - writer.append("return ").append(context.getParameterName(1)).append(";").softNewLine(); + case "getTypeName": + writer.append("(typeof "); + context.writeExpr(context.getArgument(0)); + writer.append(")"); + break; + case "get": + context.writeExpr(context.getArgument(0)); + renderProperty(context.getArgument(1), context); + break; + case "set": + writer.append('('); + context.writeExpr(context.getArgument(0)); + renderProperty(context.getArgument(1), context); + writer.ws().append('=').ws(); + context.writeExpr(context.getArgument(2)); + writer.append(')'); + break; + case "invoke": + context.writeExpr(context.getArgument(0)); + renderProperty(context.getArgument(1), context); + writer.append('('); + for (int i = 2; i < context.argumentCount(); ++i) { + if (i > 2) { + writer.append(',').ws(); } + context.writeExpr(context.getArgument(i)); } + writer.append(')'); + break; + case "wrap": + case "unwrap": + context.writeExpr(context.getArgument(0)); break; } } @@ -80,15 +98,51 @@ public class JSNativeGenerator implements Generator { writer.append("return result;").softNewLine(); } - private void generateInvoke(GeneratorContext context, SourceWriter writer, int argNum) throws IOException { - writer.append("return ").append(context.getParameterName(1)).append("[") - .append(context.getParameterName(2)).append("]("); - for (int i = 0; i < argNum; ++i) { - if (i > 0) { - writer.append(",").ws(); - } - writer.append(context.getParameterName(i + 3)); + private void renderProperty(Expr property, InjectorContext context) throws IOException { + SourceWriter writer = context.getWriter(); + String name = extractPropertyName(property); + if (name == null) { + writer.append('['); + context.writeExpr(property); + writer.append(']'); + } else if (!isIdentifier(name)) { + writer.append("[\""); + context.writeEscaped(name); + writer.append("\"]"); + } else { + writer.append(".").append(name); } - writer.append(");").softNewLine(); + } + + private String extractPropertyName(Expr propertyName) { + if (!(propertyName instanceof InvocationExpr)) { + return null; + } + InvocationExpr invoke = (InvocationExpr)propertyName; + if (!invoke.getMethod().getClassName().equals(JS.class.getName())) { + return null; + } + if (!invoke.getMethod().getName().equals("wrap") || + !invoke.getMethod().getDescriptor().parameterType(0).isObject("java.lang.String")) { + return null; + } + Expr arg = invoke.getArguments().get(0); + if (!(arg instanceof ConstantExpr)) { + return null; + } + ConstantExpr constant = (ConstantExpr)arg; + return constant.getValue() instanceof String ? (String)constant.getValue() : null; + } + + private boolean isIdentifier(String name) { + if (name.isEmpty() || !Character.isJavaIdentifierStart(name.charAt(0))) { + return false; + } + for (int i = 1; i < name.length(); ++i) { + if (!Character.isJavaIdentifierPart(name.charAt(i))) { + return false; + } + } + return true; } }