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:47 UTC

[14/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-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java
new file mode 100644
index 0000000..1663fa8
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java
@@ -0,0 +1,548 @@
+/**
+ * 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.js.tests;
+
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import org.netbeans.html.boot.spi.Fn;
+import org.netbeans.html.json.tck.KOTest;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class JavaScriptBodyTest {
+    @KOTest public void sumTwoNumbers() {
+        int res = Bodies.sum(5, 3);
+        assertEquals(res, 8, "Expecting 8: " + res);
+    }
+
+    @KOTest public void sumFromCallback() {
+        int res = Bodies.sumJS(5, 3);
+        assertEquals(res, 8, "Expecting 8: " + res);
+    }
+    
+    @KOTest public void accessJsObject() {
+        Object o = Bodies.instance(10);
+        int ten = Bodies.readIntX(o);
+        assertEquals(ten, 10, "Expecting ten: " + ten);
+    }
+
+    @KOTest public void callWithNoReturnType() {
+        Object o = Bodies.instance(10);
+        Bodies.incrementX(o);
+        int ten = Bodies.readIntX(o);
+        assertEquals(ten, 11, "Expecting eleven: " + ten);
+    }
+    
+    @KOTest public void callbackToRunnable() {
+        R run = new R();
+        Bodies.callback(run);
+        assertEquals(run.cnt, 1, "Can call even private implementation classes: " + run.cnt);
+    }
+    
+    private R asyncRun;
+    @KOTest public void asyncCallbackToRunnable() throws InterruptedException {
+        if (asyncRun == null) {
+            asyncRun = new R();
+            Bodies.asyncCallback(asyncRun);
+        }
+        if (asyncRun.cnt == 0) {
+            throw new InterruptedException();
+        }
+        assertEquals(asyncRun.cnt, 1, "Even async callback must arrive once: " + asyncRun.cnt);
+    }
+
+    @KOTest public void asyncCallbackFlushed() throws InterruptedException {
+        R r = new R();
+        for (int i = 0; i < 10; i++) {
+            Bodies.asyncCallback(r);
+        }
+        int fourtyTwo = Bodies.sum(35, 7);
+        assertEquals(r.cnt, 10, "Ten calls: " + r.cnt);
+        assertEquals(fourtyTwo, 42, "Meaning of the world expected: " + fourtyTwo);
+    }
+    
+    @KOTest public void typeOfCharacter() {
+        String charType = Bodies.typeof('a', false);
+        assertEquals("number", charType, "Expecting number type: " + charType);
+    }
+    @KOTest public void typeOfBoolean() {
+        String booleanType = Bodies.typeof(true, false);
+        assertEquals("boolean", booleanType, "Expecting boolean type: " + booleanType);
+    }
+
+    @KOTest public void typeOfPrimitiveBoolean() {
+        String booleanType = Bodies.typeof(true);
+        assertTrue("boolean".equals(booleanType) || "number".equals(booleanType), 
+            "Expecting boolean or at least number type: " + booleanType);
+    }
+
+    @KOTest public void typeOfInteger() {
+        String intType = Bodies.typeof(1, false);
+        assertEquals("number", intType, "Expecting number type: " + intType);
+    }
+
+    @KOTest public void typeOfString() {
+        String strType = Bodies.typeof("Ahoj", false);
+        assertEquals("string", strType, "Expecting string type: " + strType);
+    }
+
+    @KOTest public void typeOfDouble() {
+        String doubleType = Bodies.typeof(0.33, false);
+        assertEquals("number", doubleType, "Expecting number type: " + doubleType);
+    }
+    
+    @KOTest public void typeOfBooleanValueOf() {
+        String booleanType = Bodies.typeof(true, true);
+        assertEquals("boolean", booleanType, "Expecting boolean type: " + booleanType);
+    }
+
+    @KOTest public void typeOfIntegerValueOf() {
+        String intType = Bodies.typeof(1, true);
+        assertEquals("number", intType, "Expecting number type: " + intType);
+    }
+
+    @KOTest public void typeOfStringValueOf() {
+        String strType = Bodies.typeof("Ahoj", true);
+        assertEquals("string", strType, "Expecting string type: " + strType);
+    }
+
+    @KOTest public void typeOfDoubleValueOf() {
+        String doubleType = Bodies.typeof(0.33, true);
+        assertEquals("number", doubleType, "Expecting number type: " + doubleType);
+    }
+
+    @KOTest public void computeInARunnable() {
+        final int[] sum = new int[2];
+        class First implements Runnable {
+            @Override public void run() {
+                sum[0] = Bodies.sum(22, 20);
+                sum[1] = Bodies.sum(32, 10);
+            }
+        }
+        Bodies.callback(new First());
+        assertEquals(sum[0], 42, "Computed OK " + sum[0]);
+        assertEquals(sum[1], 42, "Computed OK too: " + sum[1]);
+    }
+    
+    @KOTest public void doubleCallbackToRunnable() {
+        final R run = new R();
+        final R r2 = new R();
+        class First implements Runnable {
+            @Override public void run() {
+                Bodies.callback(run);
+                Bodies.callback(r2);
+            }
+        }
+        Bodies.callback(new First());
+        assertEquals(run.cnt, 1, "Can call even private implementation classes: " + run.cnt);
+        assertEquals(r2.cnt, 1, "Can call even private implementation classes: " + r2.cnt);
+    }
+    
+    @KOTest public void identity() {
+        Object p = new Object();
+        Object r = Bodies.id(p);
+        assertEquals(r, p, "The object is the same");
+    }
+
+    @KOTest public void encodingString() {
+        Object p = "Ji\n\"Hi\"\nHon";
+        Object r = Bodies.id(p);
+        assertEquals(p, r, "The object is the same: " + p + " != " + r);
+    }
+
+    @KOTest public void encodingBackslashString() {
+        Object p = "{\"firstName\":\"/*\\n * Copyright (c) 2013\",\"lastName\":null,\"sex\":\"MALE\",\"address\":{\"street\":null}}";
+        Object r = Bodies.id(p);
+        assertEquals(p, r, "The object is the same: " + p + " != " + r);
+    }
+
+    @KOTest public void nullIsNull() {
+        Object p = null;
+        Object r = Bodies.id(p);
+        assertEquals(r, p, "The null is the same");
+    }
+    
+    @KOTest public void callbackWithTrueResult() {
+        Callable<Boolean> c = new C(true);
+        String b = Bodies.yesNo(c);
+        assertEquals(b, "yes", "Should return true");
+    }
+
+    @KOTest public void callbackWithFalseResult() {
+        Callable<Boolean> c = new C(false);
+        String b = Bodies.yesNo(c);
+        assertEquals(b, "no", "Should return false");
+    }
+    
+    @KOTest public void callbackWithParameters() throws InterruptedException {
+        Sum s = new Sum();
+        int res = Bodies.sumIndirect(s, 40, 2);
+        assertEquals(res, 42, "Expecting 42");
+    }
+    
+    @KOTest public void selectFromStringJavaArray() {
+        String[] arr = { "Ahoj", "Wo\nrld" };
+        Object res = Bodies.select(arr, 1);
+        assertEquals("Wo\nrld", res, "Expecting World, but was: " + res);
+    }
+
+    @KOTest public void selectFromObjectJavaArray() {
+        Object[] arr = { new Object(), new Object() };
+        Object res = Bodies.select(arr, 1);
+        assertEquals(arr[1], res, "Expecting " + arr[1] + ", but was: " + res);
+    }
+
+    @KOTest public void lengthOfJavaArray() {
+        String[] arr = { "Ahoj", "World" };
+        int res = Bodies.length(arr);
+        assertEquals(res, 2, "Expecting 2, but was: " + res);
+    }
+
+    @KOTest public void isJavaArray() {
+        String[] arr = { "Ahoj", "World" };
+        boolean is = Bodies.isArray(arr);
+        assertTrue(is, "Expecting it to be an array: " + is);
+    }
+
+    @KOTest public void javaArrayInOutIsCopied() {
+        String[] arr = { "Ahoj", "Wo\nrld" };
+        Object res = Bodies.id(arr);
+        assertNotNull(res, "Non-null is returned");
+        assertTrue(res instanceof Object[], "Returned an array: " + res);
+        assertFalse(res instanceof String[], "Not returned a string array: " + res);
+        
+        Object[] ret = (Object[]) res;
+        assertEquals(arr.length, ret.length, "Same length: " + ret.length);
+        assertEquals(arr[0], ret[0], "Same first elem");
+        assertEquals(arr[1], ret[1], "Same 2nd elem");
+    }
+
+    @KOTest public void modifyJavaArrayHasNoEffect() {
+        String[] arr = { "Ah\noj", "World" };
+        String value = Bodies.modify(arr, 0, "H\tello");
+        assertEquals("H\tello", value, "Inside JS the value is changed: " + value);
+        assertEquals("Ah\noj", arr[0], "From a Java point of view it remains: " + arr[0]);
+    }
+
+    @KOTest
+    public void callbackWithArray() {
+        class A implements Callable<String[]> {
+            @Override
+            public String[] call() throws Exception {
+                return new String[] { "He\nllo" };
+            }
+        }
+        Callable<String[]> a = new A();
+        Object b = Bodies.callbackAndPush(a, "Worl\nd!");
+        assertTrue(b instanceof Object[], "Returns an array: " + b);
+        Object[] arr = (Object[]) b;
+        String str = Arrays.toString(arr);
+        assertEquals(arr.length, 2, "Size is two " + str);
+        assertEquals("He\nllo", arr[0], "Hello expected: " + arr[0]);
+        assertEquals("Worl\nd!", arr[1], "World! expected: " + arr[1]);
+    }
+    
+    @KOTest public void sumVector() {
+        double[] arr = { 1.0, 2.0, 3.0 };
+        double res = Bodies.sumVector(arr);
+        assertEquals(6.0, res, "Expecting six: " + res);
+    }
+
+    @KOTest public void sumMatrix() {
+        double[][] arr = { { 1.0 }, { 1.0, 1.0 }, { 1.0, 1.0, 1.0 } };
+        double res = Bodies.sumMatrix(arr);
+        assertEquals(6.0, res, "Expecting six: " + res);
+    }
+
+    @KOTest public void truth() {
+        assertTrue(Bodies.truth(), "True is true");
+    }
+    
+    @KOTest public void factorial2() {
+        assertEquals(new Factorial().factorial(2), 2);
+    }
+    
+    @KOTest public void factorial3() {
+        assertEquals(new Factorial().factorial(3), 6);
+    }
+    
+    @KOTest public void factorial4() {
+        assertEquals(new Factorial().factorial(4), 24);
+    }
+    
+    @KOTest public void factorial5() {
+        assertEquals(new Factorial().factorial(5), 120);
+    }
+    
+    @KOTest public void factorial6() {
+        assertEquals(new Factorial().factorial(6), 720);
+    }
+    
+    @KOTest public void sumArray() {
+        int r = Bodies.sumArr(new Sum());
+        assertEquals(r, 6, "Sum is six: " + r);
+    }
+    
+    @KOTest public void staticCallback() {
+        int r = Bodies.staticCallback();
+        assertEquals(r, 42, "Expecting 42: " + r);
+    }
+
+    @KOTest public void delayCallback() {
+        Object fn = Bodies.delayCallback();
+        Object r = Bodies.invokeFn(fn);
+        assertNotNull(r, "Is not null");
+        assertTrue(r instanceof Number, "Is number " + r);
+        assertEquals(((Number)r).intValue(), 42, "Expecting 42: " + r);
+    }
+    
+    @KOTest public void asyncCallFromAJSCallbackNeedToFinishBeforeReturnToJS() {
+        int r = Bodies.incAsync();
+        assertEquals(r, 42, "Expecting 42: " + r);
+    }
+    
+    @KOTest public void iterateArray() {
+        String[] arr = { "Ahoj", "Hi", "Ciao" };
+        Object[] ret = Bodies.forIn(arr);
+        assertEquals(ret.length, 3, "Three elements returned: " + ret.length);
+        assertNotEquals(ret, arr, "Different arrays");
+        assertEquals(ret[0], "Ahoj", "Expecting Ahoj: " + ret[0]);
+        assertEquals(ret[1], "Hi", "Expecting Hi: " + ret[1]);
+        assertEquals(ret[2], "Ciao", "Expecting Ciao: " + ret[2]);
+    }
+    
+    @KOTest public void primitiveTypes() {
+        String all = Bodies.primitiveTypes(new Sum());
+        assertEquals("Ahojfalse12356.07.0 TheEND", all, "Valid return type: " + all);
+    }
+
+    @KOTest public void returnUnknown() {
+        Object o = Bodies.unknown();
+        assertNull(o, "Unknown is converted to null");
+    }
+
+    @KOTest public void returnUndefinedString() {
+        Object o = Bodies.id("undefined");
+        assertNotNull(o, "String remains string");
+    }
+
+    @KOTest public void returnUnknownArray() {
+        Object[] arr = Bodies.unknownArray();
+        assertEquals(arr.length, 2, "Two elements");
+        assertNull(arr[0], "1st element is null");
+        assertNull(arr[1], "2nd element is null");
+    }
+
+    @KOTest public void callbackKnown() {
+        Sum s = new Sum();
+        boolean nonNull = Bodies.nonNull(s, "x");
+        assertTrue(nonNull, "x property exists");
+    }
+    
+    @KOTest public void callbackUnknown() {
+        Sum s = new Sum();
+        boolean nonNull = Bodies.nonNull(s, "y");
+        assertFalse(nonNull, "y property doesn't exist");
+    }
+
+    @KOTest public void callbackUnknownArray() {
+        Sum s = new Sum();
+        int nullAndUnknown = Bodies.sumNonNull(s);
+        assertEquals(nullAndUnknown, 1, "Only one slot");
+    }
+
+    @KOTest public void problematicString() {
+        String orig = Bodies.problematicString();
+        String js = Bodies.problematicCallback();
+        if (orig.equals(js)) {
+            return;
+        }
+        int len = Math.min(orig.length(), js.length());
+        for (int i = 0; i < len; i++) {
+            if (orig.charAt(i) != js.charAt(i)) {
+                fail("Difference at position " + i + 
+                    "\norig: " +
+                    orig.substring(i - 5, Math.min(i + 10, orig.length())) +
+                    "\n  js: " +
+                    js.substring(i - 5, Math.min(i + 10, js.length())));
+            }
+        }
+        fail("The JS string is different: " + js);
+    }
+
+    @KOTest
+    public void doubleInAnArray() throws Exception {
+        Double val = 2.2;
+        boolean res = Bodies.isInArray(new Object[] { val }, val);
+        assertTrue(res, "Should be in the array");
+    }
+    
+    Later l;
+    @KOTest public void callLater() throws Exception{
+        final Fn.Presenter p = Fn.activePresenter();
+        if (p == null) {
+            return;
+        }
+        if (l == null) {
+            p.loadScript(new StringReader(
+                "if (typeof window === 'undefined') window = {};"
+            ));
+            l = new Later();
+            l.register();
+            p.loadScript(new StringReader(
+                "window.later();"
+            ));
+        }
+        if (l.call != 42) {
+            throw new InterruptedException();
+        }
+        assertEquals(l.call, 42, "Method was called: " + l.call);
+    }
+
+    @KOTest
+    public void globalStringAvailable() throws Exception {
+        assertEquals("HTML/Java", GlobalString.init());
+        assertEquals("HTML/Java", Bodies.readGlobalString());
+    }
+
+    @KOTest
+    public void globalValueInCallbackAvailable() throws Exception {
+        final String[] value = { null, null };
+        Bodies.callback(new Runnable() {
+            @Override
+            public void run() {
+                value[0] = Global2String.init();
+                value[1] = Bodies.readGlobal2String();
+            }
+        });
+        assertEquals(value[0], "NetBeans", "As a returned value from defining method");
+        assertEquals(value[1], "NetBeans", "As read later by different method");
+    }
+    
+    private static class R implements Runnable {
+        int cnt;
+        private final Thread initThread;
+        
+        public R() {
+            initThread = Thread.currentThread();
+        }
+
+        @Override
+        public void run() {
+            assertEquals(initThread, Thread.currentThread(), "Expecting to run in " + initThread + " but running in " + Thread.currentThread());
+            cnt++;
+        }
+    }
+    
+    private static class C implements Callable<Boolean> {
+        private final boolean ret;
+
+        public C(boolean ret) {
+            this.ret = ret;
+        }
+        
+        @Override
+        public Boolean call() throws Exception {
+            return ret;
+        }
+    }
+    static void assertEquals(Object a, Object b, String msg) {
+        if (a == b) {
+            return;
+        }
+        if (a != null && a.equals(b)) {
+            return;
+        }
+        throw new AssertionError(msg);
+    }
+    private static void assertNotEquals(Object a, Object b, String msg) {
+        if (a == null) {
+            if (b == null) {
+                throw new AssertionError(msg);
+            }
+            return;
+        }
+        if (a.equals(b)) {
+            throw new AssertionError(msg);
+        }
+    }
+    static void assertEquals(Object a, Object b) {
+        if (a == b) {
+            return;
+        }
+        if (a != null && a.equals(b)) {
+            return;
+        }
+        throw new AssertionError("Expecting " + b + " but found " + a);
+    }
+    private static void fail(String msg) {
+        throw new AssertionError(msg);
+    }
+
+    private static void assertTrue(boolean c, String msg) {
+        if (!c) {
+            throw new AssertionError(msg);
+        }
+    }
+
+    private static void assertFalse(boolean c, String msg) {
+        if (c) {
+            throw new AssertionError(msg);
+        }
+    }
+
+    private static void assertNull(Object o, String msg) {
+        if (o != null) {
+            throw new AssertionError(msg);
+        }
+    }
+
+    static void assertNotNull(Object o, String msg) {
+        if (o == null) {
+            throw new AssertionError(msg);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/js/tests/Later.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/js/tests/Later.java b/json-tck/src/main/java/net/java/html/js/tests/Later.java
new file mode 100644
index 0000000..619de61
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/js/tests/Later.java
@@ -0,0 +1,65 @@
+/**
+ * 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.js.tests;
+
+import net.java.html.js.JavaScriptBody;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public final class Later {
+    volatile int call;
+
+    @JavaScriptBody(args = {  }, javacall = true, body = 
+        "var self = this;"
+        + "window.later = function() {"
+        + "  self.@net.java.html.js.tests.Later::call(I)(42);"
+       + "};"
+    )
+    native void register();
+    
+    void call(int value) {
+        this.call = value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/js/tests/Receiver.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/js/tests/Receiver.java b/json-tck/src/main/java/net/java/html/js/tests/Receiver.java
new file mode 100644
index 0000000..3ed831e
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/js/tests/Receiver.java
@@ -0,0 +1,80 @@
+/**
+ * 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.js.tests;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import net.java.html.js.JavaScriptBody;
+
+/**
+ */
+public final class Receiver {
+    private final Object fn;
+    Object value;
+    final Reference<Object> ref;
+
+    public Receiver(Object v) {
+        this.fn = initFn(v);
+        this.ref = new WeakReference<Object>(v);
+        this.value = this;
+    }
+    
+    public void apply() {
+        fnApply(fn, this);
+    }
+    
+    void set(Object v) {
+        value = v;
+    }
+    
+    @JavaScriptBody(args = { "v" }, keepAlive = false, javacall = true, 
+        body = "return function(rec) {\n"
+        + "  rec.@net.java.html.js.tests.Receiver::set(Ljava/lang/Object;)(v);\n"
+        + "};\n")
+    private static native Object initFn(Object v);
+    
+    @JavaScriptBody(args = { "fn", "thiz" }, body =
+        "fn(thiz);"
+    )
+    private static native void fnApply(Object fn, Receiver thiz);
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/js/tests/Sum.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/js/tests/Sum.java b/json-tck/src/main/java/net/java/html/js/tests/Sum.java
new file mode 100644
index 0000000..d995a7a
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/js/tests/Sum.java
@@ -0,0 +1,81 @@
+/**
+ * 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.js.tests;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public final class Sum {
+    public int sum(int a, int b) {
+        return a + b;
+    }
+    
+    public int sum(Object[] arr) {
+        int s = 0;
+        for (int i = 0; i < arr.length; i++) {
+            if (arr[i] instanceof Number) {
+                s += ((Number)arr[i]).intValue();
+            }
+        }
+        return s;
+    }
+
+    public int sumNonNull(Object[] arr) {
+        int s = 0;
+        for (int i = 0; i < arr.length; i++) {
+            if (arr[i] != null) {
+                s++;
+            }
+        }
+        return s;
+    }
+
+    public boolean checkNonNull(Object obj) {
+        return obj != null;
+    }
+    
+    public String all(boolean z, byte b, short s, int i, long l, float f, double d, char ch, String str) {
+        return "Ahoj" + z + b + s + i + l + f + d + ch + str;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/ConvertTypesTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/ConvertTypesTest.java b/json-tck/src/main/java/net/java/html/json/tests/ConvertTypesTest.java
new file mode 100644
index 0000000..bd558fd
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/ConvertTypesTest.java
@@ -0,0 +1,322 @@
+/**
+ * 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.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import net.java.html.BrwsrCtx;
+import net.java.html.json.Models;
+import org.netbeans.html.json.tck.KOTest;
+import static net.java.html.json.tests.Utils.assertEquals;
+import static net.java.html.json.tests.Utils.assertNull;
+import static net.java.html.json.tests.Utils.assertNotNull;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public final class ConvertTypesTest {
+    private static InputStream createIS(String prefix, boolean includeSex, boolean includeAddress, int array, String suffix)
+    throws UnsupportedEncodingException {
+        StringBuilder sb = new StringBuilder();
+        if (prefix != null) {
+            sb.append(prefix);
+        }
+        int repeat;
+        if (array != -1) {
+            sb.append("[\n");
+            repeat = array;
+        } else {
+            repeat = 1;
+        }
+        for (int i = 0; i < repeat; i++) {
+            sb.append("{ \"firstName\" : \"son\",\n");
+            sb.append("  \"lastName\" : \"dj\" \n");
+            if (includeSex) {
+                sb.append(",  \"sex\" : \"MALE\" \n");
+            }
+            if (includeAddress) {
+                sb.append(",  \"address\" : { \"street\" : \"Schnirchova\" } \n");
+            }
+            sb.append("}\n");
+            if (i < array - 1) {
+                sb.append(",");
+            }
+        }
+        if (array != -1) {
+            sb.append(']');
+        }
+        if (suffix != null) {
+            sb.append(suffix);
+        }
+        return new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
+    }
+    private static Object createJSON(boolean includeSex) 
+    throws UnsupportedEncodingException {
+        Map<String,Object> map = new HashMap<String,Object>();
+        map.put("firstName", "son");
+        map.put("lastName", "dj");
+        if (includeSex) {
+            map.put("sex", "MALE");
+        }
+        return Utils.createObject(map, ConvertTypesTest.class);
+    }
+    
+    @KOTest
+    public void testConvertToPeople() throws Exception {
+        final Object o = createJSON(true);
+        
+        Person p = Models.fromRaw(newContext(), Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertEquals(Sex.MALE, p.getSex(), "Sex: " + p.getSex());
+    }
+
+    @KOTest
+    public void parseConvertToPeople() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, true, false, -1, null);
+        
+        Person p = Models.parse(c, Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertEquals(Sex.MALE, p.getSex(), "Sex: " + p.getSex());
+    }
+    
+    @KOTest
+    public void parseConvertToPeopleWithAddress() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, true, true, -1, null);
+        
+        Person p = Models.parse(c, Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertEquals(Sex.MALE, p.getSex(), "Sex: " + p.getSex());
+        assertNotNull(p.getAddress(), "Some address provided");
+        assertEquals(p.getAddress().getStreet(), "Schnirchova", "Is Schnirchova: " + p.getAddress());
+    }
+
+    @KOTest
+    public void parseConvertToPeopleWithAddressIntoAnArray() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, true, true, -1, null);
+        
+        List<Person> arr = new ArrayList<Person>();
+        Models.parse(c, Person.class, o, arr);
+        
+        assertEquals(arr.size(), 1, "There is one item in " + arr);
+        
+        Person p = arr.get(0);
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertEquals(Sex.MALE, p.getSex(), "Sex: " + p.getSex());
+        assertNotNull(p.getAddress() , "Some address provided");
+        assertEquals(p.getAddress().getStreet(), "Schnirchova", "Is Schnirchova: " + p.getAddress());
+    }
+    
+    @KOTest 
+    public void parseNullValue() throws Exception {
+        final BrwsrCtx c = newContext();
+        
+        StringBuilder sb = new StringBuilder();
+        sb.append("{ \"firstName\" : \"son\",\n");
+        sb.append("  \"lastName\" : null } \n");  
+        
+        final ByteArrayInputStream is = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
+        Person p = Models.parse(c, Person.class, is);
+
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertNull(p.getLastName(), "Last name: " + p.getLastName());
+    }
+
+    @KOTest 
+    public void parseNullArrayValue() throws Exception {
+        final BrwsrCtx c = newContext();
+        
+        StringBuilder sb = new StringBuilder();
+        sb.append("[ null, { \"firstName\" : \"son\",\n");
+        sb.append("  \"lastName\" : null } ]\n");  
+        
+        final ByteArrayInputStream is = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
+        List<Person> arr = new ArrayList<Person>();
+        Models.parse(c, Person.class, is, arr);
+        
+        assertEquals(arr.size(), 2, "There are two items in " + arr);
+        assertNull(arr.get(0), "first is null " + arr);
+        
+        Person p = arr.get(1);
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertNull(p.getLastName(), "Last name: " + p.getLastName());
+    }
+
+    @KOTest
+    public void testConvertToPeopleWithoutSex() throws Exception {
+        final Object o = createJSON(false);
+        
+        Person p = Models.fromRaw(newContext(), Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertNull(p.getSex(), "No sex: " + p.getSex());
+    }
+    
+    @KOTest
+    public void parseConvertToPeopleWithoutSex() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, false, false, -1, null);
+        Person p = Models.parse(c, Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertNull(p.getSex(), "No sex: " + p.getSex());
+    }
+    
+    @KOTest
+    public void parseConvertToPeopleWithAddressOnArray() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, true, true, 1, null);
+        
+        Person p = Models.parse(c, Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertEquals(Sex.MALE, p.getSex(), "Sex: " + p.getSex());
+        assertNotNull(p.getAddress(), "Some address provided");
+        assertEquals(p.getAddress().getStreet(), "Schnirchova", "Is Schnirchova: " + p.getAddress());
+    }
+
+    @KOTest
+    public void parseConvertToPeopleWithoutSexOnArray() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, false, false, 1, null);
+        Person p = Models.parse(c, Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertNull(p.getSex(), "No sex: " + p.getSex());
+    }
+
+    @KOTest
+    public void parseFirstElementFromAbiggerArray() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, false, false, 5, null);
+        Person p = Models.parse(c, Person.class, o);
+        
+        assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+        assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+        assertNull(p.getSex(), "No sex: " + p.getSex());
+    }
+
+    @KOTest
+    public void parseAllElementFromAbiggerArray() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, false, false, 5, null);
+        
+        List<Person> res = new ArrayList<Person>();
+        Models.parse(c, Person.class, o, res);
+        
+        assertEquals(res.size(), 5, "Five elements found" + res);
+        
+        for (Person p : res) {
+            assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+            assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+            assertNull(p.getSex(), "No sex: " + p.getSex());
+        }
+    }
+
+    @KOTest
+    public void parseFiveElementsAsAnArray() throws Exception {
+        doParseInnerArray(5, 5);
+    }
+
+    @KOTest
+    public void parseInnerElementAsAnArray() throws Exception {
+        doParseInnerArray(-1, 1);
+    }
+    private void doParseInnerArray(int array, int expect) throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS("{ \"info\" : ", false, false, array, "}");
+
+        List<People> res = new ArrayList<People>();
+        Models.parse(c, People.class, o, res);
+
+        assertEquals(res.size(), 1, "One people" + res);
+
+        int cnt = 0;
+        for (Person p : res.get(0).getInfo()) {
+            assertEquals("son", p.getFirstName(), "First name: " + p.getFirstName());
+            assertEquals("dj", p.getLastName(), "Last name: " + p.getLastName());
+            assertNull(p.getSex(), "No sex: " + p.getSex());
+            cnt++;
+        }
+
+        assertEquals(cnt, expect, "Person found in info");
+    }
+    
+    @KOTest
+    public void parseOnEmptyArray() throws Exception {
+        final BrwsrCtx c = newContext();
+        final InputStream o = createIS(null, false, false, 0, null);
+        
+        try {
+            Models.parse(c, Person.class, o);
+        } catch (EOFException ex) {
+            // OK
+            return;
+        }
+        throw new IllegalStateException("Should throw end of file exception, as the array is empty");
+    }
+    
+    private static BrwsrCtx newContext() {
+        return Utils.newContext(ConvertTypesTest.class);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java
new file mode 100644
index 0000000..1cb2b7c
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java
@@ -0,0 +1,139 @@
+/**
+ * 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.tests;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import net.java.html.BrwsrCtx;
+import net.java.html.json.Model;
+import net.java.html.json.Models;
+import net.java.html.json.Property;
+import org.netbeans.html.json.tck.KOTest;
+import static net.java.html.json.tests.Utils.assertEquals;
+
+@Model(className = "GC", properties = {
+    @Property(name = "all", type = Fullname.class, array = true)
+})
+public class GCKnockoutTest {
+    @Model(className = "Fullname", properties = {
+        @Property(name = "firstName", type = String.class),
+        @Property(name = "lastName", type = String.class)
+    })
+    static class FullnameCntrl {
+    }
+    
+    @KOTest public void noLongerNeededArrayElementsCanDisappear() throws Exception {
+        BrwsrCtx ctx = Utils.newContext(GCKnockoutTest.class);
+        Object exp = Utils.exposeHTML(GCKnockoutTest.class,
+            "<ul id='ul' data-bind='foreach: all'>\n"
+            + "  <li data-bind='text: firstName'/>\n"
+            + "</ul>\n"
+        );
+        try {
+            GC m = Models.bind(new GC(), ctx);
+            m.getAll().add(new Fullname("Jarda", "Tulach"));
+            Models.applyBindings(m);
+
+            int cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
+            assertEquals(cnt, 1, "One child, but was " + cnt);
+
+            m.getAll().add(new Fullname("HTML", "Java"));
+
+            cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Now two " + cnt);
+
+            Fullname removed = m.getAll().get(0);
+            m.getAll().remove(0);
+
+            cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
+            assertEquals(cnt, 1, "Again One " + cnt);
+
+            Reference<?> ref = new WeakReference<Object>(removed);
+            removed = null;
+            assertGC(ref, "Can removed object disappear?");
+            
+            ref = new WeakReference<Object>(m);
+            m = null;
+            assertNotGC(ref, "Root model cannot GC");
+        } finally {
+            Utils.exposeHTML(GCKnockoutTest.class, "");
+        }
+        
+    }
+    
+    private void assertGC(Reference<?> ref, String msg) throws Exception {
+        for (int i = 0; i < 100; i++) {
+            if (ref.get() == null) {
+                return;
+            }
+            String gc = "var max = arguments[0];\n"
+                    +  "var arr = [];\n"
+                    + "for (var i = 0; i < max; i++) {\n"
+                    + "  arr.push(i);\n"
+                    + "}\n"
+                    + "return arr.length;";
+            Object cnt = Utils.executeScript(GCKnockoutTest.class, gc, Math.pow(2.0, i));
+            System.gc();
+            System.runFinalization();
+        }
+        throw new OutOfMemoryError(msg);
+    }
+    
+    private void assertNotGC(Reference<?> ref, String msg) throws Exception {
+        for (int i = 0; i < 10; i++) {
+            if (ref.get() == null) {
+                throw new IllegalStateException(msg);
+            }
+            String gc = "var max = arguments[0];\n"
+                    +  "var arr = [];\n"
+                    + "for (var i = 0; i < max; i++) {\n"
+                    + "  arr.push(i);\n"
+                    + "}\n"
+                    + "return arr.length;";
+            Object cnt = Utils.executeScript(GCKnockoutTest.class, gc, Math.pow(2.0, i));
+            System.gc();
+            System.runFinalization();
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java b/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java
new file mode 100644
index 0000000..0021e2b
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java
@@ -0,0 +1,637 @@
+/**
+ * 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.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import net.java.html.BrwsrCtx;
+import net.java.html.json.Model;
+import net.java.html.json.ModelOperation;
+import net.java.html.json.Models;
+import net.java.html.json.OnReceive;
+import net.java.html.json.Property;
+import static net.java.html.json.tests.Utils.assertEquals;
+import static net.java.html.json.tests.Utils.assertNotNull;
+import static net.java.html.json.tests.Utils.assertNull;
+import static net.java.html.json.tests.Utils.assertTrue;
+import org.netbeans.html.json.tck.KOTest;
+
+/** Need to verify that models produce reasonable JSON objects.
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "JSONik", targetId = "", properties = {
+    @Property(name = "fetched", type = Person.class),
+    @Property(name = "fetchedCount", type = int.class),
+    @Property(name = "fetchedResponse", type = String.class),
+    @Property(name = "fetchedSex", type = Sex.class, array = true)
+})
+public final class JSONTest {
+    private JSONik js;
+    private Integer orig;
+    private String url;
+
+    @ModelOperation static void assignFetched(JSONik m, Person p) {
+        m.setFetched(p);
+    }
+    private BrwsrCtx ctx;
+
+    @KOTest public void toJSONInABrowser() throws Throwable {
+        Person p = Models.bind(new Person(), newContext());
+        p.setSex(Sex.MALE);
+        p.setFirstName("Jarda");
+        p.setLastName("Tulach");
+
+        Object json;
+        try {
+            json = parseJSON(p.toString());
+        } catch (Throwable ex) {
+            throw new IllegalStateException("Can't parse " + p).initCause(ex);
+        }
+
+        Person p2 = Models.fromRaw(newContext(), Person.class, json);
+
+        assertEquals(p2.getFirstName(), p.getFirstName(),
+            "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName());
+    }
+
+    @KOTest public void toJSONWithEscapeCharactersInABrowser() throws Throwable {
+        Person p = Models.bind(new Person(), newContext());
+        p.setSex(Sex.MALE);
+        p.setFirstName("/*\n * Copyright (c) 2013");
+
+
+        final String txt = p.toString();
+        Object json;
+        try {
+            json = parseJSON(txt);
+        } catch (Throwable ex) {
+            throw new IllegalStateException("Can't parse " + txt).initCause(ex);
+        }
+
+        Person p2 = Models.fromRaw(newContext(), Person.class, json);
+
+        assertEquals(p2.getFirstName(), p.getFirstName(),
+            "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName());
+    }
+
+    @KOTest public void toJSONWithDoubleSlashInABrowser() throws Throwable {
+        Person p = Models.bind(new Person(), newContext());
+        p.setSex(Sex.MALE);
+        p.setFirstName("/*\\n * Copyright (c) 2013");
+
+
+        final String txt = p.toString();
+        Object json;
+        try {
+            json = parseJSON(txt);
+        } catch (Throwable ex) {
+            throw new IllegalStateException("Can't parse " + txt).initCause(ex);
+        }
+
+        Person p2 = Models.fromRaw(newContext(), Person.class, json);
+
+        assertEquals(p2.getFirstName(), p.getFirstName(),
+            "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName());
+    }
+
+    @KOTest public void toJSONWithApostrophInABrowser() throws Throwable {
+        Person p = Models.bind(new Person(), newContext());
+        p.setSex(Sex.MALE);
+        p.setFirstName("Jimmy 'Jim' Rambo");
+
+
+        final String txt = p.toString();
+        Object json;
+        try {
+            json = parseJSON(txt);
+        } catch (Throwable ex) {
+            throw new IllegalStateException("Can't parse " + txt).initCause(ex);
+        }
+
+        Person p2 = Models.fromRaw(newContext(), Person.class, json);
+
+        assertEquals(p2.getFirstName(), p.getFirstName(),
+            "Should be the same: " + p.getFirstName() + " != " + p2.getFirstName());
+    }
+
+    private static BrwsrCtx onCallback;
+
+    @OnReceive(url="{url}")
+    static void fetch(JSONik model, Person p) {
+        model.setFetched(p);
+        onCallback = BrwsrCtx.findDefault(model.getClass());
+    }
+
+    @OnReceive(url="{url}")
+    static void fetchPlain(JSONik model, String p) {
+        onCallback = BrwsrCtx.findDefault(model.getClass());
+        model.setFetchedResponse(p);
+    }
+
+    @OnReceive(url="{url}", onError = "setMessage")
+    static void fetchArray(JSONik model, Person[] p) {
+        model.setFetchedCount(p.length);
+        model.setFetched(p[0]);
+        onCallback = BrwsrCtx.findDefault(model.getClass());
+    }
+
+    static void setMessage(JSONik m, Exception t) {
+        assertNotNull(t, "Exception provided");
+        m.setFetchedResponse("Exception");
+    }
+
+    @OnReceive(url="{url}")
+    static void fetchPeople(JSONik model, People p) {
+        final int size = p.getInfo().size();
+        if (size > 0) {
+            model.setFetched(p.getInfo().get(0));
+        }
+        model.setFetchedCount(size);
+    }
+
+    @OnReceive(url="{url}")
+    static void fetchPeopleAge(JSONik model, People p) {
+        int sum = 0;
+        for (int a : p.getAge()) {
+            sum += a;
+        }
+        model.setFetchedCount(sum);
+    }
+
+    @KOTest public void loadAndParseJSON() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}",
+                "application/json"
+            );
+            js = Models.bind(new JSONik(), ctx = newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            js.fetch(url);
+        }
+
+        Person p = js.getFetched();
+        if (p == null) {
+            throw new InterruptedException();
+        }
+
+        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName());
+        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+
+        assertEquals(ctx, onCallback, "Context is the same");
+    }
+
+    @KOTest public void loadAndParsePlainText() throws Exception {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}",
+                "text/plain"
+            );
+            js = Models.bind(new JSONik(), ctx = newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            js.fetchPlain(url);
+        }
+
+        String s = js.getFetchedResponse();
+        if (s == null) {
+            throw new InterruptedException();
+        }
+
+        assertTrue(s.contains("Sitar"), "The text contains Sitar value: " + s);
+        assertTrue(s.contains("MALE"), "The text contains MALE value: " + s);
+
+        Person p = Models.parse(ctx, Person.class, new ByteArrayInputStream(s.getBytes()));
+
+        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName());
+        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+
+        assertEquals(ctx, onCallback, "Same context");
+    }
+
+    @KOTest public void loadAndParsePlainTextOnArray() throws Exception {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "[ {'firstName': 'Sitar', 'sex': 'MALE'} ]",
+                "text/plain"
+            );
+            js = Models.bind(new JSONik(), ctx = newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            js.fetchPlain(url);
+        }
+
+        String s = js.getFetchedResponse();
+        if (s == null) {
+            throw new InterruptedException();
+        }
+
+        assertTrue(s.contains("Sitar"), "The text contains Sitar value: " + s);
+        assertTrue(s.contains("MALE"), "The text contains MALE value: " + s);
+
+        Person p = Models.parse(ctx, Person.class, new ByteArrayInputStream(s.getBytes()));
+
+        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName());
+        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+
+        assertEquals(ctx, onCallback, "Same context");
+    }
+
+    @OnReceive(url="{url}?callme={me}", jsonp = "me")
+    static void fetchViaJSONP(JSONik model, Person p) {
+        model.setFetched(p);
+    }
+
+    @KOTest public void loadAndParseJSONP() throws InterruptedException, Exception {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "$0({'firstName': 'Mitar', 'sex': 'MALE'})",
+                "application/javascript",
+                "callme"
+            );
+            orig = scriptElements();
+            assertTrue(orig > 0, "There should be some scripts on the page");
+
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            js.fetchViaJSONP(url);
+        }
+
+        Person p = js.getFetched();
+        if (p == null) {
+            throw new InterruptedException();
+        }
+
+        assertEquals("Mitar", p.getFirstName(), "Unexpected: " + p.getFirstName());
+        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+
+        int now = scriptElements();
+
+        assertEquals(orig, now, "The set of elements is unchanged. Delta: " + (now - orig));
+    }
+
+
+
+    @OnReceive(url="{url}", method = "PUT", data = Person.class)
+    static void putPerson(JSONik model, String reply) {
+        model.setFetchedCount(1);
+        model.setFetchedResponse(reply);
+    }
+
+    @KOTest public void putPeopleUsesRightMethod() throws InterruptedException, Exception {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "$0\n$1",
+                "text/plain",
+                "http.method", "http.requestBody"
+            );
+            orig = scriptElements();
+            assertTrue(orig > 0, "There should be some scripts on the page");
+
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            Person p = Models.bind(new Person(), BrwsrCtx.EMPTY);
+            p.setFirstName("Jarda");
+            js.putPerson(url, p);
+        }
+
+        int cnt = js.getFetchedCount();
+        if (cnt == 0) {
+            throw new InterruptedException();
+        }
+        String res = js.getFetchedResponse();
+        int line = res.indexOf('\n');
+        String msg;
+        if (line >= 0) {
+            msg = res.substring(line + 1);
+            res = res.substring(0, line);
+        } else {
+            msg = res;
+        }
+
+        assertEquals("PUT", res, "Server was queried with PUT method: " + js.getFetchedResponse());
+
+        assertTrue(msg.contains("Jarda"), "Data transferred to the server: " + msg);
+    }
+
+    private static int scriptElements() throws Exception {
+        return ((Number)Utils.executeScript(
+            JSONTest.class,
+            "return window.document.getElementsByTagName('script').length;")).intValue();
+    }
+
+    private static Object parseJSON(String s) throws Exception {
+        return Utils.executeScript(
+            JSONTest.class,
+            "return window.JSON.parse(arguments[0]);", s);
+    }
+
+    @KOTest public void loadAndParseJSONSentToArray() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}",
+                "application/json"
+            );
+
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            js.fetchArray(url);
+        }
+
+        Person p = js.getFetched();
+        if (p == null) {
+            throw new InterruptedException();
+        }
+
+        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName());
+        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+    }
+
+    @KOTest public void loadAndParseJSONArraySingle() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "[{'firstName': 'Gitar', 'sex': 'FEMALE'}]",
+                "application/json"
+            );
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            js.fetch(url);
+        }
+
+        Person p = js.getFetched();
+        if (p == null) {
+            throw new InterruptedException();
+        }
+
+        assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + p.getFirstName());
+        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex());
+    }
+
+    @KOTest public void loadAndParseArrayInPeople() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'info':[{'firstName': 'Gitar', 'sex': 'FEMALE'}]}",
+                "application/json"
+            );
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            js.fetchPeople(url);
+        }
+
+        if (0 == js.getFetchedCount()) {
+            throw new InterruptedException();
+        }
+
+        assertEquals(js.getFetchedCount(), 1, "One person loaded: " + js.getFetchedCount());
+
+        Person p = js.getFetched();
+
+        assertNotNull(p, "We should get our person back: " + p);
+        assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + p.getFirstName());
+        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex());
+    }
+
+    @KOTest public void loadAndParseArrayInPeopleWithHeaders() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'info':[{'firstName': '$0$1$2$3$4', 'sex': 'FEMALE'}]}",
+                "application/json",
+                "http.header.Easy",
+                "http.header.H-a!r*d^e.r",
+                "http.header.Repeat-ed",
+                "http.header.Repeat*ed",
+                "http.header.Same-URL"
+            );
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            js.fetchPeopleWithHeaders(url, "easy", "harder", "rep");
+        }
+
+        if (0 == js.getFetchedCount()) {
+            throw new InterruptedException();
+        }
+
+        assertEquals(js.getFetchedCount(), 1, "One person loaded: " + js.getFetchedCount());
+
+        Person p = js.getFetched();
+
+        assertNotNull(p, "We should get our person back: " + p);
+        assertEquals("easyharderreprep" + url, p.getFirstName(), "Expecting header mess: " + p.getFirstName());
+        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex());
+    }
+
+    @OnReceive(url="{url}", headers={
+        "Easy: {easy}",
+        "H-a!r*d^e.r: {harder}",
+        "Repeat-ed: {rep}",
+        "Repeat*ed: {rep}",
+        "Same-URL: {url}"
+    })
+    static void fetchPeopleWithHeaders(JSONik model, People p) {
+        final int size = p.getInfo().size();
+        if (size > 0) {
+            model.setFetched(p.getInfo().get(0));
+        }
+        model.setFetchedCount(size);
+    }
+
+    @KOTest public void loadAndParseArrayOfIntegers() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'age':[1, 2, 3]}",
+                "application/json"
+            );
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            js.fetchPeopleAge(url);
+        }
+
+        if (0 == js.getFetchedCount()) {
+            throw new InterruptedException();
+        }
+
+        assertEquals(js.getFetchedCount(), 6, "1 + 2 + 3 is " + js.getFetchedCount());
+    }
+
+    @OnReceive(url="{url}")
+    static void fetchPeopleSex(JSONik model, People p) {
+        model.setFetchedCount(1);
+        model.getFetchedSex().addAll(p.getSex());
+    }
+
+    @KOTest public void loadAndParseArrayOfEnums() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'sex':['FEMALE', 'MALE', 'MALE']}",
+                "application/json"
+            );
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+
+            js.fetchPeopleSex(url);
+        }
+
+        if (0 == js.getFetchedCount()) {
+            throw new InterruptedException();
+        }
+
+        assertEquals(js.getFetchedCount(), 1, "Loaded");
+
+        assertEquals(js.getFetchedSex().size(), 3, "Three values " + js.getFetchedSex());
+        assertEquals(js.getFetchedSex().get(0), Sex.FEMALE, "Female first " + js.getFetchedSex());
+        assertEquals(js.getFetchedSex().get(1), Sex.MALE, "male 2nd " + js.getFetchedSex());
+        assertEquals(js.getFetchedSex().get(2), Sex.MALE, "male 3rd " + js.getFetchedSex());
+    }
+
+    @KOTest public void loadAndParseJSONArray() throws InterruptedException {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "[{'firstName': 'Gitar', 'sex': 'FEMALE'},"
+                + "{'firstName': 'Peter', 'sex': 'MALE'}"
+                + "]",
+                "application/json"
+            );
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+            js.setFetched(null);
+
+            js.fetchArray(url);
+        }
+
+
+        Person p = js.getFetched();
+        if (p == null) {
+            throw new InterruptedException();
+        }
+
+        assertEquals(js.getFetchedCount(), 2, "We got two values: " + js.getFetchedCount());
+        assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + p.getFirstName());
+        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex());
+    }
+
+    @KOTest public void loadError() throws InterruptedException {
+        if (js == null) {
+            js = Models.bind(new JSONik(), newContext());
+            js.applyBindings();
+            js.setFetched(null);
+
+            js.fetchArray("http://127.0.0.1:54253/does/not/exist.txt");
+        }
+
+
+        if (js.getFetchedResponse() == null) {
+            throw new InterruptedException();
+        }
+
+        assertEquals("Exception", js.getFetchedResponse(), "Response " + js.getFetchedResponse());
+    }
+
+    @Model(className = "NameAndValue", properties = {
+        @Property(name = "name", type = String.class),
+        @Property(name = "value", type = long.class),
+        @Property(name = "small", type = byte.class)
+    })
+    static class NandV {
+    }
+
+    @KOTest public void parseNullNumber() throws Exception {
+        String txt = "{ \"name\":\"M\" }";
+        ByteArrayInputStream is = new ByteArrayInputStream(txt.getBytes("UTF-8"));
+        NameAndValue v = Models.parse(newContext(), NameAndValue.class, is);
+        assertEquals("M", v.getName(), "Name is 'M': " + v.getName());
+        assertEquals(0L, v.getValue(), "Value is empty: " + v.getValue());
+        assertEquals((byte)0, v.getSmall(), "Small value is empty: " + v.getSmall());
+    }
+
+    @KOTest public void deserializeWrongEnum() throws Exception {
+        PrintStream prev;
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        try {
+            prev = System.err;
+            System.setErr(new PrintStream(err));
+        } catch (SecurityException e) {
+            err = null;
+            prev = null;
+        } catch (LinkageError e) {
+            err = null;
+            prev = null;
+        }
+
+        String str = "{ \"sex\" : \"unknown\" }";
+        ByteArrayInputStream is = new ByteArrayInputStream(str.getBytes("UTF-8"));
+        Person p = Models.parse(newContext(), Person.class, is);
+        assertNull(p.getSex(), "Wrong sex means null, but was: " + p.getSex());
+
+        if (err != null) {
+            assertTrue(err.toString().contains("unknown") && err.toString().contains("Sex"), "Expecting error: " + err.toString());
+        }
+        if (prev != null) {
+            try {
+                System.setErr(prev);
+            } catch (LinkageError e) {
+                // ignore
+            }
+        }
+    }
+
+
+    private static BrwsrCtx newContext() {
+        return Utils.newContext(JSONTest.class);
+    }
+
+}