#270481, #270553: Bugfix for multiple observers and better tests for GC behavior in the TCK
Project: http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/commit/7ddcc5f1 Tree: http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/tree/7ddcc5f1 Diff: http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/diff/7ddcc5f1 Branch: refs/heads/master Commit: 7ddcc5f1ab52f0f674369743202b43e3e1fdb6b4 Parents: e58ccc7 Author: Jaroslav Tulach <[email protected]> Authored: Sat Sep 9 07:28:34 2017 +0200 Committer: Jaroslav Tulach <[email protected]> Committed: Sat Sep 9 07:28:34 2017 +0200 ---------------------------------------------------------------------- .../html/boot/fx/AbstractFXPresenter.java | 2 +- .../java/net/java/html/js/tests/GCBodyTest.java | 20 ++ .../main/java/net/java/html/js/tests/Sum.java | 18 ++ .../java/html/json/tests/GCKnockoutTest.java | 15 +- .../org/netbeans/html/json/impl/JSONList.java | 2 +- .../org/netbeans/html/json/spi/Observers.java | 44 +++- .../java/org/netbeans/html/json/spi/Proto.java | 3 +- .../netbeans/html/json/impl/DeepChangeTest.java | 77 +++++- .../html/json/impl/DependsChangeTest.java | 238 +++++++++++++++++++ .../java/org/netbeans/html/ko4j/KOTech.java | 3 +- .../java/org/netbeans/html/ko4j/Knockout.java | 51 ++-- src/main/javadoc/overview.html | 7 + 12 files changed, 435 insertions(+), 45 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java ---------------------------------------------------------------------- diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java index ed40fe5..08590c1 100644 --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java @@ -406,7 +406,7 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { LOG.log(Level.FINER, " params: {0}", Arrays.asList(args)); } List<Object> all = new ArrayList<Object>(args.length + 1); - all.add(thiz == null ? presenter.undefined() : thiz); + all.add(thiz == null ? presenter.undefined() : presenter.toJavaScript(thiz, true)); for (int i = 0; i < args.length; i++) { Object conv = args[i]; if (arrayChecks) { http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java ---------------------------------------------------------------------- diff --git a/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java index 1d28cf6..c092a4a 100644 --- a/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java +++ b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java @@ -99,6 +99,26 @@ public class GCBodyTest { assertEquals(r.value, null, "Setter called with null value"); } + @KOTest public void thisIsHeldStrongly() throws Exception { + Sum s = new Sum(); + Object res = s.jsSum(12, 30); + int intRes = Bodies.readIntX(res); + assertEquals(42, intRes); + WeakReference<Sum> ref = new WeakReference<Sum>(s); + s = null; + assertNotGC(ref, true, "s cannot disappear: we have reference to s via res.y field"); + } + + @KOTest public void argsArentHeldStrongly() throws Exception { + Sum s = new Sum(); + Object res = Sum.jsStaticSum(s, 12, 30); + int intRes = Bodies.readIntX(res); + assertEquals(42, intRes); + WeakReference<Sum> ref = new WeakReference<Sum>(s); + s = null; + assertGC(ref, "Reference to s via res.y field is weak"); + } + private static Reference<?> sendRunnable(final int[] arr) { Runnable r = new Runnable() { @Override http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json-tck/src/main/java/net/java/html/js/tests/Sum.java ---------------------------------------------------------------------- diff --git a/json-tck/src/main/java/net/java/html/js/tests/Sum.java b/json-tck/src/main/java/net/java/html/js/tests/Sum.java index bf03645..215df29 100644 --- a/json-tck/src/main/java/net/java/html/js/tests/Sum.java +++ b/json-tck/src/main/java/net/java/html/js/tests/Sum.java @@ -18,6 +18,8 @@ */ package net.java.html.js.tests; +import net.java.html.js.JavaScriptBody; + /** * * @author Jaroslav Tulach @@ -26,7 +28,23 @@ public final class Sum { public int sum(int a, int b) { return a + b; } + + @JavaScriptBody(args = { "a", "b" }, javacall = true, keepAlive = false, body = + "return {\n" + + " 'x' : [email protected]::sum(II)(a, b),\n" + + " 'y' : this\n" + + "}\n" + ) + public native Object jsSum(int a, int b); + @JavaScriptBody(args = { "thiz", "a", "b" }, javacall = true, keepAlive = false, body = + "return {\n" + + " 'x' : [email protected]::sum(II)(a, b),\n" + + " 'y' : thiz\n" + + "}\n" + ) + public static native Object jsStaticSum(Sum thiz, int a, int b); + public int sum(Object[] arr) { int s = 0; for (int i = 0; i < arr.length; i++) { http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java ---------------------------------------------------------------------- diff --git a/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java index 967a033..946f2bc 100644 --- a/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java +++ b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java @@ -37,7 +37,7 @@ public class GCKnockoutTest { }) static class FullnameCntrl { } - + @KOTest public void noLongerNeededArrayElementsCanDisappear() throws Exception { BrwsrCtx ctx = Utils.newContext(GCKnockoutTest.class); Object exp = Utils.exposeHTML(GCKnockoutTest.class, @@ -58,8 +58,7 @@ public class GCKnockoutTest { cnt = Utils.countChildren(GCKnockoutTest.class, "ul"); assertEquals(cnt, 2, "Now two " + cnt); - Fullname removed = m.getAll().get(0); - m.getAll().remove(0); + Fullname removed = m.getAll().remove(0); cnt = Utils.countChildren(GCKnockoutTest.class, "ul"); assertEquals(cnt, 1, "Again One " + cnt); @@ -67,16 +66,16 @@ public class GCKnockoutTest { Reference<?> ref = new WeakReference<Object>(removed); removed = null; assertGC(ref, "Can removed object disappear?"); - + ref = new WeakReference<Object>(m); m = null; assertNotGC(ref, "Root model cannot GC"); } finally { Utils.exposeHTML(GCKnockoutTest.class, ""); } - + } - + private void assertGC(Reference<?> ref, String msg) throws Exception { for (int i = 0; i < 100; i++) { if (ref.get() == null) { @@ -94,7 +93,7 @@ public class GCKnockoutTest { } throw new OutOfMemoryError(msg); } - + private void assertNotGC(Reference<?> ref, String msg) throws Exception { for (int i = 0; i < 10; i++) { if (ref.get() == null) { @@ -111,5 +110,5 @@ public class GCKnockoutTest { System.runFinalization(); } } - + } http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json/src/main/java/org/netbeans/html/json/impl/JSONList.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/impl/JSONList.java b/json/src/main/java/org/netbeans/html/json/impl/JSONList.java index bafe1cd..6988c81 100644 --- a/json/src/main/java/org/netbeans/html/json/impl/JSONList.java +++ b/json/src/main/java/org/netbeans/html/json/impl/JSONList.java @@ -197,9 +197,9 @@ public final class JSONList<T> extends ArrayList<T> { proto.getContext().execute(new Runnable() { @Override public void run() { + proto.valueHasMutated(name); Bindings m = PropertyBindingAccessor.getBindings(proto, false, null); if (m != null) { - m.valueHasMutated(name, null, JSONList.this); for (String dependant : deps) { m.valueHasMutated(dependant, null, null); } http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json/src/main/java/org/netbeans/html/json/spi/Observers.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/spi/Observers.java b/json/src/main/java/org/netbeans/html/json/spi/Observers.java index 163c793..59c225e 100644 --- a/json/src/main/java/org/netbeans/html/json/spi/Observers.java +++ b/json/src/main/java/org/netbeans/html/json/spi/Observers.java @@ -18,11 +18,12 @@ */ package org.netbeans.html.json.spi; -import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * @@ -37,11 +38,12 @@ final class Observers { assert Thread.holdsLock(GLOBAL); } - static void beginComputing(Proto p, String name) { + static Usages beginComputing(Proto p, String name, Usages usages) { synchronized (GLOBAL) { verifyUnlocked(p); final Watcher nw = new Watcher(p, name); GLOBAL.push(nw); + return Usages.register(name, nw, usages); } } @@ -88,11 +90,12 @@ final class Observers { } } - private static final class Ref extends WeakReference<Watcher> { + private static final class Ref { + private final Watcher ref; private final String prop; public Ref(Watcher ref, String prop) { - super(ref); + this.ref = ref; this.prop = prop; } @@ -110,6 +113,10 @@ final class Observers { } return null; } + + Watcher get() { + return ref.proto == null ? null : ref; + } } private Watcher find(String prop) { @@ -183,7 +190,7 @@ final class Observers { it.remove(); continue; } - if (rw == w && r.prop.equals(r.prop)) { + if (rw == w && ref.prop.equals(r.prop)) { return; } } @@ -191,8 +198,8 @@ final class Observers { } private static final class Watcher { + Proto proto; final Thread owner; - final Proto proto; final String prop; Watcher(Proto proto, String prop) { @@ -205,5 +212,30 @@ final class Observers { public String toString() { return "Watcher: " + proto + ", " + prop; } + + void destroy() { + proto = null; + } + } + + static final class Usages { + private final Map<String,Watcher> watchers = new HashMap<String, Watcher>(); + + private Usages() { + } + + static Usages register(String propName, Watcher w, Usages usages) { + if (propName != null) { + if (usages == null) { + usages = new Usages(); + } + Observers.Watcher prev = usages.watchers.put(propName, w); + if (prev != null) { + prev.destroy(); + } + } + return usages; + } + } } http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json/src/main/java/org/netbeans/html/json/spi/Proto.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/spi/Proto.java b/json/src/main/java/org/netbeans/html/json/spi/Proto.java index db51a8b..626a6cb 100644 --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java @@ -50,6 +50,7 @@ public final class Proto { private final net.java.html.BrwsrCtx context; private org.netbeans.html.json.impl.Bindings ko; private Observers observers; + private Observers.Usages usages; Proto(Object obj, Type type, BrwsrCtx context) { this.obj = obj; @@ -88,7 +89,7 @@ public final class Proto { * @since 0.9 */ public void acquireLock(String propName) throws IllegalStateException { - Observers.beginComputing(this, propName); + usages = Observers.beginComputing(this, propName, usages); } /** A property on this proto object is about to be accessed. Verifies http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java ---------------------------------------------------------------------- diff --git a/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java b/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java index d3774ab..4e3427a 100644 --- a/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java +++ b/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java @@ -20,6 +20,8 @@ package org.netbeans.html.json.impl; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; @@ -278,10 +280,62 @@ public class DeepChangeTest { assertTrue(o.pb.isReadOnly(), "Derived property"); assertEquals(o.get(), "Hi"); - p.getX().getAll().get(0).setValue("Nazdar"); + final List<MyY> all = p.getX().getAll(); + MyY refStrong = all.get(0); + Reference<MyY> ref = new WeakReference<MyY>(refStrong); + refStrong.setValue("Nazdar"); assertEquals(o.get(), "Nazdar"); assertEquals(o.changes, 1, "One change so far"); + + final MyY hi = Models.bind(new MyY("Ciao", 33), c); + all.set(0, hi); + + assertEquals(o.changes, 2, "Second change"); + assertEquals(o.get(), "Ciao"); + + refStrong.setValue("Ignore"); + assertEquals(o.changes, 2, "Still two changes"); + + refStrong = null; + assertGC(ref, "Original MyY can now disappear"); + } + + @Test + public void disappearModel() throws Exception { + MyOverall p = Models.bind( + new MyOverall(new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)) + ), c); + + MyY refStrong = disappearModelOperations(p); + + Reference<MyOverall> ref = new WeakReference<MyOverall>(p); + p = null; + assertGC(ref, "MyOverall can now disappear"); + assertNotNull(refStrong, "Submodel still used"); + } + + private MyY disappearModelOperations(MyOverall p) throws InvocationTargetException, IllegalAccessException, IllegalArgumentException { + Models.applyBindings(p); + Map m = (Map)Models.toRaw(p); + Object v = m.get("valueAccross"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertEquals(o.changes, 0, "No changes so far"); + assertTrue(o.pb.isReadOnly(), "Derived property"); + assertEquals(o.get(), "Hi"); + final List<MyY> all = p.getX().getAll(); + MyY refStrong = all.get(0); + refStrong.setValue("Nazdar"); + assertEquals(o.get(), "Nazdar"); + assertEquals(o.changes, 1, "One change so far"); + all.clear(); + assertEquals(o.changes, 2, "Second change"); + assertNull(o.get(), "MyY array is empty now"); + refStrong.setValue("Ignore"); + assertEquals(o.changes, 2, "Still two changes"); + return refStrong; } @Test public void secondChangeInArrayIgnored() throws Exception { @@ -589,5 +643,24 @@ public class DeepChangeTest { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } } - + + private static void assertGC(Reference<?> ref, String msg) throws InterruptedException { + for (int i = 0; i < 100; i++) { + if (isGone(ref)) { + return; + } + try { + System.gc(); + System.runFinalization(); + } catch (Error err) { + err.printStackTrace(); + } + } + throw new InterruptedException(msg); + } + + private static boolean isGone(Reference<?> ref) { + return ref.get() == null; + } + } http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/json/src/test/java/org/netbeans/html/json/impl/DependsChangeTest.java ---------------------------------------------------------------------- diff --git a/json/src/test/java/org/netbeans/html/json/impl/DependsChangeTest.java b/json/src/test/java/org/netbeans/html/json/impl/DependsChangeTest.java new file mode 100644 index 0000000..3102e08 --- /dev/null +++ b/json/src/test/java/org/netbeans/html/json/impl/DependsChangeTest.java @@ -0,0 +1,238 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.html.json.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import net.java.html.BrwsrCtx; +import net.java.html.json.ComputedProperty; +import net.java.html.json.Model; +import net.java.html.json.Models; +import net.java.html.json.Property; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.FunctionBinding; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.PropertyBinding; +import org.netbeans.html.json.spi.Technology; +import org.netbeans.html.json.spi.Transfer; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class DependsChangeTest { + private MapTechnology t; + private BrwsrCtx c; + + @BeforeMethod public void initTechnology() { + t = new MapTechnology(); + c = Contexts.newBuilder().register(Technology.class, t, 1). + register(Transfer.class, t, 1).build(); + } + + @Model(className = "Depends", instance = true, properties = { + @Property(name = "value", type = int.class), + @Property(name = "next", type = Depends.class), + }) + static class DependsCntrl { + @ComputedProperty @Transitive(deep = true) + static int sumPositive(Depends next, int value) { + while (next != null && next.getValue() > 0) { + value += next.getValue(); + next = next.getNext(); + } + return value; + } + } + + @Test + public void disappearModel() throws Exception { + Depends p = Models.bind( + new Depends(10, new Depends(20, new Depends(30, null)) + ), c); + + Depends refStrong = disappearModelOperations(p); + + Reference<Object> ref = new WeakReference<Object>(p); + p = null; + assertGC(ref, "MyOverall can now disappear"); + assertNotNull(refStrong, "Submodel still used"); + } + + private Depends disappearModelOperations(Depends p) throws InvocationTargetException, IllegalAccessException, IllegalArgumentException { + Models.applyBindings(p); + Map m = (Map)Models.toRaw(p); + Object v = m.get("sumPositive"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertEquals(o.changes, 0, "No changes so far"); + assertTrue(o.pb.isReadOnly(), "Derived property"); + assertEquals(o.get(), 60); + Depends refStrong = p.getNext().getNext(); + p.getNext().setNext(null); + assertEquals(o.changes, 1, "Change in sum"); + assertEquals(o.get(), 30); + return refStrong; + } + + static final class One { + + int changes; + final PropertyBinding pb; + final FunctionBinding fb; + + One(Object m, PropertyBinding pb) throws NoSuchMethodException { + this.pb = pb; + this.fb = null; + } + + One(Object m, FunctionBinding fb) throws NoSuchMethodException { + this.pb = null; + this.fb = fb; + } + + Object get() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + return pb.getValue(); + } + + void set(Object v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + pb.setValue(v); + } + + void assertNoChange(String msg) { + assertEquals(changes, 0, msg); + } + + void assertChange(String msg) { + if (changes == 0) { + fail(msg); + } + changes = 0; + } + } + + static final class MapTechnology + implements Technology<Map<String, One>>, Transfer { + + @Override + public Map<String, One> wrapModel(Object model) { + return new HashMap<String, One>(); + } + + @Override + public void valueHasMutated(Map<String, One> data, String propertyName) { + One p = data.get(propertyName); + if (p != null) { + p.changes++; + } + } + + @Override + public void bind(PropertyBinding b, Object model, Map<String, One> data) { + try { + One o = new One(model, b); + data.put(b.getPropertyName(), o); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void expose(FunctionBinding fb, Object model, Map<String, One> data) { + try { + data.put(fb.getFunctionName(), new One(model, fb)); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void applyBindings(Map<String, One> data) { + } + + @Override + public Object wrapArray(Object[] arr) { + return arr; + } + + @Override + public void extract(Object obj, String[] props, Object[] values) { + Map<?, ?> map = obj instanceof Map ? (Map<?, ?>) obj : null; + for (int i = 0; i < Math.min(props.length, values.length); i++) { + if (map == null) { + values[i] = null; + } else { + values[i] = map.get(props[i]); + if (values[i] instanceof One) { + values[i] = ((One) values[i]).pb.getValue(); + } + } + } + } + + @Override + public void loadJSON(JSONCall call) { + call.notifyError(new UnsupportedOperationException()); + } + + @Override + public <M> M toModel(Class<M> modelClass, Object data) { + return modelClass.cast(data); + } + + @Override + public Object toJSON(InputStream is) throws IOException { + throw new IOException(); + } + + @Override + public void runSafe(Runnable r) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + } + + private static void assertGC(Reference<?> ref, String msg) throws InterruptedException { + for (int i = 0; i < 100; i++) { + if (isGone(ref)) { + return; + } + try { + System.gc(); + System.runFinalization(); + } catch (Error err) { + err.printStackTrace(); + } + } + throw new InterruptedException(msg); + } + + private static boolean isGone(Reference<?> ref) { + return ref.get() == null; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java ---------------------------------------------------------------------- diff --git a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java index 426399a..dd1ba6d 100644 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java @@ -70,7 +70,8 @@ implements Technology.BatchCopy<Object>, Technology.ValueMutated<Object>, Techno if (ko != null) { ko[0] = newKO; } - newKO.wrapModel( + Knockout.wrapModel( + newKO, ret, copyFrom, propNames, propInfo, propValues, funcNames ); http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java ---------------------------------------------------------------------- diff --git a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java index 0eefdfd..f175ee0 100644 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java @@ -34,7 +34,7 @@ import org.netbeans.html.json.spi.PropertyBinding; * to access the functionality. * <p> * Provides binding between {@link Model models} and knockout.js running - * inside a JavaFX WebView. + * inside a JavaFX WebView. * * @author Jaroslav Tulach */ @@ -43,17 +43,17 @@ final class Knockout extends WeakReference<Object> { private static final ReferenceQueue<Object> QUEUE = new ReferenceQueue(); private static final Set<Knockout> active = Collections.synchronizedSet(new HashSet<Knockout>()); - @JavaScriptBody(args = {"object", "property"}, body = - "var ret;\n" + - "if (property === null) ret = object;\n" + - "else if (object === null) ret = null;\n" + - "else ret = object[property];\n" + + @JavaScriptBody(args = {"object", "property"}, body = + "var ret;\n" + + "if (property === null) ret = object;\n" + + "else if (object === null) ret = null;\n" + + "else ret = object[property];\n" + "return ret ? ko['utils']['unwrapObservable'](ret) : null;" ) static Object getProperty(Object object, String property) { return null; } - + private PropertyBinding[] props; private FunctionBinding[] funcs; private Object js; @@ -72,7 +72,7 @@ final class Knockout extends WeakReference<Object> { } active.add(this); } - + static void cleanUp() { for (;;) { Knockout ko = (Knockout)QUEUE.poll(); @@ -86,27 +86,27 @@ final class Knockout extends WeakReference<Object> { ko.funcs = null; } } - + final void hold() { strong = get(); } - + final Object getValue(int index) { return props[index].getValue(); } - + final void setValue(int index, Object v) { if (v instanceof Knockout) { v = ((Knockout)v).get(); } props[index].setValue(v); } - + final void call(int index, Object data, Object ev) { funcs[index].call(data, ev); } - - @JavaScriptBody(args = { "model", "prop", "oldValue", "newValue" }, + + @JavaScriptBody(args = { "model", "prop", "oldValue", "newValue" }, wait4js = false, body = "if (model) {\n" @@ -127,7 +127,7 @@ final class Knockout extends WeakReference<Object> { Object model, String prop, Object oldValue, Object newValue ); - @JavaScriptBody(args = { "id", "bindings" }, body = + @JavaScriptBody(args = { "id", "bindings" }, body = "var d = window['document'];\n" + "var e = id ? d['getElementById'](id) : d['body'];\n" + "ko['cleanNode'](e);\n" + @@ -135,21 +135,21 @@ final class Knockout extends WeakReference<Object> { "return bindings['ko4j'];\n" ) native static Object applyBindings(String id, Object bindings); - - @JavaScriptBody(args = { "cnt" }, body = + + @JavaScriptBody(args = { "cnt" }, body = "var arr = new Array(cnt);\n" + "for (var i = 0; i < cnt; i++) arr[i] = new Object();\n" + "return arr;\n" ) native static Object[] allocJS(int cnt); - + @JavaScriptBody( javacall = true, keepAlive = false, wait4js = false, - args = { "ret", "copyFrom", "propNames", "propInfo", "propValues", "funcNames" }, - body = - "Object.defineProperty(ret, 'ko4j', { value : this });\n" + args = { "thiz", "ret", "copyFrom", "propNames", "propInfo", "propValues", "funcNames" }, + body = + "Object.defineProperty(ret, 'ko4j', { value : thiz });\n" + "function normalValue(r) {\n" + " if (r) try { var br = r.valueOf(); } catch (err) {}\n" + " return br === undefined ? r: br;\n" @@ -224,14 +224,15 @@ final class Knockout extends WeakReference<Object> { + " koExpose(i, funcNames[i]);\n" + "}\n" ) - native void wrapModel( + static native void wrapModel( + Knockout thiz, Object ret, Object copyFrom, String[] propNames, Number[] propInfo, Object propValues, String[] funcNames ); - - @JavaScriptBody(args = { "js" }, wait4js = false, body = + + @JavaScriptBody(args = { "js" }, wait4js = false, body = "delete js['ko4j'];\n" + "for (var p in js) {\n" + " delete js[p];\n" + @@ -239,7 +240,7 @@ final class Knockout extends WeakReference<Object> { "\n" ) private static native void clean(Object js); - + @JavaScriptBody(args = { "o" }, body = "return o['ko4j'] ? o['ko4j'] : o;") private static native Object toModelImpl(Object wrapper); static Object toModel(Object wrapper) { http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/7ddcc5f1/src/main/javadoc/overview.html ---------------------------------------------------------------------- diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html index 58c7cf3..408e0cb 100644 --- a/src/main/javadoc/overview.html +++ b/src/main/javadoc/overview.html @@ -51,6 +51,13 @@ yet the application code can be written in Java. </p> + <h3>New in version 1.4+</h3> + + Bug fix for <a target="_blank" href="https://netbeans.org/bugzilla/show_bug.cgi?id=270481"> + multiple observers</a> on a single model object. + Better <a target="_blank" href="https://netbeans.org/bugzilla/show_bug.cgi?id=270553"> + GC behavior</a> specified in TCK and used in Knockout for Java implementation. + <h3>New features in version 1.4</h3> Both values <code>null</code> and <code>undefined</code> are
