Deep checking of @ComputedProperties is now default if one depends on another model class
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
32 * If you wish your version of this file to be governed by only the CDDL
33 * or only the GPL Version 2, indicate your decision by adding
34 * "[Contributor] elects to include this software in this distribution
35 * under the [CDDL or GPL Version 2] license." If you do not indicate a
36 * single choice of license, a recipient has the option to distribute
37 * your version of this file under either the CDDL, the GPL Version 2 or
38 * to extend the choice of license to its licensees as provided above.
39 * However, if you add GPL Version 2 code and therefore, elected the GPL
40 * Version 2 license, then the option applies only if the new code is
41 * made subject to such option by the copyright holder.
43 package org.netbeans.html.json.impl;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.lang.reflect.InvocationTargetException;
48 import java.util.HashMap;
49 import java.util.List;
51 import net.java.html.BrwsrCtx;
52 import net.java.html.json.ComputedProperty;
53 import net.java.html.json.Model;
54 import net.java.html.json.Models;
55 import net.java.html.json.Property;
56 import org.apidesign.html.context.spi.Contexts;
57 import org.apidesign.html.json.spi.FunctionBinding;
58 import org.apidesign.html.json.spi.JSONCall;
59 import org.apidesign.html.json.spi.PropertyBinding;
60 import org.apidesign.html.json.spi.Technology;
61 import org.apidesign.html.json.spi.Transfer;
62 import static org.testng.Assert.*;
63 import org.testng.annotations.BeforeMethod;
64 import org.testng.annotations.Test;
68 * @author Jaroslav Tulach <jtulach@netbeans.org>
70 public class DeepChangeTest {
71 private MapTechnology t;
74 @BeforeMethod public void initTechnology() {
75 t = new MapTechnology();
76 c = Contexts.newBuilder().register(Technology.class, t, 1).
77 register(Transfer.class, t, 1).build();
80 @Model(className = "MyX", properties = {
81 @Property(name = "one", type = MyY.class),
82 @Property(name = "all", type = MyY.class, array = true)
85 @ComputedProperty @Transitive(deep = true)
86 static String oneName(MyY one) {
87 return one.getValue();
90 static String sndName(MyY one) {
91 return one.getValue().toUpperCase();
93 @ComputedProperty @Transitive(deep = false)
94 static String noName(MyY one) {
95 return one.getValue().toUpperCase();
97 @ComputedProperty @Transitive(deep = true)
98 static String thrdName(MyY one) {
99 return "X" + one.getCount();
103 static String allNames(List<MyY> all) {
104 StringBuilder sb = new StringBuilder();
106 sb.append(y.getValue());
108 return sb.toString();
111 @ComputedProperty @Transitive(deep = true)
112 static String firstFromNames(List<MyY> all) {
114 if (y != null && y.getValue() != null) {
121 @Model(className = "MyY", properties = {
122 @Property(name = "value", type = String.class),
123 @Property(name = "count", type = int.class)
127 @Model(className = "MyOverall", properties = {
128 @Property(name = "x", type = MyX.class)
130 static class Overall {
131 @ComputedProperty @Transitive(deep = true)
132 static String valueAccross(MyX x) {
133 return x.getFirstFromNames();
137 @Test public void isTransitiveChangeNotifiedProperly() throws Exception {
139 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
140 ), c).applyBindings();
142 Map m = (Map)Models.toRaw(p);
143 Object v = m.get("oneName");
144 assertNotNull(v, "Value should be in the map");
145 assertEquals(v.getClass(), One.class, "It is instance of One");
147 assertEquals(o.changes, 0, "No changes so far");
148 assertTrue(o.pb.isReadOnly(), "Derived property");
149 assertEquals(o.get(), "Ahoj");
151 p.getOne().setValue("Nazdar");
153 assertEquals(o.get(), "Nazdar");
154 assertEquals(o.changes, 1, "One change so far");
157 @Test public void isTransitiveChangeInArrayNotifiedProperly() throws Exception {
159 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
160 ), c).applyBindings();
162 Map m = (Map)Models.toRaw(p);
163 Object v = m.get("allNames");
164 assertNotNull(v, "Value should be in the map");
165 assertEquals(v.getClass(), One.class, "It is instance of One");
167 assertEquals(o.changes, 0, "No changes so far");
168 assertTrue(o.pb.isReadOnly(), "Derived property");
169 assertEquals(o.get(), "HiHello");
171 p.getAll().get(0).setValue("Nazdar");
173 assertEquals(o.get(), "NazdarHello");
174 assertEquals(o.changes, 1, "One change so far");
177 @Test public void addingIntoArray() throws Exception {
179 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
180 ), c).applyBindings();
182 Map m = (Map)Models.toRaw(p);
183 Object v = m.get("allNames");
184 assertNotNull(v, "Value should be in the map");
185 assertEquals(v.getClass(), One.class, "It is instance of One");
187 assertEquals(o.changes, 0, "No changes so far");
188 assertTrue(o.pb.isReadOnly(), "Derived property");
189 assertEquals(o.get(), "HiHello");
191 MyY y = new MyY("Cus", 1);
194 assertEquals(o.changes, 1, "One change so far");
195 assertEquals(o.get(), "HiHelloCus");
197 y.setValue("Nazdar");
199 assertEquals(o.changes, 2, "2nd change so far");
200 assertEquals(o.get(), "HiHelloNazdar");
202 y.setValue("Zdravim");
204 assertEquals(o.changes, 3, "3rd change so far");
205 assertEquals(o.get(), "HiHelloZdravim");
208 @Test public void firstChangeInArrayNotifiedProperly() throws Exception {
210 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
211 ), c).applyBindings();
213 Map m = (Map)Models.toRaw(p);
214 Object v = m.get("firstFromNames");
215 assertNotNull(v, "Value should be in the map");
216 assertEquals(v.getClass(), One.class, "It is instance of One");
218 assertEquals(o.changes, 0, "No changes so far");
219 assertTrue(o.pb.isReadOnly(), "Derived property");
220 assertEquals(o.get(), "Hi");
222 p.getAll().get(0).setValue("Nazdar");
224 assertEquals(o.get(), "Nazdar");
225 assertEquals(o.changes, 1, "One change so far");
228 @Test public void firstChangeInArrayToNull() throws Exception {
230 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
231 ), c).applyBindings();
233 Map m = (Map)Models.toRaw(p);
234 Object v = m.get("firstFromNames");
235 assertNotNull(v, "Value should be in the map");
236 assertEquals(v.getClass(), One.class, "It is instance of One");
238 assertEquals(o.changes, 0, "No changes so far");
239 assertTrue(o.pb.isReadOnly(), "Derived property");
240 assertEquals(o.get(), "Hi");
242 p.getAll().get(0).setValue(null);
244 assertEquals(o.get(), "Hello");
245 assertEquals(o.changes, 1, "One change so far");
247 p.getAll().get(0).setValue("Nazdar");
249 assertEquals(o.get(), "Nazdar");
250 assertEquals(o.changes, 2, "2nd change so far");
253 @Test public void firstChangeInArrayNotifiedTransitively() throws Exception {
254 MyOverall p = Models.bind(
255 new MyOverall(new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999))
256 ), c).applyBindings();
258 Map m = (Map)Models.toRaw(p);
259 Object v = m.get("valueAccross");
260 assertNotNull(v, "Value should be in the map");
261 assertEquals(v.getClass(), One.class, "It is instance of One");
263 assertEquals(o.changes, 0, "No changes so far");
264 assertTrue(o.pb.isReadOnly(), "Derived property");
265 assertEquals(o.get(), "Hi");
267 p.getX().getAll().get(0).setValue("Nazdar");
269 assertEquals(o.get(), "Nazdar");
270 assertEquals(o.changes, 1, "One change so far");
273 @Test public void secondChangeInArrayIgnored() throws Exception {
275 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
276 ), c).applyBindings();
278 Map m = (Map)Models.toRaw(p);
279 Object v = m.get("firstFromNames");
280 assertNotNull(v, "Value should be in the map");
281 assertEquals(v.getClass(), One.class, "It is instance of One");
283 assertEquals(o.changes, 0, "No changes so far");
284 assertTrue(o.pb.isReadOnly(), "Derived property");
285 assertEquals(o.get(), "Hi");
287 p.getAll().get(1).setValue("Nazdar");
289 assertEquals(o.get(), "Hi");
290 assertEquals(o.changes, 0, "No change so far");
293 @Test public void changeInArraySizeNeedsToBeRecomputed() throws Exception {
295 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
296 ), c).applyBindings();
298 Map m = (Map)Models.toRaw(p);
299 Object v = m.get("firstFromNames");
300 assertNotNull(v, "Value should be in the map");
301 assertEquals(v.getClass(), One.class, "It is instance of One");
303 assertEquals(o.changes, 0, "No changes so far");
304 assertTrue(o.pb.isReadOnly(), "Derived property");
305 assertEquals(o.get(), "Hi");
307 p.getAll().remove(1);
309 assertEquals(o.get(), "Hi");
310 assertEquals(o.changes, 1, "This required a change");
313 @Test public void doublePropertyChangeNotified() throws Exception {
315 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
316 ), c).applyBindings();
318 Map m = (Map)Models.toRaw(p);
319 Object v = m.get("oneName");
320 assertNotNull(v, "Value should be in the map");
321 Object v2 = m.get("sndName");
322 assertNotNull(v2, "Value2 should be in the map");
325 assertEquals(o.changes, 0, "No changes so far");
326 assertEquals(o2.changes, 0, "No changes so far");
327 assertTrue(o.pb.isReadOnly(), "Derived property");
328 assertEquals(o.get(), "Ahoj");
329 assertEquals(o2.get(), "AHOJ");
331 p.getOne().setValue("Nazdar");
333 assertEquals(o.get(), "Nazdar");
334 assertEquals(o.changes, 1, "One change so far");
335 assertEquals(o2.changes, 1, "One change so far");
336 assertEquals(o2.get(), "NAZDAR");
339 @Test public void onlyAffectedPropertyChangeNotified() throws Exception {
341 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
342 ), c).applyBindings();
344 Map m = (Map)Models.toRaw(p);
345 Object v = m.get("oneName");
346 assertNotNull(v, "Value should be in the map");
347 Object v2 = m.get("thrdName");
348 assertNotNull(v2, "Value2 should be in the map");
351 assertEquals(o.changes, 0, "No changes so far");
352 assertEquals(o2.changes, 0, "No changes so far");
353 assertTrue(o.pb.isReadOnly(), "Derived property");
354 assertEquals(o.get(), "Ahoj");
355 assertEquals(o2.get(), "X0");
357 p.getOne().setCount(10);
359 assertEquals(o.get(), "Ahoj");
360 assertEquals(o.changes, 0, "Still no change");
361 assertEquals(o2.changes, 1, "One change so far");
362 assertEquals(o2.get(), "X10");
365 @Test public void onlyDeepPropsAreNotified() throws Exception {
367 new MyX(new MyY("Ahoj", 0), new MyY("Hi", 333), new MyY("Hello", 999)
368 ), c).applyBindings();
370 Map m = (Map)Models.toRaw(p);
371 Object v = m.get("oneName");
372 assertNotNull(v, "Value should be in the map");
373 Object v2 = m.get("noName");
374 assertNotNull(v2, "Value2 should be in the map");
377 assertEquals(o.changes, 0, "No changes so far");
378 assertEquals(o2.changes, 0, "No changes so far");
379 assertTrue(o.pb.isReadOnly(), "Derived property");
380 assertEquals(o.get(), "Ahoj");
381 assertEquals(o2.get(), "AHOJ");
383 p.getOne().setValue("Nazdar");
385 assertEquals(o.get(), "Nazdar");
386 assertEquals(o.changes, 1, "One change so far");
387 assertEquals(o2.changes, 0, "This change is not noticed");
388 assertEquals(o2.get(), "NAZDAR", "but property value changes when computed");
391 static final class One {
394 final PropertyBinding pb;
395 final FunctionBinding fb;
397 One(Object m, PropertyBinding pb) throws NoSuchMethodException {
402 One(Object m, FunctionBinding fb) throws NoSuchMethodException {
407 Object get() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
408 return pb.getValue();
411 void set(Object v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
416 static final class MapTechnology
417 implements Technology<Map<String, One>>, Transfer {
420 public Map<String, One> wrapModel(Object model) {
421 return new HashMap<String, One>();
425 public void valueHasMutated(Map<String, One> data, String propertyName) {
426 One p = data.get(propertyName);
433 public void bind(PropertyBinding b, Object model, Map<String, One> data) {
435 One o = new One(model, b);
436 data.put(b.getPropertyName(), o);
437 } catch (NoSuchMethodException ex) {
438 throw new IllegalStateException(ex);
443 public void expose(FunctionBinding fb, Object model, Map<String, One> data) {
445 data.put(fb.getFunctionName(), new One(model, fb));
446 } catch (NoSuchMethodException ex) {
447 throw new IllegalStateException(ex);
452 public void applyBindings(Map<String, One> data) {
456 public Object wrapArray(Object[] arr) {
461 public void extract(Object obj, String[] props, Object[] values) {
462 Map<?, ?> map = obj instanceof Map ? (Map<?, ?>) obj : null;
463 for (int i = 0; i < Math.min(props.length, values.length); i++) {
467 values[i] = map.get(props[i]);
468 if (values[i] instanceof One) {
469 values[i] = ((One) values[i]).pb.getValue();
476 public void loadJSON(JSONCall call) {
477 call.notifyError(new UnsupportedOperationException());
481 public <M> M toModel(Class<M> modelClass, Object data) {
482 return modelClass.cast(data);
486 public Object toJSON(InputStream is) throws IOException {
487 throw new IOException();
491 public void runSafe(Runnable r) {
492 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.