http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/net/java/html/json/OnReceive.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/net/java/html/json/OnReceive.java b/json/src/main/java/net/java/html/json/OnReceive.java new file mode 100644 index 0000000..e6fc741 --- /dev/null +++ b/json/src/main/java/net/java/html/json/OnReceive.java @@ -0,0 +1,204 @@ +/** + * 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-2016 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 net.java.html.json; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Static methods in classes annotated by {@link Model} + * can be marked by this annotation to establish a + * <a href="http://en.wikipedia.org/wiki/JSON">JSON</a> + * communication point. The first argument should be the + * associated {@link Model} class. The second argument can + * be another class generated by {@link Model} annotation, + * or array of such classes or (since 0.8.1 version) a + * {@link java.util.List} of such classes. + * The associated model class then gets new method to invoke a network + * connection asynchronously. Example follows: + * + * <pre> + * {@link Model @Model}(className="MyModel", properties={ + * {@link Property @Property}(name = "people", type=Person.class, array=true) + * }) + * class MyModelImpl { + * {@link Model @Model}(className="Person", properties={ + * {@link Property @Property}(name = "firstName", type=String.class), + * {@link Property @Property}(name = "lastName", type=String.class) + * }) + * static class PersonImpl { + * {@link ComputedProperty @ComputedProperty} + * static String fullName(String firstName, String lastName) { + * return firstName + " " + lastName; + * } + * } + * + * {@link OnReceive @OnReceive}(url = "{protocol}://your.server.com/person/{name}") + * static void getANewPerson(MyModel m, Person p) { + * System.out.println("Adding " + p.getFullName() + '!'); + * m.getPeople().add(p); + * } + * + * // the above will generate method <code>getANewPerson</code> in class <code>MyModel</code>. + * // with <code>protocol</code> and <code>name</code> arguments + * // which asynchronously contacts the server and in case of success calls + * // your {@link OnReceive @OnReceive} with parsed in data + * + * {@link Function @Function} + * static void requestSmith(MyModel m) { + * m.getANewPerson("http", "Smith"); + * } + * } + * </pre> + * When the server returns <code>{ "firstName" : "John", "lastName" : "Smith" }</code> + * the system will print a message <em>Adding John Smith!</em>. It is not + * necessary to fully describe the server message - enumerate only the fields + * in the response you are interested in. The others will be discarded. So, + * if the server <code>{ "firstName" : "John", "lastName" : "Smith", "age" : 33 }</code> + * the above code will behave the same (e.g. ignore the <code>age</code> + * value). + * <p> + * One can use this method to communicate with the server + * via <a href="doc-files/websockets.html">WebSocket</a> protocol since version 0.6. + * Read the <a href="doc-files/websockets.html">tutorial</a> to see how. + * The method shall be non-private + * and unless {@link Model#instance() instance mode} is on also static. + * <p> + * Visit an <a target="_blank" href="http://dew.apidesign.org/dew/#7138581">on-line demo</a> + * to see REST access via {@link OnReceive} annotation. + * + * @author Jaroslav Tulach + * @since 0.3 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface OnReceive { + /** The URL to connect to. Can contain variable names surrounded by '{' and '}'. + * Those names will then become parameters of the associated method. + * + * @return the (possibly parametrized) url to connect to + */ + String url(); + + /** Specifies HTTP request headers. Array of header lines + * can contain variable names surrounded by '{' and '}'. + * Those names will then become parameters of the associated method + * (in addition to those added by {@link #url()} specification) + * and can only be used with plain JSON(P) requests. + * Headers are currently <b>not</b> supported by the + * <a href="doc-files/websockets.html">WebSockets protocol</a>. + * A sample follows. If you want to transmit <b>X-Birthday</b> header, + * you can do it like this: + * <pre> + * {@code @}{@link OnReceive}(url="http://your.server.org", headers = { + * "X-Birthday: {dayOfBirth}" + * }) + * <b>static void</b> knowingTheBirth({@link Model YourModel} model) { + * // handle the reply + * }</pre> + * a method <b>knowingTheBirth</b> is generated in + * <code>YourModel</code> class with the <code>dayOfBirth</code> argument + * which can be called like this: + * <pre> + * yourModel.knowingTheBirth("10. 12. 1973"); + * </pre> + * + * @return array of header lines - each line should be plain text with + * a header name, followed by ":" and value usually specified as + * '{' and '}' surrounded variable. The line shouldn't contain + * newline or other control characters + * @since 1.2 + */ + String[] headers() default {}; + + /** Support for <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a> requires + * a callback from the server generated page to a function defined in the + * system. The name of such function is usually specified as a property + * (of possibly different names). By defining the <code>jsonp</code> attribute + * one turns on the <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a> + * transmission and specifies the name of the property. The property should + * also be used in the {@link #url()} attribute on appropriate place. + * + * @return name of a property to carry the name of <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a> + * callback function. + */ + String jsonp() default ""; + + /** The model class to be send to the server as JSON data. + * By default no data are sent. However certain {@link #method() transport methods} + * (like <code>"PUT"</code> and <code>"POST"</code>) require the + * data to be specified. + * + * @return name of a class generated using {@link Model @Model} annotation + * @since 0.3 + */ + Class<?> data() default Object.class; + + /** The HTTP transfer method to use. Defaults to <code>"GET"</code>. + * Other typical methods include <code>"HEAD"</code>, + * <code>"DELETE"</code>, <code>"POST"</code>, <code>"PUT"</code>. + * The last two mentioned methods require {@link #data()} to be specified. + * <p> + * When {@link #jsonp() JSONP} transport is requested, the method + * has to be <code>"GET"</code>. + * <p> + * Since version 0.5 one can specify "<a href="doc-files/websockets.html">WebSocket</a>" + * as the communication method. + * + * @return name of the HTTP transfer method + * @since 0.3 + */ + String method() default "GET"; + + /** Name of a method in this class which should be called in case of + * an error. The method has to be non-private and take one model and + * one {@link Exception} + * parameter. If this method is not specified, the exception is just + * printed to console. + * + * @return name of method in this class + * @since 0.5 + */ + public String onError() default ""; +}
http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/net/java/html/json/Property.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/net/java/html/json/Property.java b/json/src/main/java/net/java/html/json/Property.java new file mode 100644 index 0000000..dfb1a8f --- /dev/null +++ b/json/src/main/java/net/java/html/json/Property.java @@ -0,0 +1,98 @@ +/** + * 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-2016 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 net.java.html.json; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.Technology; + +/** Represents a property in a class defined with {@link Model} annotation. + * + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target({}) +public @interface Property { + /** Name of the property. Will be used to define proper getter and setter + * in the associated class. + * + * @return valid java identifier + */ + String name(); + + /** Type of the property. Can either be primitive type (like <code>int.class</code>, + * <code>double.class</code>, etc.), {@link String}, {@link Enum enum} or complex model + * class (defined by {@link Model} property). + * + * @return the class of the property + */ + Class<?> type(); + + /** Is this property an array of the {@link #type()} or a single value? + * If the property is an array, only its getter (returning mutable {@link List} of + * the boxed {@link #type()}) is generated. + * + * @return true, if this property is supposed to represent an array of values + */ + boolean array() default false; + + /** Can the value of the property be mutated without restriction or not. + * If a property is defined as <em>not mutable</em>, it defines + * semi-immutable value that can only be changed in construction time + * before the object is passed to underlying {@link Technology}. + * Attempts to modify the object later yield {@link IllegalStateException}. + * + * Technologies may decide to represent such non-mutable + * property in more effective way - for + * example Knockout Java Bindings technology (with {@link Contexts.Id id} "ko4j") + * uses plain JavaScript value (number, string, array, boolean) rather + * than classical observable. + * + * @return false if the value cannot change after its <em>first use</em> + * @since 1.3 + */ + boolean mutable() default true; +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/net/java/html/json/doc-files/DukeHTML.png ---------------------------------------------------------------------- diff --git a/json/src/main/java/net/java/html/json/doc-files/DukeHTML.png b/json/src/main/java/net/java/html/json/doc-files/DukeHTML.png new file mode 100644 index 0000000..8d7dc3e Binary files /dev/null and b/json/src/main/java/net/java/html/json/doc-files/DukeHTML.png differ http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/net/java/html/json/doc-files/html4j.png ---------------------------------------------------------------------- diff --git a/json/src/main/java/net/java/html/json/doc-files/html4j.png b/json/src/main/java/net/java/html/json/doc-files/html4j.png new file mode 100644 index 0000000..e281147 Binary files /dev/null and b/json/src/main/java/net/java/html/json/doc-files/html4j.png differ http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/net/java/html/json/doc-files/websockets.html ---------------------------------------------------------------------- diff --git a/json/src/main/java/net/java/html/json/doc-files/websockets.html b/json/src/main/java/net/java/html/json/doc-files/websockets.html new file mode 100644 index 0000000..6cc93f2 --- /dev/null +++ b/json/src/main/java/net/java/html/json/doc-files/websockets.html @@ -0,0 +1,185 @@ +<!-- + + 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-2016 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. + +--> +<!DOCTYPE html> +<html> + <head> + <title>WebSockets via @OnReceive</title> + <meta charset="UTF-8"> + <link rel="stylesheet" type="text/css" href="../../../../../stylesheet.css" title="Style"> + </head> + <body> + <h1>Using <a href="../OnReceive.html">@OnReceive</a> to Communicate + via WebSockets Protocol</h1> + + There is a simple, yet flexible way to communicate with a server + via <a href="http://www.w3.org/TR/websockets/">WebSockets protocol</a>. + The support can transfer any classes generated by + <a href="../Model.html">@Model</a> annotation and reuses already + existing <a href="../OnReceive.html">@OnReceive</a> infrastructure - + just defines detailed special behavior, which is described here. + + <h3>Define JSON Class</h3> + + The first step in using <em>WebSockets</em> is to create a model classes + to encapsulate communiation with the server. For example one for + sending requests and one for receiving replies: + <pre> + <a href="../Model.html">@Model</a>(className = "Comm", properties={}) + <b>final class</b> Communication { + <a href="../Model.html">@Model</a>(className = "Request", properties = { + <a href="../Property.html">@Property</a>(name = "msg", type = MsgType<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "username", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "password", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "gameId", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "color", type = Color<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "from", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "to", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "observer", type = boolean<b>.class</b>), + }) + <b>static class</b> <em>RequestModel</em> { + } + + <a href="../Model.html">@Model</a>(className = "Response", properties = { + <a href="../Property.html">@Property</a>(name = "msg", type = MsgType<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "turn", type = Color<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "color", type = Color<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "gameId", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "status", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "moves", type = String<b>.class</b>, array = <b>true</b>), + <a href="../Property.html">@Property</a>(name = "from", type = String<b>.class</b>), + <a href="../Property.html">@Property</a>(name = "to", type = String<b>.class</b>), + }) + <b>static class</b> <em>ResponseModel</em> { + } + + <b>enum</b> <em>MsgType</em> { + CreateGame, QueryGames, SendMove, JoinGame, UpdateGame; + } + <b>enum</b> <em>Color</em> { + W, B; + } + } + </pre> +And then it is just a matter of creating the communication end point. As +usual with <a href="../OnReceive.html">@OnReceive</a> annotation one starts +with defining the response handler: + <pre> + <a href="../OnReceive.html">@OnReceive</a>( + data = <em>Request</em>.<b>class</b>, + url = "{url}", + method = <em>"WebSocket"</em>, + onError = "anErrorHappened" + ) + <b>static void</b> queryServer(Comm c, Response r) { + <b>if</b> (r == null) { + // <em>connection stablished!</em> + <b>return</b>; + } + // <em>message arrived!</em> + <b>switch</b> (r.getMsg()) { + <b>case</b> CreateGame: /* do something */ <b>break</b>; + <b>case</b> QueryGames: /* do something */ <b>break</b>; + <b>case</b> SendMove: /* do something */ <b>break</b>; + <b>case</b> JoinGame: /* do something */ <b>break</b>; + <b>case</b> UpdateGame: /* do something */ <b>break</b>; + } + } + <b>static void</b> anErrorHappened(Comm c, Exception t) { + if (t == null) { + // <em>OK, connection has been closed</em> + } else { + // <em>handle the error t somehow</em> + } + } + </pre> + The <a href="http://www.w3.org/TR/websockets/">WebSockets</a> specification + usually defines what should happen <em>onopen, onmessage, onclose and onerror</em>. + All these messages are supported in the previous example as well: + <ul> + <li><b>onopen</b> - the <em>queryServer</em> method is called with + <code>null</code> message + </li> + <li><b>onmessage</b> - the <em>queryServer</em> method is called with + non-null message + </li> + <li><b>onclose</b> - the <em>anErrorHappened</em> method is called with + <code>null</code> exception + </li> + <li><b>onerror</b> - the <em>anErrorHappened</em> method is called with + non-null exception + </li> + </ul> + Using the <a href="../OnReceive.html">@OnReceive</a> annotation instructs + its associated annotation processor to add appropriate send method + into the generated <code>Comm</code> class. One can use it to establish communication, + send messages and close the communication channel. Here are three methods + showing how to do it: + <pre> + <b>static void </b>connect(Comm c) { + // open a websocket channel: + c.queryServer("ws://server/path", <b>null</b>); + // the method returns immediately and starts establishing the connection + // once that is finished either <b>onopen</b> or <b>onerror</b> type of + // message is delivered + } + + <b>static void </b>sendMessage(Comm c, Request r) { + // sends the message to already openned websocket channel: + c.queryServer("ws://server/path", r); + } + + <b>static void </b>disconnect(Comm c) { + // sending <code>null</code> again, closes the connection + c.queryServer("ws://server/path", <b>null</b>); + } + </pre> + One cannot change the URL while a connection is open otherwise one + risks <code>IllegalStateException</code> runtime exceptions. To connect + to different web socket server, one needs to close the connection first and + only later open new one with different URL. + <p> + Enjoy <em>WebSocket</em>s via <a href="../OnReceive.html">@OnReceive</a>! + </body> +</html> http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/net/java/html/json/package.html ---------------------------------------------------------------------- diff --git a/json/src/main/java/net/java/html/json/package.html b/json/src/main/java/net/java/html/json/package.html new file mode 100644 index 0000000..9384b47 --- /dev/null +++ b/json/src/main/java/net/java/html/json/package.html @@ -0,0 +1,118 @@ +<!-- + + 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-2016 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. + +--> +<body> + + <p> + This API allows you to write your application logic in Java and + present it using modern HTML rendering technologies like + <a href="http://knockoutjs.com">Knockout</a> + and communicate with + a server via <a href="OnReceive.html">REST</a> or + <a href="doc-files/websockets.html">WebSocket</a>s. + </p> + <img src="doc-files/html4j.png"> + <p> + Use <a href="Model.html">@Model</a> annotation to define one or more + model classes with <a href="Property.html">properties</a>. Don't waste + time writing setters or getters - they will be generated for you. + Just instantiate your classes and use them! + </p> + <p> + The class generator does not stop with getters and setters -- internally + it generates bindings for various HTML technologies. Just include appropriate + technology implementation on classpath of your application and your model + class(es) will automatically be bound to your HTML elements (after calling + <code>applyBindings()</code> on your model). + </p> + <p> + You don't have to bother with JavaScript. All your application logic is in + Java. The necessary JavaScript needed for the HTML bindings remains hidden + as an implementation detail of communication between the generated model + class(es) and appropriate technology bridge. For example + the <code>ko4j</code> module: + </p> + <pre> +<dependency> + <groupId>org.netbeans.html</groupId> + <artifactId>ko4j</artifactId> + <scope>runtime</scope> +</dependency> + </pre> + <p> + In case you decide to use <a target="ko" href="http://knockoutjs.com">Knockout</a> + as your rendering technology, it is recommended to look at its + <a target="ko" href="http://knockoutjs.com/documentation/introduction.html">documentation</a> + (especially its HTML part) as <code>data-bind</code> syntax is exactly + what one has to use in the HTML file(s). Of course, one does not need + to bother with the JavaScript part, that is completely hidden in the + code generated when processing the {@link net.java.html.json.Model} + annotation. + </p> + + <p> + The model classes can be used for JSON based server communication. Just + use <a href="OnReceive.html">@OnReceive</a> annotation to define a communication + point in the model class. Please note, that the model classes can easily + be used on server as well - the same code can run + in your client as well as on your server. Just add following to your <em>pom.xml</em> + to use your classes generated by <a href="Model.html">@Model</a> annotation + as <a href="http://jersey.java.net" target="new">Jersey</a> entities: + </p> + <pre> +<dependency> + <groupId>org.glassfish.jersey.media</groupId> + <artifactId>html-json</artifactId> + <version>2.6</version> + <scope>runtime</scope> +</dependency> + </pre> + <p> + Behavior of model classes can be enriched by using + <a href="ComputedProperty.html">@ComputedProperty</a> annotation (to + define <em>derived</em> properties) and by + <a href="Function.html">@Function</a> annotation to define handlers + to be invoked from the HTML elements. + </p> +</body> http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/impl/Bindings.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/impl/Bindings.java b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java new file mode 100644 index 0000000..a8b0cc4 --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/impl/Bindings.java @@ -0,0 +1,133 @@ +/** + * 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-2016 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.util.logging.Level; +import java.util.logging.Logger; +import net.java.html.BrwsrCtx; +import org.netbeans.html.json.spi.FunctionBinding; +import org.netbeans.html.json.spi.PropertyBinding; +import org.netbeans.html.json.spi.Proto; +import org.netbeans.html.json.spi.Technology; + +/** + * + * @author Jaroslav Tulach + */ +public final class Bindings<Data> { + private static final Logger LOG = Logger.getLogger(Bindings.class.getName()); + + private Data data; + private final Technology<Data> bp; + + private Bindings(Technology<Data> bp) { + this.bp = bp; + } + + public <M> PropertyBinding registerProperty(String propName, int index, M model, Proto.Type<M> access, byte propertyType) { + return PropertyBindingAccessor.create(access, this, propName, index, model, propertyType); + } + + public static Bindings<?> apply(BrwsrCtx c) { + Technology<?> bp = JSON.findTechnology(c); + return apply(bp); + } + + private static <Data> Bindings<Data> apply(Technology<Data> bp) { + return new Bindings<Data>(bp); + } + + public final void finish(Object model, Object copyFrom, PropertyBinding[] propArr, FunctionBinding[] funcArr) { + assert data == null; + if (bp instanceof Technology.BatchCopy) { + Technology.BatchCopy<Data> bi = (Technology.BatchCopy<Data>)bp; + data = bi.wrapModel(model, copyFrom, propArr, funcArr); + } else if (bp instanceof Technology.BatchInit) { + Technology.BatchInit<Data> bi = (Technology.BatchInit<Data>)bp; + data = bi.wrapModel(model, propArr, funcArr); + } else { + data = bp.wrapModel(model); + for (PropertyBinding b : propArr) { + bp.bind(b, model, data); + } + for (FunctionBinding b : funcArr) { + bp.expose(b, model, data); + } + } + } + + + public Data koData() { + return data; + } + + public void valueHasMutated(String firstName, Object oldValue, Object newValue) { + if (bp instanceof Technology.ValueMutated) { + Technology.ValueMutated<Data> vm = (Technology.ValueMutated<Data>)bp; + Object ov = JSON.find(oldValue, this); + Object nv = JSON.find(newValue, this); + vm.valueHasMutated(data, firstName, ov, nv); + } else { + bp.valueHasMutated(data, firstName); + } + } + + public void applyBindings(String id) { + if (bp instanceof Technology.ApplyId) { + Technology.ApplyId<Data> ai = (Technology.ApplyId<Data>) bp; + ai.applyBindings(id, data); + return; + } + if (id != null) { + LOG.log(Level.WARNING, + "Technology {0} does not implement ApplyId extension. Can't apply to {1}. Applying globally.", + new Object[]{bp, id} + ); + } + bp.applyBindings(data); + } + + Object wrapArray(Object[] arr) { + return bp.wrapArray(arr); + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/impl/JSON.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/impl/JSON.java b/json/src/main/java/org/netbeans/html/json/impl/JSON.java new file mode 100644 index 0000000..287979c --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/impl/JSON.java @@ -0,0 +1,533 @@ +/** + * 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-2016 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.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import net.java.html.BrwsrCtx; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.FunctionBinding; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.PropertyBinding; +import org.netbeans.html.json.spi.Proto; +import org.netbeans.html.json.spi.Technology; +import org.netbeans.html.json.spi.Transfer; +import org.netbeans.html.json.spi.WSTransfer; + +/** + * + * @author Jaroslav Tulach + */ +public final class JSON { + private JSON() { + } + + static Technology<?> findTechnology(BrwsrCtx c) { + Technology<?> t = Contexts.find(c, Technology.class); + return t == null ? EmptyTech.EMPTY : t; + } + + public static Transfer findTransfer(BrwsrCtx c) { + Transfer t = Contexts.find(c, Transfer.class); + return t == null ? EmptyTech.EMPTY : t; + } + + public static WSTransfer<?> findWSTransfer(BrwsrCtx c) { + WSTransfer<?> t = Contexts.find(c, WSTransfer.class); + return t == null ? EmptyTech.EMPTY : t; + } + + public static <M> void readBindings(BrwsrCtx c, M model, Object value) { + Technology<?> tech = findTechnology(c); + if (tech instanceof Technology.BatchCopy) { + Proto p = findProto(model); + PropertyBindingAccessor.getBindings(p, true, value); + } + } + public static void extract(BrwsrCtx c, Object value, String[] props, Object[] values) { + Transfer t = findTransfer(c); + t.extract(value, props, values); + } + + private static Object getProperty(BrwsrCtx c, Object obj, String prop) { + if (prop == null) return obj; + + String[] arr = { prop }; + Object[] val = { null }; + extract(c, obj, arr, val); + return val[0]; + } + + public static String toJSON(Object value) { + if (value == null) { + return "null"; + } + if (value instanceof Enum) { + value = value.toString(); + } + if (value instanceof Character) { + value = Character.toString((Character)value); + } + if (value instanceof String) { + String s = (String)value; + int len = s.length(); + StringBuilder sb = new StringBuilder(len + 10); + sb.append('"'); + for (int i = 0; i < len; i++) { + char ch = s.charAt(i); + switch (ch) { + case '\"': sb.append("\\\""); break; + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + case '\\': sb.append("\\\\"); break; + default: sb.append(ch); + } + } + sb.append('"'); + return sb.toString(); + } + return value.toString(); + } + + public static String toString(BrwsrCtx c, Object obj, String prop) { + obj = getProperty(c, obj, prop); + return obj == null ? null : obj.toString(); + } + public static Number toNumber(BrwsrCtx c, Object obj, String prop) { + obj = getProperty(c, obj, prop); + if (obj instanceof Character) { + obj = (int)(Character)obj; + } + if (!(obj instanceof Number)) { + obj = Double.NaN; + } + return (Number)obj; + } + public static <M> M toModel(BrwsrCtx c, Class<M> aClass, Object data, Object object) { + Technology<?> t = findTechnology(c); + Object o = t.toModel(aClass, data); + return aClass.cast(o); + } + + public static boolean isSame(int a, int b) { + return a == b; + } + + public static boolean isSame(double a, double b) { + return a == b; + } + + public static boolean isSame(Object a, Object b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return a.equals(b); + } + + public static int hashPlus(Object o, int h) { + return o == null ? h : h ^ o.hashCode(); + } + + public static <T> T extractValue(Class<T> type, Object val) { + if (Number.class.isAssignableFrom(type)) { + val = numberValue(val); + } + if (Boolean.class == type) { + val = boolValue(val); + } + if (String.class == type) { + val = stringValue(val); + } + if (Character.class == type) { + val = charValue(val); + } + if (Integer.class == type) { + val = val instanceof Number ? ((Number)val).intValue() : 0; + } + if (Long.class == type) { + val = val instanceof Number ? ((Number)val).longValue() : 0; + } + if (Short.class == type) { + val = val instanceof Number ? ((Number)val).shortValue() : 0; + } + if (Byte.class == type) { + val = val instanceof Number ? ((Number)val).byteValue() : 0; + } + if (Double.class == type) { + val = val instanceof Number ? ((Number)val).doubleValue() : Double.NaN; + } + if (Float.class == type) { + val = val instanceof Number ? ((Number)val).floatValue() : Float.NaN; + } + return type.cast(val); + } + + static boolean isNumeric(Object val) { + return ((val instanceof Integer) || (val instanceof Long) || (val instanceof Short) || (val instanceof Byte)); + } + + public static String stringValue(Object val) { + if (val instanceof Boolean) { + return ((Boolean)val ? "true" : "false"); + } + if (isNumeric(val)) { + return Long.toString(((Number)val).longValue()); + } + if (val instanceof Float) { + return Float.toString((Float)val); + } + if (val instanceof Double) { + return Double.toString((Double)val); + } + return (String)val; + } + + public static Number numberValue(Object val) { + if (val instanceof String) { + try { + return Double.valueOf((String)val); + } catch (NumberFormatException ex) { + return Double.NaN; + } + } + if (val instanceof Boolean) { + return (Boolean)val ? 1 : 0; + } + return (Number)val; + } + + public static Character charValue(Object val) { + if (val instanceof Number) { + return Character.toChars(numberValue(val).intValue())[0]; + } + if (val instanceof Boolean) { + return (Boolean)val ? (char)1 : (char)0; + } + if (val instanceof String) { + String s = (String)val; + return s.isEmpty() ? (char)0 : s.charAt(0); + } + return (Character)val; + } + + public static Boolean boolValue(Object val) { + if (val instanceof String) { + return Boolean.parseBoolean((String)val); + } + if (val instanceof Number) { + return numberValue(val).doubleValue() != 0.0; + } + + return Boolean.TRUE.equals(val); + } + + public static Object find(Object object, Bindings model) { + if (object == null) { + return null; + } + if (object instanceof JSONList) { + return ((JSONList<?>) object).koData(); + } + if (object instanceof Collection) { + return JSONList.koData((Collection<?>) object, model); + } + if ( + object instanceof String || + object instanceof Boolean || + object instanceof Number || + object instanceof Character || + object instanceof Enum<?> + ) { + return object; + } + Proto proto = findProto(object); + if (proto == null) { + return null; + } + final Bindings b = PropertyBindingAccessor.getBindings(proto, true, null); + return b == null ? null : b.koData(); + } + + private static Proto findProto(Object object) { + Proto.Type<?> type = JSON.findType(object.getClass()); + if (type == null) { + return null; + } + final Proto proto = PropertyBindingAccessor.protoFor(type, object); + return proto; + } + + public static Object find(Object object) { + return find(object, null); + } + + public static void applyBindings(Object object, String id) { + final Proto proto = findProto(object); + if (proto == null) { + throw new IllegalArgumentException("Not a model: " + object.getClass()); + } + proto.applyBindings(id); + } + + public static abstract class WS { + private WS() { + } + + public abstract void send(BrwsrCtx ctx, String headers, String url, Object model); + public static <Socket> WS create(WSTransfer<Socket> t, RcvrJSON r) { + return new WSImpl<Socket>(t, r); + } + } + + private static final class WSImpl<Socket> extends WS { + + private final WSTransfer<Socket> trans; + private final RcvrJSON rcvr; + private Socket socket; + private String prevURL; + + private WSImpl(WSTransfer<Socket> trans, RcvrJSON rcvr) { + this.trans = trans; + this.rcvr = rcvr; + } + + @Override + public void send(BrwsrCtx ctx, String headers, String url, Object data) { + Socket s = socket; + if (s == null) { + if (data != null) { + throw new IllegalStateException("WebSocket is not opened yet. Call with null data, was: " + data); + } + JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, headers, url, null, "WebSocket", null); + socket = trans.open(url, call); + prevURL = url; + return; + } + if (data == null) { + trans.close(s); + socket = null; + return; + } + if (!prevURL.equals(url)) { + throw new IllegalStateException( + "Can't call to different URL " + url + " was: " + prevURL + "!" + + " Close the socket by calling it will null data first!" + ); + } + JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, headers, prevURL, null, "WebSocket", data); + trans.send(s, call); + } + + } + + private static final Map<Class,Proto.Type<?>> modelTypes; + static { + modelTypes = new HashMap<Class, Proto.Type<?>>(); + } + public static void register(Class c, Proto.Type<?> type) { + modelTypes.put(c, type); + } + + public static boolean isModel(Class<?> clazz) { + return findType(clazz) != null; + } + + static Proto.Type<?> findType(Class<?> clazz) { + for (int i = 0; i < 2; i++) { + Proto.Type<?> from = modelTypes.get(clazz); + if (from == null) { + initClass(clazz); + } else { + return from; + } + } + return null; + } + + public static <Model> Model bindTo(Model model, BrwsrCtx c) { + Proto.Type<Model> from = (Proto.Type<Model>) findType(model.getClass()); + if (from == null) { + throw new IllegalArgumentException(); + } + return PropertyBindingAccessor.clone(from, model, c); + } + + public static <T> T readStream(BrwsrCtx c, Class<T> modelClazz, InputStream data, Collection<? super T> collectTo) + throws IOException { + Transfer tr = findTransfer(c); + Object rawJSON = tr.toJSON((InputStream)data); + if (rawJSON instanceof Object[]) { + final Object[] arr = (Object[])rawJSON; + if (collectTo != null) { + for (int i = 0; i < arr.length; i++) { + collectTo.add(read(c, modelClazz, arr[i])); + } + return null; + } + if (arr.length == 0) { + throw new EOFException("Recieved an empty array"); + } + rawJSON = arr[0]; + } + T res = read(c, modelClazz, rawJSON); + if (collectTo != null) { + collectTo.add(res); + } + return res; + } + public static <T> T read(BrwsrCtx c, Class<T> modelClazz, Object data) { + if (data == null) { + return null; + } + if (modelClazz == String.class) { + return modelClazz.cast(data.toString()); + } + for (int i = 0; i < 2; i++) { + Proto.Type<?> from = modelTypes.get(modelClazz); + if (from == null) { + initClass(modelClazz); + } else { + return modelClazz.cast(PropertyBindingAccessor.readFrom(from, c, data)); + } + } + throw new NullPointerException(); + } + static void initClass(Class<?> modelClazz) { + try { + // try to resolve the class + ClassLoader l; + try { + l = modelClazz.getClassLoader(); + } catch (SecurityException ex) { + l = null; + } + if (l != null) { + Class.forName(modelClazz.getName(), true, l); + } + modelClazz.newInstance(); + } catch (Exception ex) { + // ignore and try again + } + } + + private static final class EmptyTech + implements Technology<Object>, Transfer, WSTransfer<Void> { + private static final EmptyTech EMPTY = new EmptyTech(); + + @Override + public Object wrapModel(Object model) { + return model; + } + + @Override + public void valueHasMutated(Object data, String propertyName) { + } + + @Override + public void bind(PropertyBinding b, Object model, Object data) { + } + + @Override + public void expose(FunctionBinding fb, Object model, Object d) { + } + + @Override + public void applyBindings(Object data) { + } + + @Override + public Object wrapArray(Object[] arr) { + return arr; + } + + @Override + public void extract(Object obj, String[] props, Object[] values) { + for (int i = 0; i < values.length; i++) { + values[i] = null; + } + } + + @Override + public void loadJSON(JSONCall call) { + call.notifyError(new UnsupportedOperationException()); + } + + @Override + public <M> M toModel(Class<M> modelClass, Object data) { + return modelClass.cast(data); + } + + @Override + public Object toJSON(InputStream is) throws IOException { + throw new IOException("Not supported"); + } + + @Override + public void runSafe(Runnable r) { + r.run(); + } + + @Override + public Void open(String url, JSONCall onReply) { + onReply.notifyError(new UnsupportedOperationException("WebSockets not supported!")); + return null; + } + + @Override + public void send(Void socket, JSONCall data) { + } + + @Override + public void close(Void socket) { + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/impl/JSONList.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/impl/JSONList.java b/json/src/main/java/org/netbeans/html/json/impl/JSONList.java new file mode 100644 index 0000000..2a602cc --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/impl/JSONList.java @@ -0,0 +1,257 @@ +/** + * 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-2016 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.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import org.netbeans.html.json.spi.Proto; + +/** + * + * @author Jaroslav Tulach + */ +public final class JSONList<T> extends ArrayList<T> { + private final Proto proto; + private final String name; + private final String[] deps; + private final int index; + + public JSONList(Proto proto, String name, int changeIndex, String... deps) { + this.proto = proto; + this.name = name; + this.deps = deps; + this.index = changeIndex; + } + + public void init(Object values) { + int len; + if (values == null || (len = Array.getLength(values)) == 0) { + return; + } + for (int i = 0; i < len; i++) { + Object data = Array.get(values, i); + super.add((T)data); + } + } + public static <T> void init(Collection<T> to, Object values) { + int len; + if (values == null || (len = Array.getLength(values)) == 0) { + return; + } + for (int i = 0; i < len; i++) { + Object data = Array.get(values, i); + to.add((T)data); + } + } + + @Override + public boolean add(T e) { + prepareChange(); + boolean ret = super.add(e); + notifyChange(); + return ret; + } + + @Override + public boolean addAll(Collection<? extends T> c) { + prepareChange(); + boolean ret = super.addAll(c); + notifyChange(); + return ret; + } + + @Override + public boolean addAll(int index, Collection<? extends T> c) { + prepareChange(); + boolean ret = super.addAll(index, c); + notifyChange(); + return ret; + } + + public void fastReplace(Collection<? extends T> c) { + prepareChange(); + super.clear(); + super.addAll(c); + notifyChange(); + } + + @Override + public boolean remove(Object o) { + prepareChange(); + boolean ret = super.remove(o); + notifyChange(); + return ret; + } + + @Override + public void clear() { + prepareChange(); + super.clear(); + notifyChange(); + } + + @Override + public boolean removeAll(Collection<?> c) { + prepareChange(); + boolean ret = super.removeAll(c); + notifyChange(); + return ret; + } + + public void sort(Comparator<? super T> c) { + Object[] arr = this.toArray(); + Arrays.sort(arr, (Comparator<Object>) c); + for (int i = 0; i < arr.length; i++) { + super.set(i, (T) arr[i]); + } + notifyChange(); + } + + @Override + public boolean retainAll(Collection<?> c) { + prepareChange(); + boolean ret = super.retainAll(c); + notifyChange(); + return ret; + } + + @Override + public T set(int index, T element) { + prepareChange(); + T ret = super.set(index, element); + notifyChange(); + return ret; + } + + @Override + public void add(int index, T element) { + prepareChange(); + super.add(index, element); + notifyChange(); + } + + @Override + public T remove(int index) { + prepareChange(); + T ret = super.remove(index); + notifyChange(); + return ret; + } + + @Override + protected void removeRange(int fromIndex, int toIndex) { + super.removeRange(fromIndex, toIndex); + notifyChange(); + } + + @Override + public String toString() { + Iterator<T> it = iterator(); + if (!it.hasNext()) { + return "[]"; + } + String sep = ""; + StringBuilder sb = new StringBuilder(); + sb.append('['); + while (it.hasNext()) { + T t = it.next(); + sb.append(sep); + sb.append(JSON.toJSON(t)); + sep = ","; + } + sb.append(']'); + return sb.toString(); + } + + private void prepareChange() { + if (index == Integer.MIN_VALUE) { + try { + proto.initTo(null, null); + } catch (IllegalStateException ex) { + throw new UnsupportedOperationException(); + } + } + } + + private void notifyChange() { + proto.getContext().execute(new Runnable() { + @Override + public void run() { + Bindings m = PropertyBindingAccessor.getBindings(proto, false, null); + if (m != null) { + m.valueHasMutated(name, null, JSONList.this); + for (String dependant : deps) { + m.valueHasMutated(dependant, null, null); + } + if (index >= 0) { + PropertyBindingAccessor.notifyProtoChange(proto, index); + } + } + } + }); + } + + @Override + public JSONList clone() { + throw new UnsupportedOperationException(); + } + + static final Object koData(Collection<?> c, Bindings m) { + Object[] arr = c.toArray(new Object[c.size()]); + for (int i = 0; i < arr.length; i++) { + Object r = JSON.find(arr[i], m); + if (r != null) { + arr[i] = r; + } + } + return m.wrapArray(arr); + } + + final Object koData() { + return koData(this, PropertyBindingAccessor.getBindings(proto, true, null)); + } +}
