1.1 --- a/json/src/main/java/org/apidesign/html/json/spi/Proto.java Fri Aug 01 12:36:56 2014 +0200
1.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/Proto.java Fri Aug 01 13:02:35 2014 +0200
1.3 @@ -69,7 +69,7 @@
1.4 private final Type type;
1.5 private final net.java.html.BrwsrCtx context;
1.6 private org.netbeans.html.json.impl.Bindings ko;
1.7 - private Watcher myOwn;
1.8 + private Watcher mine;
1.9 private Watcher.Ref observers;
1.10 private static Watcher locked;
1.11
1.12 @@ -103,7 +103,7 @@
1.13
1.14 public void accessValue(String propName) {
1.15 if (locked != null) {
1.16 - if (locked.proto == this) {
1.17 + if (locked.forbiddenValue(this)) {
1.18 throw new IllegalStateException();
1.19 }
1.20 observers = locked.observe(observers, propName);
1.21 @@ -121,7 +121,7 @@
1.22 * unlocked state by calling this method.
1.23 */
1.24 public void releaseLock() {
1.25 - myOwn = locked;
1.26 + mine = Watcher.register(mine, locked);
1.27 locked = null;
1.28 }
1.29
1.30 @@ -462,8 +462,7 @@
1.31 }
1.32
1.33 final Watcher watcher(String prop) {
1.34 - assert myOwn == null || prop.equals(myOwn.prop);
1.35 - return myOwn;
1.36 + return Watcher.find(mine, prop);
1.37 }
1.38
1.39 /** Functionality used by the code generated by annotation
2.1 --- a/json/src/main/java/org/apidesign/html/json/spi/Watcher.java Fri Aug 01 12:36:56 2014 +0200
2.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/Watcher.java Fri Aug 01 13:02:35 2014 +0200
2.3 @@ -50,15 +50,44 @@
2.4 */
2.5 final class Watcher {
2.6 static final Watcher DUMMY = new Watcher(null, null);
2.7 -
2.8 - final Proto proto;
2.9 - final String prop;
2.10 +
2.11 + private final Proto proto;
2.12 + private final String prop;
2.13 + private Watcher next;
2.14
2.15 private Watcher(Proto proto, String prop) {
2.16 this.proto = proto;
2.17 this.prop = prop;
2.18 }
2.19
2.20 + static Watcher find(Watcher first, String prop) {
2.21 + for (;;) {
2.22 + if (prop.equals(first.prop)) {
2.23 + return first;
2.24 + }
2.25 + first = first.next;
2.26 + }
2.27 + }
2.28 +
2.29 + static Watcher register(Watcher mine, Watcher locked) {
2.30 + Watcher current = mine;
2.31 + for (;;) {
2.32 + if (current == null) {
2.33 + return locked;
2.34 + }
2.35 + Watcher next = current.next;
2.36 + if (next == null) {
2.37 + current.next = locked;
2.38 + return mine;
2.39 + }
2.40 + if (next.prop.equals(locked.prop)) {
2.41 + locked.next = next.next;
2.42 + current.next = locked;
2.43 + return mine;
2.44 + }
2.45 + current = next;
2.46 + }
2.47 + }
2.48
2.49 static Watcher computing(Proto proto, String prop) {
2.50 proto.getClass();
2.51 @@ -72,6 +101,10 @@
2.52 }
2.53 return new Ref(this, prop).chain(prev);
2.54 }
2.55 +
2.56 + final boolean forbiddenValue(Proto aThis) {
2.57 + return proto == aThis;
2.58 + }
2.59
2.60 static final class Ref extends WeakReference<Watcher> {
2.61 private final String prop;
3.1 --- a/json/src/test/java/net/java/html/json/DeepChangeTest.java Fri Aug 01 12:36:56 2014 +0200
3.2 +++ b/json/src/test/java/net/java/html/json/DeepChangeTest.java Fri Aug 01 13:02:35 2014 +0200
3.3 @@ -76,16 +76,25 @@
3.4 static String oneName(MyY one) {
3.5 return one.getValue();
3.6 }
3.7 + @ComputedProperty(deep = true)
3.8 + static String sndName(MyY one) {
3.9 + return one.getValue().toUpperCase();
3.10 + }
3.11 + @ComputedProperty(deep = true)
3.12 + static String thrdName(MyY one) {
3.13 + return "X" + one.getCount();
3.14 + }
3.15 }
3.16 @Model(className = "MyY", properties = {
3.17 - @Property(name = "value", type = String.class)
3.18 + @Property(name = "value", type = String.class),
3.19 + @Property(name = "count", type = int.class)
3.20 })
3.21 static class Y {
3.22 }
3.23
3.24 @Test public void isTransitiveChangeNotifiedProperly() throws Exception {
3.25 MyX p = Models.bind(
3.26 - new MyX(new MyY("Ahoj"), new MyY("Hi"), new MyY("Hello")
3.27 + new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
3.28 ), c).applyBindings();
3.29
3.30 Map m = (Map)Models.toRaw(p);
3.31 @@ -102,4 +111,56 @@
3.32 assertEquals(o.get(), "Nazdar");
3.33 assertEquals(o.changes, 1, "One change so far");
3.34 }
3.35 +
3.36 + @Test public void doublePropertyChangeNotified() throws Exception {
3.37 + MyX p = Models.bind(
3.38 + new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
3.39 + ), c).applyBindings();
3.40 +
3.41 + Map m = (Map)Models.toRaw(p);
3.42 + Object v = m.get("oneName");
3.43 + assertNotNull(v, "Value should be in the map");
3.44 + Object v2 = m.get("sndName");
3.45 + assertNotNull(v2, "Value2 should be in the map");
3.46 + One o = (One)v;
3.47 + One o2 = (One)v2;
3.48 + assertEquals(o.changes, 0, "No changes so far");
3.49 + assertEquals(o2.changes, 0, "No changes so far");
3.50 + assertTrue(o.pb.isReadOnly(), "Derived property");
3.51 + assertEquals(o.get(), "Ahoj");
3.52 + assertEquals(o2.get(), "AHOJ");
3.53 +
3.54 + p.getOne().setValue("Nazdar");
3.55 +
3.56 + assertEquals(o.get(), "Nazdar");
3.57 + assertEquals(o.changes, 1, "One change so far");
3.58 + assertEquals(o2.changes, 1, "One change so far");
3.59 + assertEquals(o2.get(), "NAZDAR");
3.60 + }
3.61 +
3.62 + @Test public void onlyAffectedPropertyChangeNotified() throws Exception {
3.63 + MyX p = Models.bind(
3.64 + new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
3.65 + ), c).applyBindings();
3.66 +
3.67 + Map m = (Map)Models.toRaw(p);
3.68 + Object v = m.get("oneName");
3.69 + assertNotNull(v, "Value should be in the map");
3.70 + Object v2 = m.get("thrdName");
3.71 + assertNotNull(v2, "Value2 should be in the map");
3.72 + One o = (One)v;
3.73 + One o2 = (One)v2;
3.74 + assertEquals(o.changes, 0, "No changes so far");
3.75 + assertEquals(o2.changes, 0, "No changes so far");
3.76 + assertTrue(o.pb.isReadOnly(), "Derived property");
3.77 + assertEquals(o.get(), "Ahoj");
3.78 + assertEquals(o2.get(), "X0");
3.79 +
3.80 + p.getOne().setCount(10);
3.81 +
3.82 + assertEquals(o.get(), "Ahoj");
3.83 + assertEquals(o.changes, 0, "Still no change");
3.84 + assertEquals(o2.changes, 1, "One change so far");
3.85 + assertEquals(o2.get(), "X10");
3.86 + }
3.87 }