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<Address>} 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>