diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java index d54ff12ab..69bf0e7a7 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java @@ -25,7 +25,6 @@ import org.teavm.model.MethodReference; * @author Alexey Andreev */ public class StringNativeGenerator implements Injector { - @Override public void generate(InjectorContext context, MethodReference methodRef) throws IOException { switch (methodRef.getName()) { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java index d1ea4eea3..3e2caaa36 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java @@ -123,7 +123,7 @@ public class TInteger extends TNumber implements TComparable { if (integerCache == null) { integerCache = new TInteger[256]; for (int j = 0; j < integerCache.length; ++j) { - integerCache[j - 128] = new TInteger(j); + integerCache[j] = new TInteger(j - 128); } } } diff --git a/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java b/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java index 225eeea3d..2a9684fc5 100644 --- a/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java +++ b/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java @@ -57,9 +57,10 @@ public class DefaultNamingStrategy implements NamingStrategy { @Override public String getNameFor(MethodReference method) { + MethodReference origMethod = method; method = getRealMethod(method); if (method == null) { - throw new NamingException("Can't provide name for method as it was not found: " + method); + throw new NamingException("Can't provide name for method as it was not found: " + origMethod); } ClassHolder clsHolder = classSource.get(method.getClassName()); MethodHolder methodHolder = clsHolder.getMethod(method.getDescriptor()); diff --git a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js index 3e3800468..02f6e9043 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -43,6 +43,10 @@ $rt_createArray = function(cls, sz) { } return arr; } +$rt_wrapArray = function(cls, data) { + var arr = new ($rt_arraycls(cls))(data); + return arr; +} $rt_createUnfilledArray = function(cls, sz) { return new ($rt_arraycls(cls))(new Array(sz)); } diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyConverter.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyConverter.java new file mode 100644 index 000000000..1dc0d0fdd --- /dev/null +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyConverter.java @@ -0,0 +1,33 @@ +/* + * 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.ni.GeneratedBy; + +/** + * + * @author Alexey Andreev + */ +public final class JavaScriptBodyConverter { + private JavaScriptBodyConverter() { + } + + @GeneratedBy(JavaScriptBodyConverterGenerator.class) + public static native Object toJavaScript(Object obj); + + @GeneratedBy(JavaScriptBodyConverterGenerator.class) + public static native Object fromJavaScript(Object obj); +} diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyConverterGenerator.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyConverterGenerator.java new file mode 100644 index 000000000..930695608 --- /dev/null +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyConverterGenerator.java @@ -0,0 +1,105 @@ +/* + * 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 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 JavaScriptBodyConverterGenerator implements Generator { + private static final String convCls = JavaScriptBodyConverter.class.getName(); + static final MethodReference intValueMethod = new MethodReference("java.lang.Integer", + new MethodDescriptor("intValue", ValueType.INTEGER)); + private static final ValueType objType = ValueType.object("java.lang.Object"); + static final MethodReference toJsMethod = new MethodReference(convCls, new MethodDescriptor( + "toJavaScript", objType, objType)); + static final MethodReference fromJsMethod = new MethodReference(convCls, new MethodDescriptor( + "fromJavaScript", objType, objType)); + + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + switch (methodRef.getName()) { + case "toJavaScript": + generateToJavaScript(context, writer); + break; + case "fromJavaScript": + generateFromJavaScript(context, writer); + break; + } + } + + private void generateToJavaScript(GeneratorContext context, SourceWriter writer) throws IOException { + String obj = context.getParameterName(1); + writer.append("if (" + obj + " === null) {").softNewLine().indent(); + writer.append("return null;").softNewLine(); + writer.outdent().append("} else if (" + obj + ".constructor.$meta.item) {").indent().softNewLine(); + writer.append("var arr = new Array(" + obj + ".data.length);").softNewLine(); + writer.append("for (var i = 0; i < arr.length; ++i) {").indent().softNewLine(); + writer.append("arr[i] = ").appendMethodBody(toJsMethod).append("(" + obj + ".data[i]);").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("return arr;").softNewLine(); + writer.outdent().append("}"); + writer.append(" else if (" + obj + ".constructor === ").appendClass("java.lang.String") + .append(") {").indent().softNewLine(); + generateStringToJavaScript(context, writer); + writer.outdent().append("} else if (" + obj + ".constructor === ").appendClass("java.lang.Integer") + .append(") {").indent().softNewLine(); + writer.append("return ").appendMethodBody(intValueMethod).append("(" + obj + ");").softNewLine(); + writer.outdent().append("}"); + writer.append(" else {").indent().softNewLine(); + writer.append("return " + obj + ";").softNewLine(); + writer.outdent().append("}").softNewLine(); + } + + private void generateFromJavaScript(GeneratorContext context, SourceWriter writer) throws IOException { + String obj = context.getParameterName(1); + writer.append("if (" + obj +" === null || " + obj + " === undefined)").ws().append("{") + .softNewLine().indent(); + writer.append("return null;").softNewLine(); + writer.outdent().append("} else if (" + obj + " instanceof Array) {").indent().softNewLine(); + writer.append("var arr = $rt_createArray($rt_objcls(), " + obj + ".length);").softNewLine(); + writer.append("for (var i = 0; i < arr.data.length; ++i) {").indent().softNewLine(); + writer.append("arr.data[i] = ").appendMethodBody(fromJsMethod).append("(" + obj + "[i]);") + .softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("return arr;").softNewLine(); + writer.outdent().append("}"); + writer.append(" else if (" + obj + ".constructor === ").appendClass("java.lang.String") + .append(") {").indent().softNewLine(); + writer.append("return $rt_str(" + obj + ");").softNewLine(); + writer.outdent().append("}"); + writer.ws().append("else").ws().append("{").indent().softNewLine(); + writer.append("return ").append(obj).append(";").softNewLine(); + writer.outdent().append("}").softNewLine(); + } + + private void generateStringToJavaScript(GeneratorContext context, SourceWriter writer) throws IOException { + FieldReference charsField = new FieldReference("java.lang.String", "characters"); + writer.append("var result = \"\";").softNewLine(); + writer.append("var data = ").append(context.getParameterName(1)).append('.') + .appendField(charsField).append(".data;").softNewLine(); + writer.append("for (var i = 0; i < data.length; i = (i + 1) | 0) {").indent().softNewLine(); + writer.append("result += String.fromCharCode(data[i]);").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("return result;").softNewLine(); + } +} diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java index ae6eb87ef..ded276f45 100644 --- a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java @@ -30,8 +30,6 @@ public class JavaScriptBodyDependency implements DependencyListener { public void started(DependencyChecker dependencyChecker) { allClassesNode = dependencyChecker.createNode(); allClassesNode.setTag("JavaScriptBody:global"); - allClassesNode.getArrayItem().addConsumer(new OneDirectionalConnection(allClassesNode)); - allClassesNode.getArrayItem().getArrayItem().addConsumer(new OneDirectionalConnection(allClassesNode)); } private static class OneDirectionalConnection implements DependencyConsumer { @@ -55,6 +53,7 @@ public class JavaScriptBodyDependency implements DependencyListener { MethodHolder method = cls.getMethod(methodRef.getDescriptor()); AnnotationReader annot = method.getAnnotations().get(JavaScriptBody.class.getName()); if (annot != null) { + includeDefaultDependencies(dependencyChecker); AnnotationValue javacall = annot.getValue("javacall"); MethodGraph graph = dependencyChecker.attachMethodGraph(methodRef); if (graph.getResult() != null) { @@ -76,6 +75,12 @@ public class JavaScriptBodyDependency implements DependencyListener { } } + private void includeDefaultDependencies(DependencyChecker dependencyChecker) { + dependencyChecker.attachMethodGraph(JavaScriptBodyConverterGenerator.fromJsMethod); + dependencyChecker.attachMethodGraph(JavaScriptBodyConverterGenerator.toJsMethod); + dependencyChecker.attachMethodGraph(JavaScriptBodyConverterGenerator.intValueMethod); + } + @Override public void fieldAchieved(DependencyChecker dependencyChecker, FieldReference field) { } diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java index d56bd9f2d..88c7677b2 100644 --- a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java @@ -41,7 +41,7 @@ public class JavaScriptBodyGenerator implements Generator { GeneratorJsCallback callbackGen = new GeneratorJsCallback(context.getClassSource(), writer.getNaming()); body = callbackGen.parse(body); } - writer.append("return (function("); + writer.append("var result = (function("); for (int i = 0; i < args.size(); ++i) { if (i > 0) { writer.append(",").ws(); @@ -53,9 +53,30 @@ public class JavaScriptBodyGenerator implements Generator { 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)); + wrapParameter(writer, methodRef.getDescriptor().parameterType(i), context.getParameterName(i + 1)); + } + writer.append(")").softNewLine(); + writer.append("return "); + unwrapValue(writer, method.getResultType(), "result"); + writer.append(";").softNewLine(); + } + + private void wrapParameter(SourceWriter writer, ValueType type, String param) throws IOException { + if (type.isObject("java.lang.Object")) { + writer.appendMethodBody(JavaScriptBodyConverterGenerator.toJsMethod); + writer.append("(").append(param).append(")"); + } else { + writer.append(param); + } + } + + private void unwrapValue(SourceWriter writer, ValueType type, String param) throws IOException { + if (type.isObject("java.lang.Object")) { + writer.appendMethodBody(JavaScriptBodyConverterGenerator.fromJsMethod); + writer.append("(").append(param).append(")"); + } else { + writer.append(param); } - writer.append(");").softNewLine(); } private static class GeneratorJsCallback extends JsCallback { diff --git a/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyConversionTests.java b/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyConversionTests.java new file mode 100644 index 000000000..b64d34f37 --- /dev/null +++ b/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyConversionTests.java @@ -0,0 +1,34 @@ +/* + * 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 JavaScriptBodyConversionTests { + @Test + public void convertsInteger() { + assertEquals(23, returnAsInt(23)); + } + + @JavaScriptBody(args = { "value" }, body = "return value;") + private native int returnAsInt(Object value); +} 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 index b0a4b44d0..a49ab769a 100644 --- a/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyTests.java +++ b/teavm-html4j/src/test/java/org/teavm/html4j/test/JavaScriptBodyTests.java @@ -49,21 +49,17 @@ public class JavaScriptBodyTests { assertEquals(23, array[0].foo()); } - @Test - public void dependencyPropagatedBackThroughArray() { - A[] first = new A[1]; - storeObject(first); - Object[] second = (Object[])retrieveObject(); - second[0] = new AImpl(); - assertEquals(23, first[0].foo()); - } - @Test public void valuePropagatedToCallback() { A a = new AImpl(); assertEquals(23, invokeCallback(a)); } + @Test + public void staticCallbackInvoked() { + assertEquals(23, invokeStaticCallback(new AImpl())); + } + private static class AImpl implements A { @Override public int foo() { return 23; @@ -90,4 +86,13 @@ public class JavaScriptBodyTests { "@org.teavm.html4j.test.B::bar(" + "Lorg/teavm/html4j/test/A;)(_global_)", javacall = true) private native int invokeCallback(B callback); + + public static int staticCallback(A a) { + return a.foo(); + } + + @JavaScriptBody(args = { "a" }, body = "return " + + "@org.teavm.html4j.test.JavaScriptBodyTests::staticCallback(" + + "Lorg/teavm/html4j/test/A;)(a)", javacall = true) + private native int invokeStaticCallback(A a); }