From afc85c9780f4f90fd27c5ddf9792859cbcf31a4b Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Fri, 14 Feb 2014 13:21:18 +0400 Subject: [PATCH] Adds initial source code of plugin to support html4j --- pom.xml | 9 + teavm-html4j/.gitignore | 5 + teavm-html4j/pom.xml | 101 ++++++++++ .../java/org/teavm/html4j/HTML4JPlugin.java | 31 ++++ .../html4j/JavaScriptBodyDependency.java | 129 +++++++++++++ .../teavm/html4j/JavaScriptBodyGenerator.java | 108 +++++++++++ .../html4j/JavaScriptBodyTransformer.java | 38 ++++ .../java/org/teavm/html4j/JsCallback.java | 174 ++++++++++++++++++ ...g.teavm.javascript.JavascriptBuilderPlugin | 1 + .../html4j/test/JavaScriptBodyTests.java | 51 +++++ teavm-jso/pom.xml | 2 +- 11 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 teavm-html4j/.gitignore create mode 100644 teavm-html4j/pom.xml create mode 100644 teavm-html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java create mode 100644 teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java create mode 100644 teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java create mode 100644 teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java create mode 100644 teavm-html4j/src/main/java/org/teavm/html4j/JsCallback.java create mode 100644 teavm-html4j/src/main/resources/META-INF/services/org.teavm.javascript.JavascriptBuilderPlugin create mode 100644 teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyTests.java diff --git a/pom.xml b/pom.xml index 573f919be..00c6274b6 100644 --- a/pom.xml +++ b/pom.xml @@ -34,4 +34,13 @@ + + + + enable-html4j-support + + teavm-html4j + + + \ No newline at end of file diff --git a/teavm-html4j/.gitignore b/teavm-html4j/.gitignore new file mode 100644 index 000000000..df70de676 --- /dev/null +++ b/teavm-html4j/.gitignore @@ -0,0 +1,5 @@ +/target +/.settings +/.classpath +/.factorypath +/.project diff --git a/teavm-html4j/pom.xml b/teavm-html4j/pom.xml new file mode 100644 index 000000000..aad32b294 --- /dev/null +++ b/teavm-html4j/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + org.teavm + teavm + 0.0.1-SNAPSHOT + + teavm-html4j + + + + org.teavm + teavm-core + 0.0.1-SNAPSHOT + + + org.teavm + teavm-classlib + 0.0.1-SNAPSHOT + + + org.netbeans.html + net.java.html.boot + 0.8-SNAPSHOT + + + junit + junit + 4.11 + test + + + + + + + org.teavm + teavm-maven-plugin + 0.0.1-SNAPSHOT + + + generate-javascript-tests + + build-junit + + process-test-classes + + false + 1 + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.teavm + teavm-maven-plugin + [0.0.1-SNAPSHOT,) + + build-junit + + + + + + + + + + + + + + \ No newline at end of file diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java b/teavm-html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.java new file mode 100644 index 000000000..9e93d8a9c --- /dev/null +++ b/teavm-html4j/src/main/java/org/teavm/html4j/HTML4JPlugin.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.html4j; + +import org.teavm.javascript.JavascriptBuilderHost; +import org.teavm.javascript.JavascriptBuilderPlugin; + +/** + * + * @author Alexey Andreev + */ +public class HTML4JPlugin implements JavascriptBuilderPlugin { + @Override + public void install(JavascriptBuilderHost host) { + host.add(new JavaScriptBodyDependency()); + host.add(new JavaScriptBodyTransformer()); + } +} diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java new file mode 100644 index 000000000..d3b9b1d50 --- /dev/null +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java @@ -0,0 +1,129 @@ +/* + * 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.html4j; + +import net.java.html.js.JavaScriptBody; +import org.teavm.dependency.*; +import org.teavm.model.*; + +/** + * + * @author Alexey Andreev + */ +public class JavaScriptBodyDependency implements DependencyListener { + private DependencyNode allClassesNode; + + @Override + public void started(DependencyChecker dependencyChecker) { + allClassesNode = dependencyChecker.createNode(); + } + + @Override + public void classAchieved(DependencyChecker dependencyChecker, String className) { + allClassesNode.propagate(className); + } + + @Override + public void methodAchieved(DependencyChecker dependencyChecker, MethodReference methodRef) { + ClassHolder cls = dependencyChecker.getClassSource().get(methodRef.getClassName()); + MethodHolder method = cls.getMethod(methodRef.getDescriptor()); + AnnotationReader annot = method.getAnnotations().get(JavaScriptBody.class.getName()); + if (annot != null) { + AnnotationValue javacall = annot.getValue("javacall"); + MethodGraph graph = dependencyChecker.attachMethodGraph(methodRef); + if (graph.getResult() != null) { + allClassesNode.connect(graph.getResult()); + } + if (javacall != null && javacall.getBoolean()) { + String body = annot.getValue("body").getString(); + new GeneratorJsCallback(dependencyChecker.getClassSource(), dependencyChecker).parse(body); + } + } + } + + @Override + public void fieldAchieved(DependencyChecker dependencyChecker, FieldReference field) { + } + + private static MethodReader findMethod(ClassReaderSource classSource, String clsName, MethodDescriptor desc) { + while (clsName != null) { + ClassReader cls = classSource.get(clsName); + for (MethodReader method : cls.getMethods()) { + if (method.getName().equals(desc.getName()) && sameParams(method.getDescriptor(), desc)) { + return method; + } + } + clsName = cls.getParent(); + } + return null; + } + private static boolean sameParams(MethodDescriptor a, MethodDescriptor b) { + if (a.parameterCount() != b.parameterCount()) { + return false; + } + for (int i = 0; i < a.parameterCount(); ++i) { + if (!a.parameterType(i).equals(b.parameterType(i))) { + return false; + } + } + return true; + } + + private class GeneratorJsCallback extends JsCallback { + private ClassReaderSource classSource; + private DependencyChecker dependencyChecker; + public GeneratorJsCallback(ClassReaderSource classSource, DependencyChecker dependencyChecker) { + this.classSource = classSource; + this.dependencyChecker = dependencyChecker; + } + @Override protected CharSequence callMethod(String ident, String fqn, String method, String params) { + MethodDescriptor desc = MethodDescriptor.parse(method + "V"); + MethodReader reader = findMethod(classSource, fqn, desc); + if (reader != null) { + if (reader.hasModifier(ElementModifier.STATIC) || reader.hasModifier(ElementModifier.FINAL)) { + MethodGraph graph = dependencyChecker.attachMethodGraph(reader.getReference()); + for (int i = 0; i <= graph.getParameterCount(); ++i) { + allClassesNode.connect(graph.getVariable(i)); + } + } else { + allClassesNode.addConsumer(new VirtualCallbackConsumer(classSource, dependencyChecker, desc)); + } + } + return ""; + } + } + + private class VirtualCallbackConsumer implements DependencyConsumer { + private ClassReaderSource classSource; + private DependencyChecker dependencyChecker; + private MethodDescriptor desc; + public VirtualCallbackConsumer(ClassReaderSource classSource, DependencyChecker dependencyChecker, + MethodDescriptor desc) { + this.classSource = classSource; + this.dependencyChecker = dependencyChecker; + this.desc = desc; + } + @Override public void consume(String type) { + MethodReader reader = findMethod(classSource, type, desc); + if (reader != null) { + MethodGraph graph = dependencyChecker.attachMethodGraph(reader.getReference()); + for (int i = 0; i <= graph.getParameterCount(); ++i) { + allClassesNode.connect(graph.getVariable(i)); + } + } + } + } +} diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java new file mode 100644 index 000000000..7af09efcc --- /dev/null +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java @@ -0,0 +1,108 @@ +/* + * 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.html4j; + +import java.io.IOException; +import java.util.List; +import net.java.html.js.JavaScriptBody; +import org.teavm.codegen.NamingStrategy; +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 JavaScriptBodyGenerator implements Generator { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + MethodReader method = context.getClassSource().get(methodRef.getClassName()) + .getMethod(methodRef.getDescriptor()); + AnnotationReader annot = method.getAnnotations().get(JavaScriptBody.class.getName()); + String body = annot.getValue("body").getString(); + List args = annot.getValue("args").getList(); + AnnotationValue javacall = annot.getValue("javacall"); + if (javacall != null && javacall.getBoolean()) { + GeneratorJsCallback callbackGen = new GeneratorJsCallback(context.getClassSource(), writer.getNaming()); + body = callbackGen.parse(body); + } + writer.append("return (function("); + for (int i = 0; i < args.size(); ++i) { + if (i > 0) { + writer.append(",").ws(); + } + writer.append(args.get(i).getString()); + } + writer.append(")").ws().append("{").indent().softNewLine(); + writer.append(body).softNewLine(); + writer.outdent().append("}).call(").append(context.getParameterName(0)); + for (int i = 0; i < args.size(); ++i) { + writer.append(",").ws(); + writer.append(context.getParameterName(i + 1)); + } + writer.append(");"); + } + + private static class GeneratorJsCallback extends JsCallback { + private ClassReaderSource classSource; + private NamingStrategy naming; + public GeneratorJsCallback(ClassReaderSource classSource, NamingStrategy naming) { + this.classSource = classSource; + this.naming = naming; + } + @Override protected CharSequence callMethod(String ident, String fqn, String method, String params) { + MethodDescriptor desc = MethodDescriptor.parse(method + "V"); + MethodReader reader = findMethod(fqn, desc); + StringBuilder sb = new StringBuilder(); + String name = naming.getNameFor(naming.getNameFor(reader.getReference())); + if (ident != null) { + if (reader.hasModifier(ElementModifier.FINAL)) { + sb.append(name).append("(").append(ident).append(",").append(params.substring(1)); + } else { + sb.append(ident).append('.').append(name).append(params); + } + } else { + sb.append("(").append(ident).append(",").append(params.substring(1)); + } + return sb.toString(); + } + private MethodReader findMethod(String clsName, MethodDescriptor desc) { + while (clsName != null) { + ClassReader cls = classSource.get(clsName); + for (MethodReader method : cls.getMethods()) { + if (method.getName().equals(desc.getName()) && sameParams(method.getDescriptor(), desc)) { + return method; + } + } + clsName = cls.getParent(); + } + return null; + } + private boolean sameParams(MethodDescriptor a, MethodDescriptor b) { + if (a.parameterCount() != b.parameterCount()) { + return false; + } + for (int i = 0; i < a.parameterCount(); ++i) { + if (!a.parameterType(i).equals(b.parameterType(i))) { + return false; + } + } + return true; + } + } +} diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java new file mode 100644 index 000000000..00a53ea18 --- /dev/null +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyTransformer.java @@ -0,0 +1,38 @@ +/* + * 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.html4j; + +import net.java.html.js.JavaScriptBody; +import org.teavm.javascript.ni.GeneratedBy; +import org.teavm.model.*; + +/** + * + * @author Alexey Andreev + */ +public class JavaScriptBodyTransformer implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassReaderSource innerSource) { + for (MethodHolder method : cls.getMethods()) { + if (method.getAnnotations().get(JavaScriptBody.class.getName()) != null) { + AnnotationHolder genAnnot = new AnnotationHolder(GeneratedBy.class.getName()); + genAnnot.getValues().put("value", new AnnotationValue(ValueType.object( + JavaScriptBodyGenerator.class.getName()))); + method.getAnnotations().add(genAnnot); + } + } + } +} diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JsCallback.java b/teavm-html4j/src/main/java/org/teavm/html4j/JsCallback.java new file mode 100644 index 000000000..2ca89f7cf --- /dev/null +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JsCallback.java @@ -0,0 +1,174 @@ +/* + * 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. + */ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.teavm.html4j; + +/** +* +* @author Jaroslav Tulach +*/ +abstract class JsCallback { + final String parse(String body) { + StringBuilder sb = new StringBuilder(); + int pos = 0; + for (;;) { + int next = body.indexOf(".@", pos); + if (next == -1) { + sb.append(body.substring(pos)); + body = sb.toString(); + break; + } + int ident = next; + while (ident > 0) { + if (!Character.isJavaIdentifierPart(body.charAt(--ident))) { + ident++; + break; + } + } + String refId = body.substring(ident, next); + + sb.append(body.substring(pos, ident)); + + int sigBeg = body.indexOf('(', next); + int sigEnd = body.indexOf(')', sigBeg); + int colon4 = body.indexOf("::", next); + if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) { + throw new IllegalStateException( + "Wrong format of instance callback. " + + "Should be: 'inst.@pkg.Class::method(Ljava/lang/Object;)(param)':\n" + + body + ); + } + String fqn = body.substring(next + 2, colon4); + String method = body.substring(colon4 + 2, sigBeg); + String params = body.substring(sigBeg, sigEnd + 1); + + int paramBeg = body.indexOf('(', sigEnd + 1); + if (paramBeg == -1) { + throw new IllegalStateException( + "Wrong format of instance callback. " + + "Should be: 'inst.@pkg.Class::method(Ljava/lang/Object;)(param)':\n" + + body + ); + } + + sb.append(callMethod(refId, fqn, method, params)); + if (body.charAt(paramBeg + 1) != (')')) { + sb.append(","); + } + pos = paramBeg + 1; + } + pos = 0; + sb = null; + for (;;) { + int next = body.indexOf("@", pos); + if (next == -1) { + if (sb == null) { + return body; + } + sb.append(body.substring(pos)); + return sb.toString(); + } + if (sb == null) { + sb = new StringBuilder(); + } + + sb.append(body.substring(pos, next)); + + int sigBeg = body.indexOf('(', next); + int sigEnd = body.indexOf(')', sigBeg); + int colon4 = body.indexOf("::", next); + if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) { + throw new IllegalStateException( + "Wrong format of static callback. " + + "Should be: '@pkg.Class::staticMethod(Ljava/lang/Object;)(param)':\n" + + body + ); + } + String fqn = body.substring(next + 1, colon4); + String method = body.substring(colon4 + 2, sigBeg); + String params = body.substring(sigBeg, sigEnd + 1); + + int paramBeg = body.indexOf('(', sigEnd + 1); + + sb.append(callMethod(null, fqn, method, params)); + pos = paramBeg + 1; + } + } + + protected abstract CharSequence callMethod( + String ident, String fqn, String method, String params + ); + + static String mangle(String fqn, String method, String params) { + if (params.startsWith("(")) { + params = params.substring(1); + } + if (params.endsWith(")")) { + params = params.substring(0, params.length() - 1); + } + return + replace(fqn) + "$" + replace(method) + "$" + replace(params); + } + + private static String replace(String orig) { + return orig.replace("_", "_1"). + replace(";", "_2"). + replace("[", "_3"). + replace('.', '_').replace('/', '_'); + } +} diff --git a/teavm-html4j/src/main/resources/META-INF/services/org.teavm.javascript.JavascriptBuilderPlugin b/teavm-html4j/src/main/resources/META-INF/services/org.teavm.javascript.JavascriptBuilderPlugin new file mode 100644 index 000000000..087f4f18f --- /dev/null +++ b/teavm-html4j/src/main/resources/META-INF/services/org.teavm.javascript.JavascriptBuilderPlugin @@ -0,0 +1 @@ +org.teavm.html4j.HTML4JPlugin \ No newline at end of file diff --git a/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyTests.java b/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyTests.java new file mode 100644 index 000000000..dae072957 --- /dev/null +++ b/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyTests.java @@ -0,0 +1,51 @@ +/* + * 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.html4j.test; + +import static org.junit.Assert.*; +import net.java.html.js.JavaScriptBody; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class JavaScriptBodyTests { + @Test + public void javaScriptBodyHandled() { + assertEquals(23, simpleNativeMethod()); + } + + @JavaScriptBody(args = {}, body = "return 23;") + private native int simpleNativeMethod(); + + @Test + public void dependencyPropagated() { + A a = (A)returnValuePassed(new AImpl()); + assertEquals(23, a.foo()); + } + + private static interface A { + public int foo(); + } + private static class AImpl implements A { + @Override public int foo() { + return 23; + } + } + @JavaScriptBody(args = { "value" }, body = "return value;") + private native Object returnValuePassed(Object value); +} diff --git a/teavm-jso/pom.xml b/teavm-jso/pom.xml index 6a6a4b9fe..6f0f73145 100644 --- a/teavm-jso/pom.xml +++ b/teavm-jso/pom.xml @@ -23,7 +23,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs 0.0.1-SNAPSHOT teavm-jso - + org.teavm