# HG changeset patch # User Jaroslav Tulach # Date 1456167512 -3600 # Node ID c61d247f087a04b34f8ec5fca89252dbd4a40f2f # Parent 4c40ceb185e5d6b7b447279c4ad6fe760a596f22 #258088: Non-mutable values aren't wrapped into ko.observable, but rather stored as primitive values diff -r 4c40ceb185e5 -r c61d247f087a json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java Mon Feb 22 06:09:33 2016 +0100 +++ b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java Mon Feb 22 19:58:32 2016 +0100 @@ -73,14 +73,14 @@ @Property(name="choice", type=KnockoutTest.Choice.class), @Property(name="archetype", type=ArchetypeData.class), @Property(name="archetypes", type=ArchetypeData.class, array = true), -}) +}) public final class KnockoutTest { private KnockoutModel js; - + enum Choice { A, B; } - + @ComputedProperty static List resultLengths(List results) { Integer[] arr = new Integer[results.size()]; for (int i = 0; i < arr.length; i++) { @@ -88,9 +88,9 @@ } return Arrays.asList(arr); } - + @KOTest public void modifyValueAssertChangeInModelOnEnum() throws Throwable { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "Latitude: \n" ); try { @@ -143,9 +143,9 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void modifyValueAssertChangeInModelOnDouble() throws Throwable { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "Latitude: \n" ); try { @@ -167,7 +167,7 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void rawObject() throws Exception { if (js == null) { final BrwsrCtx ctx = newContext(); @@ -200,7 +200,7 @@ p1.setFirstName("Ondra"); assertEquals(p1.getFirstName(), "Ondra", "1st name updated in original object"); - + js.getPeople().add(p1); } @@ -239,9 +239,9 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "Latitude: \n" ); try { @@ -263,9 +263,9 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void modifyValueAssertChangeInModel() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "

Loading Bck2Brwsr's Hello World...

\n" + "Your name: \n" + "\n" @@ -287,7 +287,7 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + private static String getSetSelected(int index, Object value) throws Exception { String s = "var index = arguments[0];\n" + "var value = arguments[1];\n" @@ -305,7 +305,7 @@ ); return ret == null ? null : ret.toString(); } - + @Model(className = "ArchetypeData", properties = { @Property(name = "artifactId", type = String.class), @Property(name = "groupId", type = String.class), @@ -316,35 +316,35 @@ }) static class ArchModel { } - + @KOTest public void selectWorksOnModels() throws Exception { if (js == null) { - Utils.exposeHTML(KnockoutTest.class, + Utils.exposeHTML(KnockoutTest.class, "\n" + "" ); - + js = Models.bind(new KnockoutModel(), newContext()); js.getArchetypes().add(new ArchetypeData("ko4j", "org.netbeans.html", "0.8.3", "ko4j", "ko4j", null)); js.getArchetypes().add(new ArchetypeData("crud", "org.netbeans.html", "0.8.3", "crud", "crud", null)); js.getArchetypes().add(new ArchetypeData("3rd", "org.netbeans.html", "0.8.3", "3rd", "3rd", null)); js.setArchetype(js.getArchetypes().get(1)); js.applyBindings(); - + String v = getSetSelected(0, null); assertEquals("crud", v, "Second index (e.g. crud) is selected: " + v); - + String sel = getSetSelected(2, Models.toRaw(js.getArchetypes().get(2))); assertEquals("3rd", sel, "3rd is selected now: " + sel); } - + if (js.getArchetype() != js.getArchetypes().get(2)) { throw new InterruptedException(); } - + Utils.exposeHTML(KnockoutTest.class, ""); } @@ -374,22 +374,22 @@ assertEquals("org.netbeans.html", v, "groupId has been changed"); Utils.exposeHTML(KnockoutTest.class, ""); } - + @KOTest public void modifyValueAssertAsyncChangeInModel() throws Exception { if (js == null) { - Utils.exposeHTML(KnockoutTest.class, + Utils.exposeHTML(KnockoutTest.class, "

