Deeply watching changes in @ComputedProperty state
authorJaroslav Tulach <jtulach@netbeans.org>
Sat, 02 Aug 2014 07:56:36 +0200
changeset 78964ddd3d0e8d9
parent 772 3285b4511afc
parent 788 f8ac4d547ad3
child 790 30f20d9c0986
Deeply watching changes in @ComputedProperty state
     1.1 --- a/json/src/main/java/net/java/html/json/ComputedProperty.java	Thu Jul 31 14:37:06 2014 +0200
     1.2 +++ b/json/src/main/java/net/java/html/json/ComputedProperty.java	Sat Aug 02 07:56:36 2014 +0200
     1.3 @@ -54,7 +54,11 @@
     1.4   * The name of the derived property is the name of the method. The arguments
     1.5   * of the method must match names and types of some of the properties 
     1.6   * from {@link Model#properties()} list. As soon as one of these properties
     1.7 - * changes, the method is called to recompute its new value.
     1.8 + * changes, the method is called to recompute its new value. 
     1.9 + * This applies to inner changes in derived properties as well - e.g.
    1.10 + * if the dependant property is another type generated by {@link Model @Model} annotation -
    1.11 + * changes in its own properties trigger recomputation of this derived 
    1.12 + * property as well (since version 0.9).
    1.13   * <p>
    1.14   * Method's return type defines the type of the derived property. It may be
    1.15   * any primitive type, {@link String}, {@link Enum enum type} or a 
     2.1 --- a/json/src/main/java/net/java/html/json/Model.java	Thu Jul 31 14:37:06 2014 +0200
     2.2 +++ b/json/src/main/java/net/java/html/json/Model.java	Sat Aug 02 07:56:36 2014 +0200
     2.3 @@ -47,6 +47,7 @@
     2.4  import java.lang.annotation.RetentionPolicy;
     2.5  import java.lang.annotation.Target;
     2.6  import java.net.URL;
     2.7 +import java.util.List;
     2.8  
     2.9  /** Defines a model class that represents a single 
    2.10   * <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>-like object
    2.11 @@ -82,6 +83,14 @@
    2.12   *     return firstName + " " + lastName;
    2.13   *   }
    2.14   * 
    2.15 + *   {@link ComputedProperty @ComputedProperty}
    2.16 + *   static String mainAddress({@link List List&lt;Address&gt;} addresses) {
    2.17 + *     for (Address a : addresses) {
    2.18 + *       return a.getStreet() + " " + a.getTown();
    2.19 + *     }
    2.20 + *     return "No address";
    2.21 + *   }
    2.22 + * 
    2.23   *   {@link Model @Model}(className="Address", properties={
    2.24   *     {@link Property @Property}(name = "street", type=String.class),
    2.25   *     {@link Property @Property}(name = "town", type=String.class)
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/Observers.java	Sat Aug 02 07:56:36 2014 +0200
     3.3 @@ -0,0 +1,221 @@
     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 org.apidesign.html.json.spi;
    3.47 +
    3.48 +import java.lang.ref.WeakReference;
    3.49 +import java.util.ArrayList;
    3.50 +import java.util.Iterator;
    3.51 +import java.util.LinkedList;
    3.52 +import java.util.List;
    3.53 +
    3.54 +/**
    3.55 + *
    3.56 + * @author Jaroslav Tulach
    3.57 + */
    3.58 +final class Observers {
    3.59 +    private static final LinkedList<Watcher> GLOBAL = new LinkedList<Watcher>();
    3.60 +    private final List<Watcher> watchers = new ArrayList<Watcher>();
    3.61 +    private final List<Ref> observers = new ArrayList<Ref>();
    3.62 +
    3.63 +    Observers() {
    3.64 +        assert Thread.holdsLock(GLOBAL);
    3.65 +    }
    3.66 +    
    3.67 +    static void beginComputing(Proto p, String name) {
    3.68 +        synchronized (GLOBAL) {
    3.69 +            verifyUnlocked(p);
    3.70 +            final Watcher nw = new Watcher(p, name);
    3.71 +            GLOBAL.push(nw);
    3.72 +        }
    3.73 +    }
    3.74 +    
    3.75 +    static void verifyUnlocked(Proto p) {
    3.76 +        synchronized (GLOBAL) {
    3.77 +            for (Watcher w : GLOBAL) {
    3.78 +                if (w.proto == p) {
    3.79 +                    throw new IllegalStateException("Re-entrant attempt to access " + p);
    3.80 +                }
    3.81 +            }
    3.82 +        }        
    3.83 +    }
    3.84 +
    3.85 +    static void accessingValue(Proto p, String propName) {
    3.86 +        synchronized (GLOBAL) {
    3.87 +            verifyUnlocked(p);
    3.88 +            for (Watcher w : GLOBAL) {
    3.89 +                Observers mine = p.observers(true);
    3.90 +                mine.add(w, new Ref(w, propName));
    3.91 +            }
    3.92 +        }
    3.93 +    }
    3.94 +    
    3.95 +    static void finishComputing(Proto p) {
    3.96 +        synchronized (GLOBAL) {
    3.97 +            Watcher w = GLOBAL.pop();
    3.98 +            if (w.proto != p) {
    3.99 +                throw new IllegalStateException("Inconsistency: " + w.proto + " != " + p);
   3.100 +            }
   3.101 +            if (w.prop != null) {
   3.102 +                Observers mine = p.observers(true);
   3.103 +                mine.add(w);
   3.104 +            }
   3.105 +        }
   3.106 +    }
   3.107 +    
   3.108 +    private static final class Ref extends WeakReference<Watcher> {
   3.109 +        private final String prop;
   3.110 +        
   3.111 +        public Ref(Watcher ref, String prop) {
   3.112 +            super(ref);
   3.113 +            this.prop = prop;
   3.114 +        }
   3.115 +        
   3.116 +        final Watcher watcher() {
   3.117 +            Watcher w = get();
   3.118 +            if (w == null) {
   3.119 +                return null;
   3.120 +            }
   3.121 +            final Observers o = w.proto.observers(false);
   3.122 +            if (o == null) {
   3.123 +                return null;
   3.124 +            }
   3.125 +            if (o.find(w.prop) == w) {
   3.126 +                return w;
   3.127 +            }
   3.128 +            return null;
   3.129 +        }
   3.130 +    }
   3.131 +    
   3.132 +    private Watcher find(String prop) {
   3.133 +        if (prop == null) {
   3.134 +            return null;
   3.135 +        }
   3.136 +        for (Watcher w : watchers) {
   3.137 +            if (prop.equals(w.prop)) {
   3.138 +                return w;
   3.139 +            }
   3.140 +        }
   3.141 +        return null;
   3.142 +    }
   3.143 +
   3.144 +        final void add(Watcher w) {
   3.145 +        for (int i = 0; i < watchers.size(); i++) {
   3.146 +            Watcher ith = watchers.get(i);
   3.147 +            if (w.prop == null) {
   3.148 +                if (ith.prop == null) {
   3.149 +                    watchers.set(i, w);
   3.150 +                    return;
   3.151 +                }
   3.152 +            } else if (w.prop.equals(ith.prop)) {
   3.153 +                watchers.set(i, w);
   3.154 +                return;
   3.155 +            }
   3.156 +        }
   3.157 +        watchers.add(w);
   3.158 +    }
   3.159 +
   3.160 +    static final void valueHasMutated(Proto p, String propName) {
   3.161 +        List<Watcher> mutated = new LinkedList<Watcher>();
   3.162 +        synchronized (GLOBAL) {
   3.163 +            Observers mine = p.observers(false);
   3.164 +            if (mine == null) {
   3.165 +                return;
   3.166 +            }
   3.167 +            Iterator<Ref> it = mine.observers.iterator();
   3.168 +            while (it.hasNext()) {
   3.169 +                Ref ref = it.next();
   3.170 +                if (ref.get() == null) {
   3.171 +                    it.remove();
   3.172 +                    continue;
   3.173 +                }
   3.174 +                if (ref.prop.equals(propName)) {
   3.175 +                    Watcher w = ref.watcher();
   3.176 +                    if (w != null) {
   3.177 +                        mutated.add(w);
   3.178 +                    }
   3.179 +                }
   3.180 +            }
   3.181 +        }
   3.182 +        for (Watcher w : mutated) {
   3.183 +            w.proto.valueHasMutated(w.prop);
   3.184 +        }
   3.185 +    }
   3.186 +
   3.187 +    void add(Watcher w, Ref r) {
   3.188 +        Thread.holdsLock(GLOBAL);
   3.189 +        if (w == null) {
   3.190 +            return;
   3.191 +        }
   3.192 +        Iterator<Ref> it = observers.iterator();
   3.193 +        while (it.hasNext()) {
   3.194 +            Ref ref = it.next();
   3.195 +            if (r == ref) {
   3.196 +                return;
   3.197 +            }
   3.198 +            final Watcher rw = ref.get();
   3.199 +            if (rw == null) {
   3.200 +                it.remove();
   3.201 +                continue;
   3.202 +            }
   3.203 +            if (rw == w && r.prop.equals(r.prop)) {
   3.204 +                return;
   3.205 +            }
   3.206 +        }
   3.207 +        observers.add(r);
   3.208 +    }
   3.209 +    
   3.210 +    private static final class Watcher {
   3.211 +        final Proto proto;
   3.212 +        final String prop;
   3.213 +
   3.214 +        Watcher(Proto proto, String prop) {
   3.215 +            this.proto = proto;
   3.216 +            this.prop = prop;
   3.217 +        }
   3.218 +        
   3.219 +        @Override
   3.220 +        public String toString() {
   3.221 +            return "Watcher: " + proto + ", " + prop;
   3.222 +        }
   3.223 +    }
   3.224 +}
     4.1 --- a/json/src/main/java/org/apidesign/html/json/spi/Proto.java	Thu Jul 31 14:37:06 2014 +0200
     4.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/Proto.java	Sat Aug 02 07:56:36 2014 +0200
     4.3 @@ -68,8 +68,8 @@
     4.4      private final Object obj;
     4.5      private final Type type;
     4.6      private final net.java.html.BrwsrCtx context;
     4.7 -    private boolean locked;
     4.8      private org.netbeans.html.json.impl.Bindings ko;
     4.9 +    private Observers observers;
    4.10  
    4.11      Proto(Object obj, Type type, BrwsrCtx context) {
    4.12          this.obj = obj;
    4.13 @@ -86,27 +86,58 @@
    4.14          return context;
    4.15      }
    4.16  
    4.17 -    /** Before doing modification of the model properties, the
    4.18 -     * generated code enters write lock by calling this method.
    4.19 +    /** Acquires global lock to compute a {@link ComputedProperty derived property}
    4.20 +     * on this proto object. This proto object must not be locked yet. No
    4.21 +     * dependency tracking is performed.
    4.22 +     * 
    4.23       * @throws IllegalStateException if already locked
    4.24       */
    4.25      public void acquireLock() throws IllegalStateException {
    4.26 -        if (locked) throw new IllegalStateException();
    4.27 -        locked = true;
    4.28 +        acquireLock(null);
    4.29 +    }
    4.30 +    
    4.31 +    /** Acquires global lock to compute a {@link ComputedProperty derived property}
    4.32 +     * on this proto object. This proto object must not be locked yet. The
    4.33 +     * name of the property is used to track dependencies on own
    4.34 +     * properties of other proto objects - when they are changed, this
    4.35 +     * {@link #valueHasMutated(java.lang.String) property is changed too}.
    4.36 +     * 
    4.37 +     * @param propName name of property we are about to compute
    4.38 +     * @throws IllegalStateException thrown when there is a cyclic
    4.39 +     *   call is detected
    4.40 +     * @since 0.9
    4.41 +     */
    4.42 +    public void acquireLock(String propName) throws IllegalStateException {
    4.43 +        Observers.beginComputing(this, propName);
    4.44 +    }
    4.45 +    
    4.46 +    /** A property on this proto object is about to be accessed. Verifies
    4.47 +     * whether this proto object is accessible - e.g. it has not been
    4.48 +     * {@link #acquireLock() locked yet}. If everything is OK, the
    4.49 +     * <code>propName</code> is recorded in the chain of dependencies
    4.50 +     * tracked by {@link #acquireLock(java.lang.String)} and watched by
    4.51 +     * {@link #valueHasMutated(java.lang.String)}.
    4.52 +     * 
    4.53 +     * @param propName name of the property that is requested
    4.54 +     * @throws IllegalStateException if the model is locked
    4.55 +     * @since 0.9
    4.56 +     */
    4.57 +    public void accessProperty(String propName) throws IllegalStateException {
    4.58 +        Observers.accessingValue(this, propName);
    4.59      }
    4.60      
    4.61      /** Verifies the model is not locked otherwise throws an exception.
    4.62       * @throws IllegalStateException if the model is locked
    4.63       */
    4.64      public void verifyUnlocked() throws IllegalStateException {
    4.65 -        if (locked) throw new IllegalStateException();
    4.66 +        Observers.verifyUnlocked(this);
    4.67      }
    4.68      
    4.69      /** When modifications are over, the model is switched into 
    4.70       * unlocked state by calling this method.
    4.71       */
    4.72      public void releaseLock() {
    4.73 -        locked = false;
    4.74 +        Observers.finishComputing(this);
    4.75      }
    4.76      
    4.77      /** Whenever model changes a property. It should notify the
    4.78 @@ -124,6 +155,7 @@
    4.79                  if (ko != null) {
    4.80                      ko.valueHasMutated(propName, null, null);
    4.81                  }
    4.82 +                Observers.valueHasMutated(Proto.this, propName);
    4.83              }
    4.84          });
    4.85      }
    4.86 @@ -151,6 +183,7 @@
    4.87                  if (ko != null) {
    4.88                      ko.valueHasMutated(propName, oldValue, newValue);
    4.89                  }
    4.90 +                Observers.valueHasMutated(Proto.this, propName);
    4.91              }
    4.92          });
    4.93      }
    4.94 @@ -409,6 +442,9 @@
    4.95      // internal state
    4.96      //
    4.97      
    4.98 +    final String toStr() {
    4.99 +        return "Proto[" + obj + "]@" + Integer.toHexString(System.identityHashCode(this));
   4.100 +    }
   4.101      
   4.102      final Bindings initBindings() {
   4.103          if (ko == null) {
   4.104 @@ -439,6 +475,13 @@
   4.105          type.onChange(obj, index);
   4.106      }
   4.107  
   4.108 +    final Observers observers(boolean create) {
   4.109 +        if (create && observers == null) {
   4.110 +            observers = new Observers();
   4.111 +        }
   4.112 +        return observers;
   4.113 +    }
   4.114 +
   4.115      /** Functionality used by the code generated by annotation
   4.116       * processor for the {@link net.java.html.json.Model} annotation.
   4.117       * 
     5.1 --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java	Thu Jul 31 14:37:06 2014 +0200
     5.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java	Sat Aug 02 07:56:36 2014 +0200
     5.3 @@ -544,14 +544,14 @@
     5.4              
     5.5                  castTo = "java.util.List";
     5.6                  w.write("  public java.util.List<" + tn + "> " + gs[0] + "() {\n");
     5.7 -                w.write("    proto.verifyUnlocked();\n");
     5.8 +                w.write("    proto.accessProperty(\"" + p.name() + "\");\n");
     5.9                  w.write("    return prop_" + p.name() + ";\n");
    5.10                  w.write("  }\n");
    5.11              } else {
    5.12                  castTo = tn;
    5.13                  w.write("  private " + tn + " prop_" + p.name() + ";\n");
    5.14                  w.write("  public " + tn + " " + gs[0] + "() {\n");
    5.15 -                w.write("    proto.verifyUnlocked();\n");
    5.16 +                w.write("    proto.accessProperty(\"" + p.name() + "\");\n");
    5.17                  w.write("    return prop_" + p.name() + ";\n");
    5.18                  w.write("  }\n");
    5.19                  w.write("  public void " + gs[1] + "(" + tn + " v) {\n");
    5.20 @@ -606,7 +606,9 @@
    5.21              if (e.getKind() != ElementKind.METHOD) {
    5.22                  continue;
    5.23              }
    5.24 -            if (e.getAnnotation(ComputedProperty.class) == null) {
    5.25 +            final ComputedProperty cp = e.getAnnotation(ComputedProperty.class);
    5.26 +            final Transitive tp = e.getAnnotation(Transitive.class);
    5.27 +            if (cp == null) {
    5.28                  continue;
    5.29              }
    5.30              if (!e.getModifiers().contains(Modifier.STATIC)) {
    5.31 @@ -656,14 +658,24 @@
    5.32              }
    5.33              w.write(" " + gs[0] + "() {\n");
    5.34              int arg = 0;
    5.35 +            boolean deep = false;
    5.36              for (VariableElement pe : ee.getParameters()) {
    5.37                  final String dn = pe.getSimpleName().toString();
    5.38                  
    5.39                  if (!verifyPropName(pe, dn, fixedProps)) {
    5.40                      ok = false;
    5.41                  }
    5.42 -                
    5.43 -                final String dt = fqn(pe.asType(), ee);
    5.44 +                final TypeMirror pt = pe.asType();
    5.45 +                if (isModel(pt)) {
    5.46 +                    deep = true;
    5.47 +                }
    5.48 +                final String dt = fqn(pt, ee);
    5.49 +                if (dt.startsWith("java.util.List") && pt instanceof DeclaredType) {
    5.50 +                    final List<? extends TypeMirror> ptArgs = ((DeclaredType)pt).getTypeArguments();
    5.51 +                    if (ptArgs.size() == 1 && isModel(ptArgs.get(0))) {
    5.52 +                        deep = true;
    5.53 +                    }
    5.54 +                }
    5.55                  String[] call = toGetSet(dn, dt, false);
    5.56                  w.write("    " + dt + " arg" + (++arg) + " = ");
    5.57                  w.write(call[0] + "();\n");
    5.58 @@ -676,7 +688,14 @@
    5.59                  depends.add(new String[] { sn, gs[0] });
    5.60              }
    5.61              w.write("    try {\n");
    5.62 -            w.write("      proto.acquireLock();\n");
    5.63 +            if (tp != null) {
    5.64 +                deep = tp.deep();
    5.65 +            }
    5.66 +            if (deep) {
    5.67 +                w.write("      proto.acquireLock(\"" + sn + "\");\n");
    5.68 +            } else {
    5.69 +                w.write("      proto.acquireLock();\n");
    5.70 +            }
    5.71              w.write("      return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
    5.72              String sep = "";
    5.73              for (int i = 1; i <= arg; i++) {
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/Transitive.java	Sat Aug 02 07:56:36 2014 +0200
     6.3 @@ -0,0 +1,60 @@
     6.4 +/**
     6.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     6.6 + *
     6.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     6.8 + *
     6.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    6.10 + * Other names may be trademarks of their respective owners.
    6.11 + *
    6.12 + * The contents of this file are subject to the terms of either the GNU
    6.13 + * General Public License Version 2 only ("GPL") or the Common
    6.14 + * Development and Distribution License("CDDL") (collectively, the
    6.15 + * "License"). You may not use this file except in compliance with the
    6.16 + * License. You can obtain a copy of the License at
    6.17 + * http://www.netbeans.org/cddl-gplv2.html
    6.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    6.19 + * specific language governing permissions and limitations under the
    6.20 + * License.  When distributing the software, include this License Header
    6.21 + * Notice in each file and include the License file at
    6.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    6.23 + * particular file as subject to the "Classpath" exception as provided
    6.24 + * by Oracle in the GPL Version 2 section of the License file that
    6.25 + * accompanied this code. If applicable, add the following below the
    6.26 + * License Header, with the fields enclosed by brackets [] replaced by
    6.27 + * your own identifying information:
    6.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    6.29 + *
    6.30 + * Contributor(s):
    6.31 + *
    6.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    6.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    6.34 + *
    6.35 + * If you wish your version of this file to be governed by only the CDDL
    6.36 + * or only the GPL Version 2, indicate your decision by adding
    6.37 + * "[Contributor] elects to include this software in this distribution
    6.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    6.39 + * single choice of license, a recipient has the option to distribute
    6.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    6.41 + * to extend the choice of license to its licensees as provided above.
    6.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    6.43 + * Version 2 license, then the option applies only if the new code is
    6.44 + * made subject to such option by the copyright holder.
    6.45 + */
    6.46 +package org.netbeans.html.json.impl;
    6.47 +
    6.48 +import java.lang.annotation.ElementType;
    6.49 +import java.lang.annotation.Retention;
    6.50 +import java.lang.annotation.RetentionPolicy;
    6.51 +import java.lang.annotation.Target;
    6.52 +
    6.53 +/** A way to control {@link ComputedProperty} behavior - whether it tracks
    6.54 + * deeply or not. Not public yet, maybe it won't be needed in the API at all.
    6.55 + * Used in tests only.
    6.56 + * 
    6.57 + * @author Jaroslav Tulach
    6.58 + */
    6.59 +@Retention(RetentionPolicy.SOURCE)
    6.60 +@Target(ElementType.METHOD)
    6.61 +@interface Transitive {
    6.62 +    boolean deep() default false;
    6.63 +}
     7.1 --- a/json/src/test/java/net/java/html/json/MapModelTest.java	Thu Jul 31 14:37:06 2014 +0200
     7.2 +++ b/json/src/test/java/net/java/html/json/MapModelTest.java	Sat Aug 02 07:56:36 2014 +0200
     7.3 @@ -54,7 +54,6 @@
     7.4  import org.apidesign.html.json.spi.PropertyBinding;
     7.5  import org.apidesign.html.json.spi.Technology;
     7.6  import org.apidesign.html.json.spi.Transfer;
     7.7 -import org.netbeans.html.json.impl.JSON;
     7.8  import org.testng.annotations.BeforeMethod;
     7.9  import org.testng.annotations.Test;
    7.10  import static org.testng.Assert.*;
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/json/src/test/java/org/netbeans/html/json/impl/DeepChangeTest.java	Sat Aug 02 07:56:36 2014 +0200
     8.3 @@ -0,0 +1,496 @@
     8.4 +/**
     8.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     8.6 + *
     8.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     8.8 + *
     8.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    8.10 + * Other names may be trademarks of their respective owners.
    8.11 + *
    8.12 + * The contents of this file are subject to the terms of either the GNU
    8.13 + * General Public License Version 2 only ("GPL") or the Common
    8.14 + * Development and Distribution License("CDDL") (collectively, the
    8.15 + * "License"). You may not use this file except in compliance with the
    8.16 + * License. You can obtain a copy of the License at
    8.17 + * http://www.netbeans.org/cddl-gplv2.html
    8.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    8.19 + * specific language governing permissions and limitations under the
    8.20 + * License.  When distributing the software, include this License Header
    8.21 + * Notice in each file and include the License file at
    8.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    8.23 + * particular file as subject to the "Classpath" exception as provided
    8.24 + * by Oracle in the GPL Version 2 section of the License file that
    8.25 + * accompanied this code. If applicable, add the following below the
    8.26 + * License Header, with the fields enclosed by brackets [] replaced by
    8.27 + * your own identifying information:
    8.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    8.29 + *
    8.30 + * Contributor(s):
    8.31 + *
    8.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    8.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    8.34 + *
    8.35 + * If you wish your version of this file to be governed by only the CDDL
    8.36 + * or only the GPL Version 2, indicate your decision by adding
    8.37 + * "[Contributor] elects to include this software in this distribution
    8.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    8.39 + * single choice of license, a recipient has the option to distribute
    8.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    8.41 + * to extend the choice of license to its licensees as provided above.
    8.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    8.43 + * Version 2 license, then the option applies only if the new code is
    8.44 + * made subject to such option by the copyright holder.
    8.45 + */
    8.46 +package org.netbeans.html.json.impl;
    8.47 +
    8.48 +import java.io.IOException;
    8.49 +import java.io.InputStream;
    8.50 +import java.lang.reflect.InvocationTargetException;
    8.51 +import java.util.HashMap;
    8.52 +import java.util.List;
    8.53 +import java.util.Map;
    8.54 +import net.java.html.BrwsrCtx;
    8.55 +import net.java.html.json.ComputedProperty;
    8.56 +import net.java.html.json.Model;
    8.57 +import net.java.html.json.Models;
    8.58 +import net.java.html.json.Property;
    8.59 +import org.apidesign.html.context.spi.Contexts;
    8.60 +import org.apidesign.html.json.spi.FunctionBinding;
    8.61 +import org.apidesign.html.json.spi.JSONCall;
    8.62 +import org.apidesign.html.json.spi.PropertyBinding;
    8.63 +import org.apidesign.html.json.spi.Technology;
    8.64 +import org.apidesign.html.json.spi.Transfer;
    8.65 +import static org.testng.Assert.*;
    8.66 +import org.testng.annotations.BeforeMethod;
    8.67 +import org.testng.annotations.Test;
    8.68 +
    8.69 +/**
    8.70 + *
    8.71 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.72 + */
    8.73 +public class DeepChangeTest {
    8.74 +    private MapTechnology t;
    8.75 +    private BrwsrCtx c;
    8.76 +
    8.77 +    @BeforeMethod public void initTechnology() {
    8.78 +        t = new MapTechnology();
    8.79 +        c = Contexts.newBuilder().register(Technology.class, t, 1).
    8.80 +            register(Transfer.class, t, 1).build();
    8.81 +    }
    8.82 +    
    8.83 +    @Model(className = "MyX", properties = {
    8.84 +        @Property(name = "one", type = MyY.class),
    8.85 +        @Property(name = "all", type = MyY.class, array = true)
    8.86 +    })
    8.87 +    static class X {
    8.88 +        @ComputedProperty @Transitive(deep = true) 
    8.89 +        static String oneName(MyY one) {
    8.90 +            return one.getValue();
    8.91 +        }
    8.92 +        @ComputedProperty
    8.93 +        static String sndName(MyY one) {
    8.94 +            return one.getValue().toUpperCase();
    8.95 +        }
    8.96 +        @ComputedProperty @Transitive(deep = false) 
    8.97 +        static String noName(MyY one) {
    8.98 +            return one.getValue().toUpperCase();
    8.99 +        }
   8.100 +        @ComputedProperty @Transitive(deep = true) 
   8.101 +        static String thrdName(MyY one) {
   8.102 +            return "X" + one.getCount();
   8.103 +        }
   8.104 +        
   8.105 +        @ComputedProperty
   8.106 +        static String allNames(List<MyY> all) {
   8.107 +            StringBuilder sb = new StringBuilder();
   8.108 +            for (MyY y : all) {
   8.109 +                sb.append(y.getValue());
   8.110 +            }
   8.111 +            return sb.toString();
   8.112 +        }
   8.113 +
   8.114 +        @ComputedProperty @Transitive(deep = true)
   8.115 +        static String firstFromNames(List<MyY> all) {
   8.116 +            for (MyY y : all) {
   8.117 +                if (y != null && y.getValue() != null) {
   8.118 +                    return y.getValue();
   8.119 +                }
   8.120 +            }
   8.121 +            return null;
   8.122 +        }
   8.123 +    }
   8.124 +    @Model(className = "MyY", properties = {
   8.125 +        @Property(name = "value", type = String.class),
   8.126 +        @Property(name = "count", type = int.class)
   8.127 +    })
   8.128 +    static class Y {
   8.129 +    }
   8.130 +    @Model(className = "MyOverall", properties = {
   8.131 +        @Property(name = "x", type = MyX.class)
   8.132 +    })
   8.133 +    static class Overall {
   8.134 +        @ComputedProperty @Transitive(deep = true) 
   8.135 +        static String valueAccross(MyX x) {
   8.136 +            return x.getFirstFromNames();
   8.137 +        }
   8.138 +    }
   8.139 +    
   8.140 +    @Test public void isTransitiveChangeNotifiedProperly() throws Exception {
   8.141 +        MyX p = Models.bind(
   8.142 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.143 +        ), c).applyBindings();
   8.144 +        
   8.145 +        Map m = (Map)Models.toRaw(p);
   8.146 +        Object v = m.get("oneName");
   8.147 +        assertNotNull(v, "Value should be in the map");
   8.148 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.149 +        One o = (One)v;
   8.150 +        assertEquals(o.changes, 0, "No changes so far");
   8.151 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.152 +        assertEquals(o.get(), "Ahoj");
   8.153 +
   8.154 +        p.getOne().setValue("Nazdar");
   8.155 +        
   8.156 +        assertEquals(o.get(), "Nazdar");
   8.157 +        assertEquals(o.changes, 1, "One change so far");
   8.158 +    }
   8.159 +    
   8.160 +    @Test public void isTransitiveChangeInArrayNotifiedProperly() throws Exception {
   8.161 +        MyX p = Models.bind(
   8.162 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.163 +        ), c).applyBindings();
   8.164 +        
   8.165 +        Map m = (Map)Models.toRaw(p);
   8.166 +        Object v = m.get("allNames");
   8.167 +        assertNotNull(v, "Value should be in the map");
   8.168 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.169 +        One o = (One)v;
   8.170 +        assertEquals(o.changes, 0, "No changes so far");
   8.171 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.172 +        assertEquals(o.get(), "HiHello");
   8.173 +
   8.174 +        p.getAll().get(0).setValue("Nazdar");
   8.175 +        
   8.176 +        assertEquals(o.get(), "NazdarHello");
   8.177 +        assertEquals(o.changes, 1, "One change so far");
   8.178 +    }
   8.179 +
   8.180 +    @Test public void addingIntoArray() throws Exception {
   8.181 +        MyX p = Models.bind(
   8.182 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.183 +        ), c).applyBindings();
   8.184 +        
   8.185 +        Map m = (Map)Models.toRaw(p);
   8.186 +        Object v = m.get("allNames");
   8.187 +        assertNotNull(v, "Value should be in the map");
   8.188 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.189 +        One o = (One)v;
   8.190 +        assertEquals(o.changes, 0, "No changes so far");
   8.191 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.192 +        assertEquals(o.get(), "HiHello");
   8.193 +
   8.194 +        MyY y = new MyY("Cus", 1);
   8.195 +        p.getAll().add(y);
   8.196 +        
   8.197 +        assertEquals(o.changes, 1, "One change so far");
   8.198 +        assertEquals(o.get(), "HiHelloCus");
   8.199 +        
   8.200 +        y.setValue("Nazdar");
   8.201 +        
   8.202 +        assertEquals(o.changes, 2, "2nd change so far");
   8.203 +        assertEquals(o.get(), "HiHelloNazdar");
   8.204 +        
   8.205 +        y.setValue("Zdravim");
   8.206 +
   8.207 +        assertEquals(o.changes, 3, "3rd change so far");
   8.208 +        assertEquals(o.get(), "HiHelloZdravim");
   8.209 +    }
   8.210 +    
   8.211 +    @Test public void firstChangeInArrayNotifiedProperly() throws Exception {
   8.212 +        MyX p = Models.bind(
   8.213 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.214 +        ), c).applyBindings();
   8.215 +        
   8.216 +        Map m = (Map)Models.toRaw(p);
   8.217 +        Object v = m.get("firstFromNames");
   8.218 +        assertNotNull(v, "Value should be in the map");
   8.219 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.220 +        One o = (One)v;
   8.221 +        assertEquals(o.changes, 0, "No changes so far");
   8.222 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.223 +        assertEquals(o.get(), "Hi");
   8.224 +
   8.225 +        p.getAll().get(0).setValue("Nazdar");
   8.226 +        
   8.227 +        assertEquals(o.get(), "Nazdar");
   8.228 +        assertEquals(o.changes, 1, "One change so far");
   8.229 +    }
   8.230 +    
   8.231 +    @Test public void firstChangeInArrayToNull() throws Exception {
   8.232 +        MyX p = Models.bind(
   8.233 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.234 +        ), c).applyBindings();
   8.235 +        
   8.236 +        Map m = (Map)Models.toRaw(p);
   8.237 +        Object v = m.get("firstFromNames");
   8.238 +        assertNotNull(v, "Value should be in the map");
   8.239 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.240 +        One o = (One)v;
   8.241 +        assertEquals(o.changes, 0, "No changes so far");
   8.242 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.243 +        assertEquals(o.get(), "Hi");
   8.244 +
   8.245 +        p.getAll().get(0).setValue(null);
   8.246 +        
   8.247 +        assertEquals(o.get(), "Hello");
   8.248 +        assertEquals(o.changes, 1, "One change so far");
   8.249 +        
   8.250 +        p.getAll().get(0).setValue("Nazdar");
   8.251 +
   8.252 +        assertEquals(o.get(), "Nazdar");
   8.253 +        assertEquals(o.changes, 2, "2nd change so far");
   8.254 +    }
   8.255 +    
   8.256 +    @Test public void firstChangeInArrayNotifiedTransitively() throws Exception {
   8.257 +        MyOverall p = Models.bind(
   8.258 +            new MyOverall(new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999))
   8.259 +        ), c).applyBindings();
   8.260 +        
   8.261 +        Map m = (Map)Models.toRaw(p);
   8.262 +        Object v = m.get("valueAccross");
   8.263 +        assertNotNull(v, "Value should be in the map");
   8.264 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.265 +        One o = (One)v;
   8.266 +        assertEquals(o.changes, 0, "No changes so far");
   8.267 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.268 +        assertEquals(o.get(), "Hi");
   8.269 +
   8.270 +        p.getX().getAll().get(0).setValue("Nazdar");
   8.271 +        
   8.272 +        assertEquals(o.get(), "Nazdar");
   8.273 +        assertEquals(o.changes, 1, "One change so far");
   8.274 +    }
   8.275 +    
   8.276 +    @Test public void secondChangeInArrayIgnored() throws Exception {
   8.277 +        MyX p = Models.bind(
   8.278 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.279 +        ), c).applyBindings();
   8.280 +        
   8.281 +        Map m = (Map)Models.toRaw(p);
   8.282 +        Object v = m.get("firstFromNames");
   8.283 +        assertNotNull(v, "Value should be in the map");
   8.284 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.285 +        One o = (One)v;
   8.286 +        assertEquals(o.changes, 0, "No changes so far");
   8.287 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.288 +        assertEquals(o.get(), "Hi");
   8.289 +
   8.290 +        p.getAll().get(1).setValue("Nazdar");
   8.291 +        
   8.292 +        assertEquals(o.get(), "Hi");
   8.293 +        assertEquals(o.changes, 0, "No change so far");
   8.294 +    }
   8.295 +    
   8.296 +    @Test public void changeInArraySizeNeedsToBeRecomputed() throws Exception {
   8.297 +        MyX p = Models.bind(
   8.298 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.299 +        ), c).applyBindings();
   8.300 +        
   8.301 +        Map m = (Map)Models.toRaw(p);
   8.302 +        Object v = m.get("firstFromNames");
   8.303 +        assertNotNull(v, "Value should be in the map");
   8.304 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   8.305 +        One o = (One)v;
   8.306 +        assertEquals(o.changes, 0, "No changes so far");
   8.307 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.308 +        assertEquals(o.get(), "Hi");
   8.309 +
   8.310 +        p.getAll().remove(1);
   8.311 +        
   8.312 +        assertEquals(o.get(), "Hi");
   8.313 +        assertEquals(o.changes, 1, "This required a change");
   8.314 +    }
   8.315 +    
   8.316 +    @Test public void doublePropertyChangeNotified() throws Exception {
   8.317 +        MyX p = Models.bind(
   8.318 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.319 +        ), c).applyBindings();
   8.320 +        
   8.321 +        Map m = (Map)Models.toRaw(p);
   8.322 +        Object v = m.get("oneName");
   8.323 +        assertNotNull(v, "Value should be in the map");
   8.324 +        Object v2 = m.get("sndName");
   8.325 +        assertNotNull(v2, "Value2 should be in the map");
   8.326 +        One o = (One)v;
   8.327 +        One o2 = (One)v2;
   8.328 +        assertEquals(o.changes, 0, "No changes so far");
   8.329 +        assertEquals(o2.changes, 0, "No changes so far");
   8.330 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.331 +        assertEquals(o.get(), "Ahoj");
   8.332 +        assertEquals(o2.get(), "AHOJ");
   8.333 +
   8.334 +        p.getOne().setValue("Nazdar");
   8.335 +        
   8.336 +        assertEquals(o.get(), "Nazdar");
   8.337 +        assertEquals(o.changes, 1, "One change so far");
   8.338 +        assertEquals(o2.changes, 1, "One change so far");
   8.339 +        assertEquals(o2.get(), "NAZDAR");
   8.340 +    }
   8.341 +    
   8.342 +    @Test public void onlyAffectedPropertyChangeNotified() throws Exception {
   8.343 +        MyX p = Models.bind(
   8.344 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.345 +        ), c).applyBindings();
   8.346 +        
   8.347 +        Map m = (Map)Models.toRaw(p);
   8.348 +        Object v = m.get("oneName");
   8.349 +        assertNotNull(v, "Value should be in the map");
   8.350 +        Object v2 = m.get("thrdName");
   8.351 +        assertNotNull(v2, "Value2 should be in the map");
   8.352 +        One o = (One)v;
   8.353 +        One o2 = (One)v2;
   8.354 +        assertEquals(o.changes, 0, "No changes so far");
   8.355 +        assertEquals(o2.changes, 0, "No changes so far");
   8.356 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.357 +        assertEquals(o.get(), "Ahoj");
   8.358 +        assertEquals(o2.get(), "X0");
   8.359 +
   8.360 +        p.getOne().setCount(10);
   8.361 +        
   8.362 +        assertEquals(o.get(), "Ahoj");
   8.363 +        assertEquals(o.changes, 0, "Still no change");
   8.364 +        assertEquals(o2.changes, 1, "One change so far");
   8.365 +        assertEquals(o2.get(), "X10");
   8.366 +    }
   8.367 +    
   8.368 +    @Test public void onlyDeepPropsAreNotified() throws Exception {
   8.369 +        MyX p = Models.bind(
   8.370 +            new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
   8.371 +        ), c).applyBindings();
   8.372 +        
   8.373 +        Map m = (Map)Models.toRaw(p);
   8.374 +        Object v = m.get("oneName");
   8.375 +        assertNotNull(v, "Value should be in the map");
   8.376 +        Object v2 = m.get("noName");
   8.377 +        assertNotNull(v2, "Value2 should be in the map");
   8.378 +        One o = (One)v;
   8.379 +        One o2 = (One)v2;
   8.380 +        assertEquals(o.changes, 0, "No changes so far");
   8.381 +        assertEquals(o2.changes, 0, "No changes so far");
   8.382 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   8.383 +        assertEquals(o.get(), "Ahoj");
   8.384 +        assertEquals(o2.get(), "AHOJ");
   8.385 +
   8.386 +        p.getOne().setValue("Nazdar");
   8.387 +        
   8.388 +        assertEquals(o.get(), "Nazdar");
   8.389 +        assertEquals(o.changes, 1, "One change so far");
   8.390 +        assertEquals(o2.changes, 0, "This change is not noticed");
   8.391 +        assertEquals(o2.get(), "NAZDAR", "but property value changes when computed");
   8.392 +    }
   8.393 +
   8.394 +    static final class One {
   8.395 +
   8.396 +        int changes;
   8.397 +        final PropertyBinding pb;
   8.398 +        final FunctionBinding fb;
   8.399 +
   8.400 +        One(Object m, PropertyBinding pb) throws NoSuchMethodException {
   8.401 +            this.pb = pb;
   8.402 +            this.fb = null;
   8.403 +        }
   8.404 +
   8.405 +        One(Object m, FunctionBinding fb) throws NoSuchMethodException {
   8.406 +            this.pb = null;
   8.407 +            this.fb = fb;
   8.408 +        }
   8.409 +
   8.410 +        Object get() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
   8.411 +            return pb.getValue();
   8.412 +        }
   8.413 +
   8.414 +        void set(Object v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
   8.415 +            pb.setValue(v);
   8.416 +        }
   8.417 +    }
   8.418 +
   8.419 +    static final class MapTechnology
   8.420 +            implements Technology<Map<String, One>>, Transfer {
   8.421 +
   8.422 +        @Override
   8.423 +        public Map<String, One> wrapModel(Object model) {
   8.424 +            return new HashMap<String, One>();
   8.425 +        }
   8.426 +
   8.427 +        @Override
   8.428 +        public void valueHasMutated(Map<String, One> data, String propertyName) {
   8.429 +            One p = data.get(propertyName);
   8.430 +            if (p != null) {
   8.431 +                p.changes++;
   8.432 +            }
   8.433 +        }
   8.434 +
   8.435 +        @Override
   8.436 +        public void bind(PropertyBinding b, Object model, Map<String, One> data) {
   8.437 +            try {
   8.438 +                One o = new One(model, b);
   8.439 +                data.put(b.getPropertyName(), o);
   8.440 +            } catch (NoSuchMethodException ex) {
   8.441 +                throw new IllegalStateException(ex);
   8.442 +            }
   8.443 +        }
   8.444 +
   8.445 +        @Override
   8.446 +        public void expose(FunctionBinding fb, Object model, Map<String, One> data) {
   8.447 +            try {
   8.448 +                data.put(fb.getFunctionName(), new One(model, fb));
   8.449 +            } catch (NoSuchMethodException ex) {
   8.450 +                throw new IllegalStateException(ex);
   8.451 +            }
   8.452 +        }
   8.453 +
   8.454 +        @Override
   8.455 +        public void applyBindings(Map<String, One> data) {
   8.456 +        }
   8.457 +
   8.458 +        @Override
   8.459 +        public Object wrapArray(Object[] arr) {
   8.460 +            return arr;
   8.461 +        }
   8.462 +
   8.463 +        @Override
   8.464 +        public void extract(Object obj, String[] props, Object[] values) {
   8.465 +            Map<?, ?> map = obj instanceof Map ? (Map<?, ?>) obj : null;
   8.466 +            for (int i = 0; i < Math.min(props.length, values.length); i++) {
   8.467 +                if (map == null) {
   8.468 +                    values[i] = null;
   8.469 +                } else {
   8.470 +                    values[i] = map.get(props[i]);
   8.471 +                    if (values[i] instanceof One) {
   8.472 +                        values[i] = ((One) values[i]).pb.getValue();
   8.473 +                    }
   8.474 +                }
   8.475 +            }
   8.476 +        }
   8.477 +
   8.478 +        @Override
   8.479 +        public void loadJSON(JSONCall call) {
   8.480 +            call.notifyError(new UnsupportedOperationException());
   8.481 +        }
   8.482 +
   8.483 +        @Override
   8.484 +        public <M> M toModel(Class<M> modelClass, Object data) {
   8.485 +            return modelClass.cast(data);
   8.486 +        }
   8.487 +
   8.488 +        @Override
   8.489 +        public Object toJSON(InputStream is) throws IOException {
   8.490 +            throw new IOException();
   8.491 +        }
   8.492 +
   8.493 +        @Override
   8.494 +        public void runSafe(Runnable r) {
   8.495 +            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
   8.496 +        }
   8.497 +    }
   8.498 +    
   8.499 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/json/src/test/java/org/netbeans/html/json/impl/ToDoTest.java	Sat Aug 02 07:56:36 2014 +0200
     9.3 @@ -0,0 +1,131 @@
     9.4 +/**
     9.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     9.6 + *
     9.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     9.8 + *
     9.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    9.10 + * Other names may be trademarks of their respective owners.
    9.11 + *
    9.12 + * The contents of this file are subject to the terms of either the GNU
    9.13 + * General Public License Version 2 only ("GPL") or the Common
    9.14 + * Development and Distribution License("CDDL") (collectively, the
    9.15 + * "License"). You may not use this file except in compliance with the
    9.16 + * License. You can obtain a copy of the License at
    9.17 + * http://www.netbeans.org/cddl-gplv2.html
    9.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    9.19 + * specific language governing permissions and limitations under the
    9.20 + * License.  When distributing the software, include this License Header
    9.21 + * Notice in each file and include the License file at
    9.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    9.23 + * particular file as subject to the "Classpath" exception as provided
    9.24 + * by Oracle in the GPL Version 2 section of the License file that
    9.25 + * accompanied this code. If applicable, add the following below the
    9.26 + * License Header, with the fields enclosed by brackets [] replaced by
    9.27 + * your own identifying information:
    9.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    9.29 + *
    9.30 + * Contributor(s):
    9.31 + *
    9.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    9.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    9.34 + *
    9.35 + * If you wish your version of this file to be governed by only the CDDL
    9.36 + * or only the GPL Version 2, indicate your decision by adding
    9.37 + * "[Contributor] elects to include this software in this distribution
    9.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    9.39 + * single choice of license, a recipient has the option to distribute
    9.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    9.41 + * to extend the choice of license to its licensees as provided above.
    9.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    9.43 + * Version 2 license, then the option applies only if the new code is
    9.44 + * made subject to such option by the copyright holder.
    9.45 + */
    9.46 +package org.netbeans.html.json.impl;
    9.47 +
    9.48 +import java.util.List;
    9.49 +import java.util.Map;
    9.50 +import net.java.html.BrwsrCtx;
    9.51 +import net.java.html.json.ComputedProperty;
    9.52 +import net.java.html.json.Model;
    9.53 +import net.java.html.json.Models;
    9.54 +import net.java.html.json.Property;
    9.55 +import org.apidesign.html.context.spi.Contexts;
    9.56 +import org.apidesign.html.json.spi.Technology;
    9.57 +import org.apidesign.html.json.spi.Transfer;
    9.58 +import org.netbeans.html.json.impl.DeepChangeTest.MapTechnology;
    9.59 +import org.netbeans.html.json.impl.DeepChangeTest.One;
    9.60 +import static org.testng.Assert.*;
    9.61 +import org.testng.annotations.BeforeMethod;
    9.62 +import org.testng.annotations.Test;
    9.63 +
    9.64 +/**
    9.65 + *
    9.66 + * @author Jaroslav Tulach
    9.67 + */
    9.68 +@Model(className = "TodoUI", properties = {
    9.69 +    @Property(name = "todos", type = Todo.class, array = true),
    9.70 +    @Property(name = "todoText", type = String.class)
    9.71 +})
    9.72 +public class ToDoTest {
    9.73 +    @Model(className = "Todo", properties = {
    9.74 +        @Property(name = "text", type = String.class),
    9.75 +        @Property(name = "done", type = boolean.class)
    9.76 +    })
    9.77 +    static class ItemCtrl {
    9.78 +    }
    9.79 +
    9.80 +    @ComputedProperty
    9.81 +    static int remaining(
    9.82 +        List<Todo> todos, String todoText
    9.83 +    ) {
    9.84 +        int count = 0;
    9.85 +        for (Todo d : todos) {
    9.86 +            if (!d.isDone()) {
    9.87 +                count++;
    9.88 +            }
    9.89 +        }
    9.90 +        return count;
    9.91 +    }
    9.92 +    
    9.93 +    private MapTechnology t;
    9.94 +    private BrwsrCtx c;
    9.95 +
    9.96 +    @BeforeMethod
    9.97 +    public void initTechnology() {
    9.98 +        t = new MapTechnology();
    9.99 +        c = Contexts.newBuilder().register(Technology.class, t, 1).
   9.100 +                register(Transfer.class, t, 1).build();
   9.101 +    }
   9.102 +    
   9.103 +    
   9.104 +    @Test public void checkAndUncheckFirstItem() throws Exception {
   9.105 +        TodoUI ui = Models.bind(
   9.106 +                new TodoUI(
   9.107 +                    null,
   9.108 +                    new Todo("First", false),
   9.109 +                    new Todo("2nd", true),
   9.110 +                    new Todo("Third", false)
   9.111 +                ), c).applyBindings();
   9.112 +
   9.113 +        Map m = (Map) Models.toRaw(ui);
   9.114 +        Object v = m.get("remaining");
   9.115 +        assertNotNull(v, "Value should be in the map");
   9.116 +        assertEquals(v.getClass(), One.class, "It is instance of One");
   9.117 +        One o = (One) v;
   9.118 +        assertEquals(o.changes, 0, "No changes so far");
   9.119 +        assertTrue(o.pb.isReadOnly(), "Derived property");
   9.120 +        assertEquals(o.get(), 2);
   9.121 +
   9.122 +        ui.getTodos().get(0).setDone(true);
   9.123 +
   9.124 +        assertEquals(o.get(), 1);
   9.125 +        assertEquals(o.changes, 1, "One change so far");
   9.126 +
   9.127 +        ui.getTodos().get(0).setDone(false);
   9.128 +
   9.129 +        assertEquals(o.get(), 2);
   9.130 +        assertEquals(o.changes, 2, "2nd change so far");
   9.131 +        
   9.132 +    }
   9.133 +    
   9.134 +}
    10.1 --- a/src/main/javadoc/overview.html	Thu Jul 31 14:37:06 2014 +0200
    10.2 +++ b/src/main/javadoc/overview.html	Sat Aug 02 07:56:36 2014 +0200
    10.3 @@ -79,6 +79,9 @@
    10.4          
    10.5          <p>
    10.6              System can run in {@link net.java.html.boot.BrowserBuilder#classloader(java.lang.ClassLoader) Felix OSGi container} (originally only Equinox).
    10.7 +            {@link net.java.html.json.ComputedProperty Derived properties}
    10.8 +            now deeply check changes in other {@link net.java.html.json.Model model
    10.9 +            classes} they depend on and recompute their values accordingly.
   10.10          </p>
   10.11          
   10.12          <h3>What's New in 0.8.x Versions?</h3>