You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jt...@apache.org on 2017/09/03 12:48:40 UTC

[07/24] incubator-netbeans-html4j git commit: [INFRA-15006] Initial donation of HTML/Java NetBeans API. Equivalent to the content of html4j-donation-review.zip donated as part of ApacheNetBeansDonation1.zip with SHA256 being 7f2ca0f61953a190613c9a0fbcc1b

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/test/java/net/java/html/json/MapModelTest.java
----------------------------------------------------------------------
diff --git a/json/src/test/java/net/java/html/json/MapModelTest.java b/json/src/test/java/net/java/html/json/MapModelTest.java
new file mode 100644
index 0000000..3a9143d
--- /dev/null
+++ b/json/src/test/java/net/java/html/json/MapModelTest.java
@@ -0,0 +1,521 @@
+/**
+ * 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-2016 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.Collections;
+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<String> 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<String> 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 subListChange() {
+        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");
+
+        p.getNicknames().subList(1, 2).clear();
+
+        assertEquals(p.getNicknames().size(), 2, "Two elements");
+
+        ListIterator<String> it = p.getNicknames().listIterator(0);
+        assertEquals(it.next(), "One");
+        assertEquals(it.next(), "Three");
+        assertFalse(it.hasNext());
+
+
+        assertEquals(one.changes, 1, "One change");
+    }
+
+    @Test public void sort() {
+        People p = Models.bind(new People(), c);
+        p.getNicknames().add("One");
+        p.getNicknames().add("Two");
+        p.getNicknames().add("Three");
+        p.getNicknames().add("Four");
+
+        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");
+
+        Collections.sort(p.getNicknames());
+
+        Iterator<String> it = p.getNicknames().iterator();
+        assertEquals(it.next(), "Four");
+        assertEquals(it.next(), "One");
+        assertEquals(it.next(), "Three");
+        assertEquals(it.next(), "Two");
+        assertFalse(it.hasNext());
+
+
+        assertNotEquals(one.changes, 0, "At least one change");
+
+        if (isJDK8()) {
+            assertEquals(one.changes, 1, "Exactly one echange");
+        }
+    }
+
+    @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<String,Object> obj = new HashMap<String, Object>();
+        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);
+    }
+    
+    @Test
+    public void addAge42ThreeTimes() {
+        People p = Models.bind(new People(), c);
+        Map m = (Map)Models.toRaw(p);
+        assertNotNull(m);
+        
+        class Inc implements Runnable {
+            int cnt;
+            
+            @Override
+            public void run() {
+                cnt++;
+            }
+        }
+        Inc incThreeTimes = new Inc();
+        p.onInfoChange(incThreeTimes);
+        
+        p.addAge42();
+        p.addAge42();
+        p.addAge42();
+        final int[] cnt = { 0, 0 };
+        p.readAddAgeCount(cnt, new Runnable() {
+            @Override
+            public void run() {
+                cnt[1] = 1;
+            }
+        });
+        assertEquals(cnt[1], 1, "Callback called");
+        assertEquals(cnt[0], 3, "Internal state kept");
+        assertEquals(incThreeTimes.cnt, 3, "Property change delivered three times");
+    }
+
+    private static boolean isJDK8() {
+        try {
+            Class.forName("java.lang.FunctionalInterface");
+            return true;
+        } catch (ClassNotFoundException ex) {
+            return false;
+        }
+    }
+    
+    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<Map<String,One>>, Transfer {
+        private Map<String, One> appliedData;
+        private String appliedId;
+
+        @Override
+        public Map<String, One> wrapModel(Object model) {
+            return new HashMap<String, One>();
+        }
+
+        @Override
+        public void valueHasMutated(Map<String, One> data, String propertyName) {
+            One p = data.get(propertyName);
+            if (p != null) {
+                p.changes++;
+            }
+        }
+
+        @Override
+        public void bind(PropertyBinding b, Object model, Map<String, One> 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<String, One> data) {
+            try {
+                data.put(fb.getFunctionName(), new One(model, fb));
+            } catch (NoSuchMethodException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        public void applyBindings(Map<String, One> 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> M toModel(Class<M> 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<String, One> data) {
+            this.appliedId = id;
+            this.appliedData = data;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/test/java/net/java/html/json/ModelProcessorTest.java
----------------------------------------------------------------------
diff --git a/json/src/test/java/net/java/html/json/ModelProcessorTest.java b/json/src/test/java/net/java/html/json/ModelProcessorTest.java
new file mode 100644
index 0000000..9c0d336
--- /dev/null
+++ b/json/src/test/java/net/java/html/json/ModelProcessorTest.java
@@ -0,0 +1,823 @@
+/**
+ * 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-2016 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 java.io.IOException;
+import java.util.Locale;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import static org.testng.Assert.*;
+import org.testng.annotations.Test;
+
+/** Verify errors emitted by the processor.
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelProcessorTest {
+    @Test public void verifyWrongType() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=Runnable.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("Runnable")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about Runnable:" + msgs);
+        }
+    }
+
+    @Test public void verifyWrongTypeInInnerClass() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "class X {\n"
+            + "  @Model(className=\"XModel\", properties={\n"
+            + "    @Property(name=\"prop\", type=Runnable.class)\n"
+            + "  })\n"
+            + "  static class Inner {\n"
+            + "  }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("Runnable")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about Runnable:" + msgs);
+        }
+    }
+
+    @Test public void warnOnNonStatic() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    @ComputedProperty int y(int prop) {\n"
+            + "        return prop;\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("y has to be static")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about non-static method:" + msgs);
+        }
+    }
+
+    @Test public void warnOnDuplicated() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop1\", type=int.class),\n"
+            + "  @Property(name=\"prop2\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    @ComputedProperty static int y(int prop1) {\n"
+            + "        return prop1;\n"
+            + "    }\n"
+            + "    @ComputedProperty static int y(int prop1, int prop2) {\n"
+            + "        return prop2;\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("Cannot have the property y defined twice")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning duplicated property:" + msgs);
+        }
+    }
+
+    @Test public void warnOnDuplicatedWithNormalProp() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop1\", type=int.class),\n"
+            + "  @Property(name=\"prop2\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    @ComputedProperty static int prop2(int prop1) {\n"
+            + "        return prop1;\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("Cannot have the property prop2 defined twice")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning duplicated property:" + msgs);
+        }
+    }
+
+    @Test public void tooManyProperties() throws IOException {
+        manyProperties(255, false, 0);
+    }
+
+    @Test public void tooManyArrayPropertiesIsOK() throws IOException {
+        manyProperties(0, true, 300);
+    }
+
+    @Test public void justEnoughProperties() throws IOException {
+        manyProperties(254, true, 0);
+    }
+
+    @Test public void justEnoughPropertiesWithArrayOne() throws IOException {
+        manyProperties(253, true, 300);
+    }
+
+    @Test public void justEnoughPropertiesButOneArrayOne() throws IOException {
+        manyProperties(254, false, 300);
+    }
+
+    private void manyProperties(
+        int cnt, boolean constructorWithParams, int arrayCnt
+    ) throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        StringBuilder code = new StringBuilder();
+        code.append("package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+        );
+        for (int i = 1; i <= cnt; i++) {
+            code.append("  @Property(name=\"prop").append(i).append("\", ");
+            code.append("type=int.class),\n");
+        }
+        for (int i = 1; i <= arrayCnt; i++) {
+            code.append("  @Property(name=\"array").append(i).append("\", ");
+            code.append("array=true, ");
+            code.append("type=int.class),\n");
+        }
+        code.append(""
+            + "})\n"
+            + "class X {\n"
+            + "    static {\n"
+            + "      new XModel();\n"
+            + "      new XModel("
+        );
+        if (constructorWithParams) {
+            code.append("0");
+            for (int i = 1; i < cnt; i++) {
+                code.append(",\n").append(i);
+            }
+        }
+        code.append(");\n"
+            + "    }\n"
+            + "}\n"
+        );
+
+        Compile c = Compile.create(html, code.toString());
+        assertTrue(c.getErrors().isEmpty(), "Compiles OK: " + c.getErrors());
+    }
+
+    @Test public void writeableComputedPropertyMissingWrite() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    static @ComputedProperty(write=\"setY\") int y(int prop) {\n"
+            + "        return prop;\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("Cannot find setY")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about non-static method:" + msgs);
+        }
+    }
+
+    @Test public void writeableComputedPropertyWrongWriteType() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    static @ComputedProperty(write=\"setY\") int y(int prop) {\n"
+            + "        return prop;\n"
+            + "    }\n"
+            + "    static void setY(XModel model, String prop) {\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("Write method first argument needs to be XModel and second int or Object")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about non-static method:" + msgs);
+        }
+    }
+
+    @Test public void writeableComputedPropertyReturnsVoid() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    static @ComputedProperty(write=\"setY\") int y(int prop) {\n"
+            + "        return prop;\n"
+            + "    }\n"
+            + "    static Number setY(XModel model, int prop) {\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("Write method has to return void")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about non-static method:" + msgs);
+        }
+    }
+
+    @Test public void computedCantReturnVoid() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    @ComputedProperty static void y(int prop) {\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("y cannot return void")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about non-static method:" + msgs);
+        }
+    }
+
+    @Test public void computedCantReturnRunnable() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=int.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "    @ComputedProperty static Runnable y(int prop) {\n"
+            + "       return null;\n"
+            + "    }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        boolean ok = false;
+        StringBuilder msgs = new StringBuilder();
+        for (Diagnostic<? extends JavaFileObject> e : c.getErrors()) {
+            String msg = e.getMessage(Locale.ENGLISH);
+            if (msg.contains("y cannot return java.lang.Runnable")) {
+                ok = true;
+            }
+            msgs.append("\n").append(msg);
+        }
+        if (!ok) {
+            fail("Should contain warning about non-static method:" + msgs);
+        }
+    }
+
+    @Test public void canWeCompileWithJDK1_5SourceLevel() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  @ComputedProperty static double derived(long prop) { return prop; }"
+            + "}\n";
+
+        Compile c = Compile.create(html, code, "1.5");
+        assertTrue(c.getErrors().isEmpty(), "No errors: " + c.getErrors());
+    }
+    
+    @Test public void instanceNeedsDefaultConstructor() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", instance=true, properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  X(int x) {}\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        c.assertError("Needs non-private default constructor when instance=true");
+    }
+    
+    @Test public void instanceNeedsNonPrivateConstructor() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", instance=true, properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  private X() {}\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        c.assertError("Needs non-private default constructor when instance=true");
+    }
+
+    @Test public void instanceNoConstructorIsOK() throws IOException {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.ComputedProperty;\n"
+            + "@Model(className=\"XModel\", instance=true, properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        c.assertNoErrors();
+    }
+
+    @Test public void putNeedsDataArgument() throws Exception {
+        needsAnArg("PUT");
+    }
+
+    @Test public void postNeedsDataArgument() throws Exception {
+        needsAnArg("POST");
+    }
+
+    private void needsAnArg(String method) throws Exception {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.OnReceive;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  @Model(className=\"PQ\", properties={})\n"
+            + "  class PImpl {\n"
+            + "  }\n"
+            + "  @OnReceive(method=\"" + method + "\", url=\"whereever\")\n"
+            + "  static void obtained(XModel m, PQ p) { }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        for (Diagnostic<? extends JavaFileObject> diagnostic : c.getErrors()) {
+            String msg = diagnostic.getMessage(Locale.ENGLISH);
+            if (msg.contains("specify a data()")) {
+                return;
+            }
+        }
+        fail("Needs an error message about missing data():\n" + c.getErrors());
+
+    }
+
+
+    @Test public void jsonNeedsToUseGet () throws Exception {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.OnReceive;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  @Model(className=\"PQ\", properties={})\n"
+            + "  class PImpl {\n"
+            + "  }\n"
+            + "  @OnReceive(method=\"POST\", jsonp=\"callback\", url=\"whereever\")\n"
+            + "  static void obtained(XModel m, PQ p) { }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        for (Diagnostic<? extends JavaFileObject> diagnostic : c.getErrors()) {
+            String msg = diagnostic.getMessage(Locale.ENGLISH);
+            if (msg.contains("JSONP works only with GET")) {
+                return;
+            }
+        }
+        fail("Needs an error message about wrong method:\n" + c.getErrors());
+
+    }
+
+    @Test public void noHeadersForWebSockets() throws Exception {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.OnReceive;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  @Model(className=\"PQ\", properties={})\n"
+            + "  class PImpl {\n"
+            + "  }\n"
+            + "  @OnReceive(method=\"WebSocket\", data = PQ.class, headers=\"SomeHeader: {some}\", url=\"whereever\")\n"
+            + "  static void obtained(XModel m, PQ p) { }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        for (Diagnostic<? extends JavaFileObject> diagnostic : c.getErrors()) {
+            String msg = diagnostic.getMessage(Locale.ENGLISH);
+            if (msg.contains("WebSocket spec does not support headers")) {
+                return;
+            }
+        }
+        fail("Needs an error message about headers:\n" + c.getErrors());
+
+    }
+
+    @Test public void webSocketsWithoutDataIsError() throws Exception {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.OnReceive;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  @Model(className=\"PQ\", properties={})\n"
+            + "  class PImpl {\n"
+            + "  }\n"
+            + "  @OnReceive(method=\"WebSocket\", url=\"whereever\")\n"
+            + "  static void obtained(XModel m, PQ p) { }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        for (Diagnostic<? extends JavaFileObject> diagnostic : c.getErrors()) {
+            String msg = diagnostic.getMessage(Locale.ENGLISH);
+            if (msg.contains("eeds to specify a data()")) {
+                return;
+            }
+        }
+        fail("Needs data attribute :\n" + c.getErrors());
+    }
+
+    @Test public void noNewLinesInHeaderLines() throws Exception {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.OnReceive;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  @Model(className=\"PQ\", properties={})\n"
+            + "  class PImpl {\n"
+            + "  }\n"
+            + "  @OnReceive(headers=\"SomeHeader\\n: {some}\", url=\"whereever\")\n"
+            + "  static void obtained(XModel m, PQ p) { }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        for (Diagnostic<? extends JavaFileObject> diagnostic : c.getErrors()) {
+            String msg = diagnostic.getMessage(Locale.ENGLISH);
+            if (msg.contains("Header line cannot contain line separator")) {
+                return;
+            }
+        }
+        fail("Needs an error message about headers:\n" + c.getErrors());
+
+    }
+
+    @Test public void noReturnInHeaderLines() throws Exception {
+        String html = "<html><body>"
+            + "</body></html>";
+        String code = "package x.y.z;\n"
+            + "import net.java.html.json.Model;\n"
+            + "import net.java.html.json.Property;\n"
+            + "import net.java.html.json.OnReceive;\n"
+            + "@Model(className=\"XModel\", properties={\n"
+            + "  @Property(name=\"prop\", type=long.class)\n"
+            + "})\n"
+            + "class X {\n"
+            + "  @Model(className=\"PQ\", properties={})\n"
+            + "  class PImpl {\n"
+            + "  }\n"
+            + "  @OnReceive(headers=\"Some\\rHeader: {some}\", url=\"whereever\")\n"
+            + "  static void obtained(XModel m, PQ p) { }\n"
+            + "}\n";
+
+        Compile c = Compile.create(html, code);
+        assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors());
+        for (Diagnostic<? extends JavaFileObject> diagnostic : c.getErrors()) {
+            String msg = diagnostic.getMessage(Locale.ENGLISH);
+            if (msg.contains("Header line cannot contain line separator")) {
+                return;
+            }
+        }
+        fail("Needs an error message about headers:\n" + c.getErrors());
+
+    }
+
+    @Test public void onErrorHasToExist() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class)\n"
+            + "})\n"
+            + "class UseOnReceive {\n"
+            + "  @net.java.html.json.OnReceive(url=\"http://nowhere.com\", onError=\"doesNotExist\")\n"
+            + "  static void onMessage(MyModel model, String value) {\n"
+            + "  }\n"
+            + "}\n"
+        );
+        res.assertErrors();
+        res.assertError("not find doesNotExist");
+    }
+
+    @Test public void usingListIsOK() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class)\n"
+            + "})\n"
+            + "class UseOnReceive {\n"
+            + "  @net.java.html.json.OnReceive(url=\"http://nowhere.com\")\n"
+            + "  static void onMessage(MyModel model, java.util.List<MyData> value) {\n"
+            + "  }\n"
+            + "\n"
+            + "  @net.java.html.json.Model(className=\"MyData\", properties={\n"
+            + "  })\n"
+            + "  static class MyDataModel {\n"
+            + "  }\n"
+            + "}\n"
+        );
+        res.assertNoErrors();
+    }
+
+    @Test public void functionAndPropertyCollide() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class)\n"
+            + "})\n"
+            + "class Collision {\n"
+            + "  @net.java.html.json.Function\n"
+            + "  static void x(MyModel model, String value) {\n"
+            + "  }\n"
+            + "}\n"
+        );
+        res.assertErrors();
+        res.assertError("cannot have the name");
+    }
+
+    @Test public void twoPropertiesCollide() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class),\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=int.class)\n"
+            + "})\n"
+            + "class Collision {\n"
+            + "}\n"
+        );
+        res.assertErrors();
+        res.assertError("Cannot have the property");
+    }
+
+    @Test public void propertyAndComputedOneCollide() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class),\n"
+            + "})\n"
+            + "class Collision {\n"
+            + "  @net.java.html.json.ComputedProperty static int x(String x) {\n"
+            + "    return x.length();\n"
+            + "  }\n"
+            + "}\n"
+        );
+        res.assertErrors();
+        res.assertError("Cannot have the property");
+    }
+
+    @Test public void onWebSocketJustTwoArgs() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class)\n"
+            + "})\n"
+            + "class UseOnReceive {\n"
+            + "  @net.java.html.json.OnReceive(url=\"http://nowhere.com\", method=\"WebSocket\", data=String.class)\n"
+            + "  static void onMessage(MyModel model, String value, int arg) {\n"
+            + "  }\n"
+            + "}\n"
+        );
+        res.assertErrors();
+        res.assertError("only have two arg");
+    }
+
+    @Test public void onErrorWouldHaveToBeStatic() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class)\n"
+            + "})\n"
+            + "class UseOnReceive {\n"
+            + "  @net.java.html.json.OnReceive(url=\"http://nowhere.com\", onError=\"notStatic\")\n"
+            + "  static void onMessage(MyModel model, String value) {\n"
+            + "  }\n"
+            + "  void notStatic(Exception e) {}\n"
+            + "}\n"
+        );
+        res.assertErrors();
+        res.assertError("have to be static");
+    }
+
+    @Test public void onErrorMustAcceptExceptionArgument() throws IOException {
+        Compile res = Compile.create("", "package x;\n"
+            + "@net.java.html.json.Model(className=\"MyModel\", properties= {\n"
+            + "  @net.java.html.json.Property(name=\"x\", type=String.class)\n"
+            + "})\n"
+            + "class UseOnReceive {\n"
+            + "  @net.java.html.json.OnReceive(url=\"http://nowhere.com\", onError=\"subclass\")\n"
+            + "  static void onMessage(MyModel model, String value) {\n"
+            + "  }\n"
+            + "  static void subclass(java.io.IOException e) {}\n"
+            + "}\n"
+        );
+        res.assertErrors();
+        res.assertError("Error method first argument needs to be MyModel and second Exception");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/test/java/net/java/html/json/ModelTest.java
----------------------------------------------------------------------
diff --git a/json/src/test/java/net/java/html/json/ModelTest.java b/json/src/test/java/net/java/html/json/ModelTest.java
new file mode 100644
index 0000000..bb4a7be
--- /dev/null
+++ b/json/src/test/java/net/java/html/json/ModelTest.java
@@ -0,0 +1,506 @@
+/**
+ * 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-2016 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import net.java.html.BrwsrCtx;
+import org.netbeans.html.context.spi.Contexts;
+import org.netbeans.html.json.spi.FunctionBinding;
+import org.netbeans.html.json.spi.PropertyBinding;
+import org.netbeans.html.json.spi.Technology;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "Modelik", builder = "change", targetId = "", properties = {
+    @Property(name = "value", type = int.class),
+    @Property(name = "count", type = int.class),
+    @Property(name = "unrelated", type = long.class),
+    @Property(name = "names", type = String.class, array = true),
+    @Property(name = "values", type = int.class, array = true),
+    @Property(name = "people", type = Person.class, array = true),
+    @Property(name = "changedProperty", type=String.class)
+})
+public class ModelTest {
+    private MockTechnology my;
+    private Modelik model;
+    private static Modelik leakedModel;
+
+    @BeforeMethod
+    public void createModel() {
+        my = new MockTechnology();
+        final BrwsrCtx c = Contexts.newBuilder().register(Technology.class, my, 1).build();
+        model = Models.bind(new Modelik(), c);
+    }
+
+    @Test public void classGeneratedWithSetterGetter() {
+        model.setValue(10);
+        assertEquals(10, model.getValue(), "Value changed");
+    }
+
+    @Test public void computedMethod() {
+        model.setValue(4);
+        assertEquals(16, model.getPowerValue());
+    }
+
+    @Test public void equalsAndHashCode() {
+        Modelik m1 = new Modelik();
+        m1.setValue(10);
+        m1.setCount(20);
+        m1.setUnrelated(30);
+        m1.setChangedProperty("changed");
+        m1.getNames().add("firstName");
+        Modelik m2 = new Modelik().
+            changeValue(10).
+            changeCount(20).
+            changeUnrelated(30).
+            changeChangedProperty("changed").
+            changeNames("firstName");
+
+        assertTrue(m1.equals(m2), "They are the same");
+        assertEquals(m1.hashCode(), m2.hashCode(), "Hashcode is the same");
+
+        m1.setCount(33);
+
+        assertFalse(m1.equals(m2), "No longer the same");
+        assertFalse(m1.hashCode() == m2.hashCode(), "No longe is hashcode is the same");
+    }
+
+    @Test public void arrayIsMutable() {
+        assertEquals(model.getNames().size(), 0, "Is empty");
+        model.getNames().add("Jarda");
+        assertEquals(model.getNames().size(), 1, "One element");
+    }
+
+    @Test public void arrayChangesNotNotifiedUntilInitied() {
+        model.getNames().add("Hello");
+        assertTrue(my.mutated.isEmpty(), "No change now " + my.mutated);
+        model.getNames().remove("Hello");
+        assertTrue(my.mutated.isEmpty(), "No change still " + my.mutated);
+        assertTrue(model.getNames().isEmpty(), "No empty");
+    }
+
+    @Test public void arrayChangesNotified() {
+        Models.applyBindings(model);
+        model.getNames().add("Hello");
+
+        assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
+        assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
+
+        my.mutated.clear();
+
+        Iterator<String> it = model.getNames().iterator();
+        assertEquals(it.next(), "Hello");
+        it.remove();
+
+        assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
+        assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
+
+        my.mutated.clear();
+
+        ListIterator<String> lit = model.getNames().listIterator();
+        lit.add("Jarda");
+
+        assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
+        assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
+    }
+
+    @Test public void autoboxedArray() {
+        model.getValues().add(10);
+
+        assertEquals(model.getValues().get(0), Integer.valueOf(10), "Really ten");
+    }
+
+    @Test public void derivedArrayProp() {
+        model.applyBindings();
+        model.setCount(10);
+
+        List<String> arr = model.getRepeat();
+        assertEquals(arr.size(), 10, "Ten items: " + arr);
+
+        my.mutated.clear();
+
+        model.setCount(5);
+
+        arr = model.getRepeat();
+        assertEquals(arr.size(), 5, "Five items: " + arr);
+
+        assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated);
+        assertTrue(my.mutated.contains("repeat"), "Array is in there: " + my.mutated);
+        assertTrue(my.mutated.contains("count"), "Count is in there: " + my.mutated);
+    }
+
+    @Test public void derivedArrayPropChange() {
+        model.applyBindings();
+        model.setCount(5);
+
+        List<String> arr = model.getRepeat();
+        assertEquals(arr.size(), 5, "Five items: " + arr);
+
+        model.setRepeat(10);
+        assertEquals(model.getCount(), 10, "Changing repeat changes count");
+    }
+
+    @Test public void derivedPropertiesAreNotified() {
+        model.applyBindings();
+
+        model.setValue(33);
+
+        // not interested in change of this property
+        my.mutated.remove("changedProperty");
+
+        assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated);
+        assertTrue(my.mutated.contains("powerValue"), "Power value is in there: " + my.mutated);
+        assertTrue(my.mutated.contains("value"), "Simple value is in there: " + my.mutated);
+
+        my.mutated.clear();
+
+        model.setUnrelated(44);
+
+
+        // not interested in change of this property
+        my.mutated.remove("changedProperty");
+        assertEquals(my.mutated.size(), 1, "One property changed: " + my.mutated);
+        assertTrue(my.mutated.contains("unrelated"), "Its name is unrelated");
+    }
+
+    @Test public void computedPropertyCannotWriteToModel() {
+        leakedModel = model;
+        try {
+            String res = model.getNotAllowedWrite();
+            fail("We should not be allowed to write to the model: " + res);
+        } catch (IllegalStateException ex) {
+            // OK, we can't read
+        }
+    }
+
+    @Test public void computedPropertyCannotReadToModel() {
+        leakedModel = model;
+        try {
+            String res = model.getNotAllowedRead();
+            fail("We should not be allowed to read from the model: " + res);
+        } catch (IllegalStateException ex) {
+            // OK, we can't read
+        }
+    }
+
+    @OnReceive(url = "{protocol}://{host}?query={query}", data = Person.class, onError = "errorState")
+    static void loadPeople(Modelik thiz, People p) {
+        Modelik m = null;
+        m.applyBindings();
+        m.loadPeople("http", "apidesign.org", "query", new Person());
+    }
+
+    static void errorState(Modelik thiz, Exception ex) {
+
+    }
+
+    @OnReceive(url="{url}", headers={
+        "Easy: {easy}",
+        "H-a+r!d?e.r: {harder}",
+        "H-a+r!d?e's\"t: {harder}",
+        "Repeat-ed: {rep}",
+        "Repeat+ed: {rep}",
+        "Same-URL: {url}"
+    })
+    static void fetchPeopleWithHeaders(Modelik model, People p) {
+        model.fetchPeopleWithHeaders("url", "easy", "harder", "rep");
+    }
+
+    @OnReceive(url = "{protocol}://{host}?callback={back}&query={query}", jsonp = "back")
+    static void loadPeopleViaJSONP(Modelik thiz, People p) {
+        Modelik m = null;
+        m.applyBindings();
+        m.loadPeopleViaJSONP("http", "apidesign.org", "query");
+    }
+
+    @OnReceive(url = "{rep}://{rep}")
+    static void repeatedTest(Modelik thiz, People p) {
+        thiz.repeatedTest("justOneParameterRep");
+    }
+
+    @Function
+    static void doSomething() {
+    }
+
+    @ComputedProperty(write = "setPowerValue")
+    static int powerValue(int value) {
+        return value * value;
+    }
+
+    static void setPowerValue(Modelik m, int value) {
+        m.setValue((int)Math.sqrt(value));
+    }
+
+    @OnPropertyChange({ "powerValue", "unrelated" })
+    static void aPropertyChanged(Modelik m, String name) {
+        m.setChangedProperty(name);
+    }
+
+    @OnPropertyChange({ "values" })
+    static void anArrayPropertyChanged(String name, Modelik m) {
+        m.setChangedProperty(name);
+    }
+
+    @Test public void changeAnything() {
+        model.setCount(44);
+        assertNull(model.getChangedProperty(), "No observed value change");
+    }
+    @Test public void changeValue() {
+        model.setValue(33);
+        assertEquals(model.getChangedProperty(), "powerValue", "power property changed");
+    }
+    @Test public void changePowerValue() {
+        model.setValue(3);
+        assertEquals(model.getPowerValue(), 9, "Square");
+        model.setPowerValue(16);
+        assertEquals(model.getValue(), 4, "Square root");
+        assertEquals(model.getPowerValue(), 16, "Square changed");
+    }
+    @Test public void changeUnrelated() {
+        model.setUnrelated(333);
+        assertEquals(model.getChangedProperty(), "unrelated", "unrelated changed");
+    }
+
+    @Test public void changeInArray() {
+        model.getValues().add(10);
+        assertNull(model.getChangedProperty(), "No change before applyBindings");
+        model.applyBindings();
+        model.getValues().add(10);
+        assertEquals(model.getChangedProperty(), "values", "Something added into the array");
+    }
+
+    @ComputedProperty
+    static String notAllowedRead() {
+        return "Not allowed callback: " + leakedModel.getUnrelated();
+    }
+
+    @ComputedProperty
+    static String notAllowedWrite() {
+        leakedModel.setUnrelated(11);
+        return "Not allowed callback!";
+    }
+
+    @ComputedProperty(write="parseRepeat")
+    static List<String> repeat(int count) {
+        return Collections.nCopies(count, "Hello");
+    }
+    static void parseRepeat(Modelik m, Object v) {
+        m.setCount((Integer)v);
+    }
+
+    public @Test void hasPersonPropertyAndComputedFullName() {
+        List<Person> arr = model.getPeople();
+        assertEquals(arr.size(), 0, "By default empty");
+        Person p = null;
+        if (p != null) {
+            String fullNameGenerated = p.getFullName();
+            assertNotNull(fullNameGenerated);
+        }
+    }
+
+    public @Test void computedListIsOfTypeString() {
+        Person p = new Person("1st", "2nd", Sex.MALE);
+        String first = p.getBothNames().get(0);
+        String last = p.getBothNames().get(1);
+        assertEquals(first, "1st");
+        assertEquals(last, "2nd");
+    }
+    
+    @Model(className = "Inner", instance = true, properties = {
+        @Property(name = "x", type = int.class),
+        @Property(name = "y", type = int.class)
+    })
+    static final class InnerCntrl {
+        private BrwsrCtx ctx;
+
+        @ModelOperation
+        void init(Inner model, BrwsrCtx ctx) {
+            this.ctx = ctx;
+        }
+
+        @Function
+        void setYToTen(Inner model) {
+            assertCtx();
+            model.setY(10);
+        }
+
+        @ModelOperation
+        void modelYToTen(Inner model) {
+            assertCtx();
+            model.setY(10);
+        }
+
+        @OnPropertyChange("y")
+        void increment(Inner model) {
+            model.setX(model.getX() + 1);
+            assertCtx();
+        }
+
+        private void assertCtx() {
+            BrwsrCtx realCtx = BrwsrCtx.findDefault(InnerCntrl.class);
+            assertSame(realCtx, ctx, "Proper Ctx is provided");
+        }
+    }
+
+//   directly using setters doesn't set the context currently
+//    @Test
+//    public void incrementXOnChangeOfY() {
+//        doIncreementXOnChangeOfY(0);
+//    }
+
+    @Test
+    public void incrementXOnChangeOfYViaFunction() {
+        doIncreementXOnChangeOfY(1);
+    }
+
+    @Test
+    public void incrementXOnChangeOfYViaModel() {
+        doIncreementXOnChangeOfY(2);
+    }
+
+    private void doIncreementXOnChangeOfY(int modificationType) {
+        class Exec implements Executor {
+            int cnt;
+            @Override
+            public void execute(Runnable command) {
+                cnt++;
+                command.run();
+            }
+
+            final void assertCount(int expected, String msg) {
+                assertEquals(cnt, expected, msg);
+                cnt = 0;
+            }
+        }
+        MapModelTest.MapTechnology tech = new MapModelTest.MapTechnology();
+        Exec exec = new Exec();
+        final BrwsrCtx c = Contexts.newBuilder().
+            register(Technology.class, tech, 1).
+            register(Executor.class, exec, 5).
+            build();
+
+        Inner model = Models.bind(new Inner(), c);
+        exec.assertCount(0, "Executor not used for anything yet");
+        model.init(c);
+        exec.assertCount(1, "Executor used for initialization");
+        
+        Models.applyBindings(model);
+
+        assertEquals(model.getX(), 0, "Zero");
+        assertEquals(model.getY(), 0, "Zero too");
+        int execUse;
+        if (modificationType != 1) {
+            model.modelYToTen();
+            execUse = 3;
+        } else {
+            Object raw = Models.toRaw(model);
+            assertTrue(raw instanceof Map);
+            Map<?,?> map = (Map<?,?>) raw;
+            MapModelTest.One one = (MapModelTest.One) map.get("setYToTen");
+            assertNotNull(one);
+            one.fb.call(model, null);
+            execUse = 3;
+        }
+        assertEquals(model.getX(), 1, "One");
+        assertEquals(model.getY(), 10, "Ten");
+        exec.assertCount(execUse, "Executor used");
+    }
+
+    private static class MockTechnology implements Technology<Object> {
+        private final List<String> mutated = new ArrayList<String>();
+
+        @Override
+        public Object wrapModel(Object model) {
+            return this;
+        }
+
+        @Override
+        public void valueHasMutated(Object data, String propertyName) {
+            mutated.add(propertyName);
+        }
+
+        @Override
+        public void bind(PropertyBinding b, Object model, Object data) {
+        }
+
+        @Override
+        public void expose(FunctionBinding fb, Object model, Object d) {
+        }
+
+        @Override
+        public void applyBindings(Object data) {
+        }
+
+        @Override
+        public Object wrapArray(Object[] arr) {
+            return arr;
+        }
+
+        @Override
+        public <M> M toModel(Class<M> modelClass, Object data) {
+            return modelClass.cast(data);
+        }
+
+        @Override
+        public void runSafe(Runnable r) {
+            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/test/java/net/java/html/json/ModelsTest.java
----------------------------------------------------------------------
diff --git a/json/src/test/java/net/java/html/json/ModelsTest.java b/json/src/test/java/net/java/html/json/ModelsTest.java
new file mode 100644
index 0000000..7e7c5b0
--- /dev/null
+++ b/json/src/test/java/net/java/html/json/ModelsTest.java
@@ -0,0 +1,72 @@
+/**
+ * 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-2016 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 static org.testng.Assert.*;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class ModelsTest {
+    
+    public ModelsTest() {
+    }
+
+    @Test public void peopleAreModel() {
+        assertTrue(Models.isModel(People.class), "People are generated class");
+    }
+    
+    @Test public void personIsModel() {
+        assertTrue(Models.isModel(Person.class), "Person is generated class");
+    }
+
+    @Test public void implClassIsNotModel() {
+        assertFalse(Models.isModel(PersonImpl.class), "Impl is not model");
+    }
+
+    @Test public void randomClassIsNotModel() {
+        assertFalse(Models.isModel(StringBuilder.class), "JDK classes are not model");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/test/java/net/java/html/json/OperationTest.java
----------------------------------------------------------------------
diff --git a/json/src/test/java/net/java/html/json/OperationTest.java b/json/src/test/java/net/java/html/json/OperationTest.java
new file mode 100644
index 0000000..db30837
--- /dev/null
+++ b/json/src/test/java/net/java/html/json/OperationTest.java
@@ -0,0 +1,125 @@
+/**
+ * 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-2016 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 java.io.IOException;
+import java.util.Arrays;
+import net.java.html.BrwsrCtx;
+import org.netbeans.html.context.spi.Contexts;
+import static org.testng.Assert.*;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "OpModel", properties = {
+    @Property(name = "names", type = String.class, array = true)
+})
+public class OperationTest {
+    @ModelOperation static void add(OpModel m, String name, BrwsrCtx exp) {
+        assertSame(BrwsrCtx.findDefault(OpModel.class), exp, "Context is passed in");
+        m.getNames().add(name);
+    }
+
+    @ModelOperation static void add(OpModel m, int times, String name) throws IOException {
+        while (times-- > 0) {
+            m.getNames().add(name.toUpperCase());
+        }
+    }
+    
+    @ModelOperation static void copy(OpModel m, OpModel orig) {
+        m.getNames().clear();
+        m.getNames().addAll(orig.getNames());
+    }
+
+    @Test public void addOneToTheModel() {
+        BrwsrCtx ctx = Contexts.newBuilder().build();
+        OpModel m = Models.bind(new OpModel("One"), ctx);
+        m.add("Second", ctx);
+        assertEquals(m.getNames().size(), 2, "Both are there: " + m.getNames());
+    }
+
+    @Test public void addTwoUpperCasesToTheModel() {
+        BrwsrCtx ctx = Contexts.newBuilder().build();
+        OpModel m = Models.bind(new OpModel("One"), ctx);
+        m.add(2, "Second");
+        assertEquals(m.getNames().size(), 3, "Both are there: " + m.getNames());
+        assertEquals(m.getNames().get(1), "SECOND", "Converted to upper case");
+        assertEquals(m.getNames().get(2), "SECOND", "Also converted to upper case");
+    }
+    
+    @Test public void noAnnonymousInnerClass() {
+        int cnt = 0;
+        for (Class<?> c : OpModel.class.getDeclaredClasses()) {
+            cnt++;
+            int dolar = c.getName().lastIndexOf('$');
+            assertNotEquals(dolar, -1, "There is dolar in : " + c.getName());
+            String res = c.getName().substring(dolar + 1);
+            try {
+                int number = Integer.parseInt(res);
+                if (number == 1) {
+                    // one is OK, #2 was a problem
+                    continue;
+                }
+                fail("There seems to annonymous innerclass! " + c.getName() + "\nImplements: " 
+                    + Arrays.toString(c.getInterfaces()) + " extends: " + c.getSuperclass()
+                );
+            } catch (NumberFormatException ex) {
+                // OK, go on
+            }
+        }
+        if (cnt == 0) {
+            fail("There should be at least one inner class: " + cnt);
+        }
+    }
+    
+    @Test public void copyOperation() {
+        OpModel orig = new OpModel("Ahoj", "Jardo");
+        OpModel n = new OpModel();
+        n.copy(orig);
+        assertEquals(n.getNames().size(), 2, "Two elems");
+        assertEquals(n.getNames().get(0), "Ahoj", "1st");
+        assertEquals(n.getNames().get(1), "Jardo", "2nd");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/test/java/net/java/html/json/PersonImpl.java
----------------------------------------------------------------------
diff --git a/json/src/test/java/net/java/html/json/PersonImpl.java b/json/src/test/java/net/java/html/json/PersonImpl.java
new file mode 100644
index 0000000..581bb6a
--- /dev/null
+++ b/json/src/test/java/net/java/html/json/PersonImpl.java
@@ -0,0 +1,135 @@
+/**
+ * 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-2016 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 java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "Person", properties = {
+    @Property(name = "firstName", type = String.class),
+    @Property(name = "lastName", type = String.class),
+    @Property(name = "sex", type = Sex.class)
+})
+final class PersonImpl {
+    @ComputedProperty 
+    public static String fullName(String firstName, String lastName) {
+        return firstName + " " + lastName;
+    }
+    
+    @ComputedProperty
+    public static List<String> bothNames(String firstName, String lastName) {
+        return Arrays.asList(firstName, lastName);
+    }
+    
+    @ComputedProperty
+    public static String sexType(Sex sex) {
+        return sex == null ? "unknown" : sex.toString();
+    }
+    
+    @ComputedProperty static Sex attractedBy(Sex sex) {
+        if (sex == null) {
+            return null;
+        }
+        return sex == Sex.MALE ? Sex.FEMALE : Sex.MALE;
+    }
+    
+    @Function
+    static void changeSex(Person p, String data) {
+        if (data != null) {
+            p.setSex(Sex.valueOf(data));
+            return;
+        }
+        if (p.getSex() == Sex.MALE) {
+            p.setSex(Sex.FEMALE);
+        } else {
+            p.setSex(Sex.MALE);
+        }
+    }
+    
+    @Model(className = "People", instance = true, targetId="myPeople", properties = {
+        @Property(array = true, name = "info", type = Person.class),
+        @Property(array = true, name = "nicknames", type = String.class),
+        @Property(array = true, name = "age", type = int.class),
+        @Property(array = true, name = "sex", type = Sex.class)
+    })
+    public static class PeopleImpl {
+        private int addAgeCount;
+        private Runnable onInfoChange;
+        
+        @ModelOperation void onInfoChange(People self, Runnable r) {
+            onInfoChange = r;
+        }
+        
+        @ModelOperation void addAge42(People p) {
+            p.getAge().add(42);
+            addAgeCount++;
+        }
+
+        @OnReceive(url = "url", method = "WebSocket", data = String.class)
+        void innerClass(People p, String d) {
+        }
+        
+        @Function void inInnerClass(People p, Person data, int x, double y, String nick) throws IOException {
+            p.getInfo().add(data);
+            p.getAge().add(x);
+            p.getAge().add((int)y);
+            p.getNicknames().add(nick);
+        }
+        
+        @ModelOperation void readAddAgeCount(People p, int[] holder, Runnable whenDone) {
+            holder[0] = addAgeCount;
+            whenDone.run();
+        }
+        
+        @OnPropertyChange("age") void infoChange(People p) {
+            if (onInfoChange != null) {
+                onInfoChange.run();
+            }
+        }
+    }
+}