# HG changeset patch # User Jaroslav Tulach # Date 1455262812 -3600 # Node ID 05139f7b36297bd7f1d82afb48deb5f2e9416fc3 # Parent 6f1a8b251b7d1402a70422f10af556e058be77d6# Parent 8573ad9106a2cdc601ded43bdde2a9e997911da3 Bringing the beans branch up-to-date with release-1.2.3 diff -r 6f1a8b251b7d -r 05139f7b3629 json-beans/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-beans/pom.xml Fri Feb 12 08:40:12 2016 +0100 @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.netbeans.html + pom + 1.0-SNAPSHOT + + net.java.html.beans + JSONBeans + jar + + + ${project.groupId} + net.java.html.json + ${project.version} + + + org.testng + testng + test + + + \ No newline at end of file diff -r 6f1a8b251b7d -r 05139f7b3629 json-beans/src/main/java/net/java/html/beans/JSONBeans.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-beans/src/main/java/net/java/html/beans/JSONBeans.java Fri Feb 12 08:40:12 2016 +0100 @@ -0,0 +1,341 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ + +package net.java.html.beans; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.MethodDescriptor; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyDescriptor; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.java.html.BrwsrCtx; +import net.java.html.json.Function; +import net.java.html.json.Model; +import net.java.html.json.Models; +import net.java.html.json.Property; +import org.apidesign.html.json.spi.Proto; + +/** Plain old JavaBean objects and {@link Model JSON models} finally together. + * In can you own already written JavaBeans, you can use methods in this + * class to register them for use where ever the classes generated by the + * {@link Model} annotation can be. + * In general it is expected that one can save a lot of boilerplate code + * by using {@link Model}, but for purposes of compatibility it may be + * suitable to spend time to write JavaBeans and only treat them as + * models. + * + * @author Jaroslav Tulach + * @since 0.9 + */ +public final class JSONBeans { + private static final Logger LOG = Logger.getLogger(JSONBeans.class.getName()); + + final Html4JavaType type; + final Object model; + final Proto proto; + Object raw; + + JSONBeans(Object model, Html4JavaType type, Proto proto) { + this.model = model; + this.proto = proto; + this.type = type; + } + + final void initialize(Object object, Method addPCL) { + this.raw = Models.toRaw(model); + if (addPCL != null) try { + addPCL.invoke(object, new JBPCL()); + } catch (Exception ex) { + throw toRuntime(ex, RuntimeException.class); + } + } + + /** Registers the given class as a full featured {@link Model model}. + * It inspects the class via {@link Introspector} and uses its properties + * as {@link Property} definitions and its {@link BeanInfo#getMethodDescriptors() methods} + * as {@link Function} definitions. This method has to be called + * once and then all classes if this type can be treated as + * {@link Model instances of models}. + * + * @param javaBeanClass the JavaBean class + * @throws IntrospectionException if the JavaBean introspector fails + */ + public static void register(Class javaBeanClass) throws IntrospectionException { + BeanInfo bi = Introspector.getBeanInfo(javaBeanClass); + Method addPCL; + try { + addPCL = javaBeanClass.getMethod("addPropertyChangeListener", PropertyChangeListener.class); // NOI18N + } catch (NoSuchMethodException ex) { + addPCL = null; + } + Html4JavaType html4JavaType = new Html4JavaType(javaBeanClass, + bi.getPropertyDescriptors(), + bi.getMethodDescriptors(), + addPCL + ); + } + + @SuppressWarnings("unchecked") + static E toRuntime(Throwable ex, Class type) throws E { + throw (E)ex; + } + + static void clearProto() { + Html4JavaType.protos.get(null); + } + + private static final class Html4JavaType extends Proto.Type { + private static final Map> protos = new WeakHashMap>(); + private final PropertyDescriptor[] properties; + private final MethodDescriptor[] methods; + private final Method addPCL; + private final Class javaBeanClass; + + Html4JavaType( + Class javaBeanClass, + PropertyDescriptor[] pd, + MethodDescriptor[] md, + Method addPCL + ) { + super(javaBeanClass, javaBeanClass, pd.length, md.length); + this.javaBeanClass = javaBeanClass; + this.properties = pd; + this.methods = md; + for (int i = 0; i < pd.length; i++) { + registerProperty(pd[i].getName(), i, pd[i].getWriteMethod() == null); + } + for (int i = 0; i < md.length; i++) { + registerFunction(md[i].getName(), i); + } + this.addPCL = addPCL; + } + + @Override + protected void setValue(Object model, int index, Object value) { + try { + try { + final Method m = properties[index].getWriteMethod(); + value = extractValue(toBoxed(m.getParameterTypes()[0]), value); + m.invoke(model, value); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } catch (Throwable t) { + throw toRuntime(t, RuntimeException.class); + } + } + + @Override + protected Object getValue(Object model, int index) { + try { + Object ret = properties[index].getReadMethod().invoke(model); + if (ret instanceof Object[]) { + Object[] arr = new Object[((Object[])ret).length]; + for (int i = 0; i < arr.length; i++) { + Object ith = ((Object[])ret)[i]; + if (ith != null) { + arr[i] = Models.toRaw(ith); + } + } + return arr; + } + return ret; + } catch (Exception ex) { + throw toRuntime(ex, RuntimeException.class); + } + } + + @Override + protected void call(Object model, int index, Object data, Object event) throws Exception { + Method m = methods[index].getMethod(); + final Class[] types = m.getParameterTypes(); + Object[] args = new Object[types.length]; + if (args.length > 0) { + args[0] = extractValue(types[0], data); + } + if (args.length > 1) { + args[1] = extractValue(types[1], event); + } + m.invoke(model, args); + } + + @Override + protected Object cloneTo(Object model, BrwsrCtx ctx) { + try { + Object clone = model.getClass().newInstance(); + for (int i = 0; i < properties.length; i++) { + final Method write = properties[i].getWriteMethod(); + if (write == null) { + continue; + } + Object value = properties[i].getReadMethod().invoke(model); + write.invoke(clone, value); + } + findProto(clone, this, ctx); + return clone; + } catch (Exception ex) { + throw toRuntime(ex, RuntimeException.class); + } + } + + @Override + protected Object read(BrwsrCtx c, Object json) { + try { + Object bean = javaBeanClass.newInstance(); + Proto proto = findProto(bean, this, c); + + String[] names = new String[properties.length]; + for (int i = 0; i < names.length; i++) { + if (properties[i].getWriteMethod() != null) { + names[i] = properties[i].getName(); + } + } + Object[] values = new Object[properties.length]; + proto.extract(json, names, values); + for (int i = 0; i < names.length; i++) { + if (names[i] != null && values[i] != null) { + try { + properties[i].getWriteMethod().invoke(bean, values[i]); + } catch (IllegalArgumentException ex) { + LOG.log(Level.SEVERE, "Cannot set property " + names[i] + " of " + javaBeanClass.getName(), ex); + } catch (InvocationTargetException ex) { + LOG.log(Level.SEVERE, "Cannot set property " + names[i] + " of " + javaBeanClass.getName(), ex); + } + } + } + return bean; + } catch (InstantiationException ex) { + throw toRuntime(ex, RuntimeException.class); + } catch (IllegalAccessException ex) { + throw toRuntime(ex, RuntimeException.class); + } + } + + @Override + protected void onChange(Object model, int index) { + // no automatic dependencies between properties + } + + @Override + protected Proto protoFor(Object object) { + return findProto(object, this, null); + } + + private static Proto findProto(Object object, Html4JavaType type, BrwsrCtx ctx) { + Reference ref = protos.get(object); + JSONBeans jb = ref == null ? null : ref.get(); + if (jb != null) { + return jb.proto; + } + if (ctx == null) { + ctx = BrwsrCtx.findDefault(object.getClass()); + } + Proto p = type.createProto(object, ctx); + jb = new JSONBeans(object, type, p); + final Reference br = new WeakReference(jb); + protos.put(object, br); + jb.initialize(object, type.addPCL); + return p; + } + private static Class toBoxed(Class c) { + if (!c.isPrimitive()) { + return c; + } + if (c == Boolean.TYPE) { + return Boolean.class; + } + if (c == Byte.TYPE) { + return Byte.class; + } + if (c == Short.TYPE) { + return Short.class; + } + if (c == Integer.TYPE) { + return Integer.class; + } + if (c == Long.TYPE) { + return Long.class; + } + if (c == Float.TYPE) { + return Float.class; + } + if (c == Double.TYPE) { + return Double.class; + } + if (c == Character.TYPE) { + return Character.class; + } + return c; + } + } + + private final class JBPCL implements PropertyChangeListener { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String n = evt.getPropertyName(); + JSONBeans jb = JSONBeans.this; + if (jb == null) { + return; + } + Proto proto = jb.proto; + Html4JavaType type = jb.type; + for (int i = 0; i < type.properties.length; i++) { + PropertyDescriptor p = type.properties[i]; + if (n == null || n.equals(p.getName())) { + proto.valueHasMutated(n, evt.getOldValue(), evt.getNewValue()); + } + } + } + } +} diff -r 6f1a8b251b7d -r 05139f7b3629 json-beans/src/test/java/net/java/html/beans/MapModelGCTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-beans/src/test/java/net/java/html/beans/MapModelGCTest.java Fri Feb 12 08:40:12 2016 +0100 @@ -0,0 +1,418 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package net.java.html.json; + +import net.java.html.BrwsrCtx; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Map; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.FunctionBinding; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.PropertyBinding; +import org.netbeans.html.json.spi.Technology; +import org.netbeans.html.json.spi.Transfer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * + * @author Jaroslav Tulach + */ +public class MapModelTest { + private MapTechnology t; + private BrwsrCtx c; + + @BeforeMethod public void initTechnology() { + t = new MapTechnology(); + c = Contexts.newBuilder().register(Technology.class, t, 1). + register(Transfer.class, t, 1).build(); + } + + @Test public void isThereNoApplyBinding() throws Exception { + try { + Person.class.getMethod("applyBindings"); + } catch (NoSuchMethodException ex) { + // OK + return; + } + fail("There should be no applyBindings() method"); + } + + @Test public void isThereABinding() throws Exception { + Person p = Models.bind(new Person(), c); + Models.applyBindings(p); + assertNull(t.appliedId, "Applied globally"); + p.setFirstName("Jarda"); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("firstName"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertEquals(o.changes, 1, "One change so far"); + assertFalse(o.pb.isReadOnly(), "Mutable property"); + + assertEquals(o.get(), "Jarda", "Value should be in the map"); + + o.set("Karle"); + + assertEquals(p.getFirstName(), "Karle", "Model value updated"); + assertEquals(o.changes, 2, "Snd change"); + } + + @Test public void applyLocally() throws Exception { + Person p = Models.bind(new Person(), c); + Models.applyBindings(p, "local"); + assertEquals(t.appliedId, "local", "Applied locally"); + } + + @Test public void dontNotifySameProperty() throws Exception { + Person p = Models.bind(new Person(), c); + p.setFirstName("Jirka"); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("firstName"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertEquals(o.changes, 0, "No change so far the only one change happened before we connected"); + + p.setFirstName(new String("Jirka")); + assertEquals(o.changes, 0, "No change so far, the value is the same"); + + p.setFirstName("Jarda"); + assertFalse(o.pb.isReadOnly(), "Mutable property"); + + assertEquals(o.get(), "Jarda", "Value should be in the map"); + + o.set("Karle"); + + assertEquals(p.getFirstName(), "Karle", "Model value updated"); + assertEquals(o.changes, 2, "Snd change"); + } + + @Test public void canSetEnumAsString() throws Exception { + Person p = Models.bind(new Person(), c); + p.setFirstName("Jirka"); + p.setSex(Sex.MALE); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("sex"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + + o.set("FEMALE"); + + assertEquals(p.getSex(), Sex.FEMALE, "Changed to female"); + } + + @Test public void derivedProperty() throws Exception { + Person p = Models.bind(new Person(), c); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("fullName"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertTrue(o.pb.isReadOnly(), "Mutable property"); + } + + @Test public void changeSex() { + Person p = Models.bind(new Person(), c); + p.setFirstName("Trans"); + p.setSex(Sex.MALE); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("changeSex"); + assertNotNull(o, "Function registered in the model"); + assertEquals(o.getClass(), One.class); + + One one = (One)o; + assertNotNull(one.fb, "Function binding specified"); + + one.fb.call(null, null); + + assertEquals(p.getSex(), Sex.FEMALE, "Changed"); + } + + @Test public void setSex() { + Person p = Models.bind(new Person(), c); + p.setFirstName("Trans"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("changeSex"); + assertNotNull(o, "Function registered in the model"); + assertEquals(o.getClass(), One.class); + + One one = (One)o; + assertNotNull(one.fb, "Function binding specified"); + + one.fb.call("FEMALE", new Object()); + + assertEquals(p.getSex(), Sex.FEMALE, "Changed"); + } + + @Test public void changeComputedProperty() { + Modelik p = Models.bind(new Modelik(), c); + p.setValue(5); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("powerValue"); + assertNotNull(o, "Value is there"); + assertEquals(o.getClass(), One.class); + + One one = (One)o; + assertNotNull(one.pb, "Prop binding specified"); + + assertEquals(one.pb.getValue(), 25, "Power of 5"); + + one.pb.setValue(16); + assertEquals(p.getValue(), 4, "Square root of 16"); + } + + @Test public void removeViaIterator() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("nicknames"); + assertNotNull(o, "List registered in the model"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + + assertEquals(one.changes, 0, "No change"); + + Iterator it = p.getNicknames().iterator(); + assertEquals(it.next(), "One"); + assertEquals(it.next(), "Two"); + it.remove(); + assertEquals(it.next(), "Three"); + assertFalse(it.hasNext()); + + + assertEquals(one.changes, 1, "One change"); + } + + @Test public void removeViaListIterator() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("nicknames"); + assertNotNull(o, "List registered in the model"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + + assertEquals(one.changes, 0, "No change"); + + ListIterator it = p.getNicknames().listIterator(1); + assertEquals(it.next(), "Two"); + it.remove(); + assertEquals(it.next(), "Three"); + assertFalse(it.hasNext()); + + + assertEquals(one.changes, 1, "One change"); + + it.set("3"); + assertEquals(p.getNicknames().get(1), "3"); + + assertEquals(one.changes, 2, "Snd change"); + } + + @Test public void functionWithParameters() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("inInnerClass"); + assertNotNull(o, "functiton is available"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + Map obj = new HashMap(); + obj.put("nick", "newNick"); + obj.put("x", 42); + obj.put("y", 7.7f); + final Person data = new Person("a", "b", Sex.MALE); + + one.fb.call(data, obj); + + assertEquals(p.getInfo().size(), 1, "a+b is there: " + p.getInfo()); + assertEquals(p.getInfo().get(0), data, "Expecting data: " + p.getInfo()); + + assertEquals(p.getNicknames().size(), 4, "One more nickname: " + p.getNicknames()); + assertEquals(p.getNicknames().get(3), "newNick"); + + assertEquals(p.getAge().size(), 2, "Two new values: " + p.getAge()); + assertEquals(p.getAge().get(0).intValue(), 42); + assertEquals(p.getAge().get(1).intValue(), 7); + } + + static final class One { + int changes; + final PropertyBinding pb; + final FunctionBinding fb; + + One(Object m, PropertyBinding pb) throws NoSuchMethodException { + this.pb = pb; + this.fb = null; + } + One(Object m, FunctionBinding fb) throws NoSuchMethodException { + this.pb = null; + this.fb = fb; + } + + Object get() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + return pb.getValue(); + } + + void set(Object v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + pb.setValue(v); + } + } + + static final class MapTechnology + implements Technology.ApplyId>, Transfer { + private Map appliedData; + private String appliedId; + + @Override + public Map wrapModel(Object model) { + return new HashMap(); + } + + @Override + public void valueHasMutated(Map data, String propertyName) { + One p = data.get(propertyName); + if (p != null) { + p.changes++; + } + } + + @Override + public void bind(PropertyBinding b, Object model, Map data) { + try { + One o = new One(model, b); + data.put(b.getPropertyName(), o); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void expose(FunctionBinding fb, Object model, Map data) { + try { + data.put(fb.getFunctionName(), new One(model, fb)); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void applyBindings(Map data) { + throw new UnsupportedOperationException("Never called!"); + } + + @Override + public Object wrapArray(Object[] arr) { + return arr; + } + + @Override + public void extract(Object obj, String[] props, Object[] values) { + Map map = obj instanceof Map ? (Map)obj : null; + for (int i = 0; i < Math.min(props.length, values.length); i++) { + if (map == null) { + values[i] = null; + } else { + values[i] = map.get(props[i]); + if (values[i] instanceof One) { + values[i] = ((One)values[i]).pb.getValue(); + } + } + } + } + + @Override + public void loadJSON(JSONCall call) { + call.notifyError(new UnsupportedOperationException()); + } + + @Override + public M toModel(Class modelClass, Object data) { + return modelClass.cast(data); + } + + @Override + public Object toJSON(InputStream is) throws IOException { + throw new IOException(); + } + + @Override + public void runSafe(Runnable r) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void applyBindings(String id, Map data) { + this.appliedId = id; + this.appliedData = data; + } + } +} diff -r 6f1a8b251b7d -r 05139f7b3629 json-beans/src/test/java/net/java/html/beans/MapModelTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-beans/src/test/java/net/java/html/beans/MapModelTest.java Fri Feb 12 08:40:12 2016 +0100 @@ -0,0 +1,418 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package net.java.html.json; + +import net.java.html.BrwsrCtx; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Map; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.FunctionBinding; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.PropertyBinding; +import org.netbeans.html.json.spi.Technology; +import org.netbeans.html.json.spi.Transfer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * + * @author Jaroslav Tulach + */ +public class MapModelTest { + private MapTechnology t; + private BrwsrCtx c; + + @BeforeMethod public void initTechnology() { + t = new MapTechnology(); + c = Contexts.newBuilder().register(Technology.class, t, 1). + register(Transfer.class, t, 1).build(); + } + + @Test public void isThereNoApplyBinding() throws Exception { + try { + Person.class.getMethod("applyBindings"); + } catch (NoSuchMethodException ex) { + // OK + return; + } + fail("There should be no applyBindings() method"); + } + + @Test public void isThereABinding() throws Exception { + Person p = Models.bind(new Person(), c); + Models.applyBindings(p); + assertNull(t.appliedId, "Applied globally"); + p.setFirstName("Jarda"); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("firstName"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertEquals(o.changes, 1, "One change so far"); + assertFalse(o.pb.isReadOnly(), "Mutable property"); + + assertEquals(o.get(), "Jarda", "Value should be in the map"); + + o.set("Karle"); + + assertEquals(p.getFirstName(), "Karle", "Model value updated"); + assertEquals(o.changes, 2, "Snd change"); + } + + @Test public void applyLocally() throws Exception { + Person p = Models.bind(new Person(), c); + Models.applyBindings(p, "local"); + assertEquals(t.appliedId, "local", "Applied locally"); + } + + @Test public void dontNotifySameProperty() throws Exception { + Person p = Models.bind(new Person(), c); + p.setFirstName("Jirka"); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("firstName"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertEquals(o.changes, 0, "No change so far the only one change happened before we connected"); + + p.setFirstName(new String("Jirka")); + assertEquals(o.changes, 0, "No change so far, the value is the same"); + + p.setFirstName("Jarda"); + assertFalse(o.pb.isReadOnly(), "Mutable property"); + + assertEquals(o.get(), "Jarda", "Value should be in the map"); + + o.set("Karle"); + + assertEquals(p.getFirstName(), "Karle", "Model value updated"); + assertEquals(o.changes, 2, "Snd change"); + } + + @Test public void canSetEnumAsString() throws Exception { + Person p = Models.bind(new Person(), c); + p.setFirstName("Jirka"); + p.setSex(Sex.MALE); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("sex"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + + o.set("FEMALE"); + + assertEquals(p.getSex(), Sex.FEMALE, "Changed to female"); + } + + @Test public void derivedProperty() throws Exception { + Person p = Models.bind(new Person(), c); + + Map m = (Map)Models.toRaw(p); + Object v = m.get("fullName"); + assertNotNull(v, "Value should be in the map"); + assertEquals(v.getClass(), One.class, "It is instance of One"); + One o = (One)v; + assertTrue(o.pb.isReadOnly(), "Mutable property"); + } + + @Test public void changeSex() { + Person p = Models.bind(new Person(), c); + p.setFirstName("Trans"); + p.setSex(Sex.MALE); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("changeSex"); + assertNotNull(o, "Function registered in the model"); + assertEquals(o.getClass(), One.class); + + One one = (One)o; + assertNotNull(one.fb, "Function binding specified"); + + one.fb.call(null, null); + + assertEquals(p.getSex(), Sex.FEMALE, "Changed"); + } + + @Test public void setSex() { + Person p = Models.bind(new Person(), c); + p.setFirstName("Trans"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("changeSex"); + assertNotNull(o, "Function registered in the model"); + assertEquals(o.getClass(), One.class); + + One one = (One)o; + assertNotNull(one.fb, "Function binding specified"); + + one.fb.call("FEMALE", new Object()); + + assertEquals(p.getSex(), Sex.FEMALE, "Changed"); + } + + @Test public void changeComputedProperty() { + Modelik p = Models.bind(new Modelik(), c); + p.setValue(5); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("powerValue"); + assertNotNull(o, "Value is there"); + assertEquals(o.getClass(), One.class); + + One one = (One)o; + assertNotNull(one.pb, "Prop binding specified"); + + assertEquals(one.pb.getValue(), 25, "Power of 5"); + + one.pb.setValue(16); + assertEquals(p.getValue(), 4, "Square root of 16"); + } + + @Test public void removeViaIterator() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("nicknames"); + assertNotNull(o, "List registered in the model"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + + assertEquals(one.changes, 0, "No change"); + + Iterator it = p.getNicknames().iterator(); + assertEquals(it.next(), "One"); + assertEquals(it.next(), "Two"); + it.remove(); + assertEquals(it.next(), "Three"); + assertFalse(it.hasNext()); + + + assertEquals(one.changes, 1, "One change"); + } + + @Test public void removeViaListIterator() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("nicknames"); + assertNotNull(o, "List registered in the model"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + + assertEquals(one.changes, 0, "No change"); + + ListIterator it = p.getNicknames().listIterator(1); + assertEquals(it.next(), "Two"); + it.remove(); + assertEquals(it.next(), "Three"); + assertFalse(it.hasNext()); + + + assertEquals(one.changes, 1, "One change"); + + it.set("3"); + assertEquals(p.getNicknames().get(1), "3"); + + assertEquals(one.changes, 2, "Snd change"); + } + + @Test public void functionWithParameters() { + People p = Models.bind(new People(), c); + p.getNicknames().add("One"); + p.getNicknames().add("Two"); + p.getNicknames().add("Three"); + + Map m = (Map)Models.toRaw(p); + Object o = m.get("inInnerClass"); + assertNotNull(o, "functiton is available"); + assertEquals(o.getClass(), One.class); + One one = (One)o; + + Map obj = new HashMap(); + obj.put("nick", "newNick"); + obj.put("x", 42); + obj.put("y", 7.7f); + final Person data = new Person("a", "b", Sex.MALE); + + one.fb.call(data, obj); + + assertEquals(p.getInfo().size(), 1, "a+b is there: " + p.getInfo()); + assertEquals(p.getInfo().get(0), data, "Expecting data: " + p.getInfo()); + + assertEquals(p.getNicknames().size(), 4, "One more nickname: " + p.getNicknames()); + assertEquals(p.getNicknames().get(3), "newNick"); + + assertEquals(p.getAge().size(), 2, "Two new values: " + p.getAge()); + assertEquals(p.getAge().get(0).intValue(), 42); + assertEquals(p.getAge().get(1).intValue(), 7); + } + + static final class One { + int changes; + final PropertyBinding pb; + final FunctionBinding fb; + + One(Object m, PropertyBinding pb) throws NoSuchMethodException { + this.pb = pb; + this.fb = null; + } + One(Object m, FunctionBinding fb) throws NoSuchMethodException { + this.pb = null; + this.fb = fb; + } + + Object get() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + return pb.getValue(); + } + + void set(Object v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + pb.setValue(v); + } + } + + static final class MapTechnology + implements Technology.ApplyId>, Transfer { + private Map appliedData; + private String appliedId; + + @Override + public Map wrapModel(Object model) { + return new HashMap(); + } + + @Override + public void valueHasMutated(Map data, String propertyName) { + One p = data.get(propertyName); + if (p != null) { + p.changes++; + } + } + + @Override + public void bind(PropertyBinding b, Object model, Map data) { + try { + One o = new One(model, b); + data.put(b.getPropertyName(), o); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void expose(FunctionBinding fb, Object model, Map data) { + try { + data.put(fb.getFunctionName(), new One(model, fb)); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void applyBindings(Map data) { + throw new UnsupportedOperationException("Never called!"); + } + + @Override + public Object wrapArray(Object[] arr) { + return arr; + } + + @Override + public void extract(Object obj, String[] props, Object[] values) { + Map map = obj instanceof Map ? (Map)obj : null; + for (int i = 0; i < Math.min(props.length, values.length); i++) { + if (map == null) { + values[i] = null; + } else { + values[i] = map.get(props[i]); + if (values[i] instanceof One) { + values[i] = ((One)values[i]).pb.getValue(); + } + } + } + } + + @Override + public void loadJSON(JSONCall call) { + call.notifyError(new UnsupportedOperationException()); + } + + @Override + public M toModel(Class modelClass, Object data) { + return modelClass.cast(data); + } + + @Override + public Object toJSON(InputStream is) throws IOException { + throw new IOException(); + } + + @Override + public void runSafe(Runnable r) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void applyBindings(String id, Map data) { + this.appliedId = id; + this.appliedData = data; + } + } +} diff -r 6f1a8b251b7d -r 05139f7b3629 json-beans/src/test/java/net/java/html/beans/People.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-beans/src/test/java/net/java/html/beans/People.java Fri Feb 12 08:40:12 2016 +0100 @@ -0,0 +1,78 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package net.java.html.beans; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +/** + * + * @author Jaroslav Tulach + */ +public class People { + + private Person[] people; + + public static final String PROP_PEOPLE = "people"; + + public Person[] getPeople() { + return people; + } + + public void setPeople(Person... people) { + Person[] oldPeople = this.people; + this.people = people; + propertyChangeSupport.firePropertyChange(PROP_PEOPLE, oldPeople, people); + } + + private transient final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + +} diff -r 6f1a8b251b7d -r 05139f7b3629 json-beans/src/test/java/net/java/html/beans/Person.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-beans/src/test/java/net/java/html/beans/Person.java Fri Feb 12 08:40:12 2016 +0100 @@ -0,0 +1,142 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ + +package net.java.html.beans; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +/** + * + * @author Jaroslav Tulach + */ +public final class Person { + private String firstName; + private String lastName; + private Sex sex; + private boolean good; + + public static final String PROP_FIRSTNAME = "firstName"; + public static final String PROP_LASTNAME = "lastName"; + public static final String PROP_FULLNAME = "fullName"; + public static final String PROP_SEX = "sex"; + public static final String PROP_GOOD = "good"; + + public Person() { + } + + public Person(String firstName, String lastName, Sex sex) { + this.firstName = firstName; + this.lastName = lastName; + this.sex = sex; + } + + public Sex getSex() { + return sex; + } + + public void setSex(Sex sex) { + Sex oldSex = this.sex; + this.sex = sex; + propertyChangeSupport.firePropertyChange(PROP_SEX, oldSex, sex); + } + + public void changeSex(Sex to) { + if (to != null) { + setSex(to); + return; + } + if (getSex() == Sex.MALE) { + setSex(Sex.FEMALE); + } else { + setSex(Sex.MALE); + } + } + + public boolean isGood() { + return good; + } + + public void setGood(boolean good) { + boolean oldGood = this.good; + this.good = good; + propertyChangeSupport.firePropertyChange(PROP_GOOD, oldGood, good); + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + String oldFirstName = this.firstName; + this.firstName = firstName; + propertyChangeSupport.firePropertyChange(PROP_FIRSTNAME, oldFirstName, firstName); + propertyChangeSupport.firePropertyChange(PROP_FULLNAME, null, null); + } + + public String getLastName() { + return lastName; + } + + public String getFullName() { + return firstName + " " + lastName; + } + + public void setLastName(String lastName) { + String oldLastName = this.lastName; + this.lastName = lastName; + propertyChangeSupport.firePropertyChange(PROP_LASTNAME, oldLastName, lastName); + propertyChangeSupport.firePropertyChange(PROP_FULLNAME, null, null); + } + + private transient final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + +} diff -r 6f1a8b251b7d -r 05139f7b3629 json-beans/src/test/java/net/java/html/beans/Sex.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-beans/src/test/java/net/java/html/beans/Sex.java Fri Feb 12 08:40:12 2016 +0100 @@ -0,0 +1,51 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package net.java.html.beans; + +/** + * + * @author Jaroslav Tulach + */ +public enum Sex { + MALE, FEMALE; +} diff -r 6f1a8b251b7d -r 05139f7b3629 json/src/main/java/org/netbeans/html/json/spi/Proto.java --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java Mon Sep 21 21:19:13 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java Fri Feb 12 08:40:12 2016 +0100 @@ -563,7 +563,7 @@ ) { assert getClass().getName().endsWith("$Html4JavaType"); try { - assert getClass().getDeclaringClass() == clazz; + assert clazz == modelFor || getClass().getDeclaringClass() == clazz; } catch (SecurityException ex) { // OK, no check }