Preventing re-entrant access to the same model object, but otherwise one is free to however anywhere. DeepWatch
authorJaroslav Tulach <jtulach@netbeans.org>
Fri, 01 Aug 2014 15:24:57 +0200
branchDeepWatch
changeset 778942545f35be7
parent 777 c865a31c848d
child 779 ba2e5404d486
Preventing re-entrant access to the same model object, but otherwise one is free to however anywhere.
json/src/main/java/org/apidesign/html/json/spi/Proto.java
json/src/main/java/org/apidesign/html/json/spi/Watcher.java
json/src/test/java/net/java/html/json/DeepChangeTest.java
     1.1 --- a/json/src/main/java/org/apidesign/html/json/spi/Proto.java	Fri Aug 01 14:16:19 2014 +0200
     1.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/Proto.java	Fri Aug 01 15:24:57 2014 +0200
     1.3 @@ -71,7 +71,6 @@
     1.4      private org.netbeans.html.json.impl.Bindings ko;
     1.5      private Watcher mine;
     1.6      private Watcher.Ref observers;
     1.7 -    private static Watcher locked;
     1.8  
     1.9      Proto(Object obj, Type type, BrwsrCtx context) {
    1.10          this.obj = obj;
    1.11 @@ -93,36 +92,28 @@
    1.12       * @throws IllegalStateException if already locked
    1.13       */
    1.14      public void acquireLock() throws IllegalStateException {
    1.15 -        if (locked != null) throw new IllegalStateException();
    1.16 -        locked = Watcher.DUMMY;
    1.17 +        acquireLock(null);
    1.18      }
    1.19      public void acquireLock(String propName) throws IllegalStateException {
    1.20 -        if (locked != null) throw new IllegalStateException();
    1.21 -        locked = Watcher.computing(this, propName);
    1.22 +        Watcher.beginComputing(this, propName);
    1.23      }
    1.24      
    1.25      public void accessValue(String propName) {
    1.26 -        if (locked != null) {
    1.27 -            if (locked.forbiddenValue(this)) {
    1.28 -                throw new IllegalStateException();
    1.29 -            }
    1.30 -            observers = locked.observe(observers, propName);
    1.31 -        }
    1.32 +        observers = Watcher.accessingValue(this, observers, propName);
    1.33      }
    1.34      
    1.35      /** Verifies the model is not locked otherwise throws an exception.
    1.36       * @throws IllegalStateException if the model is locked
    1.37       */
    1.38      public void verifyUnlocked() throws IllegalStateException {
    1.39 -        if (locked != null) throw new IllegalStateException();
    1.40 +        Watcher.verifyUnlocked(this);
    1.41      }
    1.42      
    1.43      /** When modifications are over, the model is switched into 
    1.44       * unlocked state by calling this method.
    1.45       */
    1.46      public void releaseLock() {
    1.47 -        mine = Watcher.register(mine, locked);
    1.48 -        locked = null;
    1.49 +        mine = Watcher.finishComputing(this, mine);
    1.50      }
    1.51      
    1.52      /** Whenever model changes a property. It should notify the
    1.53 @@ -462,7 +453,7 @@
    1.54      }
    1.55  
    1.56      final Watcher watcher(String prop) {
    1.57 -        return Watcher.find(mine, prop);
    1.58 +        return Watcher.find(mine, null, prop);
    1.59      }
    1.60  
    1.61      /** 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 14:16:19 2014 +0200
     2.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/Watcher.java	Fri Aug 01 15:24:57 2014 +0200
     2.3 @@ -49,7 +49,8 @@
     2.4   * @author Jaroslav Tulach
     2.5   */
     2.6  final class Watcher {
     2.7 -    static final Watcher DUMMY = new Watcher(null, null);
     2.8 +    private static final Object LOCK = new Object();
     2.9 +    private static Watcher global;
    2.10  
    2.11      private final Proto proto;
    2.12      private final String prop;
    2.13 @@ -60,17 +61,75 @@
    2.14          this.prop = prop;
    2.15      }
    2.16      
    2.17 -    static Watcher find(Watcher first, String prop) {
    2.18 +    static void beginComputing(Proto p, String name) {
    2.19 +        synchronized (LOCK) {
    2.20 +            Watcher alreadyThere = find(global, p, null);
    2.21 +            if (alreadyThere != null) {
    2.22 +                throw new IllegalStateException("Re-entrant attempt to access " + p);
    2.23 +            }
    2.24 +            final Watcher nw = new Watcher(p, name);
    2.25 +            nw.next = global;
    2.26 +            global = nw;
    2.27 +        }
    2.28 +    }
    2.29 +    
    2.30 +    static void verifyUnlocked(Proto p) {
    2.31 +        synchronized (LOCK) {
    2.32 +            Watcher alreadyThere = find(global, p, null);
    2.33 +            if (alreadyThere != null) {
    2.34 +                throw new IllegalStateException("Re-entrant attempt to access " + p);
    2.35 +            }
    2.36 +        }        
    2.37 +    }
    2.38 +
    2.39 +    static Ref accessingValue(Proto p, Ref observers, String propName) {
    2.40 +        synchronized (LOCK) {
    2.41 +            Watcher alreadyThere = find(global, p, null);
    2.42 +            if (alreadyThere != null) {
    2.43 +                throw new IllegalStateException("Re-entrant attempt to access " + p);
    2.44 +            }
    2.45 +            Watcher w = global;
    2.46 +            for (;;) {
    2.47 +                if (w == null) {
    2.48 +                    return observers;
    2.49 +                }
    2.50 +                observers = w.observe(observers, propName);
    2.51 +                w = w.next;
    2.52 +            }
    2.53 +        }
    2.54 +    }
    2.55 +    
    2.56 +    static Watcher finishComputing(Proto p, Watcher mine) {
    2.57 +        synchronized (LOCK) {
    2.58 +            Watcher w = global;
    2.59 +            global = w.next;
    2.60 +            w.next = null;
    2.61 +            if (w.proto != p) {
    2.62 +                throw new IllegalStateException("Inconsistency: " + w.proto + " != " + p);
    2.63 +            }
    2.64 +            return register(mine, w);
    2.65 +        }
    2.66 +    }
    2.67 +    
    2.68 +    static Watcher find(Watcher first, Proto proto, String prop) {
    2.69 +    //    assert Thread.holdsLock(LOCK);
    2.70          for (;;) {
    2.71 -            if (prop.equals(first.prop)) {
    2.72 +            if (first == null) {
    2.73 +                return null;
    2.74 +            }
    2.75 +            if (prop != null && prop.equals(first.prop)) {
    2.76 +                return first;
    2.77 +            }
    2.78 +            if (proto != null && proto == first.proto) {
    2.79                  return first;
    2.80              }
    2.81              first = first.next;
    2.82          }
    2.83      }
    2.84  
    2.85 -    static Watcher register(Watcher mine, Watcher locked) {
    2.86 -        if (locked == DUMMY) {
    2.87 +    private static Watcher register(Watcher mine, Watcher locked) {
    2.88 +        assert Thread.holdsLock(LOCK);
    2.89 +        if (locked.prop == null) {
    2.90              return mine;
    2.91          }
    2.92          Watcher current = mine;
    2.93 @@ -99,16 +158,12 @@
    2.94      }
    2.95      
    2.96      Ref observe(Ref prev, String prop) {
    2.97 -        if (this == DUMMY) {
    2.98 +        if (prop == null) {
    2.99              return prev;
   2.100          }
   2.101          return new Ref(this, prop).chain(prev);
   2.102      }
   2.103  
   2.104 -    final boolean forbiddenValue(Proto aThis) {
   2.105 -        return this == DUMMY || proto == aThis;
   2.106 -    }
   2.107 -    
   2.108      static final class Ref extends WeakReference<Watcher> {
   2.109          private final String prop;
   2.110          private Ref next;
     3.1 --- a/json/src/test/java/net/java/html/json/DeepChangeTest.java	Fri Aug 01 14:16:19 2014 +0200
     3.2 +++ b/json/src/test/java/net/java/html/json/DeepChangeTest.java	Fri Aug 01 15:24:57 2014 +0200
     3.3 @@ -113,6 +113,15 @@
     3.4      })
     3.5      static class Y {
     3.6      }
     3.7 +    @Model(className = "MyOverall", properties = {
     3.8 +        @Property(name = "x", type = MyX.class)
     3.9 +    })
    3.10 +    static class Overall {
    3.11 +        @ComputedProperty(deep = true) 
    3.12 +        static String valueAccross(MyX x) {
    3.13 +            return x.getFirstFromNames();
    3.14 +        }
    3.15 +    }
    3.16      
    3.17      @Test public void isTransitiveChangeNotifiedProperly() throws Exception {
    3.18          MyX p = Models.bind(
    3.19 @@ -173,6 +182,27 @@
    3.20          assertEquals(o.get(), "Nazdar");
    3.21          assertEquals(o.changes, 1, "One change so far");
    3.22      }
    3.23 +    
    3.24 +    @Test public void firstChangeInArrayNotifiedTransitively() throws Exception {
    3.25 +        MyOverall p = Models.bind(
    3.26 +            new MyOverall(new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999))
    3.27 +        ), c).applyBindings();
    3.28 +        
    3.29 +        Map m = (Map)Models.toRaw(p);
    3.30 +        Object v = m.get("valueAccross");
    3.31 +        assertNotNull(v, "Value should be in the map");
    3.32 +        assertEquals(v.getClass(), One.class, "It is instance of One");
    3.33 +        One o = (One)v;
    3.34 +        assertEquals(o.changes, 0, "No changes so far");
    3.35 +        assertTrue(o.pb.isReadOnly(), "Derived property");
    3.36 +        assertEquals(o.get(), "Hi");
    3.37 +
    3.38 +        p.getX().getAll().get(0).setValue("Nazdar");
    3.39 +        
    3.40 +        assertEquals(o.get(), "Nazdar");
    3.41 +        assertEquals(o.changes, 1, "One change so far");
    3.42 +    }
    3.43 +    
    3.44      @Test public void secondChangeInArrayIgnored() throws Exception {
    3.45          MyX p = Models.bind(
    3.46              new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
    3.47 @@ -281,14 +311,7 @@
    3.48          assertEquals(o2.changes, 0, "No changes so far");
    3.49          assertTrue(o.pb.isReadOnly(), "Derived property");
    3.50          assertEquals(o.get(), "Ahoj");
    3.51 -        try {
    3.52 -            assertEquals(o2.get(), "AHOJ");
    3.53 -        } catch (IllegalStateException ex) {
    3.54 -            // is it OK to forbid access to subproperties of 
    3.55 -            // when the deep is not true?
    3.56 -            // that would be incompatible change...
    3.57 -            return;
    3.58 -        }
    3.59 +        assertEquals(o2.get(), "AHOJ");
    3.60  
    3.61          p.getOne().setValue("Nazdar");
    3.62