# HG changeset patch # User Jaroslav Tulach # Date 1376060272 -7200 # Node ID 03c9f1fb5ad41f90896c15403c83989722b95a3b # Parent 0f34c04bec7d7e8c421fafdc685adf2a4f394fab# Parent c1a4be1cd7e84d0374894c98e456d70ca2985ae0 Bringing in latest improvements in default branch diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json-tck/src/main/java/net/java/html/json/tests/JSONTest.java --- a/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java Fri Aug 09 16:57:52 2013 +0200 @@ -98,6 +98,7 @@ js = Models.bind(new JSONik(), newContext()); js.applyBindings(); + js.setFetched(null); js.fetch(url); } @@ -128,6 +129,7 @@ js = Models.bind(new JSONik(), newContext()); js.applyBindings(); + js.setFetched(null); js.fetchViaJSONP(url); } @@ -211,6 +213,7 @@ js = Models.bind(new JSONik(), newContext()); js.applyBindings(); + js.setFetched(null); js.fetchArray(url); } @@ -232,6 +235,7 @@ js = Models.bind(new JSONik(), newContext()); js.applyBindings(); + js.setFetched(null); js.fetch(url); } @@ -328,6 +332,8 @@ ); js = Models.bind(new JSONik(), newContext()); js.applyBindings(); + js.setFetched(null); + js.fetchArray(url); } diff -r 0f34c04bec7d -r 03c9f1fb5ad4 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 Thu Aug 08 13:30:23 2013 +0200 +++ b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java Fri Aug 09 16:57:52 2013 +0200 @@ -161,6 +161,73 @@ Utils.exposeHTML(KnockoutTest.class, ""); } } + + @KOTest public void displayContentOfComputedArray() throws Exception { + Object exp = Utils.exposeHTML(KnockoutTest.class, + "\n" + ); + try { + Pair m = Models.bind(new Pair("First", "Last", null), newContext()); + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 2 : "Two children now, but was " + cnt; + + triggerChildClick("ul", 1); + + assert "Last".equals(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, + "
\n" + + "" + + "
\n" + ); + try { + Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), newContext()); + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 2 : "Two children now, but was " + cnt; + + triggerChildClick("ul", 1); + + assert "Last".equals(m.getFirstName()) : "We got callback from 2nd child " + m.getFirstName(); + } finally { + Utils.exposeHTML(KnockoutTest.class, ""); + } + } + + @KOTest public void displayContentOfComputedArrayOnComputedASubpair() throws Exception { + Object exp = Utils.exposeHTML(KnockoutTest.class, + "
\n" + + "" + + "
\n" + ); + try { + Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), newContext()); + m.applyBindings(); + + int cnt = countChildren("ul"); + assert cnt == 2 : "Two children now, but was " + cnt; + + triggerChildClick("ul", 1); + + assert "Last".equals(m.getFirstName()) : "We got callback from 2nd child " + m.getFirstName(); + } finally { + Utils.exposeHTML(KnockoutTest.class, ""); + } + } @KOTest public void checkBoxToBooleanBinding() throws Exception { Object exp = Utils.exposeHTML(KnockoutTest.class, diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json-tck/src/main/java/net/java/html/json/tests/PairModel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json-tck/src/main/java/net/java/html/json/tests/PairModel.java Fri Aug 09 16:57:52 2013 +0200 @@ -0,0 +1,54 @@ +/** + * HTML via Java(tm) Language Bindings + * Copyright (C) 2013 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. apidesign.org + * designates this particular file as subject to the + * "Classpath" exception as provided by apidesign.org + * in the License file that accompanied this code. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException + */ +package net.java.html.json.tests; + +import java.util.Arrays; +import java.util.List; +import net.java.html.json.ComputedProperty; +import net.java.html.json.Function; +import net.java.html.json.Model; +import net.java.html.json.Property; + +/** + * + * @author Jaroslav Tulach + */ +@Model(className = "Pair", properties = { + @Property(name = "firstName", type = String.class), + @Property(name = "lastName", type = String.class), + @Property(name = "next", type = Pair.class) +}) +class PairModel { + @ComputedProperty + static List bothNames(String firstName, String lastName) { + return Arrays.asList(firstName, lastName); + } + + @ComputedProperty + static Pair nextOne(Pair next) { + return next; + } + + @Function + static void assignFirstName(Pair m, String data) { + m.setFirstName(data); + } +} diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/net/java/html/json/ComputedProperty.java --- a/json/src/main/java/net/java/html/json/ComputedProperty.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/net/java/html/json/ComputedProperty.java Fri Aug 09 16:57:52 2013 +0200 @@ -30,8 +30,15 @@ * of {@link Property} as enumerated by {@link Model#properties()}. *

