1.1 --- a/boot/src/main/java/net/java/html/js/JavaScriptBody.java Sun Mar 09 20:12:51 2014 +0100
1.2 +++ b/boot/src/main/java/net/java/html/js/JavaScriptBody.java Sun Mar 09 22:42:42 2014 +0100
1.3 @@ -93,4 +93,31 @@
1.4 * syntax
1.5 */
1.6 public boolean javacall() default false;
1.7 +
1.8 + /** Should we wait before the JavaScript snippet execution finishes?
1.9 + * Or not.
1.10 + * <p>
1.11 + * Some implementations that recognize the {@link JavaScriptBody} annotation
1.12 + * need to reschedule the JavaScript execution into different thread and
1.13 + * then it is easier for them to perform the execution asynchronously
1.14 + * and not wait for the result of the execution. This may however be
1.15 + * unexpected (for example when one awaits a callback into Java)
1.16 + * and as such it has to be explicitly allowed by specifying
1.17 + * <code>wait4js = false</code>. Such methods need to return <code>void</code>.
1.18 + * <p>
1.19 + * Implementations that execute the JavaScript synchronously may ignore
1.20 + * this attribute.
1.21 + * <p>
1.22 + * Implementations that delay execution of JavaScript need to guarantee
1.23 + * the order of snippets. Those that were submitted sooner, need to be
1.24 + * executed sooner. Each snippet need to be executed in a timely manner
1.25 + * (e.g. by a second, or so) even if there are no other calls made
1.26 + * in the main program.
1.27 + * <p>
1.28 + *
1.29 + * @since 0.7.6
1.30 + * @return <code>false</code> in case one allows asynchronous execution
1.31 + * of the JavaScript snippet
1.32 + */
1.33 + public boolean wait4js() default true;
1.34 }
2.1 --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Sun Mar 09 20:12:51 2014 +0100
2.2 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Sun Mar 09 22:42:42 2014 +0100
2.3 @@ -52,6 +52,8 @@
2.4 import java.util.Map;
2.5 import java.util.Set;
2.6 import java.util.concurrent.Executor;
2.7 +import java.util.logging.Level;
2.8 +import java.util.logging.Logger;
2.9 import net.java.html.js.JavaScriptBody;
2.10 import org.netbeans.html.boot.impl.FnContext;
2.11
2.12 @@ -140,6 +142,17 @@
2.13 return new Fn(fn.presenter()) {
2.14 @Override
2.15 public Object invoke(Object thiz, Object... args) throws Exception {
2.16 + loadResource();
2.17 + return fn.invoke(thiz, args);
2.18 + }
2.19 +
2.20 + @Override
2.21 + public void invokeLater(Object thiz, Object... args) throws Exception {
2.22 + loadResource();
2.23 + fn.invokeLater(thiz, args);
2.24 + }
2.25 +
2.26 + private void loadResource() throws Exception {
2.27 Presenter p = presenter();
2.28 if (p == null) {
2.29 p = FnContext.currentPresenter(false);
2.30 @@ -160,10 +173,10 @@
2.31 }
2.32 }
2.33 }
2.34 - return fn.invoke(thiz, args);
2.35 }
2.36 };
2.37 }
2.38 +
2.39
2.40 /** The currently active presenter.
2.41 *
2.42 @@ -200,6 +213,20 @@
2.43 * @throws Exception if something goes wrong, as exception may be thrown
2.44 */
2.45 public abstract Object invoke(Object thiz, Object... args) throws Exception;
2.46 +
2.47 + /** Invokes the defined function with specified <code>this</code> and
2.48 + * appropriate arguments asynchronously. The invocation may be
2.49 + * happen <em>"later"</em>.
2.50 + *
2.51 + * @param thiz the meaning of <code>this</code> inside of the JavaScript
2.52 + * function - can be <code>null</code>
2.53 + * @param args arguments for the function
2.54 + * @throws Exception if something goes wrong, as exception may be thrown
2.55 + * @since 0.7.6
2.56 + */
2.57 + public void invokeLater(Object thiz, Object... args) throws Exception {
2.58 + invoke(this, args);
2.59 + }
2.60
2.61 /** Provides the function implementation access to the presenter provided
2.62 * in {@link #Fn(org.apidesign.html.boot.spi.Fn.Presenter) the constructor}.
3.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnContext.java Sun Mar 09 20:12:51 2014 +0100
3.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnContext.java Sun Mar 09 22:42:42 2014 +0100
3.3 @@ -43,6 +43,7 @@
3.4 package org.netbeans.html.boot.impl;
3.5
3.6 import java.io.Closeable;
3.7 +import java.io.Flushable;
3.8 import java.io.IOException;
3.9 import java.util.logging.Logger;
3.10 import org.apidesign.html.boot.spi.Fn;
3.11 @@ -53,10 +54,17 @@
3.12 */
3.13 public final class FnContext implements Closeable {
3.14 private static final Logger LOG = Logger.getLogger(FnContext.class.getName());
3.15 + private static final FnContext DUMMY;
3.16 + static {
3.17 + DUMMY = new FnContext(null, null);
3.18 + DUMMY.prev = DUMMY;
3.19 + }
3.20
3.21 private Object prev;
3.22 - private FnContext(Fn.Presenter p) {
3.23 - this.prev = p;
3.24 + private final Fn.Presenter current;
3.25 + private FnContext(Fn.Presenter prevP, Fn.Presenter newP) {
3.26 + this.current = newP;
3.27 + this.prev = prevP;
3.28 }
3.29
3.30 @Override
3.31 @@ -64,6 +72,9 @@
3.32 if (prev != this) {
3.33 currentPresenter((Fn.Presenter)prev);
3.34 prev = this;
3.35 + if (current instanceof Flushable) {
3.36 + ((Flushable)current).flush();
3.37 + }
3.38 }
3.39 }
3.40 /*
3.41 @@ -75,7 +86,11 @@
3.42 }
3.43 */
3.44 public static Closeable activate(Fn.Presenter newP) {
3.45 - return new FnContext(currentPresenter(newP));
3.46 + final Fn.Presenter oldP = currentPresenter(newP);
3.47 + if (oldP == newP) {
3.48 + return DUMMY;
3.49 + }
3.50 + return new FnContext(oldP, newP);
3.51 }
3.52
3.53
4.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java Sun Mar 09 20:12:51 2014 +0100
4.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java Sun Mar 09 22:42:42 2014 +0100
4.3 @@ -467,31 +467,38 @@
4.4 FindInMethod.super.visitInsn(Opcodes.AASTORE);
4.5 }
4.6
4.7 - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
4.8 - "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
4.9 - );
4.10 - switch (sv.returnType.getSort()) {
4.11 - case Type.VOID:
4.12 - super.visitInsn(Opcodes.RETURN);
4.13 - break;
4.14 - case Type.ARRAY:
4.15 - case Type.OBJECT:
4.16 - super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
4.17 - super.visitInsn(Opcodes.ARETURN);
4.18 - break;
4.19 - case Type.BOOLEAN:
4.20 - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
4.21 - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
4.22 - "java/lang/Boolean", "booleanValue", "()Z"
4.23 - );
4.24 - super.visitInsn(Opcodes.IRETURN);
4.25 - break;
4.26 - default:
4.27 - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
4.28 - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
4.29 - "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
4.30 - );
4.31 - super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
4.32 + if (fia.wait4js) {
4.33 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
4.34 + "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
4.35 + );
4.36 + switch (sv.returnType.getSort()) {
4.37 + case Type.VOID:
4.38 + super.visitInsn(Opcodes.RETURN);
4.39 + break;
4.40 + case Type.ARRAY:
4.41 + case Type.OBJECT:
4.42 + super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
4.43 + super.visitInsn(Opcodes.ARETURN);
4.44 + break;
4.45 + case Type.BOOLEAN:
4.46 + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
4.47 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
4.48 + "java/lang/Boolean", "booleanValue", "()Z"
4.49 + );
4.50 + super.visitInsn(Opcodes.IRETURN);
4.51 + break;
4.52 + default:
4.53 + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
4.54 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
4.55 + "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
4.56 + );
4.57 + super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
4.58 + }
4.59 + } else {
4.60 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
4.61 + "org/apidesign/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V"
4.62 + );
4.63 + super.visitInsn(Opcodes.RETURN);
4.64 }
4.65 if (hasCode) {
4.66 super.visitLabel(noPresenter);
4.67 @@ -522,6 +529,7 @@
4.68 List<String> args = new ArrayList<String>();
4.69 String body;
4.70 boolean javacall = false;
4.71 + boolean wait4js = true;
4.72
4.73 public FindInAnno() {
4.74 super(Opcodes.ASM4);
4.75 @@ -537,6 +545,10 @@
4.76 javacall = (Boolean) value;
4.77 return;
4.78 }
4.79 + if (name.equals("wait4js")) { // NOI18N
4.80 + wait4js = (Boolean) value;
4.81 + return;
4.82 + }
4.83 assert name.equals("body");
4.84 body = (String) value;
4.85 }
5.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java Sun Mar 09 20:12:51 2014 +0100
5.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/JavaScriptProcesor.java Sun Mar 09 22:42:42 2014 +0100
5.3 @@ -129,6 +129,9 @@
5.4 if (params.size() != arr.length) {
5.5 msg.printMessage(Diagnostic.Kind.ERROR, "Number of args arguments does not match real arguments!", e);
5.6 }
5.7 + if (!jsb.wait4js() && ee.getReturnType().getKind() != TypeKind.VOID) {
5.8 + msg.printMessage(Diagnostic.Kind.ERROR, "Methods that don't wait for JavaScript to finish must return void!", e);
5.9 + }
5.10 if (!jsb.javacall() && jsb.body().contains(".@")) {
5.11 msg.printMessage(Diagnostic.Kind.WARNING, "Usage of .@ usually requires javacall=true", e);
5.12 }
6.1 --- a/boot/src/test/java/org/netbeans/html/boot/impl/FnTest.java Sun Mar 09 20:12:51 2014 +0100
6.2 +++ b/boot/src/test/java/org/netbeans/html/boot/impl/FnTest.java Sun Mar 09 22:42:42 2014 +0100
6.3 @@ -43,6 +43,8 @@
6.4 package org.netbeans.html.boot.impl;
6.5
6.6 import java.io.Closeable;
6.7 +import java.io.Flushable;
6.8 +import java.io.IOException;
6.9 import java.io.Reader;
6.10 import java.net.URL;
6.11 import java.net.URLClassLoader;
6.12 @@ -55,8 +57,10 @@
6.13 import javax.script.ScriptEngineManager;
6.14 import javax.script.ScriptException;
6.15 import org.apidesign.html.boot.spi.Fn;
6.16 +import static org.testng.Assert.assertEquals;
6.17 import org.testng.annotations.BeforeClass;
6.18 import org.testng.annotations.BeforeMethod;
6.19 +import org.testng.annotations.Test;
6.20
6.21 /**
6.22 *
6.23 @@ -141,6 +145,40 @@
6.24 methodClass = loader.loadClass(JsMethods.class.getName());
6.25 close.close();
6.26 }
6.27 +
6.28 + @Test public void flushingPresenter() throws IOException {
6.29 + class FP implements Fn.Presenter, Flushable {
6.30 + int flush;
6.31 +
6.32 + @Override
6.33 + public Fn defineFn(String code, String... names) {
6.34 + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
6.35 + }
6.36 +
6.37 + @Override
6.38 + public void displayPage(URL page, Runnable onPageLoad) {
6.39 + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
6.40 + }
6.41 +
6.42 + @Override
6.43 + public void loadScript(Reader code) throws Exception {
6.44 + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
6.45 + }
6.46 +
6.47 + @Override
6.48 + public void flush() throws IOException {
6.49 + flush++;
6.50 + }
6.51 + }
6.52 +
6.53 + FP p = new FP();
6.54 + Closeable c1 = Fn.activate(p);
6.55 + Closeable c2 = Fn.activate(p);
6.56 + c2.close();
6.57 + assertEquals(p.flush, 0, "No flush yet");
6.58 + c1.close();
6.59 + assertEquals(p.flush, 1, "Now flushed");
6.60 + }
6.61
6.62 @BeforeMethod public void initPresenter() {
6.63 FnContext.currentPresenter(presenter);
7.1 --- a/context/src/main/java/org/apidesign/html/context/spi/Contexts.java Sun Mar 09 20:12:51 2014 +0100
7.2 +++ b/context/src/main/java/org/apidesign/html/context/spi/Contexts.java Sun Mar 09 22:42:42 2014 +0100
7.3 @@ -170,6 +170,9 @@
7.4 * @return this builder
7.5 */
7.6 public <Tech> Builder register(Class<Tech> type, Tech impl, int position) {
7.7 + if (impl == null) {
7.8 + return this;
7.9 + }
7.10 if (position <= 0) {
7.11 throw new IllegalStateException();
7.12 }
8.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java Sun Mar 09 20:12:51 2014 +0100
8.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java Sun Mar 09 22:42:42 2014 +0100
8.3 @@ -56,6 +56,9 @@
8.4 @JavaScriptBody(args = {"r"}, javacall = true, body = "r.@java.lang.Runnable::run()();")
8.5 static native void callback(Runnable r);
8.6
8.7 + @JavaScriptBody(args = {"r"}, wait4js = false, javacall = true, body = "r.@java.lang.Runnable::run()();")
8.8 + static native void asyncCallback(Runnable r);
8.9 +
8.10 @JavaScriptBody(args = {"c"}, javacall = true, body = "return c.@java.util.concurrent.Callable::call()();")
8.11 static native Object callback(Callable<? extends Object> c);
8.12
9.1 --- a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java Sun Mar 09 20:12:51 2014 +0100
9.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java Sun Mar 09 22:42:42 2014 +0100
9.3 @@ -77,6 +77,18 @@
9.4 assert run.cnt == 1 : "Can call even private implementation classes: " + run.cnt;
9.5 }
9.6
9.7 + private R asyncRun;
9.8 + @KOTest public void asyncCallbackToRunnable() throws InterruptedException {
9.9 + if (asyncRun == null) {
9.10 + asyncRun = new R();
9.11 + Bodies.asyncCallback(asyncRun);
9.12 + }
9.13 + if (asyncRun.cnt == 0) {
9.14 + throw new InterruptedException();
9.15 + }
9.16 + assert asyncRun.cnt == 1 : "Even async callback must arrive once: " + asyncRun.cnt;
9.17 + }
9.18 +
9.19 @KOTest public void typeOfCharacter() {
9.20 String charType = Bodies.typeof('a', false);
9.21 assert "number".equals(charType) : "Expecting number type: " + charType;
10.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KO4J.java Sun Mar 09 20:12:51 2014 +0100
10.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KO4J.java Sun Mar 09 22:42:42 2014 +0100
10.3 @@ -118,9 +118,7 @@
10.4 public void fillContext(Contexts.Builder context, Class<?> requestor) {
10.5 context.register(Technology.class, knockout(), 100);
10.6 context.register(Transfer.class, transfer(), 100);
10.7 - if (c.areWebSocketsSupported()) {
10.8 - context.register(WSTransfer.class, websockets(), 100);
10.9 - }
10.10 + context.register(WSTransfer.class, websockets(), 100);
10.11 }
10.12
10.13 }
11.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Sun Mar 09 20:12:51 2014 +0100
11.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Sun Mar 09 22:42:42 2014 +0100
11.3 @@ -59,7 +59,9 @@
11.4 */
11.5 @JavaScriptResource("knockout-2.2.1.js")
11.6 final class Knockout {
11.7 - @JavaScriptBody(args = { "model", "prop", "oldValue", "newValue" }, body =
11.8 + @JavaScriptBody(args = { "model", "prop", "oldValue", "newValue" },
11.9 + wait4js = false,
11.10 + body =
11.11 "if (model) {\n"
11.12 + " var koProp = model[prop];\n"
11.13 + " if (koProp && koProp['valueHasMutated']) {\n"
11.14 @@ -75,7 +77,9 @@
11.15 Object model, String prop, Object oldValue, Object newValue
11.16 );
11.17
11.18 - @JavaScriptBody(args = { "bindings" }, body = "ko.applyBindings(bindings);\n")
11.19 + @JavaScriptBody(args = { "bindings" }, wait4js = false, body =
11.20 + "ko.applyBindings(bindings);\n"
11.21 + )
11.22 native static void applyBindings(Object bindings);
11.23
11.24 @JavaScriptBody(args = { "cnt" }, body =
11.25 @@ -87,8 +91,9 @@
11.26
11.27 @JavaScriptBody(
11.28 javacall = true,
11.29 + wait4js = false,
11.30 args = {"ret", "model", "propNames", "propReadOnly", "propValues", "propArr", "funcNames", "funcArr"},
11.31 - body =
11.32 + body =
11.33 "ret['ko-fx.model'] = model;\n"
11.34 + "function koComputed(name, readOnly, value, prop) {\n"
11.35 + " function realGetter() {\n"
11.36 @@ -140,7 +145,10 @@
11.37 );
11.38
11.39 @JavaScriptBody(args = { "o" }, body = "return o['ko-fx.model'] ? o['ko-fx.model'] : o;")
11.40 - static native Object toModel(Object wrapper);
11.41 + private static native Object toModelImpl(Object wrapper);
11.42 + static Object toModel(Object wrapper) {
11.43 + return toModelImpl(wrapper);
11.44 + }
11.45
11.46 @JavaScriptBody(args = {}, body = "if (window.WebSocket) return true; else return false;")
11.47 static final boolean areWebSocketsSupported() {