All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.html.json.Model Maven / Gradle / Ivy

Go to download

API for smooth representation of JSON objects in Java. Write your application in Java and present it using modern HTML rendering technologies like Knockout.

The newest version!
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
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;
import java.net.URL;
import java.util.List;

/** 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 method * converts the state of the object into * 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 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 PersonModel {
 *   {@link ComputedProperty @ComputedProperty}
 *   static String fullName(String firstName, String lastName) {
 *     return firstName + " " + lastName;
 *   }
 * 
 *   {@link ComputedProperty @ComputedProperty}
 *   static String mainAddress({@link List List<Address>} addresses) {
 *     for (Address a : addresses) {
 *       return a.getStreet() + " " + a.getTown();
 *     }
 *     return "No address";
 *   }
 * 
 *   {@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(); (in case you specify {@link #targetId()}. * 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>
 * 
* *

* Access Raw Knockout Observables *

* *

* One can obtain raw JavaScript object representing the * instance of {@link Model model class} (with appropriate * Knockout observable properties) * by calling {@link Models#toRaw(java.lang.Object) Models.toRaw(p)}. For * example here is a way to obtain the value of fullName property * (inefficient as it switches between Java and JavaScript back and forth, * but functional and instructive) via a JavaScript call: *

*
 * {@link net.java.html.js.JavaScriptBody @JavaScriptBody}(args = "raw", javacall = true, body =
 *   "return raw.fullName();" // yes, the Knockout property is a function
 * )
 * static native String jsFullName(Object raw);
 * // and later
 * Person p = ...;
 * String fullName = jsFullName({@link Models#toRaw(java.lang.Object) Models.toRaw(p)});
 * 
*

* The above shows how to read a value from Knockout * observable. There is a way to change the value too: * One can pass a parameter to the property-function and then * it acts like a setter (of course not in the case of read only fullName property, * but for firstName or lastName the setter is * available). Everything mentioned in this paragraph applies only when * Knockout technology is active * other technologies may behave differently. *

* *

* Copy to Plain JSON *

* *

* There is a way to pass a value of a Java {@link Model model class} instance * by copy and convert * the {@link Model the whole object} into plain * JSON. Just * print it as a string and parse it in JavaScript: *

*
 * {@link net.java.html.js.JavaScriptBody @JavaScriptBody}(args = { "txt" }, body =
 *   "return JSON.parse(txt);"
 * )
 * private static native Object parseJSON(String txt);
 * 
 * public static Object toPlainJSON(Object model) {
 *   return parseJSON(model.toString());
 * }
 * 
*

* The newly returned instance is a one time copy of the original model and is no longer * connected to it. The copy based behavior is independent on any * particular technology and should work * in Knockout as well as other * technology implementations. *

* *

* References *

* * Visit an on-line demo * to see a histogram driven by the {@link Model} annotation or try * a little math test. * * @author Jaroslav Tulach */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Model { /** Name of the model class. * @return valid Java identifier to use as a name of the model class */ String className(); /** List of properties in the model. * @return array of property definitions */ Property[] properties(); /** The id of an element to bind this model too. If this * property is specified an applyBindings method * in the model class is going to be generated which later calls * {@link Models#applyBindings(java.lang.Object, java.lang.String)} * with appropriate targetId. If the targetId * is specified as empty string, null value is passed * to {@link Models#applyBindings(java.lang.Object, java.lang.String)} method. * If the targetId is not specified at all, no public * applyBindings method is generated at all (a change compared * to previous versions of this API). * * @return an empty string (means apply globally), or ID of a (usually DOM) * element to apply this model after calling its generated * applyBindings() method to * @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 ""; /** Controls keeping of per-instance private state. Sometimes * the class generated by the {@link Model @Model annotation} needs to * keep non-public, or non-JSON like state. One can achieve that by * specifying instance=true when using the annotation. Then * the generated class gets a pointer to the instance of the annotated * class (there needs to be default constructor) and all the {@link ModelOperation @ModelOperation}, * {@link Function @Function}, {@link OnPropertyChange @OnPropertyChange} * and {@link OnReceive @OnReceive} methods may be non-static. The * instance of the implementation class isn't accessible directly, just * through calls to above defined (non-static) methods. Example: *
     * {@link Model @Model}(className="Data", instance=true, properties={
     *   {@link Property @Property}(name="message", type=String.class)
     * })
     * final class DataPrivate {
     *   private int count;
     * 
     *   {@link ModelOperation @ModelOperation} void increment(Data model) {
     *     count++;
     *   }
     * 
     *   {@link ModelOperation @ModelOperation} void hello(Data model) {
     *     model.setMessage("Hello " + count + " times!");
     *   }
     * }
     * Data data = new Data();
     * data.increment();
     * data.increment();
     * data.increment();
     * data.hello();
     * assert data.getMessage().equals("Hello 3 times!");
     * 
*

* The methods annotated by {@link ComputedProperty} need to remain static, as * they are supposed to be pure functions (e.g. depend only on their parameters) * and shouldn't use any internal state. *

*

How do I initialize private values? * The implementation class (the one annotated by {@link Model @Model} annotation) * needs to have accessible default constructor. That constructor is used to * create the instance. Obviously such constructor does not have * any parameters, so no initialization is possible. *

*

* Later one can, however, call any {@link ModelOperation @ModelOperation} * method and pass in additional configuration parameters. In the above * example it should be possible add *

*
     * {@link ModelOperation @ModelOperation} void init(Data model, int count) {
     *   this.count = count;
     * }
     * 

* and then one can initialize the model using the init as: *

*
     * Data data = new Data();
     * data.init(2);
     * data.increment();
     * data.hello();
     * assert data.getMessage().equals("Hello 3 times!");
     * 

* Why there has to be default constructor? Because instances of * classes generated by {@link Model @Model annotation} may be constructed * by the system as * {@link Models#fromRaw(net.java.html.BrwsrCtx, java.lang.Class, java.lang.Object) wrappers around existing JavaScript objects} * - then * there is nobody to provide additional parameters at construction time. *

*

How do I read private values? * The methods annotated by {@link ModelOperation} must return void * (as they can run asynchronously) and as such they aren't suitable for * returning values back to the caller. In case something like that is * needed, one can use the approach of the hello method - e.g. * set value of some {@link Property property} that has a getter: *

*
     * data.hello();
     * assert data.getMessage().equals("Hello 3 times!");
     * 

* Or one can use actor-like callbacks. Define callback interface and * use it in a {@link ModelOperation @ModelOperation} method: *

*
     * public interface ReadCount {
     *   public void notifyCount(int count);
     * }
     * {@link ModelOperation @ModelOperation} void readCount(Data model, ReadCount callback) {
     *   callback.readCount(this.count);
     * }
     * Data data = new Data();
     * data.init(2);
     * data.increment();
     * data.readCount(count -> System.out.println("count should be 3: " + count));
     * 

* The provided lambda-function callback may be invoked immediately * or asynchronously as documentation for {@link ModelOperation} * annotation describes. *

* * @return true if the model class should keep pointer to * instance of the implementation class * @since 1.3 */ boolean instance() default false; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy