Callback run() on Runnable classloader
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 25 Jun 2013 15:34:37 +0200
branchclassloader
changeset 160b5a06e2b3b92
parent 159 ceac58dd82a1
child 161 ea5ca9cc685d
Callback run() on Runnable
boot/src/main/java/net/java/html/js/JavaScriptBody.java
boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java
boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java
boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderBase.java
boot/src/test/java/org/apidesign/html/boot/impl/JsMethods.java
     1.1 --- a/boot/src/main/java/net/java/html/js/JavaScriptBody.java	Tue Jun 25 13:19:12 2013 +0200
     1.2 +++ b/boot/src/main/java/net/java/html/js/JavaScriptBody.java	Tue Jun 25 15:34:37 2013 +0200
     1.3 @@ -51,4 +51,17 @@
     1.4       * <code>this</code>
     1.5       */
     1.6      public String body();
     1.7 +
     1.8 +    /** Should a special syntax for calling back into Java object be turned on?
     1.9 +     * The syntax begins with <b>{@code @}</b> followed by fully qualified
    1.10 +     * package name of the class. Now followed by <b>::</b> and a method in
    1.11 +     * the class followed by its parameters enclosed inside <b>(...)</b>.
    1.12 +     * This is the syntax one can use to call <code>run()</code> 
    1.13 +     * method of {@link Runnable}:
    1.14 +     * <pre>r.@java.lang.Runnable::run()()</pre>.
    1.15 +     * 
    1.16 +     * @return true, if the script should be scanned for special callback
    1.17 +     *   syntax
    1.18 +     */
    1.19 +    public boolean javacall() default false;
    1.20  }
     2.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Tue Jun 25 13:19:12 2013 +0200
     2.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java	Tue Jun 25 15:34:37 2013 +0200
     2.3 @@ -20,11 +20,15 @@
     2.4   */
     2.5  package org.apidesign.html.boot.impl;
     2.6  
     2.7 +import java.lang.reflect.Method;
     2.8  import java.net.URL;
     2.9  import java.util.ArrayList;
    2.10  import java.util.Collections;
    2.11  import java.util.Enumeration;
    2.12  import java.util.List;
    2.13 +import java.util.logging.Level;
    2.14 +import java.util.logging.Logger;
    2.15 +import java.util.regex.Pattern;
    2.16  import org.apidesign.html.boot.spi.Fn;
    2.17  
    2.18  /**
    2.19 @@ -66,5 +70,56 @@
    2.20              }
    2.21          };
    2.22      }
    2.23 +
    2.24 +    static String callback(String body, ClassLoader loader) {
    2.25 +        try {
    2.26 +            return callbackImpl(body, loader);
    2.27 +        } catch (ClassNotFoundException ex) {
    2.28 +            throw new IllegalStateException("Can't parse " + body, ex);
    2.29 +        } catch (NoSuchMethodException ex) {
    2.30 +            throw new IllegalStateException("Can't parse " + body, ex);
    2.31 +        }
    2.32 +    }
    2.33 +    
    2.34 +    private static String callbackImpl(String body, ClassLoader loader)
    2.35 +    throws ClassNotFoundException, NoSuchMethodException {
    2.36 +        StringBuilder sb = new StringBuilder();
    2.37 +        int pos = 0;
    2.38 +        for (;;) {
    2.39 +            int next = body.indexOf(".@", pos);
    2.40 +            if (next == -1) {
    2.41 +                sb.append(body.substring(pos));
    2.42 +                return sb.toString();
    2.43 +            }
    2.44 +            sb.append(body.substring(pos, next));
    2.45 +            
    2.46 +            int sigBeg = body.indexOf('(', next);
    2.47 +            int sigEnd = body.indexOf(')', sigBeg);
    2.48 +            
    2.49 +            int colon4 = body.indexOf("::", next);
    2.50 +            
    2.51 +            if (sigBeg == -1 || sigEnd == -1 || colon4 == -1) {
    2.52 +                throw new IllegalStateException("Malformed body " + body);
    2.53 +            }
    2.54 +            
    2.55 +            String fqn = body.substring(next + 2, colon4);
    2.56 +            String method = body.substring(colon4 + 2, sigBeg);
    2.57 +            String params = body.substring(sigBeg + 1, sigEnd);
    2.58 +            
    2.59 +            Class<?> clazz = Class.forName(fqn, false, loader);
    2.60 +            Method m = clazz.getMethod(method);
    2.61 +            
    2.62 +            sb.append("['").append(m.getName()).append("(");
    2.63 +            String sep = "";
    2.64 +            for (Class<?> pt : m.getParameterTypes()) {
    2.65 +                sb.append(sep).append(pt.getName());
    2.66 +                sep = ",";
    2.67 +            }
    2.68 +            sb.append(")']");
    2.69 +            
    2.70 +            pos = sigEnd + 1;
    2.71 +        }
    2.72 +    }
    2.73 +
    2.74      
    2.75  }
     3.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Tue Jun 25 13:19:12 2013 +0200
     3.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java	Tue Jun 25 15:34:37 2013 +0200
     3.3 @@ -27,6 +27,8 @@
     3.4  import java.util.ArrayList;
     3.5  import java.util.Enumeration;
     3.6  import java.util.List;
     3.7 +import java.util.logging.Level;
     3.8 +import java.util.logging.Logger;
     3.9  import org.objectweb.asm.AnnotationVisitor;
    3.10  import org.objectweb.asm.ClassReader;
    3.11  import org.objectweb.asm.ClassVisitor;
    3.12 @@ -125,7 +127,7 @@
    3.13      protected abstract Fn defineFn(String code, String... names);
    3.14      
    3.15      
    3.16 -    private static final class FindInClass extends ClassVisitor {
    3.17 +    private final class FindInClass extends ClassVisitor {
    3.18          private String name;
    3.19          private int found;
    3.20          
    3.21 @@ -352,6 +354,7 @@
    3.22              private final class FindInAnno extends AnnotationVisitor {
    3.23                  private List<String> args = new ArrayList<String>();
    3.24                  private String body;
    3.25 +                private boolean javacall = false;
    3.26  
    3.27                  public FindInAnno() {
    3.28                      super(Opcodes.ASM4);
    3.29 @@ -363,6 +366,10 @@
    3.30                          args.add((String) value);
    3.31                          return;
    3.32                      }
    3.33 +                    if (name.equals("javacall")) { // NOI18N
    3.34 +                        javacall = (Boolean)value;
    3.35 +                        return;
    3.36 +                    }
    3.37                      assert name.equals("body");
    3.38                      body = (String) value;
    3.39                  }
    3.40 @@ -375,7 +382,10 @@
    3.41                  @Override
    3.42                  public void visitEnd() {
    3.43                      if (body != null) {
    3.44 -                        generateJSBody(args, body);
    3.45 +                        generateJSBody(args, javacall ? 
    3.46 +                            FnUtils.callback(body, JsClassLoader.this) : 
    3.47 +                            body
    3.48 +                        );
    3.49                      }
    3.50                  }
    3.51              }
     4.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderBase.java	Tue Jun 25 13:19:12 2013 +0200
     4.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderBase.java	Tue Jun 25 15:34:37 2013 +0200
     4.3 @@ -106,4 +106,21 @@
     4.4          assertEquals(st.invoke(null), Boolean.TRUE, "Can return boolean");
     4.5      }
     4.6      
     4.7 +    @Test public void callback() throws Throwable {
     4.8 +        class R implements Runnable {
     4.9 +            int cnt;
    4.10 +            
    4.11 +            @Override
    4.12 +            public void run() {
    4.13 +                cnt++;
    4.14 +            }
    4.15 +        }
    4.16 +        R r = new R();
    4.17 +        
    4.18 +        Method inc = methodClass.getMethod("callback", Runnable.class);
    4.19 +        inc.invoke(null, r);
    4.20 +        
    4.21 +        assertEquals(r.cnt, 1, "Callback happened");
    4.22 +    }
    4.23 +    
    4.24  }
    4.25 \ No newline at end of file
     5.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsMethods.java	Tue Jun 25 13:19:12 2013 +0200
     5.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsMethods.java	Tue Jun 25 15:34:37 2013 +0200
     5.3 @@ -51,4 +51,7 @@
     5.4      public static boolean truth() {
     5.5          return false;
     5.6      }
     5.7 +    
     5.8 +    @JavaScriptBody(args = { "r" }, javacall=true, body = "r.@java.lang.Runnable::run()()")
     5.9 +    public static native void callback(Runnable r);
    5.10  }