# HG changeset patch # User Jaroslav Tulach # Date 1446763098 -3600 # Node ID 10427ce1c0eeb10de5eeb17f9e13714a00626451 # Parent 665b10c62f3d777f61622f660c297080857ad22c #250611: Builder style properties diff -r 665b10c62f3d -r 10427ce1c0ee json/src/main/java/net/java/html/json/Model.java --- a/json/src/main/java/net/java/html/json/Model.java Sun Nov 01 16:59:42 2015 +0100 +++ b/json/src/main/java/net/java/html/json/Model.java Thu Nov 05 23:38:18 2015 +0100 @@ -48,7 +48,6 @@ import java.lang.annotation.Target; import java.net.URL; import java.util.List; -import org.netbeans.html.json.spi.Technology; /** Defines a model class that represents a single * JSON-like object @@ -222,4 +221,30 @@ * @since 1.1 */ String targetId() default ""; + + /** Controls whether builder-like setters shall be generated. Once this + * attribute is set, all {@link #properties()} will get a builder like + * setter (takes value of the property and returns this + * so invocations can be chained). When this attribute is specified, + * the non-default constructor isn't generated at all. + *

+ * Specifying builder="assign" + * and having {@link #properties() properties} name and + * age will generate method:

+     * public MyModel assignName(String name) { ... }
+     * public MyModel assignAge(int age) { ... }
+     * 
+ * These methods can then be chained as
+     * MyModel m = new MyModel().assignName("name").assignAge(3);
+     * 
+ * The builder attribute can be set to empty string "" - + * then it is possible that some property names clash with Java keywords. + * It's responsibility of the user to specify valid builder prefix, + * so the generated methods are compilable. + * + * @return the prefix to put before {@link Property property} names when + * generating their builder methods + * @since 1.3 + */ + String builder() default ""; } diff -r 665b10c62f3d -r 10427ce1c0ee json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java --- a/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Sun Nov 01 16:59:42 2015 +0100 +++ b/json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java Thu Nov 05 23:38:18 2015 +0100 @@ -189,6 +189,7 @@ Map> propsDeps = new HashMap>(); Map> functionDeps = new HashMap>(); Prprt[] props = createProps(e, m.properties()); + final String builderPrefix = findBuilderPrefix(e, m); if (!generateComputedProperties(className, body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) { ok = false; @@ -196,7 +197,7 @@ if (!generateOnChange(e, propsDeps, props, className, functionDeps)) { ok = false; } - if (!generateProperties(e, body, className, props, propsGetSet, propsDeps, functionDeps)) { + if (!generateProperties(e, builderPrefix, body, className, props, propsGetSet, propsDeps, functionDeps)) { ok = false; } if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) { @@ -271,7 +272,7 @@ } } w.append(" };\n"); - if (props.length > 0) { + if (props.length > 0 && builderPrefix == null) { StringBuilder constructorWithArguments = new StringBuilder(); constructorWithArguments.append(" public ").append(className).append("("); Prprt firstArray = null; @@ -462,8 +463,13 @@ w.append(type).append(".valueOf(TYPE.stringValue(e)));\n"); } else { if (isPrimitive(type)) { - w.append(" this.prop_").append(pn).append(".add(TYPE.numberValue(e)."); - w.append(type).append("Value());\n"); + if (type.equals("char")) { + w.append(" this.prop_").append(pn).append(".add((char)TYPE.numberValue(e)."); + w.append("intValue());\n"); + } else { + w.append(" this.prop_").append(pn).append(".add(TYPE.numberValue(e)."); + w.append(type).append("Value());\n"); + } } else { w.append(" this.prop_").append(pn).append(".add(("); w.append(type).append(")e);\n"); @@ -561,8 +567,29 @@ return ok; } + private static String findBuilderPrefix(Element e, Model m) { + if (!m.builder().isEmpty()) { + return m.builder(); + } + for (AnnotationMirror am : e.getAnnotationMirrors()) { + for (Map.Entry entry : am.getElementValues().entrySet()) { + if ("builder()".equals(entry.getKey().toString())) { + return ""; + } + } + } + return null; + } + + private static String builderMethod(String builderPrefix, Prprt p) { + if (builderPrefix.isEmpty()) { + return p.name(); + } + return builderPrefix + Character.toUpperCase(p.name().charAt(0)) + p.name().substring(1); + } + private boolean generateProperties( - Element where, + Element where, String builderPrefix, Writer w, String className, Prprt[] properties, List props, Map> deps, @@ -583,6 +610,17 @@ w.write(" proto.accessProperty(\"" + p.name() + "\");\n"); w.write(" return prop_" + p.name() + ";\n"); w.write(" }\n"); + if (builderPrefix != null) { + boolean[] isModel = {false}; + boolean[] isEnum = {false}; + boolean isPrimitive[] = {false}; + String ret = checkType(p, isModel, isEnum, isPrimitive); + w.write(" public " + className + " " + builderMethod(builderPrefix, p) + "(" + ret + "... v) {\n"); + w.write(" proto.accessProperty(\"" + p.name() + "\");\n"); + w.append(" TYPE.replaceValue(prop_").append(p.name()).append(", " + tn + ".class, v);\n"); + w.write(" return this;\n"); + w.write(" }\n"); + } } else { castTo = tn; boolean isModel[] = { false }; @@ -623,6 +661,12 @@ } } w.write(" }\n"); + if (builderPrefix != null) { + w.write(" public " + className + " " + builderMethod(builderPrefix, p) + "(" + tn + " v) {\n"); + w.write(" " + gs[1] + "(v);\n"); + w.write(" return this;\n"); + w.write(" }\n"); + } } for (int i = 0; i < props.size(); i++) { diff -r 665b10c62f3d -r 10427ce1c0ee json/src/main/java/org/netbeans/html/json/spi/Proto.java --- a/json/src/main/java/org/netbeans/html/json/spi/Proto.java Sun Nov 01 16:59:42 2015 +0100 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java Thu Nov 05 23:38:18 2015 +0100 @@ -875,15 +875,41 @@ * @since 1.0 */ public final void replaceValue(Collection arr, Class type, Object value) { - Object[] newArr; + List tmp = new ArrayList(); if (value instanceof Object[]) { - newArr = (Object[]) value; + for (Object e : (Object[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof byte[]) { + for (Object e : (byte[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof short[]) { + for (Object e : (short[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof int[]) { + for (Object e : (int[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof char[]) { + for (Object e : (char[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof long[]) { + for (Object e : (long[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof float[]) { + for (Object e : (float[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof double[]) { + for (Object e : (double[]) value) { + tmp.add(extractValue(type, e)); + } } else { - newArr = new Object[] { value }; - } - List tmp = new ArrayList(newArr.length); - for (Object e : newArr) { - tmp.add(extractValue(type, e)); + tmp.add(extractValue(type, value)); } if (arr instanceof JSONList) { JSONList jsList = (JSONList) arr; diff -r 665b10c62f3d -r 10427ce1c0ee json/src/test/java/net/java/html/json/ModelTest.java --- a/json/src/test/java/net/java/html/json/ModelTest.java Sun Nov 01 16:59:42 2015 +0100 +++ b/json/src/test/java/net/java/html/json/ModelTest.java Thu Nov 05 23:38:18 2015 +0100 @@ -65,7 +65,7 @@ * * @author Jaroslav Tulach */ -@Model(className = "Modelik", targetId = "", properties = { +@Model(className = "Modelik", builder = "change", targetId = "", properties = { @Property(name = "value", type = int.class), @Property(name = "count", type = int.class), @Property(name = "unrelated", type = long.class), @@ -97,8 +97,18 @@ } @Test public void equalsAndHashCode() { - Modelik m1 = new Modelik(10, 20, 30, "changed", "firstName"); - Modelik m2 = new Modelik(10, 20, 30, "changed", "firstName"); + Modelik m1 = new Modelik(); + m1.setValue(10); + m1.setCount(20); + m1.setUnrelated(30); + m1.setChangedProperty("changed"); + m1.getNames().add("firstName"); + Modelik m2 = new Modelik(). + changeValue(10). + changeCount(20). + changeUnrelated(30). + changeChangedProperty("changed"). + changeNames("firstName"); assertTrue(m1.equals(m2), "They are the same"); assertEquals(m1.hashCode(), m2.hashCode(), "Hashcode is the same"); diff -r 665b10c62f3d -r 10427ce1c0ee json/src/test/java/org/netbeans/html/json/impl/BuilderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/json/src/test/java/org/netbeans/html/json/impl/BuilderTest.java Thu Nov 05 23:38:18 2015 +0100 @@ -0,0 +1,117 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.impl; + +import java.lang.reflect.Constructor; +import net.java.html.json.Model; +import net.java.html.json.Property; +import static org.testng.Assert.assertEquals; +import org.testng.annotations.Test; + +@Model(className="Builder", builder = "", properties = { + @Property(name="bytes", type = byte.class, array = true), + @Property(name="chars", type = char.class, array = true), + @Property(name="shorts", type = short.class, array = true), + @Property(name="ints", type = int.class, array = true), + @Property(name="longs", type = long.class, array = true), + @Property(name="floats", type = float.class, array = true), + @Property(name="doubles", type = double.class, array = true), + @Property(name="strings", type = String.class, array = true), +}) +public class BuilderTest { + @Test + public void onlyDefaultClassLoader() { + Constructor[] arr = Builder.class.getConstructors(); + assertEquals(arr.length, 1, "One constructor"); + assertEquals(arr[0].getParameterTypes().length, 0, "No parameters"); + } + + @Test + public void assignBytes() { + Builder b = new Builder().bytes((byte)10, (byte)20, (byte)30); + assertEquals(b.getBytes().size(), 3); + assertEquals(b.getBytes().get(0).byteValue(), (byte)10); + } + @Test + public void assignChars() { + Builder b = new Builder().chars((char)10, (char)20, (char)30); + assertEquals(b.getChars().size(), 3); + assertEquals(b.getChars().get(0).charValue(), 10); + } + @Test + public void assignShort() { + Builder b = new Builder().shorts((short)10, (short)20, (short)30); + assertEquals(b.getShorts().size(), 3); + assertEquals(b.getShorts().get(0).intValue(), 10); + } + @Test + public void assignInts() { + Builder b = new Builder().ints(10, 20, 30); + assertEquals(b.getInts().size(), 3); + assertEquals(b.getInts().get(0).intValue(), 10); + } + @Test + public void assignLongs() { + Builder b = new Builder().longs(10, 20, 30); + assertEquals(b.getLongs().size(), 3); + assertEquals(b.getLongs().get(1).intValue(), 20); + } + @Test + public void assignDouble() { + Builder b = new Builder().doubles(10, 20, 30); + assertEquals(b.getDoubles().size(), 3); + assertEquals(b.getDoubles().get(0), 10.0); + } + @Test + public void assignFloats() { + Builder b = new Builder().floats(10, 20, 30); + assertEquals(b.getFloats().size(), 3); + assertEquals(b.getFloats().get(0), 10.0f); + } + @Test + public void assignStrings() { + Builder b = new Builder().strings("A", "AB", "ABC"); + assertEquals(b.getStrings().size(), 3); + assertEquals(b.getStrings().get(1), "AB"); + } +} diff -r 665b10c62f3d -r 10427ce1c0ee src/main/javadoc/overview.html --- a/src/main/javadoc/overview.html Sun Nov 01 16:59:42 2015 +0100 +++ b/src/main/javadoc/overview.html Thu Nov 05 23:38:18 2015 +0100 @@ -96,6 +96,9 @@

Improvements in version 1.3

+ {@link net.java.html.json.Model Model classes} can generate + builder-like construction methods if builder + {@link net.java.html.json.Model#builder() prefix} is specified. The JavaFX presenter can be executed in headless mode - just specify -Dfxpresenter.headless=true when launching its virtual machine and no window will be shown. This is particularly