#258088: Non-mutable values aren't wrapped into ko.observable, but rather stored as primitive values NonMutable258088
authorJaroslav Tulach <jtulach@netbeans.org>
Mon, 22 Feb 2016 19:58:32 +0100
branchNonMutable258088
changeset 1055c61d247f087a
parent 1054 4c40ceb185e5
child 1056 a753f36c67fc
#258088: Non-mutable values aren't wrapped into ko.observable, but rather stored as primitive values
json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java
json/src/main/java/org/netbeans/html/json/impl/Bindings.java
json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java
json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java
json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java
json/src/main/java/org/netbeans/html/json/spi/Proto.java
ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java
ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
     1.1 --- a/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java	Mon Feb 22 06:09:33 2016 +0100
     1.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java	Mon Feb 22 19:58:32 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/org/netbeans/html/json/impl/Bindings.java	Mon Feb 22 06:09:33 2016 +0100
     2.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java	Mon Feb 22 19:58:32 2016 +0100
     2.3 @@ -64,8 +64,8 @@
     2.4          this.bp = bp;
     2.5      }
     2.6      
     2.7 -    public <M> PropertyBinding registerProperty(String propName, int index, M model, Proto.Type<M> access, boolean readOnly) {
     2.8 -        return PropertyBindingAccessor.create(access, this, propName, index, model, readOnly);
     2.9 +    public <M> PropertyBinding registerProperty(String propName, int index, M model, Proto.Type<M> access, byte propertyType) {
    2.10 +        return PropertyBindingAccessor.create(access, this, propName, index, model, propertyType);
    2.11      }
    2.12  
    2.13      public static Bindings<?> apply(BrwsrCtx c) {
     3.1 --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java	Mon Feb 22 06:09:33 2016 +0100
     3.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java	Mon Feb 22 19:58:32 2016 +0100
     3.3 @@ -359,7 +359,7 @@
     3.4                  {
     3.5                      for (int i = 0; i < propsGetSet.size(); i++) {
     3.6                          w.append("      registerProperty(\"").append(propsGetSet.get(i).name).append("\", ");
     3.7 -                        w.append((i) + ", " + propsGetSet.get(i).readOnly + ");\n");
     3.8 +                        w.append((i) + ", " + propsGetSet.get(i).readOnly + ", " + propsGetSet.get(i).constant + ");\n");
     3.9                      }
    3.10                  }
    3.11                  {
    3.12 @@ -728,7 +728,8 @@
    3.13                  gs[0],
    3.14                  gs[1],
    3.15                  tn,
    3.16 -                gs[3] == null && !p.array()
    3.17 +                gs[3] == null && !p.array(),
    3.18 +                !p.mutable()
    3.19              ));
    3.20          }
    3.21          return ok;
    3.22 @@ -864,7 +865,8 @@
    3.23                      gs[0],
    3.24                      null,
    3.25                      tn,
    3.26 -                    true
    3.27 +                    true,
    3.28 +                    false
    3.29                  ));
    3.30              } else {
    3.31                  w.write("  public void " + gs[4] + "(" + write.getParameters().get(1).asType());
    3.32 @@ -877,6 +879,7 @@
    3.33                      gs[0],
    3.34                      gs[4],
    3.35                      tn,
    3.36 +                    false,
    3.37                      false
    3.38                  ));
    3.39              }
    3.40 @@ -2069,12 +2072,15 @@
    3.41          final String setter;
    3.42          final String type;
    3.43          final boolean readOnly;
    3.44 -        GetSet(String name, String getter, String setter, String type, boolean readOnly) {
    3.45 +        final boolean constant;
    3.46 +        
    3.47 +        GetSet(String name, String getter, String setter, String type, boolean readOnly, boolean constant) {
    3.48              this.name = name;
    3.49              this.getter = getter;
    3.50              this.setter = setter;
    3.51              this.type = type;
    3.52              this.readOnly = readOnly;
    3.53 +            this.constant = constant;
    3.54          }
    3.55      }
    3.56  
     4.1 --- a/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java	Mon Feb 22 06:09:33 2016 +0100
     4.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java	Mon Feb 22 19:58:32 2016 +0100
     4.3 @@ -64,8 +64,7 @@
     4.4      }
     4.5  
     4.6      protected abstract <M> PropertyBinding newBinding(
     4.7 -        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, boolean readOnly
     4.8 -    );
     4.9 +        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, byte propertyType);
    4.10      protected abstract JSONCall newCall(
    4.11          BrwsrCtx ctx, RcvrJSON callback,
    4.12          String headers, String urlBefore, String urlAfter,
    4.13 @@ -87,9 +86,9 @@
    4.14      }
    4.15  
    4.16      static <M> PropertyBinding create(
    4.17 -        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model , boolean readOnly
    4.18 +        Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model , byte propertyType
    4.19      ) {
    4.20 -        return DEFAULT.newBinding(access, bindings, name, index, model, readOnly);
    4.21 +        return DEFAULT.newBinding(access, bindings, name, index, model, propertyType);
    4.22      }
    4.23      public static JSONCall createCall(
    4.24          BrwsrCtx ctx, RcvrJSON callback,
     5.1 --- a/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java	Mon Feb 22 06:09:33 2016 +0100
     5.2 +++ b/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java	Mon Feb 22 19:58:32 2016 +0100
     5.3 @@ -45,6 +45,7 @@
     5.4  import java.lang.ref.Reference;
     5.5  import java.lang.ref.WeakReference;
     5.6  import net.java.html.BrwsrCtx;
     5.7 +import net.java.html.json.ComputedProperty;
     5.8  import org.netbeans.html.json.impl.Bindings;
     5.9  import org.netbeans.html.json.impl.JSON;
    5.10  import org.netbeans.html.json.impl.PropertyBindingAccessor;
    5.11 @@ -93,10 +94,8 @@
    5.12  
    5.13              @Override
    5.14              protected <M> PropertyBinding newBinding(
    5.15 -                Proto.Type<M> access, Bindings<?> bindings, String name,
    5.16 -                int index, M model, boolean readOnly
    5.17 -            ) {
    5.18 -                return new Impl(model, bindings, name, index, access, readOnly);
    5.19 +                Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, byte propertyType) {
    5.20 +                return new Impl(model, bindings, name, index, access, propertyType);
    5.21              }
    5.22          };
    5.23      }
    5.24 @@ -121,12 +120,22 @@
    5.25       */
    5.26      public abstract Object getValue();
    5.27  
    5.28 -    /** Is this property read only? Or can one call {@link #setValue(java.lang.Object)}?
    5.29 +    /** Is this property read only?. Or can one call {@link #setValue(java.lang.Object)}?
    5.30 +     * The property can still change, but only as a result of other
    5.31 +     * properties being changed, just like {@link ComputedProperty} can.
    5.32       *
    5.33       * @return true, if this property is read only
    5.34       */
    5.35      public abstract boolean isReadOnly();
    5.36  
    5.37 +    /** Is this property constant?. If a property is constant, than its
    5.38 +     * value cannot changed after it is read.
    5.39 +     *
    5.40 +     * @return true, if this property is constant
    5.41 +     * @since 1.3
    5.42 +     */
    5.43 +    public abstract boolean isConstant();
    5.44 +
    5.45      /** Returns identical version of the binding, but one that holds on the
    5.46       * original model object via weak reference.
    5.47       *
    5.48 @@ -137,17 +146,17 @@
    5.49  
    5.50      private static abstract class AImpl<M> extends PropertyBinding {
    5.51          public final String name;
    5.52 -        public final boolean readOnly;
    5.53 +        public final byte propertyType;
    5.54          final Proto.Type<M> access;
    5.55          final Bindings<?> bindings;
    5.56          final int index;
    5.57  
    5.58 -        public AImpl(Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
    5.59 +        public AImpl(Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) {
    5.60              this.bindings = bindings;
    5.61              this.name = name;
    5.62              this.index = index;
    5.63              this.access = access;
    5.64 -            this.readOnly = readOnly;
    5.65 +            this.propertyType = propertyType;
    5.66          }
    5.67  
    5.68          protected abstract M model();
    5.69 @@ -174,7 +183,12 @@
    5.70  
    5.71          @Override
    5.72          public boolean isReadOnly() {
    5.73 -            return readOnly;
    5.74 +            return (propertyType & 1) != 0;
    5.75 +        }
    5.76 +
    5.77 +        @Override
    5.78 +        public boolean isConstant() {
    5.79 +            return (propertyType & 2) != 0;
    5.80          }
    5.81  
    5.82          @Override
    5.83 @@ -186,8 +200,8 @@
    5.84      private static final class Impl<M> extends AImpl<M> {
    5.85          private final M model;
    5.86  
    5.87 -        public Impl(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
    5.88 -            super(bindings, name, index, access, readOnly);
    5.89 +        public Impl(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) {
    5.90 +            super(bindings, name, index, access, propertyType);
    5.91              this.model = model;
    5.92          }
    5.93  
    5.94 @@ -198,14 +212,14 @@
    5.95  
    5.96          @Override
    5.97          public PropertyBinding weak() {
    5.98 -            return new Weak(model, bindings, name, index, access, readOnly);
    5.99 +            return new Weak(model, bindings, name, index, access, propertyType);
   5.100          }
   5.101      }
   5.102  
   5.103      private static final class Weak<M> extends AImpl<M> {
   5.104          private final Reference<M> ref;
   5.105 -        public Weak(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, boolean readOnly) {
   5.106 -            super(bindings, name, index, access, readOnly);
   5.107 +        public Weak(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) {
   5.108 +            super(bindings, name, index, access, propertyType);
   5.109              this.ref = new WeakReference<M>(model);
   5.110          }
   5.111  
     6.1 --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java	Mon Feb 22 06:09:33 2016 +0100
     6.2 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java	Mon Feb 22 19:58:32 2016 +0100
     6.3 @@ -512,7 +512,7 @@
     6.4              PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length];
     6.5              for (int i = 0; i < pb.length; i++) {
     6.6                  pb[i] = b.registerProperty(
     6.7 -                    type.propertyNames[i], i, obj, type, type.propertyReadOnly[i]
     6.8 +                    type.propertyNames[i], i, obj, type, type.propertyType[i]
     6.9                  );
    6.10              }
    6.11              FunctionBinding[] fb = new FunctionBinding[type.functions.length];
    6.12 @@ -551,7 +551,7 @@
    6.13      public static abstract class Type<Model> {
    6.14          private final Class<Model> clazz;
    6.15          private final String[] propertyNames;
    6.16 -        private final boolean[] propertyReadOnly;
    6.17 +        private final byte[] propertyType;
    6.18          private final String[] functions;
    6.19  
    6.20          /** Constructor for subclasses generated by the annotation processor
    6.21 @@ -573,7 +573,7 @@
    6.22              }
    6.23              this.clazz = clazz;
    6.24              this.propertyNames = new String[properties];
    6.25 -            this.propertyReadOnly = new boolean[properties];
    6.26 +            this.propertyType = new byte[properties];
    6.27              this.functions = new String[functions];
    6.28              JSON.register(clazz, this);
    6.29          }
    6.30 @@ -588,7 +588,29 @@
    6.31          protected final void registerProperty(String name, int index, boolean readOnly) {
    6.32              assert propertyNames[index] == null;
    6.33              propertyNames[index] = name;
    6.34 -            propertyReadOnly[index] = readOnly;
    6.35 +            propertyType[index] = (byte) (readOnly ? 1 : 0);
    6.36 +        }
    6.37 +
    6.38 +        /** Registers property for the type. It is expected each index
    6.39 +         * is initialized only once. The difference between <code>readOnly</code>
    6.40 +         * and <code>constant</code> is: The <code>constant</code> value is
    6.41 +         * assigned only at the beginning and never changed then - like the
    6.42 +         * {@link Property#mutable() non-mutable} property. On the other
    6.43 +         * hand, a <code>readOnly</code> property can change its value,
    6.44 +         * but not via a setter - just like {@link ComputedProperty}.
    6.45 +         *
    6.46 +         * @param name name of the property
    6.47 +         * @param index index of the property
    6.48 +         * @param readOnly is the property read only?
    6.49 +         * @param constant is the property assigned once and never changed again?
    6.50 +         * @since 1.3
    6.51 +         */
    6.52 +        protected final void registerProperty(
    6.53 +            String name, int index, boolean readOnly, boolean constant
    6.54 +        ) {
    6.55 +            assert propertyNames[index] == null;
    6.56 +            propertyNames[index] = name;
    6.57 +            propertyType[index] = (byte) ((readOnly ? 1 : 0) | (constant ? 2 : 0));
    6.58          }
    6.59  
    6.60          /** Registers function of given name at given index.
     7.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java	Mon Feb 22 06:09:33 2016 +0100
     7.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java	Mon Feb 22 19:58:32 2016 +0100
     7.3 @@ -72,10 +72,12 @@
     7.4      final Object createKO(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr, Knockout[] ko) {
     7.5          String[] propNames = new String[propArr.length];
     7.6          Boolean[] propReadOnly = new Boolean[propArr.length];
     7.7 +        Boolean[] propConstant = new Boolean[propArr.length];
     7.8          Object[] propValues = new Object[propArr.length];
     7.9          for (int i = 0; i < propNames.length; i++) {
    7.10              propNames[i] = propArr[i].getPropertyName();
    7.11              propReadOnly[i] = propArr[i].isReadOnly();
    7.12 +            propConstant[i] = propArr[i].isConstant();
    7.13              Object value = propArr[i].getValue();
    7.14              if (value instanceof Enum) {
    7.15                  value = value.toString();
    7.16 @@ -93,7 +95,7 @@
    7.17          }
    7.18          newKO.wrapModel(
    7.19              ret, copyFrom,
    7.20 -            propNames, propReadOnly, propValues,
    7.21 +            propNames, propReadOnly, propConstant, propValues,
    7.22              funcNames
    7.23          );
    7.24          return ret;
     8.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Mon Feb 22 06:09:33 2016 +0100
     8.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Mon Feb 22 19:58:32 2016 +0100
     8.3 @@ -168,7 +168,7 @@
     8.4          javacall = true,
     8.5          keepAlive = false,
     8.6          wait4js = false,
     8.7 -        args = { "ret", "copyFrom", "propNames", "propReadOnly", "propValues", "funcNames" },
     8.8 +        args = { "ret", "copyFrom", "propNames", "propReadOnly", "propConstant", "propValues", "funcNames" },
     8.9          body = 
    8.10            "Object.defineProperty(ret, 'ko4j', { value : this });\n"
    8.11          + "function koComputed(index, name, readOnly, value) {\n"
    8.12 @@ -225,7 +225,11 @@
    8.13          + "  ret[name] = cmpt;\n"
    8.14          + "}\n"
    8.15          + "for (var i = 0; i < propNames.length; i++) {\n"
    8.16 -        + "  koComputed(i, propNames[i], propReadOnly[i], propValues[i]);\n"
    8.17 +        + "  if (propConstant[i]) {\n"
    8.18 +        + "    ret[propNames[i]] = propValues[i];\n"
    8.19 +        + "  } else {\n"
    8.20 +        + "    koComputed(i, propNames[i], propReadOnly[i], propValues[i]);\n"
    8.21 +        + "  }\n"
    8.22          + "}\n"
    8.23          + "function koExpose(index, name) {\n"
    8.24          + "  ret[name] = function(data, ev) {\n"
    8.25 @@ -240,7 +244,8 @@
    8.26          )
    8.27      native void wrapModel(
    8.28          Object ret, Object copyFrom,
    8.29 -        String[] propNames, Boolean[] propReadOnly, Object propValues,
    8.30 +        String[] propNames, Boolean[] propReadOnly, Boolean[] propConstant,
    8.31 +        Object propValues,
    8.32          String[] funcNames
    8.33      );
    8.34