The GC behavior seems to be better defined. Merging to default branch.
authorJaroslav Tulach <jtulach@netbeans.org>
Tue, 09 Dec 2014 10:42:58 +0100
changeset 89643a5880b2d79
parent 895 5ef42b69c1c6
parent 894 79574388270b
child 897 2e416426cab0
The GC behavior seems to be better defined. Merging to default branch.
src/main/javadoc/overview.html
     1.1 --- a/boot-fx/src/test/java/org/netbeans/html/boot/fx/FXJavaScriptTest.java	Tue Dec 09 09:24:22 2014 +0100
     1.2 +++ b/boot-fx/src/test/java/org/netbeans/html/boot/fx/FXJavaScriptTest.java	Tue Dec 09 10:42:58 2014 +0100
     1.3 @@ -47,10 +47,13 @@
     1.4  import java.util.ArrayList;
     1.5  import java.util.List;
     1.6  import java.util.concurrent.Executors;
     1.7 +import net.java.html.BrwsrCtx;
     1.8  import net.java.html.boot.BrowserBuilder;
     1.9  import org.netbeans.html.boot.spi.Fn;
    1.10  import org.netbeans.html.json.tck.KOTest;
    1.11  import org.testng.Assert;
    1.12 +import static org.testng.Assert.assertNotSame;
    1.13 +import static org.testng.Assert.assertSame;
    1.14  import org.testng.annotations.Factory;
    1.15  
    1.16  /**
    1.17 @@ -106,6 +109,11 @@
    1.18      }
    1.19      
    1.20      public static void initialized() throws Exception {
    1.21 +        BrwsrCtx b1 = BrwsrCtx.findDefault(FXJavaScriptTest.class);
    1.22 +        TestingProvider.assertCalled("Our context created");
    1.23 +        assertNotSame(b1, BrwsrCtx.EMPTY, "Browser context is not empty");
    1.24 +        BrwsrCtx b2 = BrwsrCtx.findDefault(FXJavaScriptTest.class);
    1.25 +        assertSame(b1, b2, "Browser context remains stable");
    1.26          Assert.assertSame(
    1.27              FXJavaScriptTest.class.getClassLoader(),
    1.28              ClassLoader.getSystemClassLoader(),
     2.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Tue Dec 09 09:24:22 2014 +0100
     2.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Tue Dec 09 10:42:58 2014 +0100
     2.3 @@ -83,7 +83,13 @@
     2.4      static native void incrementXAsync(Object o);
     2.5  
     2.6      @JavaScriptBody(args = "o", body = "return o.x;")
     2.7 -    public static native int readX(Object o);
     2.8 +    public static native int readIntX(Object o);
     2.9 +    
    2.10 +    @JavaScriptBody(args = "o", body = "return o.x;")
    2.11 +    public static native Object readX(Object o);
    2.12 +    
    2.13 +    @JavaScriptBody(args = { "o", "x" }, body = "o.x = x;")
    2.14 +    public static native Object setX(Object o, Object x);
    2.15  
    2.16      @JavaScriptBody(args = { "c" }, javacall = true, body = 
    2.17          "return c.@net.java.html.js.tests.Sum::sum(II)(40, 2);"
    2.18 @@ -172,6 +178,15 @@
    2.19      )
    2.20      static native Object[] forIn(Object[] in);
    2.21  
    2.22 +    @JavaScriptBody(args = { "max" }, body = 
    2.23 +        "var arr = [];\n"
    2.24 +      + "for (var i = 0; i < max; i++) {\n"
    2.25 +      + "  arr.push(i);\n"
    2.26 +      + "}\n"
    2.27 +      + "return arr.length;"
    2.28 +    )
    2.29 +    static native int gc(double max);
    2.30 +
    2.31      @JavaScriptBody(args = {}, javacall = true, body = 
    2.32          "return @net.java.html.js.tests.Bodies::problematicString()();"
    2.33      )
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java	Tue Dec 09 10:42:58 2014 +0100
     3.3 @@ -0,0 +1,126 @@
     3.4 +/**
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     3.8 + *
     3.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    3.10 + * Other names may be trademarks of their respective owners.
    3.11 + *
    3.12 + * The contents of this file are subject to the terms of either the GNU
    3.13 + * General Public License Version 2 only ("GPL") or the Common
    3.14 + * Development and Distribution License("CDDL") (collectively, the
    3.15 + * "License"). You may not use this file except in compliance with the
    3.16 + * License. You can obtain a copy of the License at
    3.17 + * http://www.netbeans.org/cddl-gplv2.html
    3.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    3.19 + * specific language governing permissions and limitations under the
    3.20 + * License.  When distributing the software, include this License Header
    3.21 + * Notice in each file and include the License file at
    3.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    3.23 + * particular file as subject to the "Classpath" exception as provided
    3.24 + * by Oracle in the GPL Version 2 section of the License file that
    3.25 + * accompanied this code. If applicable, add the following below the
    3.26 + * License Header, with the fields enclosed by brackets [] replaced by
    3.27 + * your own identifying information:
    3.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    3.29 + *
    3.30 + * Contributor(s):
    3.31 + *
    3.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    3.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    3.34 + *
    3.35 + * If you wish your version of this file to be governed by only the CDDL
    3.36 + * or only the GPL Version 2, indicate your decision by adding
    3.37 + * "[Contributor] elects to include this software in this distribution
    3.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    3.39 + * single choice of license, a recipient has the option to distribute
    3.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    3.41 + * to extend the choice of license to its licensees as provided above.
    3.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    3.43 + * Version 2 license, then the option applies only if the new code is
    3.44 + * made subject to such option by the copyright holder.
    3.45 + */
    3.46 +package net.java.html.js.tests;
    3.47 +
    3.48 +import java.lang.ref.Reference;
    3.49 +import java.lang.ref.WeakReference;
    3.50 +import org.netbeans.html.json.tck.KOTest;
    3.51 +
    3.52 +/**
    3.53 + *
    3.54 + * @author Jaroslav Tulach
    3.55 + */
    3.56 +public class GCBodyTest {
    3.57 +    Reference<?> ref;
    3.58 +    
    3.59 +    @KOTest public void callbackWithParameters() throws InterruptedException {
    3.60 +        if (ref != null) {
    3.61 +            assertGC(ref, "Can disappear!");
    3.62 +            return;
    3.63 +        }
    3.64 +        Sum s = new Sum();
    3.65 +        int res = Bodies.sumIndirect(s);
    3.66 +        assert res == 42 : "Expecting 42";
    3.67 +        Reference<?> ref = new WeakReference<Object>(s);
    3.68 +        s = null;
    3.69 +        assertGC(ref, "Can disappear!");
    3.70 +    }
    3.71 +    
    3.72 +    @KOTest public void holdObjectAndReleaseObject() throws InterruptedException {
    3.73 +        if (ref != null) {
    3.74 +            assertGC(ref, "Can disappear!");
    3.75 +            return;
    3.76 +        }
    3.77 +        Sum s = new Sum();
    3.78 +        Object obj = Bodies.instance(0);
    3.79 +        Bodies.setX(obj, s);
    3.80 +        
    3.81 +        ref = new WeakReference<Object>(s);
    3.82 +        s = null;
    3.83 +        assertNotGC(ref, "Cannot disappear!");
    3.84 +        
    3.85 +        Bodies.setX(obj, null);
    3.86 +        obj = null;
    3.87 +        
    3.88 +        assertGC(ref, "Can disappear!");
    3.89 +    }
    3.90 +    
    3.91 +    private static void assertGC(Reference<?> ref, String msg) throws InterruptedException {
    3.92 +        for (int i = 0; i < 100; i++) {
    3.93 +            if (isGone(ref)) return;
    3.94 +            long then = System.currentTimeMillis();
    3.95 +            int size = Bodies.gc(Math.pow(2.0, i));
    3.96 +            long took = System.currentTimeMillis() - then;
    3.97 +            if (took > 3000) {
    3.98 +                throw new InterruptedException(msg + " - giving up after " + took + " ms at size of " + size);
    3.99 +            }
   3.100 +            
   3.101 +            try {
   3.102 +                System.gc();
   3.103 +                System.runFinalization();
   3.104 +            } catch (Error err) {
   3.105 +                err.printStackTrace();
   3.106 +            }
   3.107 +        }
   3.108 +        throw new InterruptedException(msg);
   3.109 +    }
   3.110 +
   3.111 +    private static boolean isGone(Reference<?> ref) {
   3.112 +        return ref.get() == null;
   3.113 +    }
   3.114 +    
   3.115 +    private static void assertNotGC(Reference<?> ref, String msg) throws InterruptedException {
   3.116 +        for (int i = 0; i < 10; i++) {
   3.117 +            if (ref.get() == null) {
   3.118 +                throw new IllegalStateException(msg);
   3.119 +            }
   3.120 +            int size = Bodies.gc(Math.pow(2.0, i));
   3.121 +            try {
   3.122 +                System.gc();
   3.123 +                System.runFinalization();
   3.124 +            } catch (Error err) {
   3.125 +                err.printStackTrace();
   3.126 +            }
   3.127 +        }
   3.128 +    }
   3.129 +}
     4.1 --- a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java	Tue Dec 09 09:24:22 2014 +0100
     4.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java	Tue Dec 09 10:42:58 2014 +0100
     4.3 @@ -43,6 +43,8 @@
     4.4  package net.java.html.js.tests;
     4.5  
     4.6  import java.io.StringReader;
     4.7 +import java.lang.ref.Reference;
     4.8 +import java.lang.ref.WeakReference;
     4.9  import java.util.Arrays;
    4.10  import java.util.concurrent.Callable;
    4.11  import org.netbeans.html.boot.spi.Fn;
    4.12 @@ -65,14 +67,14 @@
    4.13      
    4.14      @KOTest public void accessJsObject() {
    4.15          Object o = Bodies.instance(10);
    4.16 -        int ten = Bodies.readX(o);
    4.17 +        int ten = Bodies.readIntX(o);
    4.18          assert ten == 10 : "Expecting ten: " + ten;
    4.19      }
    4.20  
    4.21      @KOTest public void callWithNoReturnType() {
    4.22          Object o = Bodies.instance(10);
    4.23          Bodies.incrementX(o);
    4.24 -        int ten = Bodies.readX(o);
    4.25 +        int ten = Bodies.readIntX(o);
    4.26          assert ten == 11 : "Expecting eleven: " + ten;
    4.27      }
    4.28      
    4.29 @@ -211,8 +213,9 @@
    4.30          assert b == Boolean.TRUE : "Should return true";
    4.31      }
    4.32      
    4.33 -    @KOTest public void callbackWithParameters() {
    4.34 -        int res = Bodies.sumIndirect(new Sum());
    4.35 +    @KOTest public void callbackWithParameters() throws InterruptedException {
    4.36 +        Sum s = new Sum();
    4.37 +        int res = Bodies.sumIndirect(s);
    4.38          assert res == 42 : "Expecting 42";
    4.39      }
    4.40      
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java	Tue Dec 09 10:42:58 2014 +0100
     5.3 @@ -0,0 +1,138 @@
     5.4 +/**
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5.8 + *
     5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    5.10 + * Other names may be trademarks of their respective owners.
    5.11 + *
    5.12 + * The contents of this file are subject to the terms of either the GNU
    5.13 + * General Public License Version 2 only ("GPL") or the Common
    5.14 + * Development and Distribution License("CDDL") (collectively, the
    5.15 + * "License"). You may not use this file except in compliance with the
    5.16 + * License. You can obtain a copy of the License at
    5.17 + * http://www.netbeans.org/cddl-gplv2.html
    5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    5.19 + * specific language governing permissions and limitations under the
    5.20 + * License.  When distributing the software, include this License Header
    5.21 + * Notice in each file and include the License file at
    5.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    5.23 + * particular file as subject to the "Classpath" exception as provided
    5.24 + * by Oracle in the GPL Version 2 section of the License file that
    5.25 + * accompanied this code. If applicable, add the following below the
    5.26 + * License Header, with the fields enclosed by brackets [] replaced by
    5.27 + * your own identifying information:
    5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    5.29 + *
    5.30 + * Contributor(s):
    5.31 + *
    5.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    5.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    5.34 + *
    5.35 + * If you wish your version of this file to be governed by only the CDDL
    5.36 + * or only the GPL Version 2, indicate your decision by adding
    5.37 + * "[Contributor] elects to include this software in this distribution
    5.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    5.39 + * single choice of license, a recipient has the option to distribute
    5.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    5.41 + * to extend the choice of license to its licensees as provided above.
    5.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    5.43 + * Version 2 license, then the option applies only if the new code is
    5.44 + * made subject to such option by the copyright holder.
    5.45 + */
    5.46 +package net.java.html.json.tests;
    5.47 +
    5.48 +import java.lang.ref.Reference;
    5.49 +import java.lang.ref.WeakReference;
    5.50 +import net.java.html.BrwsrCtx;
    5.51 +import net.java.html.json.Model;
    5.52 +import net.java.html.json.Models;
    5.53 +import net.java.html.json.Property;
    5.54 +import org.netbeans.html.json.tck.KOTest;
    5.55 +
    5.56 +@Model(className = "GC", properties = {
    5.57 +    @Property(name = "all", type = Fullname.class, array = true)
    5.58 +})
    5.59 +public class GCKnockoutTest {
    5.60 +    @Model(className = "Fullname", properties = {
    5.61 +        @Property(name = "firstName", type = String.class),
    5.62 +        @Property(name = "lastName", type = String.class)
    5.63 +    })
    5.64 +    static class FullnameCntrl {
    5.65 +    }
    5.66 +    
    5.67 +    @KOTest public void noLongerNeededArrayElementsCanDisappear() throws Exception {
    5.68 +        BrwsrCtx ctx = Utils.newContext(GCKnockoutTest.class);
    5.69 +        Object exp = Utils.exposeHTML(GCKnockoutTest.class,
    5.70 +            "<ul id='ul' data-bind='foreach: all'>\n"
    5.71 +            + "  <li data-bind='text: firstName'/>\n"
    5.72 +            + "</ul>\n"
    5.73 +        );
    5.74 +        try {
    5.75 +            GC m = Models.bind(new GC(), ctx);
    5.76 +            m.getAll().add(new Fullname("Jarda", "Tulach"));
    5.77 +            m.applyBindings();
    5.78 +
    5.79 +            int cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
    5.80 +            assert cnt == 1 : "One child, but was " + cnt;
    5.81 +
    5.82 +            m.getAll().add(new Fullname("HTML", "Java"));
    5.83 +
    5.84 +            cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
    5.85 +            assert cnt == 2 : "Now two " + cnt;
    5.86 +
    5.87 +            Fullname removed = m.getAll().get(0);
    5.88 +            m.getAll().remove(0);
    5.89 +
    5.90 +            cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
    5.91 +            assert cnt == 1 : "Again One " + cnt;
    5.92 +
    5.93 +            Reference<?> ref = new WeakReference<Object>(removed);
    5.94 +            removed = null;
    5.95 +            assertGC(ref, "Can removed object disappear?");
    5.96 +            
    5.97 +            ref = new WeakReference<Object>(m);
    5.98 +            m = null;
    5.99 +            assertNotGC(ref, "Root model cannot GC");
   5.100 +        } finally {
   5.101 +            Utils.exposeHTML(GCKnockoutTest.class, "");
   5.102 +        }
   5.103 +        
   5.104 +    }
   5.105 +    
   5.106 +    private void assertGC(Reference<?> ref, String msg) throws Exception {
   5.107 +        for (int i = 0; i < 100; i++) {
   5.108 +            if (ref.get() == null) {
   5.109 +                return;
   5.110 +            }
   5.111 +            String gc = "var max = arguments[0];\n"
   5.112 +                    +  "var arr = [];\n"
   5.113 +                    + "for (var i = 0; i < max; i++) {\n"
   5.114 +                    + "  arr.push(i);\n"
   5.115 +                    + "}\n"
   5.116 +                    + "return arr.length;";
   5.117 +            Object cnt = Utils.executeScript(GCKnockoutTest.class, gc, Math.pow(2.0, i));
   5.118 +            System.gc();
   5.119 +            System.runFinalization();
   5.120 +        }
   5.121 +        throw new OutOfMemoryError(msg);
   5.122 +    }
   5.123 +    
   5.124 +    private void assertNotGC(Reference<?> ref, String msg) throws Exception {
   5.125 +        for (int i = 0; i < 10; i++) {
   5.126 +            if (ref.get() == null) {
   5.127 +                throw new IllegalStateException(msg);
   5.128 +            }
   5.129 +            String gc = "var max = arguments[0];\n"
   5.130 +                    +  "var arr = [];\n"
   5.131 +                    + "for (var i = 0; i < max; i++) {\n"
   5.132 +                    + "  arr.push(i);\n"
   5.133 +                    + "}\n"
   5.134 +                    + "return arr.length;";
   5.135 +            Object cnt = Utils.executeScript(GCKnockoutTest.class, gc, Math.pow(2.0, i));
   5.136 +            System.gc();
   5.137 +            System.runFinalization();
   5.138 +        }
   5.139 +    }
   5.140 +    
   5.141 +}
     6.1 --- a/json-tck/src/main/java/org/netbeans/html/json/tck/JavaScriptTCK.java	Tue Dec 09 09:24:22 2014 +0100
     6.2 +++ b/json-tck/src/main/java/org/netbeans/html/json/tck/JavaScriptTCK.java	Tue Dec 09 10:42:58 2014 +0100
     6.3 @@ -42,6 +42,7 @@
     6.4   */
     6.5  package org.netbeans.html.json.tck;
     6.6  
     6.7 +import net.java.html.js.tests.GCBodyTest;
     6.8  import net.java.html.js.tests.JavaScriptBodyTest;
     6.9  import org.netbeans.html.boot.spi.Fn;
    6.10  import org.netbeans.html.boot.spi.Fn.Presenter;
    6.11 @@ -65,7 +66,7 @@
    6.12       */
    6.13      protected static Class<?>[] testClasses() {
    6.14          return new Class[] { 
    6.15 -            JavaScriptBodyTest.class
    6.16 +            JavaScriptBodyTest.class, GCBodyTest.class
    6.17          };
    6.18      }
    6.19      
     7.1 --- a/json-tck/src/main/java/org/netbeans/html/json/tck/KnockoutTCK.java	Tue Dec 09 09:24:22 2014 +0100
     7.2 +++ b/json-tck/src/main/java/org/netbeans/html/json/tck/KnockoutTCK.java	Tue Dec 09 10:42:58 2014 +0100
     7.3 @@ -46,6 +46,7 @@
     7.4  import java.util.Map;
     7.5  import net.java.html.BrwsrCtx;
     7.6  import net.java.html.json.tests.ConvertTypesTest;
     7.7 +import net.java.html.json.tests.GCKnockoutTest;
     7.8  import net.java.html.json.tests.JSONTest;
     7.9  import net.java.html.json.tests.KnockoutTest;
    7.10  import net.java.html.json.tests.MinesTest;
    7.11 @@ -125,7 +126,8 @@
    7.12              KnockoutTest.class,
    7.13              MinesTest.class,
    7.14              OperationsTest.class,
    7.15 -            WebSocketTest.class
    7.16 +            WebSocketTest.class,
    7.17 +            GCKnockoutTest.class
    7.18          };
    7.19      }
    7.20  
     8.1 --- a/json/src/main/java/org/netbeans/html/json/spi/FunctionBinding.java	Tue Dec 09 09:24:22 2014 +0100
     8.2 +++ b/json/src/main/java/org/netbeans/html/json/spi/FunctionBinding.java	Tue Dec 09 10:42:58 2014 +0100
     8.3 @@ -42,6 +42,8 @@
     8.4   */
     8.5  package org.netbeans.html.json.spi;
     8.6  
     8.7 +import java.lang.ref.Reference;
     8.8 +import java.lang.ref.WeakReference;
     8.9  import net.java.html.BrwsrCtx;
    8.10  import net.java.html.json.Function;
    8.11  import net.java.html.json.Model;
    8.12 @@ -68,23 +70,31 @@
    8.13       * @param ev event (with additional properties) that triggered the event
    8.14       */
    8.15      public abstract void call(Object data, Object ev);
    8.16 +    
    8.17 +    /** Returns identical version of the binding, but one that holds on the
    8.18 +     * original model object via weak reference.
    8.19 +     * 
    8.20 +     * @return binding that uses weak reference
    8.21 +     * @since 1.1
    8.22 +     */
    8.23 +    public abstract FunctionBinding weak();
    8.24  
    8.25      static <M> FunctionBinding registerFunction(String name, int index, M model, Proto.Type<M> access) {
    8.26 -        return new Impl<M>(name, index, model, access);
    8.27 +        return new Impl<M>(model, name, index, access);
    8.28      }
    8.29      
    8.30 -    private static final class Impl<M> extends FunctionBinding {
    8.31 +    private static abstract class AImpl<M> extends FunctionBinding {
    8.32          final String name;
    8.33 -        private final M model;
    8.34 -        private final Proto.Type<M> access;
    8.35 -        private final int index;
    8.36 +        final Proto.Type<M> access;
    8.37 +        final int index;
    8.38  
    8.39 -        public Impl(String name, int index, M model, Proto.Type<M> access) {
    8.40 +        public AImpl(String name, int index, Proto.Type<M> access) {
    8.41              this.name = name;
    8.42              this.index = index;
    8.43 -            this.model = model;
    8.44              this.access = access;
    8.45          }
    8.46 +        
    8.47 +        protected abstract M model();
    8.48  
    8.49          @Override
    8.50          public String getFunctionName() {
    8.51 @@ -93,6 +103,10 @@
    8.52  
    8.53          @Override
    8.54          public void call(final Object data, final Object ev) {
    8.55 +            final M model = model();
    8.56 +            if (model == null) {
    8.57 +                return;
    8.58 +            }
    8.59              BrwsrCtx ctx = access.protoFor(model).getContext();
    8.60              class Dispatch implements Runnable {
    8.61                  @Override
    8.62 @@ -107,4 +121,42 @@
    8.63              ctx.execute(new Dispatch());
    8.64          }
    8.65      }
    8.66 +    
    8.67 +    private static final class Impl<M> extends AImpl<M> {
    8.68 +        private final M model;
    8.69 +
    8.70 +        public Impl(M model, String name, int index, Proto.Type<M> access) {
    8.71 +            super(name, index, access);
    8.72 +            this.model = model;
    8.73 +        }
    8.74 +
    8.75 +        @Override
    8.76 +        protected M model() {
    8.77 +            return model;
    8.78 +        }
    8.79 +
    8.80 +        @Override
    8.81 +        public FunctionBinding weak() {
    8.82 +            return new Weak(model, name, index, access);
    8.83 +        }
    8.84 +    }
    8.85 +    
    8.86 +    private static final class Weak<M> extends AImpl<M> {
    8.87 +        private final Reference<M> ref;
    8.88 +        
    8.89 +        public Weak(M model, String name, int index, Proto.Type<M> access) {
    8.90 +            super(name, index, access);
    8.91 +            this.ref = new WeakReference<M>(model);
    8.92 +        }
    8.93 +
    8.94 +        @Override
    8.95 +        protected M model() {
    8.96 +            return ref.get();
    8.97 +        }
    8.98 +
    8.99 +        @Override
   8.100 +        public FunctionBinding weak() {
   8.101 +            return this;
   8.102 +        }
   8.103 +    }
   8.104  }
     9.1 --- a/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java	Tue Dec 09 09:24:22 2014 +0100
     9.2 +++ b/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java	Tue Dec 09 10:42:58 2014 +0100
     9.3 @@ -42,6 +42,8 @@
     9.4   */
     9.5  package org.netbeans.html.json.spi;
     9.6  
     9.7 +import java.lang.ref.Reference;
     9.8 +import java.lang.ref.WeakReference;
     9.9  import net.java.html.BrwsrCtx;
    9.10  import org.netbeans.html.json.impl.Bindings;
    9.11  import org.netbeans.html.json.impl.JSON;
    9.12 @@ -94,7 +96,7 @@
    9.13                  Proto.Type<M> access, Bindings<?> bindings, String name,
    9.14                  int index, M model, boolean readOnly
    9.15              ) {
    9.16 -                return new Impl(bindings, name, index, model, access, readOnly);
    9.17 +                return new Impl(model, bindings, name, index, access, readOnly);
    9.18              }
    9.19          };
    9.20      }
    9.21 @@ -124,31 +126,47 @@
    9.22       * @return true, if this property is read only
    9.23       */
    9.24      public abstract boolean isReadOnly();
    9.25 +
    9.26 +    /** Returns identical version of the binding, but one that holds on the
    9.27 +     * original model object via weak reference.
    9.28 +     * 
    9.29 +     * @return binding that uses weak reference
    9.30 +     * @since 1.1
    9.31 +     */
    9.32 +    public abstract PropertyBinding weak();
    9.33      
    9.34 -    private static final class Impl<M> extends PropertyBinding {
    9.35 +    private static abstract class AImpl<M> extends PropertyBinding {
    9.36          public final String name;
    9.37          public final boolean readOnly;
    9.38 -        private final M model;
    9.39 -        private final Proto.Type<M> access;
    9.40 -        private final Bindings<?> bindings;
    9.41 -        private final int index;
    9.42 +        final Proto.Type<M> access;
    9.43 +        final Bindings<?> bindings;
    9.44 +        final int index;
    9.45  
    9.46 -        public Impl(Bindings<?> bindings, String name, int index, M model, Proto.Type<M> access, boolean readOnly) {
    9.47 +        public AImpl(Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
    9.48              this.bindings = bindings;
    9.49              this.name = name;
    9.50              this.index = index;
    9.51 -            this.model = model;
    9.52              this.access = access;
    9.53              this.readOnly = readOnly;
    9.54          }
    9.55 +        
    9.56 +        protected abstract M model();
    9.57  
    9.58          @Override
    9.59          public void setValue(Object v) {
    9.60 +            M model = model();
    9.61 +            if (model == null) {
    9.62 +                return;
    9.63 +            }
    9.64              access.setValue(model, index, v);
    9.65          }
    9.66  
    9.67          @Override
    9.68          public Object getValue() {
    9.69 +            M model = model();
    9.70 +            if (model == null) {
    9.71 +                return null;
    9.72 +            }
    9.73              Object v = access.getValue(model, index);
    9.74              Object r = JSON.find(v, bindings);
    9.75              return r == null ? v : r;
    9.76 @@ -165,4 +183,40 @@
    9.77          }
    9.78      } // end of PBData
    9.79      
    9.80 +    private static final class Impl<M> extends AImpl<M> {
    9.81 +        private final M model;
    9.82 +
    9.83 +        public Impl(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
    9.84 +            super(bindings, name, index, access, readOnly);
    9.85 +            this.model = model;
    9.86 +        }
    9.87 +
    9.88 +        @Override
    9.89 +        protected M model() {
    9.90 +            return model;
    9.91 +        }
    9.92 +
    9.93 +        @Override
    9.94 +        public PropertyBinding weak() {
    9.95 +            return new Weak(model, bindings, name, index, access, readOnly);
    9.96 +        }
    9.97 +    }
    9.98 +    
    9.99 +    private static final class Weak<M> extends AImpl<M> {
   9.100 +        private final Reference<M> ref;
   9.101 +        public Weak(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
   9.102 +            super(bindings, name, index, access, readOnly);
   9.103 +            this.ref = new WeakReference<M>(model);
   9.104 +        }
   9.105 +
   9.106 +        @Override
   9.107 +        protected M model() {
   9.108 +            return ref.get();
   9.109 +        }
   9.110 +
   9.111 +        @Override
   9.112 +        public PropertyBinding weak() {
   9.113 +            return this;
   9.114 +        }
   9.115 +    }
   9.116  }
    10.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOSockets.java	Tue Dec 09 09:24:22 2014 +0100
    10.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOSockets.java	Tue Dec 09 10:42:58 2014 +0100
    10.3 @@ -50,7 +50,6 @@
    10.4  /** This is an implementation package - just
    10.5   * include its JAR on classpath and use official {@link Context} API
    10.6   * to access the functionality.
    10.7 - * <p>
    10.8   *
    10.9   * @author Jaroslav Tulach
   10.10   */
    11.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java	Tue Dec 09 09:24:22 2014 +0100
    11.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java	Tue Dec 09 10:42:58 2014 +0100
    11.3 @@ -79,9 +79,10 @@
    11.4              funcNames[i] = funcArr[i].getFunctionName();
    11.5          }
    11.6          Object ret = getJSObject();
    11.7 -        Knockout.wrapModel(ret, model, 
    11.8 -            propNames, propReadOnly, propValues, propArr,
    11.9 -            funcNames, funcArr
   11.10 +        Knockout.wrapModel(new Knockout(model, ret, propArr, funcArr),
   11.11 +            ret, 
   11.12 +            propNames, propReadOnly, propValues,
   11.13 +            funcNames
   11.14          );
   11.15          return ret;
   11.16      }
   11.17 @@ -89,11 +90,16 @@
   11.18      private Object getJSObject() {
   11.19          int len = 64;
   11.20          if (jsObjects != null && jsIndex < (len = jsObjects.length)) {
   11.21 -            return jsObjects[jsIndex++];
   11.22 +            Object ret = jsObjects[jsIndex];
   11.23 +            jsObjects[jsIndex] = null;
   11.24 +            jsIndex++;
   11.25 +            return ret;
   11.26          }
   11.27          jsObjects = Knockout.allocJS(len * 2);
   11.28          jsIndex = 1;
   11.29 -        return jsObjects[0];
   11.30 +        Object ret = jsObjects[0];
   11.31 +        jsObjects[0] = null;
   11.32 +        return ret;
   11.33      }
   11.34      
   11.35      @Override
   11.36 @@ -108,11 +114,13 @@
   11.37  
   11.38      @Override
   11.39      public void valueHasMutated(Object data, String propertyName) {
   11.40 +        Knockout.cleanUp();
   11.41          Knockout.valueHasMutated(data, propertyName, null, null);
   11.42      }
   11.43      
   11.44      @Override
   11.45      public void valueHasMutated(Object data, String propertyName, Object oldValue, Object newValue) {
   11.46 +        Knockout.cleanUp();
   11.47          Knockout.valueHasMutated(data, propertyName, oldValue, newValue);
   11.48      }
   11.49  
   11.50 @@ -123,7 +131,10 @@
   11.51  
   11.52      @Override
   11.53      public void applyBindings(Object data) {
   11.54 -        Knockout.applyBindings(data);
   11.55 +        Object ko = Knockout.applyBindings(data);
   11.56 +        if (ko instanceof Knockout) {
   11.57 +            ((Knockout)ko).hold();
   11.58 +        }
   11.59      }
   11.60  
   11.61      @Override
    12.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Tue Dec 09 09:24:22 2014 +0100
    12.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Tue Dec 09 10:42:58 2014 +0100
    12.3 @@ -42,6 +42,8 @@
    12.4   */
    12.5  package org.netbeans.html.ko4j;
    12.6  
    12.7 +import java.lang.ref.ReferenceQueue;
    12.8 +import java.lang.ref.WeakReference;
    12.9  import net.java.html.js.JavaScriptBody;
   12.10  import net.java.html.js.JavaScriptResource;
   12.11  import net.java.html.json.Model;
   12.12 @@ -58,7 +60,59 @@
   12.13   * @author Jaroslav Tulach
   12.14   */
   12.15  @JavaScriptResource("knockout-3.2.0.debug.js")
   12.16 -final class Knockout {
   12.17 +final class Knockout extends WeakReference<Object> {
   12.18 +    private static final ReferenceQueue<Object> QUEUE = new ReferenceQueue();
   12.19 +    
   12.20 +    private PropertyBinding[] props;
   12.21 +    private FunctionBinding[] funcs;
   12.22 +    private Object js;
   12.23 +    private Object strong;
   12.24 +
   12.25 +    public Knockout(Object model, Object js, PropertyBinding[] props, FunctionBinding[] funcs) {
   12.26 +        super(model, QUEUE);
   12.27 +        this.js = js;
   12.28 +        this.props = new PropertyBinding[props.length];
   12.29 +        for (int i = 0; i < props.length; i++) {
   12.30 +            this.props[i] = props[i].weak();
   12.31 +        }
   12.32 +        this.funcs = new FunctionBinding[funcs.length];
   12.33 +        for (int i = 0; i < funcs.length; i++) {
   12.34 +            this.funcs[i] = funcs[i].weak();
   12.35 +        }
   12.36 +    }
   12.37 +    
   12.38 +    static void cleanUp() {
   12.39 +        for (;;) {
   12.40 +            Knockout ko = (Knockout)QUEUE.poll();
   12.41 +            if (ko == null) {
   12.42 +                return;
   12.43 +            }
   12.44 +            clean(ko.js);
   12.45 +            ko.js = null;
   12.46 +            ko.props = null;
   12.47 +            ko.funcs = null;
   12.48 +        }
   12.49 +    }
   12.50 +    
   12.51 +    final void hold() {
   12.52 +        strong = get();
   12.53 +    }
   12.54 +    
   12.55 +    final Object getValue(int index) {
   12.56 +        return props[index].getValue();
   12.57 +    }
   12.58 +    
   12.59 +    final void setValue(int index, Object v) {
   12.60 +        if (v instanceof Knockout) {
   12.61 +            v = ((Knockout)v).get();
   12.62 +        }
   12.63 +        props[index].setValue(v);
   12.64 +    }
   12.65 +    
   12.66 +    final void call(int index, Object data, Object ev) {
   12.67 +        funcs[index].call(data, ev);
   12.68 +    }
   12.69 +    
   12.70      @JavaScriptBody(args = { "model", "prop", "oldValue", "newValue" }, 
   12.71          wait4js = false,
   12.72          body =
   12.73 @@ -77,11 +131,12 @@
   12.74          Object model, String prop, Object oldValue, Object newValue
   12.75      );
   12.76  
   12.77 -    @JavaScriptBody(args = { "bindings" }, wait4js = false, body = 
   12.78 +    @JavaScriptBody(args = { "bindings" }, body = 
   12.79          "ko['cleanNode'](window['document']['body']);\n" +
   12.80 -        "ko['applyBindings'](bindings);\n"
   12.81 +        "ko['applyBindings'](bindings);\n" +
   12.82 +        "return bindings['ko4j'];\n"
   12.83      )
   12.84 -    native static void applyBindings(Object bindings);
   12.85 +    native static Object applyBindings(Object bindings);
   12.86      
   12.87      @JavaScriptBody(args = { "cnt" }, body = 
   12.88          "var arr = new Array(cnt);\n" +
   12.89 @@ -93,17 +148,18 @@
   12.90      @JavaScriptBody(
   12.91          javacall = true,
   12.92          wait4js = false,
   12.93 -        args = {"ret", "model", "propNames", "propReadOnly", "propValues", "propArr", "funcNames", "funcArr"},
   12.94 +        args = { "thiz", "ret", "propNames", "propReadOnly", "propValues", "funcNames" },
   12.95          body = 
   12.96 -          "ret['ko-fx.model'] = model;\n"
   12.97 -        + "function koComputed(name, readOnly, value, prop) {\n"
   12.98 +          "Object.defineProperty(ret, 'ko4j', { value : thiz });\n"
   12.99 +        + "function koComputed(index, name, readOnly, value) {\n"
  12.100          + "  var trigger = ko['observable']()['extend']({'notify':'always'});"
  12.101          + "  function realGetter() {\n"
  12.102 +        + "    var self = ret['ko4j'];\n"
  12.103          + "    try {\n"
  12.104 -        + "      var v = prop.@org.netbeans.html.json.spi.PropertyBinding::getValue()();\n"
  12.105 +        + "      var v = self ? self.@org.netbeans.html.ko4j.Knockout::getValue(I)(index) : null;\n"
  12.106          + "      return v;\n"
  12.107          + "    } catch (e) {\n"
  12.108 -        + "      alert(\"Cannot call getValue on \" + model + \" prop: \" + name + \" error: \" + e);\n"
  12.109 +        + "      alert(\"Cannot call getValue on \" + self + \" prop: \" + name + \" error: \" + e);\n"
  12.110          + "    }\n"
  12.111          + "  }\n"
  12.112          + "  var activeGetter = function() { return value; };\n"
  12.113 @@ -119,8 +175,11 @@
  12.114          + "  };\n"
  12.115          + "  if (!readOnly) {\n"
  12.116          + "    bnd['write'] = function(val) {\n"
  12.117 -        + "      var model = val['ko-fx.model'];\n"
  12.118 -        + "      prop.@org.netbeans.html.json.spi.PropertyBinding::setValue(Ljava/lang/Object;)(model ? model : val);\n"
  12.119 +        + "      var self = ret['ko4j'];\n"
  12.120 +        + "      if (!self) return;\n"
  12.121 +        + "      var model = val['ko4j'];\n"
  12.122 +        + "      var s = ret['ko4j'];\n"
  12.123 +        + "      s.@org.netbeans.html.ko4j.Knockout::setValue(ILjava/lang/Object;)(index, model ? model : val);\n"
  12.124          + "    };\n"
  12.125          + "  };\n"
  12.126          + "  var cmpt = ko['computed'](bnd);\n"
  12.127 @@ -131,26 +190,43 @@
  12.128          + "  ret[name] = cmpt;\n"
  12.129          + "}\n"
  12.130          + "for (var i = 0; i < propNames.length; i++) {\n"
  12.131 -        + "  koComputed(propNames[i], propReadOnly[i], propValues[i], propArr[i]);\n"
  12.132 +        + "  koComputed(i, propNames[i], propReadOnly[i], propValues[i]);\n"
  12.133          + "}\n"
  12.134 -        + "function koExpose(name, func) {\n"
  12.135 +        + "function koExpose(index, name) {\n"
  12.136          + "  ret[name] = function(data, ev) {\n"
  12.137 -        + "    func.@org.netbeans.html.json.spi.FunctionBinding::call(Ljava/lang/Object;Ljava/lang/Object;)(data, ev);\n"
  12.138 +        + "    var self = ret['ko4j'];\n"
  12.139 +        + "    if (!self) return;\n"
  12.140 +        + "    self.@org.netbeans.html.ko4j.Knockout::call(ILjava/lang/Object;Ljava/lang/Object;)(index, data, ev);\n"
  12.141          + "  };\n"
  12.142          + "}\n"
  12.143          + "for (var i = 0; i < funcNames.length; i++) {\n"
  12.144 -        + "  koExpose(funcNames[i], funcArr[i]);\n"
  12.145 +        + "  koExpose(i, funcNames[i]);\n"
  12.146          + "}\n"
  12.147          )
  12.148      static native void wrapModel(
  12.149 -        Object ret, Object model,
  12.150 -        String[] propNames, boolean[] propReadOnly, Object propValues, PropertyBinding[] propArr,
  12.151 -        String[] funcNames, FunctionBinding[] funcArr
  12.152 +        Knockout self,
  12.153 +        Object ret, 
  12.154 +        String[] propNames, boolean[] propReadOnly, Object propValues,
  12.155 +        String[] funcNames
  12.156      );
  12.157      
  12.158 -    @JavaScriptBody(args = { "o" }, body = "return o['ko-fx.model'] ? o['ko-fx.model'] : o;")
  12.159 +    @JavaScriptBody(args = { "js" }, wait4js = false, body = 
  12.160 +        "delete js['ko4j'];\n" +
  12.161 +        "for (var p in js) {\n" +
  12.162 +        "  delete js[p];\n" +
  12.163 +        "};\n" +
  12.164 +        "\n"
  12.165 +    )
  12.166 +    private static native void clean(Object js);
  12.167 +    
  12.168 +    @JavaScriptBody(args = { "o" }, body = "return o['ko4j'] ? o['ko4j'] : o;")
  12.169      private static native Object toModelImpl(Object wrapper);
  12.170      static Object toModel(Object wrapper) {
  12.171 -        return toModelImpl(wrapper);
  12.172 +        Object o = toModelImpl(wrapper);
  12.173 +        if (o instanceof Knockout) {
  12.174 +            return ((Knockout)o).get();
  12.175 +        } else {
  12.176 +            return o;
  12.177 +        }
  12.178      }
  12.179  }
    13.1 --- a/pom.xml	Tue Dec 09 09:24:22 2014 +0100
    13.2 +++ b/pom.xml	Tue Dec 09 10:42:58 2014 +0100
    13.3 @@ -18,6 +18,7 @@
    13.4        <license>COPYING</license>
    13.5        <publicPackages />
    13.6        <bundleSymbolicName>${project.artifactId}</bundleSymbolicName>
    13.7 +      <netbeans.compile.on.save>none</netbeans.compile.on.save>
    13.8    </properties>
    13.9    <modules>
   13.10      <module>json</module>
    14.1 --- a/src/main/javadoc/overview.html	Tue Dec 09 09:24:22 2014 +0100
    14.2 +++ b/src/main/javadoc/overview.html	Tue Dec 09 10:42:58 2014 +0100
    14.3 @@ -94,6 +94,12 @@
    14.4              {@link org.netbeans.html.json.spi.Transfer Java based JSON} and 
    14.5              {@link org.netbeans.html.json.spi.WSTransfer WebSocket} implementations
    14.6              under the name <b>tyrus</b>.
    14.7 +            Memory model when using Knockout bindings has been improved
    14.8 +            (required additions of two new methods:
    14.9 +            {@link org.netbeans.html.json.spi.PropertyBinding#weak()} and
   14.10 +            {@link org.netbeans.html.json.spi.FunctionBinding#weak()}) and
   14.11 +            now the Java {@link net.java.html.json.Model models} can garbage collect,
   14.12 +            when no longer used.
   14.13          </p>
   14.14          
   14.15          <h3>What's New in Version 1.0?</h3>