#258088: Merging @Property(mutable=false) into main development line
authorJaroslav Tulach <jtulach@netbeans.org>
Mon, 29 Feb 2016 05:33:31 +0100
changeset 10583554078c32ce
parent 1053 ed4b25eb66f3
parent 1056 a753f36c67fc
child 1059 c5223a12e761
#258088: Merging @Property(mutable=false) into main development line
     1.1 --- a/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java	Mon Feb 15 05:27:28 2016 +0100
     1.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java	Mon Feb 29 05:33:31 2016 +0100
     1.3 @@ -73,14 +73,14 @@
     1.4      @Property(name="choice", type=KnockoutTest.Choice.class),
     1.5      @Property(name="archetype", type=ArchetypeData.class),
     1.6      @Property(name="archetypes", type=ArchetypeData.class, array = true),
     1.7 -}) 
     1.8 +})
     1.9  public final class KnockoutTest {
    1.10      private KnockoutModel js;
    1.11 -    
    1.12 +
    1.13      enum Choice {
    1.14          A, B;
    1.15      }
    1.16 -    
    1.17 +
    1.18      @ComputedProperty static List<Integer> resultLengths(List<String> results) {
    1.19          Integer[] arr = new Integer[results.size()];
    1.20          for (int i = 0; i < arr.length; i++) {
    1.21 @@ -88,9 +88,9 @@
    1.22          }
    1.23          return Arrays.asList(arr);
    1.24      }
    1.25 -    
    1.26 +
    1.27      @KOTest public void modifyValueAssertChangeInModelOnEnum() throws Throwable {
    1.28 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
    1.29 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
    1.30              "Latitude: <input id='input' data-bind=\"value: choice\"></input>\n"
    1.31          );
    1.32          try {
    1.33 @@ -143,9 +143,9 @@
    1.34              Utils.exposeHTML(KnockoutTest.class, "");
    1.35          }
    1.36      }
    1.37 -    
    1.38 +
    1.39      @KOTest public void modifyValueAssertChangeInModelOnDouble() throws Throwable {
    1.40 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
    1.41 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
    1.42              "Latitude: <input id='input' data-bind=\"value: latitude\"></input>\n"
    1.43          );
    1.44          try {
    1.45 @@ -167,7 +167,7 @@
    1.46              Utils.exposeHTML(KnockoutTest.class, "");
    1.47          }
    1.48      }
    1.49 -    
    1.50 +
    1.51      @KOTest public void rawObject() throws Exception {
    1.52          if (js == null) {
    1.53              final BrwsrCtx ctx = newContext();
    1.54 @@ -200,7 +200,7 @@
    1.55  
    1.56              p1.setFirstName("Ondra");
    1.57              assertEquals(p1.getFirstName(), "Ondra", "1st name updated in original object");
    1.58 -            
    1.59 +
    1.60              js.getPeople().add(p1);
    1.61          }
    1.62  
    1.63 @@ -239,9 +239,9 @@
    1.64              Utils.exposeHTML(KnockoutTest.class, "");
    1.65          }
    1.66      }
    1.67 -    
    1.68 +
    1.69      @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
    1.70 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
    1.71 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
    1.72              "Latitude: <input id='input' data-bind=\"value: enabled\"></input>\n"
    1.73          );
    1.74          try {
    1.75 @@ -263,9 +263,9 @@
    1.76              Utils.exposeHTML(KnockoutTest.class, "");
    1.77          }
    1.78      }
    1.79 -    
    1.80 +
    1.81      @KOTest public void modifyValueAssertChangeInModel() throws Exception {
    1.82 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
    1.83 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
    1.84              "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
    1.85              "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
    1.86              "<button id=\"hello\">Say Hello!</button>\n"
    1.87 @@ -287,7 +287,7 @@
    1.88              Utils.exposeHTML(KnockoutTest.class, "");
    1.89          }
    1.90      }
    1.91 -    
    1.92 +
    1.93      private static String getSetSelected(int index, Object value) throws Exception {
    1.94          String s = "var index = arguments[0];\n"
    1.95          + "var value = arguments[1];\n"
    1.96 @@ -305,7 +305,7 @@
    1.97          );
    1.98          return ret == null ? null : ret.toString();
    1.99      }
   1.100 -    
   1.101 +
   1.102      @Model(className = "ArchetypeData", properties = {
   1.103          @Property(name = "artifactId", type = String.class),
   1.104          @Property(name = "groupId", type = String.class),
   1.105 @@ -316,35 +316,35 @@
   1.106      })
   1.107      static class ArchModel {
   1.108      }
   1.109 -    
   1.110 +
   1.111      @KOTest public void selectWorksOnModels() throws Exception {
   1.112          if (js == null) {
   1.113 -            Utils.exposeHTML(KnockoutTest.class, 
   1.114 +            Utils.exposeHTML(KnockoutTest.class,
   1.115                  "<select id='input' data-bind=\"options: archetypes,\n" +
   1.116  "                       optionsText: 'name',\n" +
   1.117  "                       value: archetype\">\n" +
   1.118  "                  </select>\n" +
   1.119  ""
   1.120              );
   1.121 -            
   1.122 +
   1.123              js = Models.bind(new KnockoutModel(), newContext());
   1.124              js.getArchetypes().add(new ArchetypeData("ko4j", "org.netbeans.html", "0.8.3", "ko4j", "ko4j", null));
   1.125              js.getArchetypes().add(new ArchetypeData("crud", "org.netbeans.html", "0.8.3", "crud", "crud", null));
   1.126              js.getArchetypes().add(new ArchetypeData("3rd", "org.netbeans.html", "0.8.3", "3rd", "3rd", null));
   1.127              js.setArchetype(js.getArchetypes().get(1));
   1.128              js.applyBindings();
   1.129 -            
   1.130 +
   1.131              String v = getSetSelected(0, null);
   1.132              assertEquals("crud", v, "Second index (e.g. crud) is selected: " + v);
   1.133 -            
   1.134 +
   1.135              String sel = getSetSelected(2, Models.toRaw(js.getArchetypes().get(2)));
   1.136              assertEquals("3rd", sel, "3rd is selected now: " + sel);
   1.137          }
   1.138 -        
   1.139 +
   1.140          if (js.getArchetype() != js.getArchetypes().get(2)) {
   1.141              throw new InterruptedException();
   1.142          }
   1.143 -        
   1.144 +
   1.145          Utils.exposeHTML(KnockoutTest.class, "");
   1.146      }
   1.147  
   1.148 @@ -374,22 +374,22 @@
   1.149          assertEquals("org.netbeans.html", v, "groupId has been changed");
   1.150          Utils.exposeHTML(KnockoutTest.class, "");
   1.151      }
   1.152 -    
   1.153 +
   1.154      @KOTest public void modifyValueAssertAsyncChangeInModel() throws Exception {
   1.155          if (js == null) {
   1.156 -            Utils.exposeHTML(KnockoutTest.class, 
   1.157 +            Utils.exposeHTML(KnockoutTest.class,
   1.158                  "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
   1.159                  "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
   1.160                  "<button id=\"hello\">Say Hello!</button>\n"
   1.161              );
   1.162 -            
   1.163 +
   1.164              js = Models.bind(new KnockoutModel(), newContext());
   1.165              js.setName("Kukuc");
   1.166              js.applyBindings();
   1.167 -            
   1.168 +
   1.169              String v = getSetInput("input", null);
   1.170              assertEquals("Kukuc", v, "Value is really kukuc: " + v);
   1.171 -            
   1.172 +
   1.173              Timer t = new Timer("Set to Jardo");
   1.174              t.schedule(new TimerTask() {
   1.175                  @Override
   1.176 @@ -398,15 +398,100 @@
   1.177                  }
   1.178              }, 1);
   1.179          }
   1.180 -        
   1.181 +
   1.182          String v = getSetInput("input", null);
   1.183          if (!"Jardo".equals(v)) {
   1.184              throw new InterruptedException();
   1.185          }
   1.186 -        
   1.187 +
   1.188          Utils.exposeHTML(KnockoutTest.class, "");
   1.189      }
   1.190 -    
   1.191 +
   1.192 +    @Model(className = "ConstantModel", targetId = "", builder = "assign", properties = {
   1.193 +        @Property(name = "doubleValue", mutable = false, type = double.class),
   1.194 +        @Property(name = "longValue", mutable = false, type = long.class),
   1.195 +        @Property(name = "stringValue", mutable = false, type = String.class),
   1.196 +        @Property(name = "boolValue", mutable = false, type = boolean.class),
   1.197 +        @Property(name = "intArray", mutable = false, type = int.class, array = true),
   1.198 +    })
   1.199 +    static class ConstantCntrl {
   1.200 +    }
   1.201 +
   1.202 +    @KOTest public void nonMutableDouble() throws Exception {
   1.203 +        Utils.exposeHTML(KnockoutTest.class,
   1.204 +            "Type: <input id='input' data-bind=\"value: typeof doubleValue\"></input>\n"
   1.205 +        );
   1.206 +
   1.207 +        ConstantModel model = Models.bind(new ConstantModel(), newContext());
   1.208 +        model.assignStringValue("Hello").assignDoubleValue(10.0);
   1.209 +        model.applyBindings();
   1.210 +
   1.211 +        String v = getSetInput("input", null);
   1.212 +        assertEquals(v, "number", "Right type found: " + v);
   1.213 +
   1.214 +        Utils.exposeHTML(KnockoutTest.class, "");
   1.215 +    }
   1.216 +
   1.217 +    @KOTest public void nonMutableString() throws Exception {
   1.218 +        Utils.exposeHTML(KnockoutTest.class,
   1.219 +            "Type: <input id='input' data-bind=\"value: typeof stringValue\"></input>\n"
   1.220 +        );
   1.221 +
   1.222 +        ConstantModel model = Models.bind(new ConstantModel(), newContext());
   1.223 +        model.assignStringValue("Hello").assignDoubleValue(10.0);
   1.224 +        model.applyBindings();
   1.225 +
   1.226 +        String v = getSetInput("input", null);
   1.227 +        assertEquals(v, "string", "Right type found: " + v);
   1.228 +
   1.229 +        Utils.exposeHTML(KnockoutTest.class, "");
   1.230 +    }
   1.231 +
   1.232 +    @KOTest public void nonMutableBoolean() throws Exception {
   1.233 +        Utils.exposeHTML(KnockoutTest.class,
   1.234 +            "Type: <input id='input' data-bind=\"value: typeof boolValue\"></input>\n"
   1.235 +        );
   1.236 +
   1.237 +        ConstantModel model = Models.bind(new ConstantModel(), newContext());
   1.238 +        model.assignStringValue("Hello").assignBoolValue(true);
   1.239 +        model.applyBindings();
   1.240 +
   1.241 +        String v = getSetInput("input", null);
   1.242 +        assertEquals(v, "boolean", "Right type found: " + v);
   1.243 +
   1.244 +        Utils.exposeHTML(KnockoutTest.class, "");
   1.245 +    }
   1.246 +
   1.247 +    @KOTest public void nonMutableLong() throws Exception {
   1.248 +        Utils.exposeHTML(KnockoutTest.class,
   1.249 +            "Type: <input id='input' data-bind=\"value: typeof longValue\"></input>\n"
   1.250 +        );
   1.251 +
   1.252 +        ConstantModel model = Models.bind(new ConstantModel(), newContext());
   1.253 +        model.assignStringValue("Hello").assignLongValue(Long.MAX_VALUE);
   1.254 +        model.applyBindings();
   1.255 +
   1.256 +        String v = getSetInput("input", null);
   1.257 +        assertEquals(v, "number", "Right type found: " + v);
   1.258 +
   1.259 +        Utils.exposeHTML(KnockoutTest.class, "");
   1.260 +    }
   1.261 +
   1.262 +    @KOTest public void nonMutableIntArray() throws Exception {
   1.263 +        Utils.exposeHTML(KnockoutTest.class,
   1.264 +            "Type: <input id='input' data-bind=\"value: typeof intArray\"></input>\n"
   1.265 +        );
   1.266 +
   1.267 +        ConstantModel model = Models.bind(new ConstantModel(), newContext());
   1.268 +        model.assignStringValue("Hello").assignLongValue(Long.MAX_VALUE).assignIntArray(1, 2, 3, 4);
   1.269 +        model.applyBindings();
   1.270 +
   1.271 +        String v = getSetInput("input", null);
   1.272 +        assertEquals(v, "object", "Right type found: " + v);
   1.273 +
   1.274 +        Utils.exposeHTML(KnockoutTest.class, "");
   1.275 +    }
   1.276 +
   1.277      private static String getSetInput(String id, String value) throws Exception {
   1.278          String s = "var value = arguments[0];\n"
   1.279          + "var n = window.document.getElementById(arguments[1]); \n "
   1.280 @@ -429,7 +514,7 @@
   1.281          );
   1.282          return Boolean.TRUE.equals(ret);
   1.283      }
   1.284 -    
   1.285 +
   1.286      public static void triggerEvent(String id, String ev) throws Exception {
   1.287          Utils.executeScript(
   1.288              KnockoutTest.class,
   1.289 @@ -437,9 +522,9 @@
   1.290              id, ev
   1.291          );
   1.292      }
   1.293 -    
   1.294 +
   1.295      @KOTest public void displayContentOfArray() throws Exception {
   1.296 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.297 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.298              "<ul id='ul' data-bind='foreach: results'>\n"
   1.299              + "  <li data-bind='text: $data, click: $root.call'/>\n"
   1.300              + "</ul>\n"
   1.301 @@ -465,10 +550,10 @@
   1.302              Utils.exposeHTML(KnockoutTest.class, "");
   1.303          }
   1.304      }
   1.305 -    
   1.306 +
   1.307      @KOTest public void displayContentOfAsyncArray() throws Exception {
   1.308          if (js == null) {
   1.309 -            Utils.exposeHTML(KnockoutTest.class, 
   1.310 +            Utils.exposeHTML(KnockoutTest.class,
   1.311                  "<ul id='ul' data-bind='foreach: results'>\n"
   1.312                  + "  <li data-bind='text: $data, click: $root.call'/>\n"
   1.313                  + "</ul>\n"
   1.314 @@ -479,7 +564,7 @@
   1.315  
   1.316              int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.317              assertEquals(cnt, 1, "One child, but was " + cnt);
   1.318 -            
   1.319 +
   1.320              Timer t = new Timer("add to array");
   1.321              t.schedule(new TimerTask() {
   1.322                  @Override
   1.323 @@ -504,9 +589,9 @@
   1.324              Utils.exposeHTML(KnockoutTest.class, "");
   1.325          }
   1.326      }
   1.327 -    
   1.328 +
   1.329      @KOTest public void displayContentOfComputedArray() throws Exception {
   1.330 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.331 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.332              "<ul id='ul' data-bind='foreach: bothNames'>\n"
   1.333              + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   1.334              + "</ul>\n"
   1.335 @@ -521,23 +606,23 @@
   1.336              triggerChildClick("ul", 1);
   1.337  
   1.338              assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   1.339 -            
   1.340 +
   1.341              m.setLastName("Verylast");
   1.342  
   1.343              cnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.344              assertEquals(cnt, 2, "Two children now, but was " + cnt);
   1.345 -            
   1.346 +
   1.347              triggerChildClick("ul", 1);
   1.348  
   1.349              assertEquals("Verylast", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   1.350 -            
   1.351 +
   1.352          } finally {
   1.353              Utils.exposeHTML(KnockoutTest.class, "");
   1.354          }
   1.355      }
   1.356 -    
   1.357 +
   1.358      @KOTest public void displayContentOfComputedArrayOnASubpair() throws Exception {
   1.359 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.360 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.361                "<div data-bind='with: next'>\n"
   1.362              + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   1.363              + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   1.364 @@ -553,7 +638,7 @@
   1.365              assertEquals(cnt, 2, "Two children now, but was " + cnt);
   1.366  
   1.367              triggerChildClick("ul", 1);
   1.368 -            
   1.369 +
   1.370              assertEquals(PairModel.ctx, ctx, "Context remains the same");
   1.371  
   1.372              assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   1.373 @@ -561,9 +646,9 @@
   1.374              Utils.exposeHTML(KnockoutTest.class, "");
   1.375          }
   1.376      }
   1.377 -    
   1.378 +
   1.379      @KOTest public void displayContentOfComputedArrayOnComputedASubpair() throws Exception {
   1.380 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.381 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.382                "<div data-bind='with: nextOne'>\n"
   1.383              + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   1.384              + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   1.385 @@ -586,7 +671,7 @@
   1.386      }
   1.387  
   1.388      @KOTest public void checkBoxToBooleanBinding() throws Exception {
   1.389 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.390 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.391              "<input type='checkbox' id='b' data-bind='checked: enabled'></input>\n"
   1.392          );
   1.393          try {
   1.394 @@ -602,11 +687,11 @@
   1.395              Utils.exposeHTML(KnockoutTest.class, "");
   1.396          }
   1.397      }
   1.398 -    
   1.399 -    
   1.400 -    
   1.401 +
   1.402 +
   1.403 +
   1.404      @KOTest public void displayContentOfDerivedArray() throws Exception {
   1.405 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.406 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.407              "<ul id='ul' data-bind='foreach: cmpResults'>\n"
   1.408              + "  <li><b data-bind='text: $data'></b></li>\n"
   1.409              + "</ul>\n"
   1.410 @@ -627,9 +712,9 @@
   1.411              Utils.exposeHTML(KnockoutTest.class, "");
   1.412          }
   1.413      }
   1.414 -    
   1.415 +
   1.416      @KOTest public void displayContentOfArrayOfPeople() throws Exception {
   1.417 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.418 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.419              "<ul id='ul' data-bind='foreach: people'>\n"
   1.420              + "  <li data-bind='text: $data.firstName, click: $root.removePerson'></li>\n"
   1.421              + "</ul>\n"
   1.422 @@ -672,14 +757,14 @@
   1.423              Utils.exposeHTML(KnockoutTest.class, "");
   1.424          }
   1.425      }
   1.426 -    
   1.427 +
   1.428      @ComputedProperty
   1.429      static Person firstPerson(List<Person> people) {
   1.430          return people.isEmpty() ? null : people.get(0);
   1.431      }
   1.432 -    
   1.433 +
   1.434      @KOTest public void accessFirstPersonWithOnFunction() throws Exception {
   1.435 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.436 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.437              "<p id='ul' data-bind='with: firstPerson'>\n"
   1.438              + "  <span data-bind='text: firstName, click: changeSex'></span>\n"
   1.439              + "</p>\n"
   1.440 @@ -690,9 +775,9 @@
   1.441              Utils.exposeHTML(KnockoutTest.class, "");
   1.442          }
   1.443      }
   1.444 -    
   1.445 +
   1.446      @KOTest public void onPersonFunction() throws Exception {
   1.447 -        Object exp = Utils.exposeHTML(KnockoutTest.class, 
   1.448 +        Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.449              "<ul id='ul' data-bind='foreach: people'>\n"
   1.450              + "  <li data-bind='text: $data.firstName, click: changeSex'></li>\n"
   1.451              + "</ul>\n"
   1.452 @@ -703,7 +788,7 @@
   1.453              Utils.exposeHTML(KnockoutTest.class, "");
   1.454          }
   1.455      }
   1.456 -    
   1.457 +
   1.458      private void trasfertToFemale() throws Exception {
   1.459          KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   1.460  
   1.461 @@ -723,7 +808,7 @@
   1.462  
   1.463          assertEquals(first.getSex(), Sex.FEMALE, "Transverted to female: " + first.getSex());
   1.464      }
   1.465 -    
   1.466 +
   1.467      @KOTest public void stringArrayModificationVisible() throws Exception {
   1.468          Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.469                  "<div>\n"
   1.470 @@ -737,19 +822,19 @@
   1.471              m.getResults().add("Ahoj");
   1.472              m.getResults().add("Hello");
   1.473              m.applyBindings();
   1.474 -            
   1.475 +
   1.476              int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.477              assertEquals(cnt, 2, "Two children " + cnt);
   1.478 -            
   1.479 +
   1.480              Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
   1.481              assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   1.482              final int len = ((Object[])arr).length;
   1.483 -            
   1.484 +
   1.485              assertEquals(len, 3, "Three elements in the array " + len);
   1.486 -            
   1.487 +
   1.488              int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.489              assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   1.490 -            
   1.491 +
   1.492              assertEquals(m.getResults().size(), 3, "Three java strings: " + m.getResults());
   1.493          } finally {
   1.494              Utils.exposeHTML(KnockoutTest.class, "");
   1.495 @@ -769,19 +854,19 @@
   1.496              m.getNumbers().add(1);
   1.497              m.getNumbers().add(31);
   1.498              m.applyBindings();
   1.499 -            
   1.500 +
   1.501              int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.502              assertEquals(cnt, 2, "Two children " + cnt);
   1.503 -            
   1.504 +
   1.505              Object arr = Utils.addChildren(KnockoutTest.class, "ul", "numbers", 42);
   1.506              assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   1.507              final int len = ((Object[])arr).length;
   1.508 -            
   1.509 +
   1.510              assertEquals(len, 3, "Three elements in the array " + len);
   1.511 -            
   1.512 +
   1.513              int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.514              assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   1.515 -            
   1.516 +
   1.517              assertEquals(m.getNumbers().size(), 3, "Three java ints: " + m.getNumbers());
   1.518              assertEquals(m.getNumbers().get(2), 42, "Meaning of world: " + m.getNumbers());
   1.519          } finally {
   1.520 @@ -802,26 +887,26 @@
   1.521              m.getResults().add("Ahoj");
   1.522              m.getResults().add("Hello");
   1.523              m.applyBindings();
   1.524 -            
   1.525 +
   1.526              int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.527              assertEquals(cnt, 2, "Two children " + cnt);
   1.528 -            
   1.529 +
   1.530              Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
   1.531              assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   1.532              final int len = ((Object[])arr).length;
   1.533 -            
   1.534 +
   1.535              assertEquals(len, 3, "Three elements in the array " + len);
   1.536 -            
   1.537 +
   1.538              int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.539              assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   1.540 -            
   1.541 +
   1.542              assertEquals(m.getResultLengths().size(), 3, "Three java ints: " + m.getResultLengths());
   1.543              assertEquals(m.getResultLengths().get(2), 2, "Size is two: " + m.getResultLengths());
   1.544          } finally {
   1.545              Utils.exposeHTML(KnockoutTest.class, "");
   1.546          }
   1.547      }
   1.548 -    
   1.549 +
   1.550      @KOTest public void archetypeArrayModificationVisible() throws Exception {
   1.551          Object exp = Utils.exposeHTML(KnockoutTest.class,
   1.552                  "<div>\n"
   1.553 @@ -833,19 +918,19 @@
   1.554          try {
   1.555              KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   1.556              m.applyBindings();
   1.557 -            
   1.558 +
   1.559              int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.560              assertEquals(cnt, 0, "No children " + cnt);
   1.561 -            
   1.562 +
   1.563              Object arr = Utils.addChildren(KnockoutTest.class, "ul", "archetypes", new ArchetypeData("aid", "gid", "v", "n", "d", "u"));
   1.564              assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   1.565              final int len = ((Object[])arr).length;
   1.566 -            
   1.567 +
   1.568              assertEquals(len, 1, "One element in the array " + len);
   1.569 -            
   1.570 +
   1.571              int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   1.572              assertEquals(newCnt, 1, "One child in the DOM: " + newCnt);
   1.573 -            
   1.574 +
   1.575              assertEquals(m.getArchetypes().size(), 1, "One archetype: " + m.getArchetypes());
   1.576              assertNotNull(m.getArchetypes().get(0), "Not null: " + m.getArchetypes());
   1.577              assertEquals(m.getArchetypes().get(0).getArtifactId(), "aid", "'aid' == " + m.getArchetypes());
   1.578 @@ -865,18 +950,18 @@
   1.579          model.setCallbackCount(model.getCallbackCount() + 1);
   1.580          model.getPeople().remove(data);
   1.581      }
   1.582 -    
   1.583 -    
   1.584 +
   1.585 +
   1.586      @ComputedProperty
   1.587      static String helloMessage(String name) {
   1.588          return "Hello " + name + "!";
   1.589      }
   1.590 -    
   1.591 +
   1.592      @ComputedProperty
   1.593      static List<String> cmpResults(List<String> results) {
   1.594          return results;
   1.595      }
   1.596 -    
   1.597 +
   1.598      private static void triggerClick(String id) throws Exception {
   1.599          String s = "var id = arguments[0];"
   1.600              + "var e = window.document.getElementById(id);\n "
   1.601 @@ -893,17 +978,17 @@
   1.602              s, id);
   1.603      }
   1.604      private static void triggerChildClick(String id, int pos) throws Exception {
   1.605 -        String s = 
   1.606 +        String s =
   1.607              "var id = arguments[0]; var pos = arguments[1];\n" +
   1.608              "var e = window.document.getElementById(id);\n " +
   1.609              "var ev = window.document.createEvent('MouseEvents');\n " +
   1.610              "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " +
   1.611              "var list = e.childNodes;\n" +
   1.612 -            "var cnt = -1;\n" + 
   1.613 -            "for (var i = 0; i < list.length; i++) {\n" + 
   1.614 -            "  if (list[i].nodeType == 1) cnt++;\n" + 
   1.615 -            "  if (cnt == pos) return list[i].dispatchEvent(ev);\n" + 
   1.616 -            "}\n" + 
   1.617 +            "var cnt = -1;\n" +
   1.618 +            "for (var i = 0; i < list.length; i++) {\n" +
   1.619 +            "  if (list[i].nodeType == 1) cnt++;\n" +
   1.620 +            "  if (cnt == pos) return list[i].dispatchEvent(ev);\n" +
   1.621 +            "}\n" +
   1.622              "return null;\n";
   1.623          Utils.executeScript(
   1.624              KnockoutTest.class,
   1.625 @@ -911,15 +996,15 @@
   1.626      }
   1.627  
   1.628      private static String childText(String id, int pos) throws Exception {
   1.629 -        String s = 
   1.630 +        String s =
   1.631              "var id = arguments[0]; var pos = arguments[1];" +
   1.632              "var e = window.document.getElementById(id);\n" +
   1.633              "var list = e.childNodes;\n" +
   1.634 -            "var cnt = -1;\n" + 
   1.635 -            "for (var i = 0; i < list.length; i++) {\n" + 
   1.636 -            "  if (list[i].nodeType == 1) cnt++;\n" + 
   1.637 -            "  if (cnt == pos) return list[i].innerHTML;\n" + 
   1.638 -            "}\n" + 
   1.639 +            "var cnt = -1;\n" +
   1.640 +            "for (var i = 0; i < list.length; i++) {\n" +
   1.641 +            "  if (list[i].nodeType == 1) cnt++;\n" +
   1.642 +            "  if (cnt == pos) return list[i].innerHTML;\n" +
   1.643 +            "}\n" +
   1.644              "return null;\n";
   1.645          return (String)Utils.executeScript(
   1.646              KnockoutTest.class,
     2.1 --- a/json/src/main/java/net/java/html/json/Property.java	Mon Feb 15 05:27:28 2016 +0100
     2.2 +++ b/json/src/main/java/net/java/html/json/Property.java	Mon Feb 29 05:33:31 2016 +0100
     2.3 @@ -46,6 +46,8 @@
     2.4  import java.lang.annotation.RetentionPolicy;
     2.5  import java.lang.annotation.Target;
     2.6  import java.util.List;
     2.7 +import org.netbeans.html.context.spi.Contexts;
     2.8 +import org.netbeans.html.json.spi.Technology;
     2.9  
    2.10  /** Represents a property in a class defined with {@link Model} annotation.
    2.11   *
    2.12 @@ -76,4 +78,21 @@
    2.13       * @return true, if this property is supposed to represent an array of values
    2.14       */
    2.15      boolean array() default false;
    2.16 +
    2.17 +    /** Can the value of the property be mutated without restriction or not.
    2.18 +     * If a property is defined as <em>not mutable</em>, it defines
    2.19 +     * semi-immutable value that can only be changed in construction time
    2.20 +     * before the object is passed to underlying {@link Technology}. 
    2.21 +     * Attempts to modify the object later yield {@link IllegalStateException}.
    2.22 +     *
    2.23 +     * Technologies may decide to represent such non-mutable
    2.24 +     * property in more effective way - for
    2.25 +     * example Knockout Java Bindings technology (with {@link Contexts.Id id} "ko4j")
    2.26 +     * uses plain JavaScript value (number, string, array, boolean) rather
    2.27 +     * than classical observable.
    2.28 +     *
    2.29 +     * @return false if the value cannot change after its <em>first use</em>
    2.30 +     * @since 1.3
    2.31 +     */
    2.32 +    boolean mutable() default true;
    2.33  }
     3.1 --- a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java	Mon Feb 15 05:27:28 2016 +0100
     3.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java	Mon Feb 29 05:33:31 2016 +0100
     3.3 @@ -64,8 +64,8 @@
     3.4          this.bp = bp;
     3.5      }
     3.6      
     3.7 -    public <M> PropertyBinding registerProperty(String propName, int index, M model, Proto.Type<M> access, boolean readOnly) {
     3.8 -        return PropertyBindingAccessor.create(access, this, propName, index, model, readOnly);
     3.9 +    public <M> PropertyBinding registerProperty(String propName, int index, M model, Proto.Type<M> access, byte propertyType) {
    3.10 +        return PropertyBindingAccessor.create(access, this, propName, index, model, propertyType);
    3.11      }
    3.12  
    3.13      public static Bindings<?> apply(BrwsrCtx c) {
     4.1 --- a/json/src/main/java/org/netbeans/html/json/impl/JSONList.java	Mon Feb 15 05:27:28 2016 +0100
     4.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/JSONList.java	Mon Feb 29 05:33:31 2016 +0100
     4.3 @@ -88,6 +88,7 @@
     4.4      
     4.5      @Override
     4.6      public boolean add(T e) {
     4.7 +        prepareChange();
     4.8          boolean ret = super.add(e);
     4.9          notifyChange();
    4.10          return ret;
    4.11 @@ -95,6 +96,7 @@
    4.12  
    4.13      @Override
    4.14      public boolean addAll(Collection<? extends T> c) {
    4.15 +        prepareChange();
    4.16          boolean ret = super.addAll(c);
    4.17          notifyChange();
    4.18          return ret;
    4.19 @@ -102,12 +104,14 @@
    4.20  
    4.21      @Override
    4.22      public boolean addAll(int index, Collection<? extends T> c) {
    4.23 +        prepareChange();
    4.24          boolean ret = super.addAll(index, c);
    4.25          notifyChange();
    4.26          return ret;
    4.27      }
    4.28  
    4.29      public void fastReplace(Collection<? extends T> c) {
    4.30 +        prepareChange();
    4.31          super.clear();
    4.32          super.addAll(c);
    4.33          notifyChange();
    4.34 @@ -115,6 +119,7 @@
    4.35  
    4.36      @Override
    4.37      public boolean remove(Object o) {
    4.38 +        prepareChange();
    4.39          boolean ret = super.remove(o);
    4.40          notifyChange();
    4.41          return ret;
    4.42 @@ -122,12 +127,14 @@
    4.43  
    4.44      @Override
    4.45      public void clear() {
    4.46 +        prepareChange();
    4.47          super.clear();
    4.48          notifyChange();
    4.49      }
    4.50  
    4.51      @Override
    4.52      public boolean removeAll(Collection<?> c) {
    4.53 +        prepareChange();
    4.54          boolean ret = super.removeAll(c);
    4.55          notifyChange();
    4.56          return ret;
    4.57 @@ -135,6 +142,7 @@
    4.58  
    4.59      @Override
    4.60      public boolean retainAll(Collection<?> c) {
    4.61 +        prepareChange();
    4.62          boolean ret = super.retainAll(c);
    4.63          notifyChange();
    4.64          return ret;
    4.65 @@ -142,6 +150,7 @@
    4.66  
    4.67      @Override
    4.68      public T set(int index, T element) {
    4.69 +        prepareChange();
    4.70          T ret = super.set(index, element);
    4.71          notifyChange();
    4.72          return ret;
    4.73 @@ -149,12 +158,14 @@
    4.74  
    4.75      @Override
    4.76      public void add(int index, T element) {
    4.77 +        prepareChange();
    4.78          super.add(index, element);
    4.79          notifyChange();
    4.80      }
    4.81  
    4.82      @Override
    4.83      public T remove(int index) {
    4.84 +        prepareChange();
    4.85          T ret = super.remove(index);
    4.86          notifyChange();
    4.87          return ret;
    4.88 @@ -179,6 +190,16 @@
    4.89          return sb.toString();
    4.90      }
    4.91  
    4.92 +    private void prepareChange() {
    4.93 +        if (index == Integer.MIN_VALUE) {
    4.94 +            try {
    4.95 +                proto.initTo(null, null);
    4.96 +            } catch (IllegalStateException ex) {
    4.97 +                throw new UnsupportedOperationException();
    4.98 +            }
    4.99 +        }
   4.100 +    }
   4.101 +
   4.102      private void notifyChange() {
   4.103          proto.getContext().execute(new Runnable() {
   4.104              @Override
     5.1 --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java	Mon Feb 15 05:27:28 2016 +0100
     5.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java	Mon Feb 29 05:33:31 2016 +0100
     5.3 @@ -263,11 +263,15 @@
     5.4                          String[] gs = toGetSet(p.name(), tn, p.array());
     5.5                          w.write("    this.prop_" + p.name() + " = proto.createList(\""
     5.6                              + p.name() + "\"");
     5.7 -                        if (functionDeps.containsKey(p.name())) {
     5.8 -                            int index = Arrays.asList(functionDeps.keySet().toArray()).indexOf(p.name());
     5.9 -                            w.write(", " + index);
    5.10 +                        if (p.mutable()) {
    5.11 +                            if (functionDeps.containsKey(p.name())) {
    5.12 +                                int index = Arrays.asList(functionDeps.keySet().toArray()).indexOf(p.name());
    5.13 +                                w.write(", " + index);
    5.14 +                            } else {
    5.15 +                                w.write(", -1");
    5.16 +                            }
    5.17                          } else {
    5.18 -                            w.write(", -1");
    5.19 +                            w.write(", java.lang.Integer.MIN_VALUE");
    5.20                          }
    5.21                          Collection<String[]> dependants = propsDeps.get(p.name());
    5.22                          if (dependants != null) {
    5.23 @@ -355,7 +359,7 @@
    5.24                  {
    5.25                      for (int i = 0; i < propsGetSet.size(); i++) {
    5.26                          w.append("      registerProperty(\"").append(propsGetSet.get(i).name).append("\", ");
    5.27 -                        w.append((i) + ", " + propsGetSet.get(i).readOnly + ");\n");
    5.28 +                        w.append((i) + ", " + propsGetSet.get(i).readOnly + ", " + propsGetSet.get(i).constant + ");\n");
    5.29                      }
    5.30                  }
    5.31                  {
    5.32 @@ -672,6 +676,9 @@
    5.33                  w.write("    return (" + tn + ")prop_" + p.name() + ";\n");
    5.34                  w.write("  }\n");
    5.35                  w.write("  public void " + gs[1] + "(" + tn + " v) {\n");
    5.36 +                if (!p.mutable()) {
    5.37 +                    w.write("    proto.initTo(null, null);\n");
    5.38 +                }
    5.39                  w.write("    proto.verifyUnlocked();\n");
    5.40                  w.write("    Object o = prop_" + p.name() + ";\n");
    5.41                  if (isModel[0]) {
    5.42 @@ -721,7 +728,8 @@
    5.43                  gs[0],
    5.44                  gs[1],
    5.45                  tn,
    5.46 -                gs[3] == null && !p.array()
    5.47 +                gs[3] == null && !p.array(),
    5.48 +                !p.mutable()
    5.49              ));
    5.50          }
    5.51          return ok;
    5.52 @@ -857,7 +865,8 @@
    5.53                      gs[0],
    5.54                      null,
    5.55                      tn,
    5.56 -                    true
    5.57 +                    true,
    5.58 +                    false
    5.59                  ));
    5.60              } else {
    5.61                  w.write("  public void " + gs[4] + "(" + write.getParameters().get(1).asType());
    5.62 @@ -870,6 +879,7 @@
    5.63                      gs[0],
    5.64                      gs[4],
    5.65                      tn,
    5.66 +                    false,
    5.67                      false
    5.68                  ));
    5.69              }
    5.70 @@ -2001,6 +2011,10 @@
    5.71              return p.array();
    5.72          }
    5.73  
    5.74 +        boolean mutable() {
    5.75 +            return p.mutable();
    5.76 +        }
    5.77 +
    5.78          String typeName(ProcessingEnvironment env) {
    5.79              RuntimeException ex;
    5.80              try {
    5.81 @@ -2058,12 +2072,15 @@
    5.82          final String setter;
    5.83          final String type;
    5.84          final boolean readOnly;
    5.85 -        GetSet(String name, String getter, String setter, String type, boolean readOnly) {
    5.86 +        final boolean constant;
    5.87 +        
    5.88 +        GetSet(String name, String getter, String setter, String type, boolean readOnly, boolean constant) {
    5.89              this.name = name;
    5.90              this.getter = getter;
    5.91              this.setter = setter;
    5.92              this.type = type;
    5.93              this.readOnly = readOnly;
    5.94 +            this.constant = constant;
    5.95          }
    5.96      }
    5.97  
     6.1 --- a/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java	Mon Feb 15 05:27:28 2016 +0100
     6.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java	Mon Feb 29 05:33:31 2016 +0100
     6.3 @@ -64,8 +64,7 @@
     6.4      }
     6.5  
     6.6      protected abstract <M> PropertyBinding newBinding(
     6.7 -        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, boolean readOnly
     6.8 -    );
     6.9 +        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, byte propertyType);
    6.10      protected abstract JSONCall newCall(
    6.11          BrwsrCtx ctx, RcvrJSON callback,
    6.12          String headers, String urlBefore, String urlAfter,
    6.13 @@ -87,9 +86,9 @@
    6.14      }
    6.15  
    6.16      static <M> PropertyBinding create(
    6.17 -        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model , boolean readOnly
    6.18 +        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model , byte propertyType
    6.19      ) {
    6.20 -        return DEFAULT.newBinding(access, bindings, name, index, model, readOnly);
    6.21 +        return DEFAULT.newBinding(access, bindings, name, index, model, propertyType);
    6.22      }
    6.23      public static JSONCall createCall(
    6.24          BrwsrCtx ctx, RcvrJSON callback,
     7.1 --- a/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java	Mon Feb 15 05:27:28 2016 +0100
     7.2 +++ b/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java	Mon Feb 29 05:33:31 2016 +0100
     7.3 @@ -45,6 +45,7 @@
     7.4  import java.lang.ref.Reference;
     7.5  import java.lang.ref.WeakReference;
     7.6  import net.java.html.BrwsrCtx;
     7.7 +import net.java.html.json.ComputedProperty;
     7.8  import org.netbeans.html.json.impl.Bindings;
     7.9  import org.netbeans.html.json.impl.JSON;
    7.10  import org.netbeans.html.json.impl.PropertyBindingAccessor;
    7.11 @@ -93,10 +94,8 @@
    7.12  
    7.13              @Override
    7.14              protected <M> PropertyBinding newBinding(
    7.15 -                Proto.Type<M> access, Bindings<?> bindings, String name,
    7.16 -                int index, M model, boolean readOnly
    7.17 -            ) {
    7.18 -                return new Impl(model, bindings, name, index, access, readOnly);
    7.19 +                Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, byte propertyType) {
    7.20 +                return new Impl(model, bindings, name, index, access, propertyType);
    7.21              }
    7.22          };
    7.23      }
    7.24 @@ -121,12 +120,22 @@
    7.25       */
    7.26      public abstract Object getValue();
    7.27  
    7.28 -    /** Is this property read only? Or can one call {@link #setValue(java.lang.Object)}?
    7.29 +    /** Is this property read only?. Or can one call {@link #setValue(java.lang.Object)}?
    7.30 +     * The property can still change, but only as a result of other
    7.31 +     * properties being changed, just like {@link ComputedProperty} can.
    7.32       *
    7.33       * @return true, if this property is read only
    7.34       */
    7.35      public abstract boolean isReadOnly();
    7.36  
    7.37 +    /** Is this property constant?. If a property is constant, than its
    7.38 +     * value cannot changed after it is read.
    7.39 +     *
    7.40 +     * @return true, if this property is constant
    7.41 +     * @since 1.3
    7.42 +     */
    7.43 +    public abstract boolean isConstant();
    7.44 +
    7.45      /** Returns identical version of the binding, but one that holds on the
    7.46       * original model object via weak reference.
    7.47       *
    7.48 @@ -137,17 +146,17 @@
    7.49  
    7.50      private static abstract class AImpl<M> extends PropertyBinding {
    7.51          public final String name;
    7.52 -        public final boolean readOnly;
    7.53 +        public final byte propertyType;
    7.54          final Proto.Type<M> access;
    7.55          final Bindings<?> bindings;
    7.56          final int index;
    7.57  
    7.58 -        public AImpl(Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
    7.59 +        public AImpl(Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) {
    7.60              this.bindings = bindings;
    7.61              this.name = name;
    7.62              this.index = index;
    7.63              this.access = access;
    7.64 -            this.readOnly = readOnly;
    7.65 +            this.propertyType = propertyType;
    7.66          }
    7.67  
    7.68          protected abstract M model();
    7.69 @@ -174,7 +183,12 @@
    7.70  
    7.71          @Override
    7.72          public boolean isReadOnly() {
    7.73 -            return readOnly;
    7.74 +            return (propertyType & 1) != 0;
    7.75 +        }
    7.76 +
    7.77 +        @Override
    7.78 +        public boolean isConstant() {
    7.79 +            return (propertyType & 2) != 0;
    7.80          }
    7.81  
    7.82          @Override
    7.83 @@ -186,8 +200,8 @@
    7.84      private static final class Impl<M> extends AImpl<M> {
    7.85          private final M model;
    7.86  
    7.87 -        public Impl(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
    7.88 -            super(bindings, name, index, access, readOnly);
    7.89 +        public Impl(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) {
    7.90 +            super(bindings, name, index, access, propertyType);
    7.91              this.model = model;
    7.92          }
    7.93  
    7.94 @@ -198,14 +212,14 @@
    7.95  
    7.96          @Override
    7.97          public PropertyBinding weak() {
    7.98 -            return new Weak(model, bindings, name, index, access, readOnly);
    7.99 +            return new Weak(model, bindings, name, index, access, propertyType);
   7.100          }
   7.101      }
   7.102  
   7.103      private static final class Weak<M> extends AImpl<M> {
   7.104          private final Reference<M> ref;
   7.105 -        public Weak(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
   7.106 -            super(bindings, name, index, access, readOnly);
   7.107 +        public Weak(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) {
   7.108 +            super(bindings, name, index, access, propertyType);
   7.109              this.ref = new WeakReference<M>(model);
   7.110          }
   7.111  
     8.1 --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java	Mon Feb 15 05:27:28 2016 +0100
     8.2 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java	Mon Feb 29 05:33:31 2016 +0100
     8.3 @@ -48,6 +48,7 @@
     8.4  import net.java.html.BrwsrCtx;
     8.5  import net.java.html.json.ComputedProperty;
     8.6  import net.java.html.json.Model;
     8.7 +import net.java.html.json.Property;
     8.8  import org.netbeans.html.json.impl.Bindings;
     8.9  import org.netbeans.html.json.impl.JSON;
    8.10  import org.netbeans.html.json.impl.JSON.WS;
    8.11 @@ -463,7 +464,10 @@
    8.12       * @param <T> the type of the list elements
    8.13       * @param propName name of a property this list is associated with
    8.14       * @param onChange index of the property to use when the list is modified
    8.15 -     *   during callback to {@link Type#onChange(java.lang.Object, int)}
    8.16 +     *   during callback to {@link Type#onChange(java.lang.Object, int)}.
    8.17 +     *   If the value is {@link Integer#MIN_VALUE}, then the list is
    8.18 +     *   not fully {@link Property#mutable()} and throws {@link UnsupportedOperationException}
    8.19 +     *   on such attempts.
    8.20       * @param dependingProps the array of {@link ComputedProperty derived properties}
    8.21       *   that depend on the value of the list
    8.22       * @return new, empty list associated with this proto-object and its model
    8.23 @@ -508,7 +512,7 @@
    8.24              PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length];
    8.25              for (int i = 0; i < pb.length; i++) {
    8.26                  pb[i] = b.registerProperty(
    8.27 -                    type.propertyNames[i], i, obj, type, type.propertyReadOnly[i]
    8.28 +                    type.propertyNames[i], i, obj, type, type.propertyType[i]
    8.29                  );
    8.30              }
    8.31              FunctionBinding[] fb = new FunctionBinding[type.functions.length];
    8.32 @@ -547,7 +551,7 @@
    8.33      public static abstract class Type<Model> {
    8.34          private final Class<Model> clazz;
    8.35          private final String[] propertyNames;
    8.36 -        private final boolean[] propertyReadOnly;
    8.37 +        private final byte[] propertyType;
    8.38          private final String[] functions;
    8.39  
    8.40          /** Constructor for subclasses generated by the annotation processor
    8.41 @@ -569,7 +573,7 @@
    8.42              }
    8.43              this.clazz = clazz;
    8.44              this.propertyNames = new String[properties];
    8.45 -            this.propertyReadOnly = new boolean[properties];
    8.46 +            this.propertyType = new byte[properties];
    8.47              this.functions = new String[functions];
    8.48              JSON.register(clazz, this);
    8.49          }
    8.50 @@ -584,7 +588,29 @@
    8.51          protected final void registerProperty(String name, int index, boolean readOnly) {
    8.52              assert propertyNames[index] == null;
    8.53              propertyNames[index] = name;
    8.54 -            propertyReadOnly[index] = readOnly;
    8.55 +            propertyType[index] = (byte) (readOnly ? 1 : 0);
    8.56 +        }
    8.57 +
    8.58 +        /** Registers property for the type. It is expected each index
    8.59 +         * is initialized only once. The difference between <code>readOnly</code>
    8.60 +         * and <code>constant</code> is: The <code>constant</code> value is
    8.61 +         * assigned only at the beginning and never changed then - like the
    8.62 +         * {@link Property#mutable() non-mutable} property. On the other
    8.63 +         * hand, a <code>readOnly</code> property can change its value,
    8.64 +         * but not via a setter - just like {@link ComputedProperty}.
    8.65 +         *
    8.66 +         * @param name name of the property
    8.67 +         * @param index index of the property
    8.68 +         * @param readOnly is the property read only?
    8.69 +         * @param constant is the property assigned once and never changed again?
    8.70 +         * @since 1.3
    8.71 +         */
    8.72 +        protected final void registerProperty(
    8.73 +            String name, int index, boolean readOnly, boolean constant
    8.74 +        ) {
    8.75 +            assert propertyNames[index] == null;
    8.76 +            propertyNames[index] = name;
    8.77 +            propertyType[index] = (byte) ((readOnly ? 1 : 0) | (constant ? 2 : 0));
    8.78          }
    8.79  
    8.80          /** Registers function of given name at given index.
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/json/src/test/java/net/java/html/json/MapModelNotMutableTest.java	Mon Feb 29 05:33:31 2016 +0100
     9.3 @@ -0,0 +1,227 @@
     9.4 +/**
     9.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     9.6 + *
     9.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     9.8 + *
     9.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    9.10 + * Other names may be trademarks of their respective owners.
    9.11 + *
    9.12 + * The contents of this file are subject to the terms of either the GNU
    9.13 + * General Public License Version 2 only ("GPL") or the Common
    9.14 + * Development and Distribution License("CDDL") (collectively, the
    9.15 + * "License"). You may not use this file except in compliance with the
    9.16 + * License. You can obtain a copy of the License at
    9.17 + * http://www.netbeans.org/cddl-gplv2.html
    9.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    9.19 + * specific language governing permissions and limitations under the
    9.20 + * License.  When distributing the software, include this License Header
    9.21 + * Notice in each file and include the License file at
    9.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    9.23 + * particular file as subject to the "Classpath" exception as provided
    9.24 + * by Oracle in the GPL Version 2 section of the License file that
    9.25 + * accompanied this code. If applicable, add the following below the
    9.26 + * License Header, with the fields enclosed by brackets [] replaced by
    9.27 + * your own identifying information:
    9.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    9.29 + *
    9.30 + * Contributor(s):
    9.31 + *
    9.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    9.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    9.34 + *
    9.35 + * If you wish your version of this file to be governed by only the CDDL
    9.36 + * or only the GPL Version 2, indicate your decision by adding
    9.37 + * "[Contributor] elects to include this software in this distribution
    9.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    9.39 + * single choice of license, a recipient has the option to distribute
    9.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    9.41 + * to extend the choice of license to its licensees as provided above.
    9.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    9.43 + * Version 2 license, then the option applies only if the new code is
    9.44 + * made subject to such option by the copyright holder.
    9.45 + */
    9.46 +package net.java.html.json;
    9.47 +
    9.48 +import java.util.Map;
    9.49 +import net.java.html.BrwsrCtx;
    9.50 +import org.netbeans.html.context.spi.Contexts;
    9.51 +import org.netbeans.html.json.spi.Technology;
    9.52 +import org.netbeans.html.json.spi.Transfer;
    9.53 +import static org.testng.Assert.assertEquals;
    9.54 +import static org.testng.Assert.assertFalse;
    9.55 +import static org.testng.Assert.assertNotNull;
    9.56 +import static org.testng.Assert.fail;
    9.57 +import org.testng.annotations.BeforeMethod;
    9.58 +import org.testng.annotations.Test;
    9.59 +
    9.60 +@Model(className = "ConstantValues", properties = {
    9.61 +    @Property(name = "byteNumber", type = byte.class, mutable = false),
    9.62 +    @Property(name = "shortNumber", type = short.class, mutable = false),
    9.63 +    @Property(name = "intNumber", type = int.class, mutable = false),
    9.64 +    @Property(name = "longNumber", type = long.class, mutable = false),
    9.65 +    @Property(name = "floatNumber", type = float.class, mutable = false),
    9.66 +    @Property(name = "doubleNumber", type = double.class, mutable = false),
    9.67 +    @Property(name = "stringValue", type = String.class, mutable = false),
    9.68 +    @Property(name = "byteArray", type = byte.class, mutable = false, array = true),
    9.69 +    @Property(name = "shortArray", type = short.class, mutable = false, array = true),
    9.70 +    @Property(name = "intArray", type = int.class, mutable = false, array = true),
    9.71 +    @Property(name = "longArray", type = long.class, mutable = false, array = true),
    9.72 +    @Property(name = "floatArray", type = float.class, mutable = false, array = true),
    9.73 +    @Property(name = "doubleArray", type = double.class, mutable = false, array = true),
    9.74 +    @Property(name = "stringArray", type = String.class, mutable = false, array = true),
    9.75 +})
    9.76 +public class MapModelNotMutableTest {
    9.77 +    private BrwsrCtx c;
    9.78 +
    9.79 +    @BeforeMethod
    9.80 +    public void initTechnology() {
    9.81 +        MapModelTest.MapTechnology t = new MapModelTest.MapTechnology();
    9.82 +        c = Contexts.newBuilder().register(Technology.class, t, 1).
    9.83 +            register(Transfer.class, t, 1).build();
    9.84 +    }
    9.85 +
    9.86 +    @Test
    9.87 +    public void byteConstant() throws Exception {
    9.88 +        ConstantValues value = Models.bind(new ConstantValues(), c);
    9.89 +        value.setByteNumber((byte)13);
    9.90 +
    9.91 +        Map m = (Map) Models.toRaw(value);
    9.92 +        Object v = m.get("byteNumber");
    9.93 +        assertNotNull(v, "Value should be in the map");
    9.94 +        assertEquals(v.getClass(), MapModelTest.One.class, "It is instance of One");
    9.95 +        MapModelTest.One o = (MapModelTest.One) v;
    9.96 +        assertEquals(o.changes, 0, "No change so far the only one change happened before we connected");
    9.97 +        assertEquals((byte)13, o.get());
    9.98 +
    9.99 +        try {
   9.100 +            value.setByteNumber((byte)15);
   9.101 +            fail("Changing value shouldn't succeed!");
   9.102 +        } catch (IllegalStateException ex) {
   9.103 +            // OK
   9.104 +        }
   9.105 +        assertEquals(o.get(), (byte)13, "Old value should still be in the map");
   9.106 +        assertEquals(o.changes, 0, "No change");
   9.107 +        assertFalse(o.pb.isReadOnly(), "Mutable property");
   9.108 +    }
   9.109 +
   9.110 +    @Test
   9.111 +    public void shortConstant() throws Exception {
   9.112 +        ConstantValues value = Models.bind(new ConstantValues(), c);
   9.113 +        value.setShortNumber((short)13);
   9.114 +
   9.115 +        Map m = (Map) Models.toRaw(value);
   9.116 +        Object v = m.get("shortNumber");
   9.117 +        assertNotNull(v, "Value should be in the map");
   9.118 +        assertEquals(v.getClass(), MapModelTest.One.class, "It is instance of One");
   9.119 +        MapModelTest.One o = (MapModelTest.One) v;
   9.120 +        assertEquals(o.changes, 0, "No change so far the only one change happened before we connected");
   9.121 +        assertEquals((short)13, o.get());
   9.122 +
   9.123 +        try {
   9.124 +            value.setShortNumber((short)15);
   9.125 +            fail("Changing value shouldn't succeed!");
   9.126 +        } catch (IllegalStateException ex) {
   9.127 +            // OK
   9.128 +        }
   9.129 +        assertEquals(o.get(), (short)13, "Old value should still be in the map");
   9.130 +        assertEquals(o.changes, 0, "No change");
   9.131 +        assertFalse(o.pb.isReadOnly(), "Mutable property");
   9.132 +    }
   9.133 +
   9.134 +    @Test
   9.135 +    public void intConstant() throws Exception {
   9.136 +        ConstantValues value = Models.bind(new ConstantValues(), c);
   9.137 +        value.setIntNumber(13);
   9.138 +
   9.139 +        Map m = (Map) Models.toRaw(value);
   9.140 +        Object v = m.get("intNumber");
   9.141 +        assertNotNull(v, "Value should be in the map");
   9.142 +        assertEquals(v.getClass(), MapModelTest.One.class, "It is instance of One");
   9.143 +        MapModelTest.One o = (MapModelTest.One) v;
   9.144 +        assertEquals(o.changes, 0, "No change so far the only one change happened before we connected");
   9.145 +        assertEquals(13, o.get());
   9.146 +
   9.147 +        try {
   9.148 +            value.setIntNumber(15);
   9.149 +            fail("Changing value shouldn't succeed!");
   9.150 +        } catch (IllegalStateException ex) {
   9.151 +            // OK
   9.152 +        }
   9.153 +        assertEquals(o.get(), 13, "Old value should still be in the map");
   9.154 +        assertEquals(o.changes, 0, "No change");
   9.155 +        assertFalse(o.pb.isReadOnly(), "Mutable property");
   9.156 +    }
   9.157 +
   9.158 +    @Test
   9.159 +    public void doubleConstant() throws Exception {
   9.160 +        ConstantValues value = Models.bind(new ConstantValues(), c);
   9.161 +        value.setDoubleNumber(13);
   9.162 +
   9.163 +        Map m = (Map) Models.toRaw(value);
   9.164 +        Object v = m.get("doubleNumber");
   9.165 +        assertNotNull(v, "Value should be in the map");
   9.166 +        assertEquals(v.getClass(), MapModelTest.One.class, "It is instance of One");
   9.167 +        MapModelTest.One o = (MapModelTest.One) v;
   9.168 +        assertEquals(o.changes, 0, "No change so far the only one change happened before we connected");
   9.169 +        assertEquals(13.0, o.get());
   9.170 +
   9.171 +        try {
   9.172 +            value.setDoubleNumber(15);
   9.173 +            fail("Changing value shouldn't succeed!");
   9.174 +        } catch (IllegalStateException ex) {
   9.175 +            // OK
   9.176 +        }
   9.177 +        assertEquals(o.get(), 13.0, "Old value should still be in the map");
   9.178 +        assertEquals(o.changes, 0, "No change");
   9.179 +        assertFalse(o.pb.isReadOnly(), "Mutable property");
   9.180 +    }
   9.181 +
   9.182 +    @Test
   9.183 +    public void stringConstant() throws Exception {
   9.184 +        ConstantValues value = Models.bind(new ConstantValues(), c);
   9.185 +        value.setStringValue("Hi");
   9.186 +
   9.187 +        Map m = (Map) Models.toRaw(value);
   9.188 +        Object v = m.get("stringValue");
   9.189 +        assertNotNull(v, "Value should be in the map");
   9.190 +        assertEquals(v.getClass(), MapModelTest.One.class, "It is instance of One");
   9.191 +        MapModelTest.One o = (MapModelTest.One) v;
   9.192 +        assertEquals(o.changes, 0, "No change so far the only one change happened before we connected");
   9.193 +        assertEquals("Hi", o.get());
   9.194 +
   9.195 +        try {
   9.196 +            value.setStringValue("Hello");
   9.197 +            fail("Changing value shouldn't succeed!");
   9.198 +        } catch (IllegalStateException ex) {
   9.199 +            // OK
   9.200 +        }
   9.201 +        assertEquals(o.get(), "Hi", "Old value should still be in the map");
   9.202 +        assertEquals(o.changes, 0, "No change");
   9.203 +        assertFalse(o.pb.isReadOnly(), "Mutable property");
   9.204 +    }
   9.205 +
   9.206 +    @Test
   9.207 +    public void stringArray() throws Exception {
   9.208 +        ConstantValues value = Models.bind(new ConstantValues(), c);
   9.209 +        value.getStringArray().add("Hi");
   9.210 +
   9.211 +        Map m = (Map) Models.toRaw(value);
   9.212 +        Object v = m.get("stringArray");
   9.213 +        assertNotNull(v, "Value should be in the map");
   9.214 +        assertEquals(v.getClass(), MapModelTest.One.class, "It is instance of One");
   9.215 +        MapModelTest.One o = (MapModelTest.One) v;
   9.216 +        assertEquals(o.changes, 0, "No change so far the only one change happened before we connected");
   9.217 +        assertEquals(o.get(), new String[] { "Hi" }, "One element");
   9.218 +
   9.219 +        try {
   9.220 +            value.getStringArray().add("Hello");
   9.221 +            fail("Changing value shouldn't succeed!");
   9.222 +        } catch (UnsupportedOperationException ex) {
   9.223 +            // OK
   9.224 +        }
   9.225 +        assertEquals(o.get(), new String[] { "Hi" }, "Old value should still be in the map");
   9.226 +        assertEquals(o.changes, 0, "No change");
   9.227 +        assertFalse(o.pb.isReadOnly(), "Mutable property");
   9.228 +    }
   9.229 +
   9.230 +}
    10.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java	Mon Feb 15 05:27:28 2016 +0100
    10.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java	Mon Feb 29 05:33:31 2016 +0100
    10.3 @@ -72,10 +72,12 @@
    10.4      final Object createKO(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr, Knockout[] ko) {
    10.5          String[] propNames = new String[propArr.length];
    10.6          Boolean[] propReadOnly = new Boolean[propArr.length];
    10.7 +        Boolean[] propConstant = new Boolean[propArr.length];
    10.8          Object[] propValues = new Object[propArr.length];
    10.9          for (int i = 0; i < propNames.length; i++) {
   10.10              propNames[i] = propArr[i].getPropertyName();
   10.11              propReadOnly[i] = propArr[i].isReadOnly();
   10.12 +            propConstant[i] = propArr[i].isConstant();
   10.13              Object value = propArr[i].getValue();
   10.14              if (value instanceof Enum) {
   10.15                  value = value.toString();
   10.16 @@ -93,7 +95,7 @@
   10.17          }
   10.18          newKO.wrapModel(
   10.19              ret, copyFrom,
   10.20 -            propNames, propReadOnly, propValues,
   10.21 +            propNames, propReadOnly, propConstant, propValues,
   10.22              funcNames
   10.23          );
   10.24          return ret;
    11.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Mon Feb 15 05:27:28 2016 +0100
    11.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Mon Feb 29 05:33:31 2016 +0100
    11.3 @@ -168,7 +168,7 @@
    11.4          javacall = true,
    11.5          keepAlive = false,
    11.6          wait4js = false,
    11.7 -        args = { "ret", "copyFrom", "propNames", "propReadOnly", "propValues", "funcNames" },
    11.8 +        args = { "ret", "copyFrom", "propNames", "propReadOnly", "propConstant", "propValues", "funcNames" },
    11.9          body = 
   11.10            "Object.defineProperty(ret, 'ko4j', { value : this });\n"
   11.11          + "function koComputed(index, name, readOnly, value) {\n"
   11.12 @@ -225,7 +225,11 @@
   11.13          + "  ret[name] = cmpt;\n"
   11.14          + "}\n"
   11.15          + "for (var i = 0; i < propNames.length; i++) {\n"
   11.16 -        + "  koComputed(i, propNames[i], propReadOnly[i], propValues[i]);\n"
   11.17 +        + "  if (propConstant[i]) {\n"
   11.18 +        + "    ret[propNames[i]] = propValues[i];\n"
   11.19 +        + "  } else {\n"
   11.20 +        + "    koComputed(i, propNames[i], propReadOnly[i], propValues[i]);\n"
   11.21 +        + "  }\n"
   11.22          + "}\n"
   11.23          + "function koExpose(index, name) {\n"
   11.24          + "  ret[name] = function(data, ev) {\n"
   11.25 @@ -240,7 +244,8 @@
   11.26          )
   11.27      native void wrapModel(
   11.28          Object ret, Object copyFrom,
   11.29 -        String[] propNames, Boolean[] propReadOnly, Object propValues,
   11.30 +        String[] propNames, Boolean[] propReadOnly, Boolean[] propConstant,
   11.31 +        Object propValues,
   11.32          String[] funcNames
   11.33      );
   11.34      
    12.1 --- a/src/main/javadoc/overview.html	Mon Feb 15 05:27:28 2016 +0100
    12.2 +++ b/src/main/javadoc/overview.html	Mon Feb 29 05:33:31 2016 +0100
    12.3 @@ -101,7 +101,11 @@
    12.4          {@link net.java.html.json.Model Model classes} can generate
    12.5          builder-like construction methods if builder
    12.6          {@link net.java.html.json.Model#builder() prefix} is specified.
    12.7 -        The <em>JavaFX</em> presenter can be executed in headless mode -
    12.8 +        {@link net.java.html.json.Property#mutable} can be <code>false</code>
    12.9 +        to define a non-mutable (almost constant) property. That
   12.10 +        in case of <em>Knockout</em> bindings means: the property is
   12.11 +        represented by a plain value rather than an observable in the JavaScript
   12.12 +        object. The <em>JavaFX</em> presenter can be executed in headless mode -
   12.13          just specify <code>-Dfxpresenter.headless=true</code> when launching
   12.14          its virtual machine and no window will be shown. This is particularly
   12.15          useful for testing. Configure your <em>surefire</em> or <em>failsafe</em>