Loading Bck2Brwsr's Hello World...

\n" + "Your name: \n" + "\n" ); - + js = Models.bind(new KnockoutModel(), newContext()); js.setName("Kukuc"); js.applyBindings(); - + String v = getSetInput("input", null); assertEquals("Kukuc", v, "Value is really kukuc: " + v); - + Timer t = new Timer("Set to Jardo"); t.schedule(new TimerTask() { @Override @@ -398,15 +398,100 @@ } }, 1); } - + String v = getSetInput("input", null); if (!"Jardo".equals(v)) { throw new InterruptedException(); } - + Utils.exposeHTML(KnockoutTest.class, ""); } - + + @Model(className = "ConstantModel", targetId = "", builder = "assign", properties = { + @Property(name = "doubleValue", mutable = false, type = double.class), + @Property(name = "longValue", mutable = false, type = long.class), + @Property(name = "stringValue", mutable = false, type = String.class), + @Property(name = "boolValue", mutable = false, type = boolean.class), + @Property(name = "intArray", mutable = false, type = int.class, array = true), + }) + static class ConstantCntrl { + } + + @KOTest public void nonMutableDouble() throws Exception { + Utils.exposeHTML(KnockoutTest.class, + "Type: \n" + ); + + ConstantModel model = Models.bind(new ConstantModel(), newContext()); + model.assignStringValue("Hello").assignDoubleValue(10.0); + model.applyBindings(); + + String v = getSetInput("input", null); + assertEquals(v, "number", "Right type found: " + v); + + Utils.exposeHTML(KnockoutTest.class, ""); + } + + @KOTest public void nonMutableString() throws Exception { + Utils.exposeHTML(KnockoutTest.class, + "Type: \n" + ); + + ConstantModel model = Models.bind(new ConstantModel(), newContext()); + model.assignStringValue("Hello").assignDoubleValue(10.0); + model.applyBindings(); + + String v = getSetInput("input", null); + assertEquals(v, "string", "Right type found: " + v); + + Utils.exposeHTML(KnockoutTest.class, ""); + } + + @KOTest public void nonMutableBoolean() throws Exception { + Utils.exposeHTML(KnockoutTest.class, + "Type: \n" + ); + + ConstantModel model = Models.bind(new ConstantModel(), newContext()); + model.assignStringValue("Hello").assignBoolValue(true); + model.applyBindings(); + + String v = getSetInput("input", null); + assertEquals(v, "boolean", "Right type found: " + v); + + Utils.exposeHTML(KnockoutTest.class, ""); + } + + @KOTest public void nonMutableLong() throws Exception { + Utils.exposeHTML(KnockoutTest.class, + "Type: \n" + ); + + ConstantModel model = Models.bind(new ConstantModel(), newContext()); + model.assignStringValue("Hello").assignLongValue(Long.MAX_VALUE); + model.applyBindings(); + + String v = getSetInput("input", null); + assertEquals(v, "number", "Right type found: " + v); + + Utils.exposeHTML(KnockoutTest.class, ""); + } + + @KOTest public void nonMutableIntArray() throws Exception { + Utils.exposeHTML(KnockoutTest.class, + "Type: \n" + ); + + ConstantModel model = Models.bind(new ConstantModel(), newContext()); + model.assignStringValue("Hello").assignLongValue(Long.MAX_VALUE).assignIntArray(1, 2, 3, 4); + model.applyBindings(); + + String v = getSetInput("input", null); + assertEquals(v, "object", "Right type found: " + v); + + Utils.exposeHTML(KnockoutTest.class, ""); + } + private static String getSetInput(String id, String value) throws Exception { String s = "var value = arguments[0];\n" + "var n = window.document.getElementById(arguments[1]); \n " @@ -429,7 +514,7 @@ ); return Boolean.TRUE.equals(ret); } - + public static void triggerEvent(String id, String ev) throws Exception { Utils.executeScript( KnockoutTest.class, @@ -437,9 +522,9 @@ id, ev ); } - + @KOTest public void displayContentOfArray() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "
    \n" + "
  • \n" + "
