The GC behavior seems to be better defined. Merging to default branch.
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>