2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
9 * The contents of this file are subject to the terms of either the GNU
10 * General Public License Version 2 only ("GPL") or the Common
11 * Development and Distribution License("CDDL") (collectively, the
12 * "License"). You may not use this file except in compliance with the
13 * License. You can obtain a copy of the License at
14 * http://www.netbeans.org/cddl-gplv2.html
15 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16 * specific language governing permissions and limitations under the
17 * License. When distributing the software, include this License Header
18 * Notice in each file and include the License file at
19 * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
20 * particular file as subject to the "Classpath" exception as provided
21 * by Oracle in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the
23 * License Header, with the fields enclosed by brackets [] replaced by
24 * your own identifying information:
25 * "Portions Copyrighted [year] [name of copyright owner]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
32 * If you wish your version of this file to be governed by only the CDDL
33 * or only the GPL Version 2, indicate your decision by adding
34 * "[Contributor] elects to include this software in this distribution
35 * under the [CDDL or GPL Version 2] license." If you do not indicate a
36 * single choice of license, a recipient has the option to distribute
37 * your version of this file under either the CDDL, the GPL Version 2 or
38 * to extend the choice of license to its licensees as provided above.
39 * However, if you add GPL Version 2 code and therefore, elected the GPL
40 * Version 2 license, then the option applies only if the new code is
41 * made subject to such option by the copyright holder.
43 package org.apidesign.html.json.spi;
45 import java.util.Collection;
46 import java.util.List;
47 import net.java.html.BrwsrCtx;
48 import net.java.html.json.ComputedProperty;
49 import net.java.html.json.Model;
50 import org.netbeans.html.json.impl.Bindings;
51 import org.netbeans.html.json.impl.JSON;
52 import org.netbeans.html.json.impl.JSONList;
53 import org.netbeans.html.json.impl.RcvrJSON;
54 import org.netbeans.html.json.impl.RcvrJSON.MsgEvnt;
56 /** Object associated with one instance of a model generated by the
57 * {@link Model} annotation. Contains methods the generated class can
58 * use to communicate with behind the scene associated {@link Technology}.
59 * Each {@link Proto} object is associated with <a href="http://wiki.apidesign.org/wiki/Singletonizer">
60 * singletonizer</a>-like interface {@link Type} which provides the
61 * associated {@link Technology} the necessary information about the
62 * generated {@link Model} class.
64 * @author Jaroslav Tulach <jtulach@netbeans.org>
67 public final class Proto {
68 private final Object obj;
69 private final Type type;
70 private final net.java.html.BrwsrCtx context;
71 private boolean locked;
72 private org.netbeans.html.json.impl.Bindings ko;
74 Proto(Object obj, Type type, BrwsrCtx context) {
77 this.context = context;
80 /** Browser context this proto object and its associated model
83 * @return the associated context
85 public BrwsrCtx getContext() {
89 /** Before doing modification of the model properties, the
90 * generated code enters write lock by calling this method.
91 * @throws IllegalStateException if already locked
93 public void acquireLock() throws IllegalStateException {
94 if (locked) throw new IllegalStateException();
98 /** Verifies the model is not locked otherwise throws an exception.
99 * @throws IllegalStateException if the model is locked
101 public void verifyUnlocked() throws IllegalStateException {
102 if (locked) throw new IllegalStateException();
105 /** When modifications are over, the model is switched into
106 * unlocked state by calling this method.
108 public void releaseLock() {
112 /** Whenever model changes a property. It should notify the
113 * associated technology by calling this method.
115 *@param propName name of the changed property
117 public void valueHasMutated(String propName) {
119 ko.valueHasMutated(propName);
123 /** Initializes the associated model in the current {@link #getContext() context}.
124 * In case of <em>knockout.js</em> technology, applies given bindings
125 * of the current model to the <em>body</em> element of the page.
127 public void applyBindings() {
128 initBindings().applyBindings();
131 /** Invokes the provided runnable in the {@link #getContext() context}
132 * of the browser. If the caller is already on the right thread, the
133 * <code>run.run()</code> is invoked immediately and synchronously.
134 * Otherwise the method returns immediately and the <code>run()</code>
135 * method is performed later
137 * @param run the action to execute
139 public void runInBrowser(Runnable run) {
140 JSON.runInBrowser(context, run);
143 /** Initializes the provided collection with a content of the <code>array</code>.
144 * The initialization can only be done soon after the the collection
145 * is created, otherwise an exception is throw
147 * @param to the collection to initialize (assumed to be empty)
148 * @param array the array to add to the collection
149 * @throws IllegalStateException if the system has already been initialized
151 public void initTo(Collection<?> to, Object array) {
153 throw new IllegalStateException();
155 if (to instanceof JSONList) {
156 ((JSONList)to).init(array);
158 JSONList.init(to, array);
162 /** Takes an object representing JSON result and extract some of its
163 * properties. It is assumed that the <code>props</code> and
164 * <code>values</code> arrays have the same length.
166 * @param json the JSON object (actual type depends on the associated
167 * {@link Technology})
168 * @param props list of properties to extract
169 * @param values array that will be filled with extracted values
171 public void extract(Object json, String[] props, Object[] values) {
172 JSON.extract(context, json, props, values);
175 /** Converts raw JSON <code>data</code> into a Java {@link Model} class.
177 * @param <T> type of the model class
178 * @param modelClass the type of the class to create
179 * @param data the raw JSON data
180 * @return newly created instance of the model class
182 public <T> T read(Class<T> modelClass, Object data) {
183 return JSON.read(context, modelClass, data);
186 /** Initializes asynchronous JSON connection to specified URL. The
187 * method returns immediately and later does callback later.
189 * @param index the callback index to be used when a reply is received
190 * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
192 * @param urlBefore the part of the URL before JSON-P callback parameter
193 * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
194 * @param method method to use for connection to the server
195 * @param data string, number or a {@link Model} generated class to send to
196 * the server when doing a query
198 public void loadJSON(final int index,
199 String urlBefore, String urlAfter, String method,
202 class Rcvr extends RcvrJSON {
204 protected void onMessage(MsgEvnt msg) {
205 type.onMessage(obj, index, 1, msg.getValues());
209 protected void onError(MsgEvnt msg) {
210 type.onMessage(obj, index, 2, msg.getException());
213 JSON.loadJSON(context, new Rcvr(), urlBefore, urlAfter, method, data);
216 /** Opens new WebSocket connection to the specified URL.
218 * @param index the index to use later during callbacks to
219 * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}
220 * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to
221 * @param data data to send to server (usually <code>null</code>)
222 * @return returns a non-null object representing the socket
223 * which can be used when calling {@link #wsSend(java.lang.Object, java.lang.String, java.lang.Object) }
225 public Object wsOpen(final int index, String url, Object data) {
226 class WSrcvr extends RcvrJSON {
228 protected void onError(MsgEvnt msg) {
229 type.onMessage(obj, index, 2, msg.getException());
233 protected void onMessage(MsgEvnt msg) {
234 type.onMessage(obj, index, 1, msg.getValues());
238 protected void onClose(MsgEvnt msg) {
239 type.onMessage(obj, index, 3, null);
243 protected void onOpen(MsgEvnt msg) {
244 type.onMessage(obj, index, 0, null);
247 return JSON.openWS(context, new WSrcvr(), url, data);
250 /** Sends a message to existing socket.
252 * @param webSocket the socket to send message to
253 * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to,
254 * preferably the same as the one used when the socket was
255 * {@link #wsOpen(int, java.lang.String, java.lang.Object) opened}
256 * @param data the data to send or <code>null</code> if the socket is
257 * supposed to be closed
259 public void wsSend(Object webSocket, String url, Object data) {
260 ((JSON.WS)webSocket).send(context, url, data);
263 /** Converts raw data (one of its properties) to string representation.
265 * @param data the object
266 * @param propName the name of object property or <code>null</code>
267 * if the whole object should be converted
268 * @return the string representation of the object or its property
270 public String toString(Object data, String propName) {
271 return JSON.toString(context, data, propName);
274 /** Converts raw data (one of its properties) to a number representation.
276 * @param data the object
277 * @param propName the name of object property or <code>null</code>
278 * if the whole object should be converted
279 * @return the number representation of the object or its property
281 public Number toNumber(Object data, String propName) {
282 return JSON.toNumber(context, data, propName);
285 /** Converts raw JSON data into a {@link Model} class representation.
287 * @param <T> type of the model to create
288 * @param type class of the model to create
289 * @param data raw JSON data (depends on associated {@link Technology})
290 * @return new instances of the model class filled with values from the
291 * <code>data</code> object
293 public <T> T toModel(Class<T> type, Object data) {
294 return JSON.toModel(context, type, data, null);
297 /** Creates new JSON like observable list.
299 * @param <T> the type of the list elements
300 * @param propName name of a property this list is associated with
301 * @param onChange index of the property to use when the list is modified
302 * during callback to {@link Type#onChange(java.lang.Object, int)}
303 * @param dependingProps the array of {@link ComputedProperty derived properties}
304 * that depend on the value of the list
305 * @return new, empty list associated with this proto-object and its model
307 public <T> List<T> createList(String propName, int onChange, String... dependingProps) {
308 return new JSONList<T>(this, propName, onChange, dependingProps);
311 /** Copies content of one collection to another, re-assigning all its
312 * elements from their current context to the new <code>ctx</code>.
314 * @param <T> type of the collections
315 * @param to the target collection to be filled with cloned values
316 * @param ctx context for the new collection
317 * @param from original collection with its data
319 public <T> void cloneList(Collection<T> to, BrwsrCtx ctx, Collection<T> from) {
320 Boolean isModel = null;
322 if (isModel == null) {
323 isModel = JSON.isModel(t.getClass());
326 to.add(JSON.bindTo(t, ctx));
338 final Bindings initBindings() {
340 Bindings b = Bindings.apply(context, obj);
341 PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length];
342 for (int i = 0; i < pb.length; i++) {
343 pb[i] = b.registerProperty(
344 type.propertyNames[i], i, obj, type, type.propertyReadOnly[i]
347 FunctionBinding[] fb = new FunctionBinding[type.functions.length];
348 for (int i = 0; i < fb.length; i++) {
349 fb[i] = FunctionBinding.registerFunction(
350 type.functions[i], i, obj, type
354 b.finish(obj, pb, fb);
359 final Bindings getBindings() {
363 final void onChange(int index) {
364 type.onChange(obj, index);
367 /** Functionality used by the code generated by annotation
368 * processor for the {@link net.java.html.json.Model} annotation.
370 * @param <Model> the generated class
373 public static abstract class Type<Model> {
374 private final Class<Model> clazz;
375 private final String[] propertyNames;
376 private final boolean[] propertyReadOnly;
377 private final String[] functions;
379 /** Constructor for subclasses generated by the annotation processor
380 * associated with {@link net.java.html.json.Model} annotation.
382 * @param clazz the generated model class
383 * @param modelFor the original class annotated by the {@link net.java.html.json.Model} annotation.
384 * @param properties number of properties the class has
385 * @param functions number of functions the class has
388 Class<Model> clazz, Class<?> modelFor, int properties, int functions
390 assert getClass().getName().endsWith("$Html4JavaType");
392 assert getClass().getDeclaringClass() == clazz;
393 } catch (SecurityException ex) {
397 this.propertyNames = new String[properties];
398 this.propertyReadOnly = new boolean[properties];
399 this.functions = new String[functions];
400 JSON.register(clazz, this);
403 /** Registers property for the type. It is expected each index
404 * is initialized only once.
406 * @param name name of the property
407 * @param index index of the property
408 * @param readOnly is the property read only?
410 protected final void registerProperty(String name, int index, boolean readOnly) {
411 assert propertyNames[index] == null;
412 propertyNames[index] = name;
413 propertyReadOnly[index] = readOnly;
416 /** Registers function of given name at given index.
418 * @param name name of the function
419 * @param index name of the type
421 protected final void registerFunction(String name, int index) {
422 assert functions[index] == null;
423 functions[index] = name;
426 /** Creates new proto-object for given {@link Model} class bound to
429 * @param obj instance of appropriate {@link Model} class
430 * @param context the browser context
431 * @return new proto-object that the generated class can use for
432 * communication with the infrastructure
434 public Proto createProto(Object obj, BrwsrCtx context) {
435 return new Proto(obj, this, context);
439 // Implemented by subclasses
442 /** Sets value of a {@link #registerProperty(java.lang.String, int, boolean) registered property}
445 * @param model the instance of {@link Model model class}
446 * @param index index of the property used during registration
447 * @param value the value to set the property to
449 protected abstract void setValue(Model model, int index, Object value);
451 /** Obtains and returns value of a
452 * {@link #registerProperty(java.lang.String, int, boolean) registered property}.
454 * @param model the instance of {@link Model model class}
455 * @param index index of the property used during registration
456 * @return current value of the property
458 protected abstract Object getValue(Model model, int index);
460 /** Invokes a {@link #registerFunction(java.lang.String, int)} registered function
463 * @param model the instance of {@link Model model class}
464 * @param index index of the property used during registration
465 * @param data the currently selected object the function is about to operate on
466 * @param event the event that triggered the event
468 protected abstract void call(Model model, int index, Object data, Object event);
470 /** Re-binds the model object to new browser context.
472 * @param model the instance of {@link Model model class}
473 * @param ctx browser context to clone the object to
474 * @return new instance of the model suitable for new context
476 protected abstract Model cloneTo(Model model, BrwsrCtx ctx);
478 /** Reads raw JSON data and converts them to our model class.
480 * @param c the browser context to work in
481 * @param json raw JSON data to get values from
482 * @return new instance of model class filled by the data
484 protected abstract Model read(BrwsrCtx c, Object json);
486 /** Called when a {@link #registerProperty(java.lang.String, int, boolean) registered property}
489 * @param model the object that has the property
490 * @param index the index of the property during registration
492 protected abstract void onChange(Model model, int index);
494 /** Finds out if there is an associated proto-object for given
497 * @param object an object, presumably (but not necessarily) instance of Model class
498 * @return associated proto-object or <code>null</code>
500 protected abstract Proto protoFor(Object object);
502 /** Called to report results of asynchronous over-the-wire
503 * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
504 * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)}.
506 * @param model the instance of the model class
507 * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
508 * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
509 * not all messages are applicable to all communication protocols (JSON has only 1 and 2).
510 * @param data <code>null</code> or string, number or a {@link Model} class
511 * obtained to the server as a response
513 protected abstract void onMessage(Model model, int index, int type, Object data);
516 // Various support methods the generated classes use
519 /** Converts and array of raw JSON objects into an array of typed
520 * Java {@link Model} classes.
522 * @param <T> the type of the destination array
523 * @param context browser context to use
524 * @param src array of raw JSON objects
525 * @param destType type of the individual array elements
526 * @param dest array to be filled with read type instances
528 public <T> void copyJSON(BrwsrCtx context, Object[] src, Class<T> destType, T[] dest) {
529 for (int i = 0; i < src.length && i < dest.length; i++) {
530 dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]);
534 /** Compares two objects that can be converted to integers.
535 * @param a first value
536 * @param b second value
537 * @return true if they are the same
539 public final boolean isSame(int a, int b) {
543 /** Compares two objects that can be converted to (floating point)
545 * @param a first value
546 * @param b second value
547 * @return true if they are the same
549 public final boolean isSame(double a, double b) {
553 /** Compares two objects for being the same - e.g. either <code>==</code>
554 * or <code>equals</code>.
555 * @param a first value
556 * @param b second value
557 * @return true if they are equals
559 public final boolean isSame(Object a, Object b) {
563 if (a == null || b == null) {
569 /** Cumulative hash function. Adds hashcode of the object to the
571 * @param o the object (or <code>null</code>)
572 * @param h the previous value of the hash
573 * @return new hash - the old one xor the object's one
575 public final int hashPlus(Object o, int h) {
576 return o == null ? h : h ^ o.hashCode();
579 /** Converts an object to its JSON value.
581 * @param obj the object to convert
582 * @return JSON representation of the object
584 public final String toJSON(Object obj) {
585 return JSON.toJSON(obj);
588 /** Converts the value to string.
590 * @param val the value
591 * @return the converted value
593 public final String stringValue(Object val) {
594 return JSON.stringValue(val);
597 /** Converts the value to number.
599 * @param val the value
600 * @return the converted value
602 public final Number numberValue(Object val) {
603 return JSON.numberValue(val);
606 /** Converts the value to character.
608 * @param val the value
609 * @return the converted value
611 public final Character charValue(Object val) {
612 return JSON.charValue(val);
615 /** Converts the value to boolean.
617 * @param val the value
618 * @return the converted value
620 public final Boolean boolValue(Object val) {
621 return JSON.boolValue(val);
624 /** Extracts value of specific type from given object.
626 * @param <T> the type of object one is interested in
627 * @param type the type
628 * @param val the object to convert to type
629 * @return the converted value
631 public final <T> T extractValue(Class<T> type, Object val) {
632 if (Number.class.isAssignableFrom(type)) {
633 val = numberValue(val);
635 if (Boolean.class == type) {
636 val = boolValue(val);
638 if (String.class == type) {
639 val = stringValue(val);
641 if (Character.class == type) {
642 val = charValue(val);
644 if (Integer.class == type) {
645 val = val instanceof Number ? ((Number) val).intValue() : 0;
647 if (Long.class == type) {
648 val = val instanceof Number ? ((Number) val).longValue() : 0;
650 if (Short.class == type) {
651 val = val instanceof Number ? ((Number) val).shortValue() : 0;
653 if (Byte.class == type) {
654 val = val instanceof Number ? ((Number) val).byteValue() : 0;
656 if (Double.class == type) {
657 val = val instanceof Number ? ((Number) val).doubleValue() : Double.NaN;
659 if (Float.class == type) {
660 val = val instanceof Number ? ((Number) val).floatValue() : Float.NaN;
662 return type.cast(val);