Adds initial source code of plugin to support html4j

This commit is contained in:
konsoletyper 2014-02-14 13:21:18 +04:00
parent cc3a89ebf7
commit afc85c9780
11 changed files with 648 additions and 1 deletions

View File

@ -34,4 +34,13 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles>
<profile>
<id>enable-html4j-support</id>
<modules>
<module>teavm-html4j</module>
</modules>
</profile>
</profiles>
</project> </project>

5
teavm-html4j/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/target
/.settings
/.classpath
/.factorypath
/.project

101
teavm-html4j/pom.xml Normal file
View File

@ -0,0 +1,101 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.teavm</groupId>
<artifactId>teavm</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>teavm-html4j</artifactId>
<dependencies>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-classlib</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.netbeans.html</groupId>
<artifactId>net.java.html.boot</artifactId>
<version>0.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.teavm</groupId>
<artifactId>teavm-maven-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<executions>
<execution>
<id>generate-javascript-tests</id>
<goals>
<goal>build-junit</goal>
</goals>
<phase>process-test-classes</phase>
<configuration>
<minifying>false</minifying>
<numThreads>1</numThreads>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.teavm</groupId>
<artifactId>teavm-maven-plugin</artifactId>
<versionRange>[0.0.1-SNAPSHOT,)</versionRange>
<goals>
<goal>build-junit</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -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 <konsoletyper@gmail.com>
*/
public class HTML4JPlugin implements JavascriptBuilderPlugin {
@Override
public void install(JavascriptBuilderHost host) {
host.add(new JavaScriptBodyDependency());
host.add(new JavaScriptBodyTransformer());
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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));
}
}
}
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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<AnnotationValue> 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;
}
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}
}
}
}

View File

@ -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 <jtulach@netbeans.org>
*/
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('/', '_');
}
}

View File

@ -0,0 +1 @@
org.teavm.html4j.HTML4JPlugin

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}