* The name of the derived property is the name of the method. The arguments - * of the method define the property names (from {@link Model#properties()} list) - * the value of property depends on. + * of the method must match names and types of some of the properties + * from {@link Model#properties()} list. As soon as one of these properties + * changes, the method is called to recompute its new value. + *

+ * Method's return type defines the type of the derived property. It may be + * any primitive type, {@link String}, {@link Enum enum type} or a + * type generated by {@link Model @Model} annotation. One may + * also return an array by returning a list of such (boxed) type + * (for example {@link java.util.List List}<{@link String}> or {@link java.util.List List}<{@link Integer}>). * * @author Jaroslav Tulach */ diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/net/java/html/json/Function.java --- a/json/src/main/java/net/java/html/json/Function.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/net/java/html/json/Function.java Fri Aug 09 16:57:52 2013 +0200 @@ -27,8 +27,50 @@ /** Methods in class annotated by {@link Model} can be * annotated by this annotation to signal that they should be available - * as functions to users of the model classes. - * + * as functions to users of the model classes. The method + * should be non-private, static and return void. + * It may take up to two arguments. One argument can be the type of + * the associated model class, the other argument can be of any type, + * but has to be named data - this one represents the + * actual data the function was invoked on. Example: + *

+ * 
+ * {@link Model @Model}(className="Names", properties={
+ *   {@link Property @Property}(name = "selectedName", type=String.class),
+ *   {@link Property @Property}(name = "names", type=String.class, array = true)
+ * })
+ * static class NamesModel {
+ *   {@link Function @Function} static void nameSelected(Names myModel, String data) {
+ *     myModel.setSelectedName(data);
+ *   }
+ * 
+ *   static void initialize() {
+ *     Names pageModel = new Names("---", "Jarda", "Pepa", "Honza", "Jirka", "Tomáš");
+ *     pageModel.applyBindings();
+ *   }
+ * }
+ * 
+ * // associated Knockout HTML page:
+ * 
+ * Selected name: <span data-bind="text: selectedName"></span>
+ * <ul data-bind="foreach: names">
+ *   <li data-bind="text: $data, click: $root.nameSelected"></li>
+ * </ul>
+ * 
+ * The above example would render: + *
+ * Selected name: --- + *
    + *
  • Jarda
  • + *
  • Pepa
  • + *
  • Honza
  • + *
  • Jirka
  • + *
  • Tomáš
  • + *
+ *
+ * and after clicking on one of the names the --- would be replaced + * by selected name. + * * @author Jaroslav Tulach */ @Target(ElementType.METHOD) diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/net/java/html/json/Model.java --- a/json/src/main/java/net/java/html/json/Model.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/net/java/html/json/Model.java Fri Aug 09 16:57:52 2013 +0200 @@ -24,31 +24,85 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.net.URL; -/** Defines a model class named {@link #className()} which contains - * properties defined via {@link #properties()}. This class can have methods - * annotated by {@link ComputedProperty} which define derived - * properties in the model class. +/** Defines a model class that represents a single + * JSON-like object + * named {@link #className()}. The generated class contains + * getters and setters for properties defined via {@link #properties()} and + * getters for other, derived properties defined by annotating methods + * of this class by {@link ComputedProperty}. Each property + * can be of primitive type, an {@link Enum enum type} or (in order to create + * nested JSON structure) + * of another {@link Model class generated by @Model} annotation. Each property + * can either represent a single value or be an array of its values. *

- * The {@link #className() generated class}'s toString + * The {@link #className() generated class}'s toString method * converts the state of the object into - * JSON format. + * JSON format. One can + * use {@link Models#parse(net.java.html.BrwsrCtx, java.lang.Class, java.io.InputStream)} + * method to read the JSON text stored in a file or other stream back into the Java model. + * One can also use {@link OnReceive @OnReceive} annotation to read the model + * asynchronously from a {@link URL}. *

- * An example where one defines class Person with three - * properties (firstName, lastName and + * An example where one defines class Person with four + * properties (firstName, lastName, array of addresses and * fullName) follows: *

  * {@link Model @Model}(className="Person", properties={
  *   {@link Property @Property}(name = "firstName", type=String.class),
  *   {@link Property @Property}(name = "lastName", type=String.class)
+ *   {@link Property @Property}(name = "addresses", type=Address.class, array = true)
  * })
- * static class PersonImpl {
+ * static class PersonModel {
  *   {@link ComputedProperty @ComputedProperty}
  *   static String fullName(String firstName, String lastName) {
  *     return firstName + " " + lastName;
  *   }
+ * 
+ *   {@link Model @Model}(className="Address", properties={
+ *     {@link Property @Property}(name = "street", type=String.class),
+ *     {@link Property @Property}(name = "town", type=String.class)
+ *   })
+ *   static class AddressModel {
+ *   }
  * }
  * 
+ * The generated model class has a default constructor, and also quick + * instantiation constructor that accepts all non-array properties + * (in the order used in the {@link #properties()} attribute) and vararg list + * for the first array property (if any). One can thus use following code + * to create an instance of the Person and Address classes: + *
+ * 
+ * Person p = new Person("Jaroslav", "Tulach",
+ *   new Address("Markoušovice", "Úpice"),
+ *   new Address("V Parku", "Praha")
+ * );
+ * // p.toString() then returns equivalent of following JSON object
+ * {
+ *   "firstName" : "Jaroslav",
+ *   "lastName" : "Tulach",
+ *   "addresses" : [
+ *     { "street" : "Markoušovice", "town" : "Úpice" },
+ *     { "street" : "V Parku", "town" : "Praha" },
+ *   ]
+ * }
+ * 
+ *

+ * In case you are using Knockout technology + * for Java then you can associate such model object with surrounding HTML page by + * calling: p.applyBindings();. The page can then use regular + * Knockout bindings to reference your + * model and create dynamic connection between your model in Java and + * live DOM structure in the browser: + *

+ * Name: <span data-bind="text: fullName">
+ * <div data-bind="foreach: addresses">
+ *   Lives in <span data-bind="text: town"/>
+ * </div>
+ * 
+ * * * @author Jaroslav Tulach */ diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/org/apidesign/html/json/impl/Bindings.java --- a/json/src/main/java/org/apidesign/html/json/impl/Bindings.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/org/apidesign/html/json/impl/Bindings.java Fri Aug 09 16:57:52 2013 +0200 @@ -41,7 +41,7 @@ } public PropertyBinding registerProperty(String propName, M model, SetAndGet access, boolean readOnly) { - PropertyBinding pb = PropertyBindingAccessor.create(new PBData(propName, model, access, readOnly)); + PropertyBinding pb = PropertyBindingAccessor.create(new PBData(this, propName, model, access, readOnly)); bp.bind(pb, model, data); return pb; } diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/org/apidesign/html/json/impl/JSONList.java --- a/json/src/main/java/org/apidesign/html/json/impl/JSONList.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/org/apidesign/html/json/impl/JSONList.java Fri Aug 09 16:57:52 2013 +0200 @@ -166,15 +166,18 @@ return ko; } - final Object koData() { - Object[] arr = toArray(); + static final Object koData(Collection c, Bindings m) { + Object[] arr = c.toArray(); for (int i = 0; i < arr.length; i++) { - Object r = WrapperObject.find(arr[i]); + Object r = WrapperObject.find(arr[i], m); if (r != null) { arr[i] = r; } } - return model.wrapArray(arr); + return m.wrapArray(arr); } - + + final Object koData() { + return koData(this, model); + } } diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java --- a/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java Fri Aug 09 16:57:52 2013 +0200 @@ -196,6 +196,56 @@ w.append(" };\n"); w.append(" public ").append(className).append("() {\n"); w.append(" this(net.java.html.BrwsrCtx.findDefault(").append(className).append(".class));\n"); + for (Prprt p : props) { + if (p.array()) { + continue; + } + boolean[] isModel = {false}; + boolean[] isEnum = {false}; + boolean isPrimitive[] = {false}; + String tn = checkType(p, isModel, isEnum, isPrimitive); + if (isModel[0]) { + w.write(" prop_" + p.name() + " = new " + tn + "();\n"); + } + } + w.append(" };\n"); + w.append(" public ").append(className).append("("); + Prprt firstArray = null; + String sep = ""; + for (Prprt p : props) { + if (p.array()) { + if (firstArray == null) { + firstArray = p; + } + continue; + } + String tn = typeName(e, p); + w.write(sep); + w.write(tn); + w.write(" " + p.name()); + sep = ", "; + } + if (firstArray != null) { + String tn = typeName(e, firstArray); + w.write(sep); + w.write(tn); + w.write("... " + firstArray.name()); + } + w.append(") {\n"); + w.append(" this(net.java.html.BrwsrCtx.findDefault(").append(className).append(".class));\n"); + for (Prprt p : props) { + if (p.array()) { + continue; + } + w.write(" this.prop_" + p.name() + " = " + p.name() + ";\n"); + } + if (firstArray != null) { + String tn = typeName(e, firstArray); + String[] gs = toGetSet(firstArray.name(), tn, true); + w.write(" for(" + tn + " $item : " + firstArray.name() + ") {\n"); + w.write(" " + gs[0] + "().add($item);\n"); + w.write(" }\n"); + } w.append(" };\n"); w.append(" private org.apidesign.html.json.impl.Bindings intKnckt() {\n"); w.append(" if (ko != null) return ko;\n"); @@ -338,6 +388,13 @@ w.append(" };\n"); writeToString(props, w); writeClone(className, props, w); + w.write(" /** Activates this model instance in the current {@link " + + "net.java.html.json.Models#bind(java.lang.Object, net.java.html.BrwsrCtx) browser context}. " + + "In case of using Knockout technology, this means to " + + "bind JSON like data in this model instance with Knockout tags in " + + "the surrounding HTML page." + + "*/" + ); w.write(" public " + className + " applyBindings() {\n"); w.write(" intKnckt().applyBindings();\n"); w.write(" return this;\n"); @@ -466,11 +523,34 @@ TypeMirror ert = tu.erasure(rt); String tn = fqn(ert, ee); boolean array = false; + final TypeMirror toCheck; if (tn.equals("java.util.List")) { array = true; + toCheck = ((DeclaredType)rt).getTypeArguments().get(0); + } else { + toCheck = rt; } final String sn = ee.getSimpleName().toString(); + + if (toCheck.getKind().isPrimitive()) { + // OK + } else { + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); + TypeMirror enumType = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType(); + + if (tu.isSubtype(toCheck, stringType)) { + // OK + } else if (tu.isSubtype(toCheck, enumType)) { + // OK + } else if (isModel(toCheck)) { + // OK + } else { + ok = false; + error(sn + " cannot return " + toCheck, e); + } + } + String[] gs = toGetSet(sn, tn, array); w.write(" public " + tn + " " + gs[0] + "() {\n"); @@ -952,7 +1032,7 @@ continue; } error( - "@On method can only accept String named 'id', " + className + " argument or argument named 'data'", + "The annotated method can only accept " + className + " argument or argument named 'data'", ee ); } diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/org/apidesign/html/json/impl/PropertyBindingAccessor.java --- a/json/src/main/java/org/apidesign/html/json/impl/PropertyBindingAccessor.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/org/apidesign/html/json/impl/PropertyBindingAccessor.java Fri Aug 09 16:57:52 2013 +0200 @@ -62,8 +62,10 @@ public final boolean readOnly; private final M model; private final SetAndGet access; + private final Bindings bindings; - public PBData(String name, M model, SetAndGet access, boolean readOnly) { + public PBData(Bindings bindings, String name, M model, SetAndGet access, boolean readOnly) { + this.bindings = bindings; this.name = name; this.model = model; this.access = access; @@ -81,6 +83,10 @@ public boolean isReadOnly() { return readOnly; } + + public Bindings getBindings() { + return bindings; + } } // end of PBData public static final class FBData { diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/org/apidesign/html/json/impl/WrapperObject.java --- a/json/src/main/java/org/apidesign/html/json/impl/WrapperObject.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/org/apidesign/html/json/impl/WrapperObject.java Fri Aug 09 16:57:52 2013 +0200 @@ -21,6 +21,9 @@ package org.apidesign.html.json.impl; +import java.util.Collection; +import org.apidesign.html.json.impl.PropertyBindingAccessor.PBData; + /** A way to extract real object from a model classes. * * @author Jaroslav Tulach @@ -35,17 +38,24 @@ this.ko = ko; } + public static Object find(Object object) { + return find(object, null); + } - public static Object find(Object model) { - if (model == null) { + public static Object find(Object object, Bindings model) { + if (object == null) { return null; } - if (model instanceof JSONList) { - return ((JSONList)model).koData(); + + if (object instanceof JSONList) { + return ((JSONList)object).koData(); + } + if (object instanceof Collection) { + return JSONList.koData((Collection)object, model); } WrapperObject ro = new WrapperObject(); - model.equals(ro); + object.equals(ro); return ro.ko; } } diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/main/java/org/apidesign/html/json/spi/PropertyBinding.java --- a/json/src/main/java/org/apidesign/html/json/spi/PropertyBinding.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/main/java/org/apidesign/html/json/spi/PropertyBinding.java Fri Aug 09 16:57:52 2013 +0200 @@ -65,7 +65,7 @@ public Object getValue() { Object v = data.getValue(); - Object r = WrapperObject.find(v); + Object r = WrapperObject.find(v, data.getBindings()); return r == null ? v : r; } diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/test/java/net/java/html/json/ModelProcessorTest.java --- a/json/src/test/java/net/java/html/json/ModelProcessorTest.java Thu Aug 08 13:30:23 2013 +0200 +++ b/json/src/test/java/net/java/html/json/ModelProcessorTest.java Fri Aug 09 16:57:52 2013 +0200 @@ -92,6 +92,69 @@ } } + @Test public void computedCantReturnVoid() throws IOException { + String html = "" + + ""; + String code = "package x.y.z;\n" + + "import net.java.html.json.Model;\n" + + "import net.java.html.json.Property;\n" + + "import net.java.html.json.ComputedProperty;\n" + + "@Model(className=\"XModel\", properties={\n" + + " @Property(name=\"prop\", type=int.class)\n" + + "})\n" + + "class X {\n" + + " @ComputedProperty static void y(int prop) {\n" + + " }\n" + + "}\n"; + + Compile c = Compile.create(html, code); + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); + boolean ok = false; + StringBuilder msgs = new StringBuilder(); + for (Diagnostic e : c.getErrors()) { + String msg = e.getMessage(Locale.ENGLISH); + if (msg.contains("y cannot return void")) { + ok = true; + } + msgs.append("\n").append(msg); + } + if (!ok) { + fail("Should contain warning about non-static method:" + msgs); + } + } + + @Test public void computedCantReturnRunnable() throws IOException { + String html = "" + + ""; + String code = "package x.y.z;\n" + + "import net.java.html.json.Model;\n" + + "import net.java.html.json.Property;\n" + + "import net.java.html.json.ComputedProperty;\n" + + "@Model(className=\"XModel\", properties={\n" + + " @Property(name=\"prop\", type=int.class)\n" + + "})\n" + + "class X {\n" + + " @ComputedProperty static Runnable y(int prop) {\n" + + " return null;\n" + + " }\n" + + "}\n"; + + Compile c = Compile.create(html, code); + assertFalse(c.getErrors().isEmpty(), "One error: " + c.getErrors()); + boolean ok = false; + StringBuilder msgs = new StringBuilder(); + for (Diagnostic e : c.getErrors()) { + String msg = e.getMessage(Locale.ENGLISH); + if (msg.contains("y cannot return java.lang.Runnable")) { + ok = true; + } + msgs.append("\n").append(msg); + } + if (!ok) { + fail("Should contain warning about non-static method:" + msgs); + } + } + @Test public void canWeCompileWithJDK1_5SourceLevel() throws IOException { String html = "" + ""; diff -r 0f34c04bec7d -r 03c9f1fb5ad4 json/src/test/java/org/apidesign/html/json/impl/ConstructorTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json/src/test/java/org/apidesign/html/json/impl/ConstructorTest.java Fri Aug 09 16:57:52 2013 +0200 @@ -0,0 +1,58 @@ +/** + * HTML via Java(tm) Language Bindings + * Copyright (C) 2013 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. apidesign.org + * designates this particular file as subject to the + * "Classpath" exception as provided by apidesign.org + * in the License file that accompanied this code. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException + */ +package org.apidesign.html.json.impl; + +import net.java.html.json.Model; +import net.java.html.json.Property; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertEquals; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +@Model(className="Man", properties={ + @Property(name = "name", type = String.class), + @Property(name = "other", type = Address.class, array = true), + @Property(name = "primary", type = Address.class), + @Property(name = "childrenNames", type = String.class, array = true) +}) +public class ConstructorTest { + @Model(className = "Address", properties = { + @Property(name = "place", type = String.class) + }) + static final class AddressModel { + } + + @Test public void initializedByDefault() { + Man m = new Man(); + assertNotNull(m.getPrimary(), "Single subobjects are initialized"); + } + + @Test public void hasRichConstructor() { + Man m = new Man("Jarda", new Address("home"), new Address("work"), new Address("hotel")); + assertEquals(m.getName(), "Jarda"); + assertNotNull(m.getPrimary(), "Primary address specified"); + assertNotNull(m.getPrimary().getPlace(), "home"); + assertEquals(m.getOther().size(), 2, "Two other addresses"); + } +}