You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jt...@apache.org on 2017/09/09 05:30:41 UTC
[3/3] incubator-netbeans-html4j git commit: #270481,
#270553: Bugfix for multiple observers and better tests for GC
behavior in the TCK
#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 <ja...@oracle.com>
Authored: Sat Sep 9 07:28:34 2017 +0200
Committer: Jaroslav Tulach <ja...@oracle.com>
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' : this.@net.java.html.js.tests.Sum::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' : thiz.@net.java.html.js.tests.Sum::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