Asynchronous support seems to be OK, so merging into default branch
authorJaroslav Tulach <jaroslav.tulach@netbeans.org>
Sun, 09 Mar 2014 22:42:42 +0100
changeset 594817985dc4508
parent 590 17e3ded623a0
parent 593 45e01459f94d
child 595 4f1d4efd0b7d
Asynchronous support seems to be OK, so merging into default branch
     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() {