Arrays are copied when passed between Java and JavaScript
authorJaroslav Tulach <jaroslav.tulach@netbeans.org>
Wed, 08 Jan 2014 12:24:21 +0100
changeset 429c39b037e0904
parent 428 5f50be4b4bf4
child 430 080b60863878
child 447 a673941d1d93
Arrays are copied when passed between Java and JavaScript
boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java
boot/src/main/java/org/apidesign/html/boot/spi/Fn.java
json-tck/src/main/java/net/java/html/js/tests/Bodies.java
json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java
     1.1 --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java	Wed Jan 08 12:10:10 2014 +0100
     1.2 +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java	Wed Jan 08 12:24:21 2014 +0100
     1.3 @@ -69,6 +69,10 @@
     1.4  
     1.5      @Override
     1.6      public Fn defineFn(String code, String... names) {
     1.7 +        return defineJSFn(code, names);
     1.8 +    }
     1.9 +    
    1.10 +    final JSFn defineJSFn(String code, String... names) {
    1.11          StringBuilder sb = new StringBuilder();
    1.12          sb.append("(function() {");
    1.13          sb.append("  return function(");
    1.14 @@ -153,6 +157,66 @@
    1.15      protected abstract void waitFinished();
    1.16  
    1.17      protected abstract WebView findView(final URL resource);
    1.18 +    
    1.19 +    final JSObject convertArrays(Object[] arr) {
    1.20 +        for (int i = 0; i < arr.length; i++) {
    1.21 +            if (arr[i] instanceof Object[]) {
    1.22 +                arr[i] = convertArrays((Object[]) arr[i]);
    1.23 +            }
    1.24 +        }
    1.25 +        final JSObject wrapArr = (JSObject)wrapArrFn().call("array", arr); // NOI18N
    1.26 +        return wrapArr;
    1.27 +    }
    1.28 +
    1.29 +    private JSObject wrapArrImpl;
    1.30 +    private final JSObject wrapArrFn() {
    1.31 +        if (wrapArrImpl == null) {
    1.32 +            try {
    1.33 +                wrapArrImpl = (JSObject)defineJSFn("  var k = {};"
    1.34 +                    + "  k.array= function() {"
    1.35 +                    + "    return Array.prototype.slice.call(arguments);"
    1.36 +                    + "  };"
    1.37 +                    + "  return k;"
    1.38 +                ).invokeImpl(null, false);
    1.39 +            } catch (Exception ex) {
    1.40 +                throw new IllegalStateException(ex);
    1.41 +            }
    1.42 +        }
    1.43 +        return wrapArrImpl;
    1.44 +    }
    1.45 +
    1.46 +    final Object checkArray(Object val) {
    1.47 +        int length = ((Number) arraySizeFn().call("array", val, null)).intValue();
    1.48 +        if (length == -1) {
    1.49 +            return val;
    1.50 +        }
    1.51 +        Object[] arr = new Object[length];
    1.52 +        arraySizeFn().call("array", val, arr);
    1.53 +        return arr;
    1.54 +    }
    1.55 +    private JSObject arraySize;
    1.56 +    private final JSObject arraySizeFn() {
    1.57 +        if (arraySize == null) {
    1.58 +            try {
    1.59 +                arraySize = (JSObject)defineJSFn("  var k = {};"
    1.60 +                    + "  k.array = function(arr, to) {"
    1.61 +                    + "    if (to === null) {"
    1.62 +                    + "      if (Object.prototype.toString.call(arr) === '[object Array]') return arr.length;"
    1.63 +                    + "      else return -1;"
    1.64 +                    + "    } else {"
    1.65 +                    + "      var l = arr.length;"
    1.66 +                    + "      for (var i = 0; i < l; i++) to[i] = arr[i];"
    1.67 +                    + "      return l;"
    1.68 +                    + "    }"
    1.69 +                    + "  };"
    1.70 +                    + "  return k;"
    1.71 +                ).invokeImpl(null, false);
    1.72 +            } catch (Exception ex) {
    1.73 +                throw new IllegalStateException(ex);
    1.74 +            }
    1.75 +        }
    1.76 +        return arraySize;
    1.77 +    }
    1.78  
    1.79      private static final class JSFn extends Fn {
    1.80  
    1.81 @@ -168,15 +232,32 @@
    1.82  
    1.83          @Override
    1.84          public Object invoke(Object thiz, Object... args) throws Exception {
    1.85 +            return invokeImpl(thiz, true, args);
    1.86 +        }
    1.87 +        
    1.88 +        final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception {
    1.89              try {
    1.90                  if (LOG.isLoggable(Level.FINE)) {
    1.91                      LOG.log(Level.FINE, "calling {0} function #{1}", new Object[]{++call, id});
    1.92                  }
    1.93                  List<Object> all = new ArrayList<Object>(args.length + 1);
    1.94                  all.add(thiz == null ? fn : thiz);
    1.95 -                all.addAll(Arrays.asList(args));
    1.96 +                for (int i = 0; i < args.length; i++) {
    1.97 +                    if (arrayChecks && args[i] instanceof Object[]) {
    1.98 +                        Object[] arr = (Object[]) args[i];
    1.99 +                        Object conv = ((AbstractFXPresenter)presenter()).convertArrays(arr);
   1.100 +                        args[i] = conv;
   1.101 +                    }
   1.102 +                    all.add(args[i]);
   1.103 +                }
   1.104                  Object ret = fn.call("call", all.toArray()); // NOI18N
   1.105 -                return ret == fn ? null : ret;
   1.106 +                if (ret == fn) {
   1.107 +                    return null;
   1.108 +                }
   1.109 +                if (!arrayChecks) {
   1.110 +                    return ret;
   1.111 +                }
   1.112 +                return ((AbstractFXPresenter)presenter()).checkArray(ret);
   1.113              } catch (Error t) {
   1.114                  t.printStackTrace();
   1.115                  throw t;
     2.1 --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Wed Jan 08 12:10:10 2014 +0100
     2.2 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java	Wed Jan 08 12:24:21 2014 +0100
     2.3 @@ -189,6 +189,16 @@
     2.4       * @throws Exception if something goes wrong, as exception may be thrown
     2.5       */
     2.6      public abstract Object invoke(Object thiz, Object... args) throws Exception;
     2.7 +    
     2.8 +    /** Provides the function implementation access to the presenter provided
     2.9 +     * in {@link #Fn(org.apidesign.html.boot.spi.Fn.Presenter) the constructor).
    2.10 +     * 
    2.11 +     * @return presenter passed in in the constructor (may be, but should not be <code>null</code>)
    2.12 +     * @since 0.7
    2.13 +     */
    2.14 +    protected final Presenter presenter() {
    2.15 +        return presenter;
    2.16 +    }
    2.17  
    2.18      /** The representation of a <em>presenter</em> - usually a browser window.
    2.19       * Should be provided by a library included in the application and registered
     3.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Wed Jan 08 12:10:10 2014 +0100
     3.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Wed Jan 08 12:24:21 2014 +0100
     3.3 @@ -81,9 +81,15 @@
     3.4  
     3.5      @JavaScriptBody(args = { "arr" }, body = "return arr.length;")
     3.6      public static native int length(Object[] arr);
     3.7 +    
     3.8 +    @JavaScriptBody(args = { "o" }, body = "return typeof o;")
     3.9 +    public static native String typeof(Object o);
    3.10  
    3.11 -    @JavaScriptBody(args = { "arr", "i", "value" }, body = "arr[i] = value;")
    3.12 -    public static native void modify(String[] arr, int i, String value);
    3.13 +    @JavaScriptBody(args = { "o" }, body = "return Array.isArray(o);")
    3.14 +    public static native boolean isArray(Object o);
    3.15 +
    3.16 +    @JavaScriptBody(args = { "arr", "i", "value" }, body = "arr[i] = value; return arr[i];")
    3.17 +    public static native String modify(String[] arr, int i, String value);
    3.18      
    3.19      @JavaScriptBody(args = {}, body = "return true;")
    3.20      public static native boolean truth();
     4.1 --- a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java	Wed Jan 08 12:10:10 2014 +0100
     4.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java	Wed Jan 08 12:24:21 2014 +0100
     4.3 @@ -130,31 +130,49 @@
     4.4          assert res == 42 : "Expecting 42";
     4.5      }
     4.6      
     4.7 -    @KOTest public void selectFromJavaArray() {
     4.8 +    @KOTest public void selectFromStringJavaArray() {
     4.9          String[] arr = { "Ahoj", "World" };
    4.10          Object res = Bodies.select(arr, 1);
    4.11          assert "World".equals(res) : "Expecting World, but was: " + res;
    4.12      }
    4.13  
    4.14 +    @KOTest public void selectFromObjectJavaArray() {
    4.15 +        Object[] arr = { new Object(), new Object() };
    4.16 +        Object res = Bodies.select(arr, 1);
    4.17 +        assert arr[1].equals(res) : "Expecting " + arr[1] + ", but was: " + res;
    4.18 +    }
    4.19 +
    4.20      @KOTest public void lengthOfJavaArray() {
    4.21          String[] arr = { "Ahoj", "World" };
    4.22          int res = Bodies.length(arr);
    4.23          assert res == 2 : "Expecting 2, but was: " + res;
    4.24      }
    4.25  
    4.26 -    @KOTest public void javaArrayInOut() {
    4.27 +    @KOTest public void isJavaArray() {
    4.28 +        String[] arr = { "Ahoj", "World" };
    4.29 +        boolean is = Bodies.isArray(arr);
    4.30 +        assert is: "Expecting it to be an array: " + is;
    4.31 +    }
    4.32 +
    4.33 +    @KOTest public void javaArrayInOutIsCopied() {
    4.34          String[] arr = { "Ahoj", "World" };
    4.35          Object res = Bodies.id(arr);
    4.36 -        assert res == arr : "Expecting same array, but was: " + res;
    4.37 +        assert res != null : "Non-null is returned";
    4.38 +        assert res instanceof Object[] : "Returned an array: " + res;
    4.39 +        assert !(res instanceof String[]) : "Not returned a string array: " + res;
    4.40 +        
    4.41 +        Object[] ret = (Object[]) res;
    4.42 +        assert arr.length == ret.length : "Same length: " + ret.length;
    4.43 +        assert arr[0].equals(ret[0]) : "Same first elem";
    4.44 +        assert arr[1].equals(ret[1]) : "Same 2nd elem";
    4.45      }
    4.46  
    4.47 -//  Modifying an array is a complex operation in the bridge:    
    4.48 -//    
    4.49 -//    @KOTest public void modifyJavaArray() {
    4.50 -//        String[] arr = { "Ahoj", "World" };
    4.51 -//        Bodies.modify(arr, 0, "Hello");
    4.52 -//        assert "Hello".equals(arr[0]) : "Expecting World, but was: " + arr[0];
    4.53 -//    }
    4.54 +    @KOTest public void modifyJavaArrayHasNoEffect() {
    4.55 +        String[] arr = { "Ahoj", "World" };
    4.56 +        String value = Bodies.modify(arr, 0, "Hello");
    4.57 +        assert "Hello".equals(value) : "Inside JS the value is changed: " + value;
    4.58 +        assert "Ahoj".equals(arr[0]) : "From a Java point of view it remains: " + arr[0];
    4.59 +    }
    4.60  
    4.61      @KOTest public void truth() {
    4.62          assert Bodies.truth() : "True is true";