\n" @@ -465,10 +550,10 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void displayContentOfAsyncArray() throws Exception { if (js == null) { - Utils.exposeHTML(KnockoutTest.class, + Utils.exposeHTML(KnockoutTest.class, "
    \n" + "
  • \n" + "
\n" @@ -479,7 +564,7 @@ int cnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(cnt, 1, "One child, but was " + cnt); - + Timer t = new Timer("add to array"); t.schedule(new TimerTask() { @Override @@ -504,9 +589,9 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void displayContentOfComputedArray() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "
    \n" + "
  • \n" + "
\n" @@ -521,23 +606,23 @@ triggerChildClick("ul", 1); assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName()); - + m.setLastName("Verylast"); cnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(cnt, 2, "Two children now, but was " + cnt); - + triggerChildClick("ul", 1); assertEquals("Verylast", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName()); - + } finally { Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void displayContentOfComputedArrayOnASubpair() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "
\n" + "
    \n" + "
  • \n" @@ -553,7 +638,7 @@ assertEquals(cnt, 2, "Two children now, but was " + cnt); triggerChildClick("ul", 1); - + assertEquals(PairModel.ctx, ctx, "Context remains the same"); assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName()); @@ -561,9 +646,9 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void displayContentOfComputedArrayOnComputedASubpair() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "
    \n" + "
      \n" + "
    • \n" @@ -586,7 +671,7 @@ } @KOTest public void checkBoxToBooleanBinding() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "\n" ); try { @@ -602,11 +687,11 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - - - + + + @KOTest public void displayContentOfDerivedArray() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "
        \n" + "
      • \n" + "
      \n" @@ -627,9 +712,9 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void displayContentOfArrayOfPeople() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "
        \n" + "
      • \n" + "
      \n" @@ -672,14 +757,14 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @ComputedProperty static Person firstPerson(List people) { return people.isEmpty() ? null : people.get(0); } - + @KOTest public void accessFirstPersonWithOnFunction() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "

      \n" + " \n" + "

      \n" @@ -690,9 +775,9 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void onPersonFunction() throws Exception { - Object exp = Utils.exposeHTML(KnockoutTest.class, + Object exp = Utils.exposeHTML(KnockoutTest.class, "
        \n" + "
      • \n" + "
      \n" @@ -703,7 +788,7 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } - + private void trasfertToFemale() throws Exception { KnockoutModel m = Models.bind(new KnockoutModel(), newContext()); @@ -723,7 +808,7 @@ assertEquals(first.getSex(), Sex.FEMALE, "Transverted to female: " + first.getSex()); } - + @KOTest public void stringArrayModificationVisible() throws Exception { Object exp = Utils.exposeHTML(KnockoutTest.class, "
      \n" @@ -737,19 +822,19 @@ m.getResults().add("Ahoj"); m.getResults().add("Hello"); m.applyBindings(); - + int cnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(cnt, 2, "Two children " + cnt); - + Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi"); assertTrue(arr instanceof Object[], "Got back an array: " + arr); final int len = ((Object[])arr).length; - + assertEquals(len, 3, "Three elements in the array " + len); - + int newCnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt); - + assertEquals(m.getResults().size(), 3, "Three java strings: " + m.getResults()); } finally { Utils.exposeHTML(KnockoutTest.class, ""); @@ -769,19 +854,19 @@ m.getNumbers().add(1); m.getNumbers().add(31); m.applyBindings(); - + int cnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(cnt, 2, "Two children " + cnt); - + Object arr = Utils.addChildren(KnockoutTest.class, "ul", "numbers", 42); assertTrue(arr instanceof Object[], "Got back an array: " + arr); final int len = ((Object[])arr).length; - + assertEquals(len, 3, "Three elements in the array " + len); - + int newCnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt); - + assertEquals(m.getNumbers().size(), 3, "Three java ints: " + m.getNumbers()); assertEquals(m.getNumbers().get(2), 42, "Meaning of world: " + m.getNumbers()); } finally { @@ -802,26 +887,26 @@ m.getResults().add("Ahoj"); m.getResults().add("Hello"); m.applyBindings(); - + int cnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(cnt, 2, "Two children " + cnt); - + Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi"); assertTrue(arr instanceof Object[], "Got back an array: " + arr); final int len = ((Object[])arr).length; - + assertEquals(len, 3, "Three elements in the array " + len); - + int newCnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt); - + assertEquals(m.getResultLengths().size(), 3, "Three java ints: " + m.getResultLengths()); assertEquals(m.getResultLengths().get(2), 2, "Size is two: " + m.getResultLengths()); } finally { Utils.exposeHTML(KnockoutTest.class, ""); } } - + @KOTest public void archetypeArrayModificationVisible() throws Exception { Object exp = Utils.exposeHTML(KnockoutTest.class, "
      \n" @@ -833,19 +918,19 @@ try { KnockoutModel m = Models.bind(new KnockoutModel(), newContext()); m.applyBindings(); - + int cnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(cnt, 0, "No children " + cnt); - + Object arr = Utils.addChildren(KnockoutTest.class, "ul", "archetypes", new ArchetypeData("aid", "gid", "v", "n", "d", "u")); assertTrue(arr instanceof Object[], "Got back an array: " + arr); final int len = ((Object[])arr).length; - + assertEquals(len, 1, "One element in the array " + len); - + int newCnt = Utils.countChildren(KnockoutTest.class, "ul"); assertEquals(newCnt, 1, "One child in the DOM: " + newCnt); - + assertEquals(m.getArchetypes().size(), 1, "One archetype: " + m.getArchetypes()); assertNotNull(m.getArchetypes().get(0), "Not null: " + m.getArchetypes()); assertEquals(m.getArchetypes().get(0).getArtifactId(), "aid", "'aid' == " + m.getArchetypes()); @@ -865,18 +950,18 @@ model.setCallbackCount(model.getCallbackCount() + 1); model.getPeople().remove(data); } - - + + @ComputedProperty static String helloMessage(String name) { return "Hello " + name + "!"; } - + @ComputedProperty static List cmpResults(List results) { return results; } - + private static void triggerClick(String id) throws Exception { String s = "var id = arguments[0];" + "var e = window.document.getElementById(id);\n " @@ -893,17 +978,17 @@ s, id); } private static void triggerChildClick(String id, int pos) throws Exception { - String s = + String s = "var id = arguments[0]; var pos = arguments[1];\n" + "var e = window.document.getElementById(id);\n " + "var ev = window.document.createEvent('MouseEvents');\n " + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " + "var list = e.childNodes;\n" + - "var cnt = -1;\n" + - "for (var i = 0; i < list.length; i++) {\n" + - " if (list[i].nodeType == 1) cnt++;\n" + - " if (cnt == pos) return list[i].dispatchEvent(ev);\n" + - "}\n" + + "var cnt = -1;\n" + + "for (var i = 0; i < list.length; i++) {\n" + + " if (list[i].nodeType == 1) cnt++;\n" + + " if (cnt == pos) return list[i].dispatchEvent(ev);\n" + + "}\n" + "return null;\n"; Utils.executeScript( KnockoutTest.class, @@ -911,15 +996,15 @@ } private static String childText(String id, int pos) throws Exception { - String s = + String s = "var id = arguments[0]; var pos = arguments[1];" + "var e = window.document.getElementById(id);\n" + "var list = e.childNodes;\n" + - "var cnt = -1;\n" + - "for (var i = 0; i < list.length; i++) {\n" + - " if (list[i].nodeType == 1) cnt++;\n" + - " if (cnt == pos) return list[i].innerHTML;\n" + - "}\n" + + "var cnt = -1;\n" + + "for (var i = 0; i < list.length; i++) {\n" + + " if (list[i].nodeType == 1) cnt++;\n" + + " if (cnt == pos) return list[i].innerHTML;\n" + + "}\n" + "return null;\n"; return (String)Utils.executeScript( KnockoutTest.class, diff -r 4c40ceb185e5 -r c61d247f087a json/src/main/java/org/netbeans/html/json/impl/Bindings.java --- a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java Mon Feb 22 06:09:33 2016 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java Mon Feb 22 19:58:32 2016 +0100 @@ -64,8 +64,8 @@ this.bp = bp; } - public PropertyBinding registerProperty(String propName, int index, M model, Proto.Type access, boolean readOnly) { - return PropertyBindingAccessor.create(access, this, propName, index, model, readOnly); + public PropertyBinding registerProperty(String propName, int index, M model, Proto.Type access, byte propertyType) { + return PropertyBindingAccessor.create(access, this, propName, index, model, propertyType); } public static Bindings apply(BrwsrCtx c) { diff -r 4c40ceb185e5 -r c61d247f087a json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Mon Feb 22 06:09:33 2016 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Mon Feb 22 19:58:32 2016 +0100 @@ -359,7 +359,7 @@ { for (int i = 0; i < propsGetSet.size(); i++) { w.append(" registerProperty(\"").append(propsGetSet.get(i).name).append("\", "); - w.append((i) + ", " + propsGetSet.get(i).readOnly + ");\n"); + w.append((i) + ", " + propsGetSet.get(i).readOnly + ", " + propsGetSet.get(i).constant + ");\n"); } } { @@ -728,7 +728,8 @@ gs[0], gs[1], tn, - gs[3] == null && !p.array() + gs[3] == null && !p.array(), + !p.mutable() )); } return ok; @@ -864,7 +865,8 @@ gs[0], null, tn, - true + true, + false )); } else { w.write(" public void " + gs[4] + "(" + write.getParameters().get(1).asType()); @@ -877,6 +879,7 @@ gs[0], gs[4], tn, + false, false )); } @@ -2069,12 +2072,15 @@ final String setter; final String type; final boolean readOnly; - GetSet(String name, String getter, String setter, String type, boolean readOnly) { + final boolean constant; + + GetSet(String name, String getter, String setter, String type, boolean readOnly, boolean constant) { this.name = name; this.getter = getter; this.setter = setter; this.type = type; this.readOnly = readOnly; + this.constant = constant; } } diff -r 4c40ceb185e5 -r c61d247f087a json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java --- a/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java Mon Feb 22 06:09:33 2016 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java Mon Feb 22 19:58:32 2016 +0100 @@ -64,8 +64,7 @@ } protected abstract PropertyBinding newBinding( - Proto.Type access, Bindings bindings, String name, int index, M model, boolean readOnly - ); + Proto.Type access, Bindings bindings, String name, int index, M model, byte propertyType); protected abstract JSONCall newCall( BrwsrCtx ctx, RcvrJSON callback, String headers, String urlBefore, String urlAfter, @@ -87,9 +86,9 @@ } static PropertyBinding create( - Proto.Type access, Bindings bindings, String name, int index, M model , boolean readOnly + Proto.Type access, Bindings bindings, String name, int index, M model , byte propertyType ) { - return DEFAULT.newBinding(access, bindings, name, index, model, readOnly); + return DEFAULT.newBinding(access, bindings, name, index, model, propertyType); } public static JSONCall createCall( BrwsrCtx ctx, RcvrJSON callback, diff -r 4c40ceb185e5 -r c61d247f087a json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java --- a/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java Mon Feb 22 06:09:33 2016 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java Mon Feb 22 19:58:32 2016 +0100 @@ -45,6 +45,7 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; import net.java.html.BrwsrCtx; +import net.java.html.json.ComputedProperty; import org.netbeans.html.json.impl.Bindings; import org.netbeans.html.json.impl.JSON; import org.netbeans.html.json.impl.PropertyBindingAccessor; @@ -93,10 +94,8 @@ @Override protected PropertyBinding newBinding( - Proto.Type access, Bindings bindings, String name, - int index, M model, boolean readOnly - ) { - return new Impl(model, bindings, name, index, access, readOnly); + Proto.Type access, Bindings bindings, String name, int index, M model, byte propertyType) { + return new Impl(model, bindings, name, index, access, propertyType); } }; } @@ -121,12 +120,22 @@ */ public abstract Object getValue(); - /** Is this property read only? Or can one call {@link #setValue(java.lang.Object)}? + /** Is this property read only?. Or can one call {@link #setValue(java.lang.Object)}? + * The property can still change, but only as a result of other + * properties being changed, just like {@link ComputedProperty} can. * * @return true, if this property is read only */ public abstract boolean isReadOnly(); + /** Is this property constant?. If a property is constant, than its + * value cannot changed after it is read. + * + * @return true, if this property is constant + * @since 1.3 + */ + public abstract boolean isConstant(); + /** Returns identical version of the binding, but one that holds on the * original model object via weak reference. * @@ -137,17 +146,17 @@ private static abstract class AImpl extends PropertyBinding { public final String name; - public final boolean readOnly; + public final byte propertyType; final Proto.Type access; final Bindings bindings; final int index; - public AImpl(Bindings bindings, String name, int index, Proto.Type access, boolean readOnly) { + public AImpl(Bindings bindings, String name, int index, Proto.Type access, byte propertyType) { this.bindings = bindings; this.name = name; this.index = index; this.access = access; - this.readOnly = readOnly; + this.propertyType = propertyType; } protected abstract M model(); @@ -174,7 +183,12 @@ @Override public boolean isReadOnly() { - return readOnly; + return (propertyType & 1) != 0; + } + + @Override + public boolean isConstant() { + return (propertyType & 2) != 0; } @Override @@ -186,8 +200,8 @@ private static final class Impl extends AImpl { private final M model; - public Impl(M model, Bindings bindings, String name, int index, Proto.Type access, boolean readOnly) { - super(bindings, name, index, access, readOnly); + public Impl(M model, Bindings bindings, String name, int index, Proto.Type access, byte propertyType) { + super(bindings, name, index, access, propertyType); this.model = model; } @@ -198,14 +212,14 @@ @Override public PropertyBinding weak() { - return new Weak(model, bindings, name, index, access, readOnly); + return new Weak(model, bindings, name, index, access, propertyType); } } private static final class Weak extends AImpl { private final Reference ref; - public Weak(M model, Bindings bindings, String name, int index, Proto.Type access, boolean readOnly) { - super(bindings, name, index, access, readOnly); + public Weak(M model, Bindings bindings, String name, int index, Proto.Type access, byte propertyType) { + super(bindings, name, index, access, propertyType); this.ref = new WeakReference(model); } diff -r 4c40ceb185e5 -r c61d247f087a json/src/main/java/org/netbeans/html/json/spi/Proto.java --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java Mon Feb 22 06:09:33 2016 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java Mon Feb 22 19:58:32 2016 +0100 @@ -512,7 +512,7 @@ PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length]; for (int i = 0; i < pb.length; i++) { pb[i] = b.registerProperty( - type.propertyNames[i], i, obj, type, type.propertyReadOnly[i] + type.propertyNames[i], i, obj, type, type.propertyType[i] ); } FunctionBinding[] fb = new FunctionBinding[type.functions.length]; @@ -551,7 +551,7 @@ public static abstract class Type { private final Class clazz; private final String[] propertyNames; - private final boolean[] propertyReadOnly; + private final byte[] propertyType; private final String[] functions; /** Constructor for subclasses generated by the annotation processor @@ -573,7 +573,7 @@ } this.clazz = clazz; this.propertyNames = new String[properties]; - this.propertyReadOnly = new boolean[properties]; + this.propertyType = new byte[properties]; this.functions = new String[functions]; JSON.register(clazz, this); } @@ -588,7 +588,29 @@ protected final void registerProperty(String name, int index, boolean readOnly) { assert propertyNames[index] == null; propertyNames[index] = name; - propertyReadOnly[index] = readOnly; + propertyType[index] = (byte) (readOnly ? 1 : 0); + } + + /** Registers property for the type. It is expected each index + * is initialized only once. The difference between readOnly + * and constant is: The constant value is + * assigned only at the beginning and never changed then - like the + * {@link Property#mutable() non-mutable} property. On the other + * hand, a readOnly property can change its value, + * but not via a setter - just like {@link ComputedProperty}. + * + * @param name name of the property + * @param index index of the property + * @param readOnly is the property read only? + * @param constant is the property assigned once and never changed again? + * @since 1.3 + */ + protected final void registerProperty( + String name, int index, boolean readOnly, boolean constant + ) { + assert propertyNames[index] == null; + propertyNames[index] = name; + propertyType[index] = (byte) ((readOnly ? 1 : 0) | (constant ? 2 : 0)); } /** Registers function of given name at given index. diff -r 4c40ceb185e5 -r c61d247f087a ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java Mon Feb 22 06:09:33 2016 +0100 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/KOTech.java Mon Feb 22 19:58:32 2016 +0100 @@ -72,10 +72,12 @@ final Object createKO(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr, Knockout[] ko) { String[] propNames = new String[propArr.length]; Boolean[] propReadOnly = new Boolean[propArr.length]; + Boolean[] propConstant = new Boolean[propArr.length]; Object[] propValues = new Object[propArr.length]; for (int i = 0; i < propNames.length; i++) { propNames[i] = propArr[i].getPropertyName(); propReadOnly[i] = propArr[i].isReadOnly(); + propConstant[i] = propArr[i].isConstant(); Object value = propArr[i].getValue(); if (value instanceof Enum) { value = value.toString(); @@ -93,7 +95,7 @@ } newKO.wrapModel( ret, copyFrom, - propNames, propReadOnly, propValues, + propNames, propReadOnly, propConstant, propValues, funcNames ); return ret; diff -r 4c40ceb185e5 -r c61d247f087a ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Mon Feb 22 06:09:33 2016 +0100 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java Mon Feb 22 19:58:32 2016 +0100 @@ -168,7 +168,7 @@ javacall = true, keepAlive = false, wait4js = false, - args = { "ret", "copyFrom", "propNames", "propReadOnly", "propValues", "funcNames" }, + args = { "ret", "copyFrom", "propNames", "propReadOnly", "propConstant", "propValues", "funcNames" }, body = "Object.defineProperty(ret, 'ko4j', { value : this });\n" + "function koComputed(index, name, readOnly, value) {\n" @@ -225,7 +225,11 @@ + " ret[name] = cmpt;\n" + "}\n" + "for (var i = 0; i < propNames.length; i++) {\n" - + " koComputed(i, propNames[i], propReadOnly[i], propValues[i]);\n" + + " if (propConstant[i]) {\n" + + " ret[propNames[i]] = propValues[i];\n" + + " } else {\n" + + " koComputed(i, propNames[i], propReadOnly[i], propValues[i]);\n" + + " }\n" + "}\n" + "function koExpose(index, name) {\n" + " ret[name] = function(data, ev) {\n" @@ -240,7 +244,8 @@ ) native void wrapModel( Object ret, Object copyFrom, - String[] propNames, Boolean[] propReadOnly, Object propValues, + String[] propNames, Boolean[] propReadOnly, Boolean[] propConstant, + Object propValues, String[] funcNames );