1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java Fri Feb 12 08:40:12 2016 +0100
1.3 @@ -0,0 +1,897 @@
1.4 +/**
1.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
1.6 + *
1.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
1.8 + *
1.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
1.10 + * Other names may be trademarks of their respective owners.
1.11 + *
1.12 + * The contents of this file are subject to the terms of either the GNU
1.13 + * General Public License Version 2 only ("GPL") or the Common
1.14 + * Development and Distribution License("CDDL") (collectively, the
1.15 + * "License"). You may not use this file except in compliance with the
1.16 + * License. You can obtain a copy of the License at
1.17 + * http://www.netbeans.org/cddl-gplv2.html
1.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
1.19 + * specific language governing permissions and limitations under the
1.20 + * License. When distributing the software, include this License Header
1.21 + * Notice in each file and include the License file at
1.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
1.23 + * particular file as subject to the "Classpath" exception as provided
1.24 + * by Oracle in the GPL Version 2 section of the License file that
1.25 + * accompanied this code. If applicable, add the following below the
1.26 + * License Header, with the fields enclosed by brackets [] replaced by
1.27 + * your own identifying information:
1.28 + * "Portions Copyrighted [year] [name of copyright owner]"
1.29 + *
1.30 + * Contributor(s):
1.31 + *
1.32 + * The Original Software is NetBeans. The Initial Developer of the Original
1.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
1.34 + *
1.35 + * If you wish your version of this file to be governed by only the CDDL
1.36 + * or only the GPL Version 2, indicate your decision by adding
1.37 + * "[Contributor] elects to include this software in this distribution
1.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
1.39 + * single choice of license, a recipient has the option to distribute
1.40 + * your version of this file under either the CDDL, the GPL Version 2 or
1.41 + * to extend the choice of license to its licensees as provided above.
1.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
1.43 + * Version 2 license, then the option applies only if the new code is
1.44 + * made subject to such option by the copyright holder.
1.45 + */
1.46 +package org.netbeans.html.json.spi;
1.47 +
1.48 +import java.util.ArrayList;
1.49 +import java.util.Collection;
1.50 +import java.util.List;
1.51 +import net.java.html.BrwsrCtx;
1.52 +import net.java.html.json.ComputedProperty;
1.53 +import net.java.html.json.Model;
1.54 +import org.netbeans.html.json.impl.Bindings;
1.55 +import org.netbeans.html.json.impl.JSON;
1.56 +import org.netbeans.html.json.impl.JSON.WS;
1.57 +import org.netbeans.html.json.impl.JSONList;
1.58 +import org.netbeans.html.json.impl.PropertyBindingAccessor;
1.59 +import org.netbeans.html.json.impl.RcvrJSON;
1.60 +import org.netbeans.html.json.impl.RcvrJSON.MsgEvnt;
1.61 +
1.62 +/** Object associated with one instance of a model generated by the
1.63 + * {@link Model} annotation. Contains methods the generated class can
1.64 + * use to communicate with behind the scene associated {@link Technology}.
1.65 + * Each {@link Proto} object is associated with <a href="http://wiki.apidesign.org/wiki/Singletonizer">
1.66 + * singletonizer</a>-like interface {@link Type} which provides the
1.67 + * associated {@link Technology} the necessary information about the
1.68 + * generated {@link Model} class.
1.69 + *
1.70 + * @author Jaroslav Tulach
1.71 + * @since 0.7
1.72 + */
1.73 +public final class Proto {
1.74 + private final Object obj;
1.75 + private final Type type;
1.76 + private final net.java.html.BrwsrCtx context;
1.77 + private org.netbeans.html.json.impl.Bindings ko;
1.78 + private Observers observers;
1.79 +
1.80 + Proto(Object obj, Type type, BrwsrCtx context) {
1.81 + this.obj = obj;
1.82 + this.type = type;
1.83 + this.context = context;
1.84 + }
1.85 +
1.86 + /** Browser context this proto object and its associated model
1.87 + * are operating-in.
1.88 + *
1.89 + * @return the associated context
1.90 + */
1.91 + public BrwsrCtx getContext() {
1.92 + return context;
1.93 + }
1.94 +
1.95 + /** Acquires global lock to compute a {@link ComputedProperty derived property}
1.96 + * on this proto object. This proto object must not be locked yet. No
1.97 + * dependency tracking is performed.
1.98 + *
1.99 + * @throws IllegalStateException if already locked
1.100 + */
1.101 + public void acquireLock() throws IllegalStateException {
1.102 + acquireLock(null);
1.103 + }
1.104 +
1.105 + /** Acquires global lock to compute a {@link ComputedProperty derived property}
1.106 + * on this proto object. This proto object must not be locked yet. The
1.107 + * name of the property is used to track dependencies on own
1.108 + * properties of other proto objects - when they are changed, this
1.109 + * {@link #valueHasMutated(java.lang.String) property is changed too}.
1.110 + *
1.111 + * @param propName name of property we are about to compute
1.112 + * @throws IllegalStateException thrown when there is a cyclic
1.113 + * call is detected
1.114 + * @since 0.9
1.115 + */
1.116 + public void acquireLock(String propName) throws IllegalStateException {
1.117 + Observers.beginComputing(this, propName);
1.118 + }
1.119 +
1.120 + /** A property on this proto object is about to be accessed. Verifies
1.121 + * whether this proto object is accessible - e.g. it has not been
1.122 + * {@link #acquireLock() locked yet}. If everything is OK, the
1.123 + * <code>propName</code> is recorded in the chain of dependencies
1.124 + * tracked by {@link #acquireLock(java.lang.String)} and watched by
1.125 + * {@link #valueHasMutated(java.lang.String)}.
1.126 + *
1.127 + * @param propName name of the property that is requested
1.128 + * @throws IllegalStateException if the model is locked
1.129 + * @since 0.9
1.130 + */
1.131 + public void accessProperty(String propName) throws IllegalStateException {
1.132 + Observers.accessingValue(this, propName);
1.133 + }
1.134 +
1.135 + /** Verifies the model is not locked otherwise throws an exception.
1.136 + * @throws IllegalStateException if the model is locked
1.137 + */
1.138 + public void verifyUnlocked() throws IllegalStateException {
1.139 + Observers.verifyUnlocked(this);
1.140 + }
1.141 +
1.142 + /** When modifications are over, the model is switched into
1.143 + * unlocked state by calling this method.
1.144 + */
1.145 + public void releaseLock() {
1.146 + Observers.finishComputing(this);
1.147 + }
1.148 +
1.149 + /** Whenever model changes a property. It should notify the
1.150 + * associated technology by calling this method.
1.151 + * Since 0.8.3: This method may be called by any thread - it reschedules
1.152 + * its actual execution into appropriate one by using
1.153 + * {@link BrwsrCtx#execute(java.lang.Runnable)}.
1.154 + *
1.155 + * @param propName name of the changed property
1.156 + */
1.157 + public void valueHasMutated(final String propName) {
1.158 + context.execute(new Runnable() {
1.159 + @Override
1.160 + public void run() {
1.161 + if (ko != null) {
1.162 + ko.valueHasMutated(propName, null, null);
1.163 + }
1.164 + Observers.valueHasMutated(Proto.this, propName);
1.165 + }
1.166 + });
1.167 + }
1.168 +
1.169 + /** Whenever model changes a propertyit should notify the
1.170 + * associated technology. Either by calling this method
1.171 + * (if the new value is known and different to the old one) or
1.172 + * via (slightly ineffective) {@link #valueHasMutated(java.lang.String)}
1.173 + * method.
1.174 + * Since 0.8.3: This method may be called by any thread - it reschedules
1.175 + * its actual execution into appropriate one by using
1.176 + * {@link BrwsrCtx#execute(java.lang.Runnable)}.
1.177 + *
1.178 + * @param propName name of the changed property
1.179 + * @param oldValue provides previous value of the property
1.180 + * @param newValue provides new value of the property
1.181 + * @since 0.7.6
1.182 + */
1.183 + public void valueHasMutated(
1.184 + final String propName, final Object oldValue, final Object newValue
1.185 + ) {
1.186 + context.execute(new Runnable() {
1.187 + @Override
1.188 + public void run() {
1.189 + if (ko != null) {
1.190 + ko.valueHasMutated(propName, oldValue, newValue);
1.191 + }
1.192 + Observers.valueHasMutated(Proto.this, propName);
1.193 + }
1.194 + });
1.195 + }
1.196 +
1.197 + /** Initializes the associated model in the current {@link #getContext() context}.
1.198 + * In case of <em>knockout.js</em> technology, applies given bindings
1.199 + * of the current model to the <em>body</em> element of the page.
1.200 + */
1.201 + public void applyBindings() {
1.202 + initBindings().applyBindings(null);
1.203 + }
1.204 +
1.205 + /** Initializes the associated model to the specified element's subtree.
1.206 + * The technology is taken from the current {@link #getContext() context} and
1.207 + * in case of <em>knockout.js</em> applies given bindings
1.208 + * of the current model to the element of the page with 'id' attribute
1.209 + * set to the specified <code>id</code> value.
1.210 + *
1.211 + * @param id the id of element to apply the binding to
1.212 + * @since 1.1
1.213 + * @see Technology.ApplyId
1.214 + */
1.215 + public void applyBindings(String id) {
1.216 + initBindings().applyBindings(id);
1.217 + }
1.218 +
1.219 + /** Invokes the provided runnable in the {@link #getContext() context}
1.220 + * of the browser. If the caller is already on the right thread, the
1.221 + * <code>run.run()</code> is invoked immediately and synchronously.
1.222 + * Otherwise the method returns immediately and the <code>run()</code>
1.223 + * method is performed later
1.224 + *
1.225 + * @param run the action to execute
1.226 + */
1.227 + public void runInBrowser(Runnable run) {
1.228 + context.execute(run);
1.229 + }
1.230 +
1.231 + /** Invokes the specified function index in the {@link #getContext() context}
1.232 + * of the browser. If the caller is already on the right thread, the
1.233 + * index-th function is invoked immediately and synchronously.
1.234 + * Otherwise the method returns immediately and the function is invoked
1.235 + * later.
1.236 + *
1.237 + * @param index the index of the function as will be passed to
1.238 + * {@link Type#call(java.lang.Object, int, java.lang.Object, java.lang.Object)}
1.239 + * method
1.240 + * @param args array of arguments that will be passed as
1.241 + * <code>data</code> argument of the <code>call</code> method.
1.242 + * @since 0.7.6
1.243 + */
1.244 + public void runInBrowser(final int index, final Object... args) {
1.245 + context.execute(new Runnable() {
1.246 + @Override
1.247 + public void run() {
1.248 + try {
1.249 + type.call(obj, index, args, null);
1.250 + } catch (Exception ex) {
1.251 + ex.printStackTrace();
1.252 + }
1.253 + }
1.254 + });
1.255 + }
1.256 +
1.257 + /** Initializes the provided collection with a content of the <code>array</code>.
1.258 + * The initialization can only be done soon after the the collection
1.259 + * is created, otherwise an exception is throw
1.260 + *
1.261 + * @param to the collection to initialize (assumed to be empty)
1.262 + * @param array the array to add to the collection
1.263 + * @throws IllegalStateException if the system has already been initialized
1.264 + */
1.265 + public void initTo(Collection<?> to, Object array) {
1.266 + if (ko != null) {
1.267 + throw new IllegalStateException();
1.268 + }
1.269 + if (to instanceof JSONList) {
1.270 + ((JSONList)to).init(array);
1.271 + } else {
1.272 + JSONList.init(to, array);
1.273 + }
1.274 + }
1.275 +
1.276 + /** Takes an object representing JSON result and extract some of its
1.277 + * properties. It is assumed that the <code>props</code> and
1.278 + * <code>values</code> arrays have the same length.
1.279 + *
1.280 + * @param json the JSON object (actual type depends on the associated
1.281 + * {@link Technology})
1.282 + * @param props list of properties to extract
1.283 + * @param values array that will be filled with extracted values
1.284 + */
1.285 + public void extract(Object json, String[] props, Object[] values) {
1.286 + JSON.extract(context, json, props, values);
1.287 + }
1.288 +
1.289 + /** Converts raw JSON <code>data</code> into a Java {@link Model} class.
1.290 + *
1.291 + * @param <T> type of the model class
1.292 + * @param modelClass the type of the class to create
1.293 + * @param data the raw JSON data
1.294 + * @return newly created instance of the model class
1.295 + */
1.296 + public <T> T read(Class<T> modelClass, Object data) {
1.297 + return JSON.read(context, modelClass, data);
1.298 + }
1.299 +
1.300 + /** Initializes asynchronous JSON connection to specified URL. Delegates
1.301 + * to {@link #loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...) }
1.302 + * with no extra parameters.
1.303 + *
1.304 + * @param index the callback index to be used when a reply is received
1.305 + * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
1.306 + *
1.307 + * @param urlBefore the part of the URL before JSON-P callback parameter
1.308 + * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
1.309 + * @param method method to use for connection to the server
1.310 + * @param data string, number or a {@link Model} generated class to send to
1.311 + * the server when doing a query
1.312 + */
1.313 + public void loadJSON(final int index,
1.314 + String urlBefore, String urlAfter, String method,
1.315 + final Object data
1.316 + ) {
1.317 + loadJSON(index, urlBefore, urlAfter, method, data, new Object[0]);
1.318 + }
1.319 +
1.320 + /** Initializes asynchronous JSON connection to specified URL. The
1.321 + * method returns immediately and later does callback later.
1.322 + *
1.323 + * @param index the callback index to be used when a reply is received
1.324 + * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
1.325 + *
1.326 + * @param urlBefore the part of the URL before JSON-P callback parameter
1.327 + * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
1.328 + * @param method method to use for connection to the server
1.329 + * @param data string, number or a {@link Model} generated class to send to
1.330 + * the server when doing a query
1.331 + * @param params extra params to pass back when calling
1.332 + * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])}
1.333 + * @since 0.8.1
1.334 + */
1.335 + public void loadJSON(final int index,
1.336 + String urlBefore, String urlAfter, String method,
1.337 + final Object data, final Object... params
1.338 + ) {
1.339 + loadJSONWithHeaders(index, null, urlBefore, urlAfter, method, data, params);
1.340 + }
1.341 +
1.342 + /** Initializes asynchronous JSON connection to specified URL. The
1.343 + * method returns immediately and later does callback later.
1.344 + *
1.345 + * @param index the callback index to be used when a reply is received
1.346 + * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
1.347 + *
1.348 + * @param headers headers to use for the request or <code>null</code> to use default ones
1.349 + * @param urlBefore the part of the URL before JSON-P callback parameter
1.350 + * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
1.351 + * @param method method to use for connection to the server
1.352 + * @param data string, number or a {@link Model} generated class to send to
1.353 + * the server when doing a query
1.354 + * @param params extra params to pass back when calling
1.355 + * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])}
1.356 + * @since 1.2
1.357 + */
1.358 + public void loadJSONWithHeaders(final int index,
1.359 + String headers,
1.360 + String urlBefore, String urlAfter, String method,
1.361 + final Object data, final Object... params
1.362 + ) {
1.363 + class Rcvr extends RcvrJSON {
1.364 + @Override
1.365 + protected void onMessage(MsgEvnt msg) {
1.366 + type.onMessage(obj, index, 1, msg.getValues(), params);
1.367 + }
1.368 +
1.369 + @Override
1.370 + protected void onError(MsgEvnt msg) {
1.371 + type.onMessage(obj, index, 2, msg.getException(), params);
1.372 + }
1.373 + }
1.374 + JSONCall call = PropertyBindingAccessor.createCall(
1.375 + context, new Rcvr(), headers, urlBefore, urlAfter, method, data
1.376 + );
1.377 + Transfer t = JSON.findTransfer(context);
1.378 + t.loadJSON(call);
1.379 + }
1.380 +
1.381 + /** Opens new WebSocket connection to the specified URL.
1.382 + *
1.383 + * @param index the index to use later during callbacks to
1.384 + * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}
1.385 + * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to
1.386 + * @param data data to send to server (usually <code>null</code>)
1.387 + * @return returns a non-null object representing the socket
1.388 + * which can be used when calling {@link #wsSend(java.lang.Object, java.lang.String, java.lang.Object) }
1.389 + */
1.390 + public Object wsOpen(final int index, String url, Object data) {
1.391 + class WSrcvr extends RcvrJSON {
1.392 + @Override
1.393 + protected void onError(MsgEvnt msg) {
1.394 + type.onMessage(obj, index, 2, msg.getException());
1.395 + }
1.396 +
1.397 + @Override
1.398 + protected void onMessage(MsgEvnt msg) {
1.399 + type.onMessage(obj, index, 1, msg.getValues());
1.400 + }
1.401 +
1.402 + @Override
1.403 + protected void onClose(MsgEvnt msg) {
1.404 + type.onMessage(obj, index, 3, null);
1.405 + }
1.406 +
1.407 + @Override
1.408 + protected void onOpen(MsgEvnt msg) {
1.409 + type.onMessage(obj, index, 0, null);
1.410 + }
1.411 + }
1.412 + WS ws = WS.create(JSON.findWSTransfer(context), new WSrcvr());
1.413 + ws.send(context, null, url, data);
1.414 + return ws;
1.415 + }
1.416 +
1.417 + /** Sends a message to existing socket.
1.418 + *
1.419 + * @param webSocket the socket to send message to
1.420 + * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to,
1.421 + * preferably the same as the one used when the socket was
1.422 + * {@link #wsOpen(int, java.lang.String, java.lang.Object) opened}
1.423 + * @param data the data to send or <code>null</code> if the socket is
1.424 + * supposed to be closed
1.425 + */
1.426 + public void wsSend(Object webSocket, String url, Object data) {
1.427 + ((JSON.WS)webSocket).send(context, null, url, data);
1.428 + }
1.429 +
1.430 + /** Converts raw data (one of its properties) to string representation.
1.431 + *
1.432 + * @param data the object
1.433 + * @param propName the name of object property or <code>null</code>
1.434 + * if the whole object should be converted
1.435 + * @return the string representation of the object or its property
1.436 + */
1.437 + public String toString(Object data, String propName) {
1.438 + return JSON.toString(context, data, propName);
1.439 + }
1.440 +
1.441 + /** Converts raw data (one of its properties) to a number representation.
1.442 + *
1.443 + * @param data the object
1.444 + * @param propName the name of object property or <code>null</code>
1.445 + * if the whole object should be converted
1.446 + * @return the number representation of the object or its property
1.447 + */
1.448 + public Number toNumber(Object data, String propName) {
1.449 + return JSON.toNumber(context, data, propName);
1.450 + }
1.451 +
1.452 + /** Converts raw JSON data into a {@link Model} class representation.
1.453 + *
1.454 + * @param <T> type of the model to create
1.455 + * @param type class of the model to create
1.456 + * @param data raw JSON data (depends on associated {@link Technology})
1.457 + * @return new instances of the model class filled with values from the
1.458 + * <code>data</code> object
1.459 + */
1.460 + public <T> T toModel(Class<T> type, Object data) {
1.461 + return JSON.toModel(context, type, data, null);
1.462 + }
1.463 +
1.464 + /** Creates new JSON like observable list.
1.465 + *
1.466 + * @param <T> the type of the list elements
1.467 + * @param propName name of a property this list is associated with
1.468 + * @param onChange index of the property to use when the list is modified
1.469 + * during callback to {@link Type#onChange(java.lang.Object, int)}
1.470 + * @param dependingProps the array of {@link ComputedProperty derived properties}
1.471 + * that depend on the value of the list
1.472 + * @return new, empty list associated with this proto-object and its model
1.473 + */
1.474 + public <T> List<T> createList(String propName, int onChange, String... dependingProps) {
1.475 + return new JSONList<T>(this, propName, onChange, dependingProps);
1.476 + }
1.477 +
1.478 + /** Copies content of one collection to another, re-assigning all its
1.479 + * elements from their current context to the new <code>ctx</code>.
1.480 + *
1.481 + * @param <T> type of the collections
1.482 + * @param to the target collection to be filled with cloned values
1.483 + * @param ctx context for the new collection
1.484 + * @param from original collection with its data
1.485 + */
1.486 + public <T> void cloneList(Collection<T> to, BrwsrCtx ctx, Collection<T> from) {
1.487 + Boolean isModel = null;
1.488 + for (T t : from) {
1.489 + if (isModel == null) {
1.490 + isModel = JSON.isModel(t.getClass());
1.491 + }
1.492 + if (isModel) {
1.493 + to.add(JSON.bindTo(t, ctx));
1.494 + } else {
1.495 + to.add(t);
1.496 + }
1.497 + }
1.498 + }
1.499 +
1.500 + //
1.501 + // internal state
1.502 + //
1.503 +
1.504 + final String toStr() {
1.505 + return "Proto[" + obj + "]@" + Integer.toHexString(System.identityHashCode(this));
1.506 + }
1.507 +
1.508 + final Bindings initBindings() {
1.509 + if (ko == null) {
1.510 + Bindings b = Bindings.apply(context, obj);
1.511 + PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length];
1.512 + for (int i = 0; i < pb.length; i++) {
1.513 + pb[i] = b.registerProperty(
1.514 + type.propertyNames[i], i, obj, type, type.propertyReadOnly[i]
1.515 + );
1.516 + }
1.517 + FunctionBinding[] fb = new FunctionBinding[type.functions.length];
1.518 + for (int i = 0; i < fb.length; i++) {
1.519 + fb[i] = FunctionBinding.registerFunction(
1.520 + type.functions[i], i, obj, type
1.521 + );
1.522 + }
1.523 + ko = b;
1.524 + b.finish(obj, pb, fb);
1.525 + }
1.526 + return ko;
1.527 + }
1.528 +
1.529 + final Bindings getBindings() {
1.530 + return ko;
1.531 + }
1.532 +
1.533 + final void onChange(int index) {
1.534 + type.onChange(obj, index);
1.535 + }
1.536 +
1.537 + final Observers observers(boolean create) {
1.538 + if (create && observers == null) {
1.539 + observers = new Observers();
1.540 + }
1.541 + return observers;
1.542 + }
1.543 +
1.544 + /** Functionality used by the code generated by annotation
1.545 + * processor for the {@link net.java.html.json.Model} annotation.
1.546 + *
1.547 + * @param <Model> the generated class
1.548 + * @since 0.7
1.549 + */
1.550 + public static abstract class Type<Model> {
1.551 + private final Class<Model> clazz;
1.552 + private final String[] propertyNames;
1.553 + private final boolean[] propertyReadOnly;
1.554 + private final String[] functions;
1.555 +
1.556 + /** Constructor for subclasses generated by the annotation processor
1.557 + * associated with {@link net.java.html.json.Model} annotation.
1.558 + *
1.559 + * @param clazz the generated model class
1.560 + * @param modelFor the original class annotated by the {@link net.java.html.json.Model} annotation.
1.561 + * @param properties number of properties the class has
1.562 + * @param functions number of functions the class has
1.563 + */
1.564 + protected Type(
1.565 + Class<Model> clazz, Class<?> modelFor, int properties, int functions
1.566 + ) {
1.567 + assert getClass().getName().endsWith("$Html4JavaType");
1.568 + try {
1.569 + assert clazz == modelFor || getClass().getDeclaringClass() == clazz;
1.570 + } catch (SecurityException ex) {
1.571 + // OK, no check
1.572 + }
1.573 + this.clazz = clazz;
1.574 + this.propertyNames = new String[properties];
1.575 + this.propertyReadOnly = new boolean[properties];
1.576 + this.functions = new String[functions];
1.577 + JSON.register(clazz, this);
1.578 + }
1.579 +
1.580 + /** Registers property for the type. It is expected each index
1.581 + * is initialized only once.
1.582 + *
1.583 + * @param name name of the property
1.584 + * @param index index of the property
1.585 + * @param readOnly is the property read only?
1.586 + */
1.587 + protected final void registerProperty(String name, int index, boolean readOnly) {
1.588 + assert propertyNames[index] == null;
1.589 + propertyNames[index] = name;
1.590 + propertyReadOnly[index] = readOnly;
1.591 + }
1.592 +
1.593 + /** Registers function of given name at given index.
1.594 + *
1.595 + * @param name name of the function
1.596 + * @param index name of the type
1.597 + */
1.598 + protected final void registerFunction(String name, int index) {
1.599 + assert functions[index] == null;
1.600 + functions[index] = name;
1.601 + }
1.602 +
1.603 + /** Creates new proto-object for given {@link Model} class bound to
1.604 + * provided context.
1.605 + *
1.606 + * @param obj instance of appropriate {@link Model} class
1.607 + * @param context the browser context
1.608 + * @return new proto-object that the generated class can use for
1.609 + * communication with the infrastructure
1.610 + */
1.611 + public Proto createProto(Object obj, BrwsrCtx context) {
1.612 + return new Proto(obj, this, context);
1.613 + }
1.614 +
1.615 + //
1.616 + // Implemented by subclasses
1.617 + //
1.618 +
1.619 + /** Sets value of a {@link #registerProperty(java.lang.String, int, boolean) registered property}
1.620 + * to new value.
1.621 + *
1.622 + * @param model the instance of {@link Model model class}
1.623 + * @param index index of the property used during registration
1.624 + * @param value the value to set the property to
1.625 + */
1.626 + protected abstract void setValue(Model model, int index, Object value);
1.627 +
1.628 + /** Obtains and returns value of a
1.629 + * {@link #registerProperty(java.lang.String, int, boolean) registered property}.
1.630 + *
1.631 + * @param model the instance of {@link Model model class}
1.632 + * @param index index of the property used during registration
1.633 + * @return current value of the property
1.634 + */
1.635 + protected abstract Object getValue(Model model, int index);
1.636 +
1.637 + /** Invokes a {@link #registerFunction(java.lang.String, int)} registered function
1.638 + * on given object.
1.639 + *
1.640 + * @param model the instance of {@link Model model class}
1.641 + * @param index index of the property used during registration
1.642 + * @param data the currently selected object the function is about to operate on
1.643 + * @param event the event that triggered the event
1.644 + * @throws Exception the method can throw exception which is then logged
1.645 + */
1.646 + protected abstract void call(Model model, int index, Object data, Object event)
1.647 + throws Exception;
1.648 +
1.649 + /** Re-binds the model object to new browser context.
1.650 + *
1.651 + * @param model the instance of {@link Model model class}
1.652 + * @param ctx browser context to clone the object to
1.653 + * @return new instance of the model suitable for new context
1.654 + */
1.655 + protected abstract Model cloneTo(Model model, BrwsrCtx ctx);
1.656 +
1.657 + /** Reads raw JSON data and converts them to our model class.
1.658 + *
1.659 + * @param c the browser context to work in
1.660 + * @param json raw JSON data to get values from
1.661 + * @return new instance of model class filled by the data
1.662 + */
1.663 + protected abstract Model read(BrwsrCtx c, Object json);
1.664 +
1.665 + /** Called when a {@link #registerProperty(java.lang.String, int, boolean) registered property}
1.666 + * changes its value.
1.667 + *
1.668 + * @param model the object that has the property
1.669 + * @param index the index of the property during registration
1.670 + */
1.671 + protected abstract void onChange(Model model, int index);
1.672 +
1.673 + /** Finds out if there is an associated proto-object for given
1.674 + * object.
1.675 + *
1.676 + * @param object an object, presumably (but not necessarily) instance of Model class
1.677 + * @return associated proto-object or <code>null</code>
1.678 + */
1.679 + protected abstract Proto protoFor(Object object);
1.680 +
1.681 + /** Called to report results of asynchronous over-the-wire
1.682 + * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
1.683 + * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}.
1.684 + *
1.685 + * @param model the instance of the model class
1.686 + * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
1.687 + * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
1.688 + * not all messages are applicable to all communication protocols (JSON has only 1 and 2).
1.689 + * @param data <code>null</code> or string, number or a {@link Model} class
1.690 + * obtained to the server as a response
1.691 + */
1.692 + protected void onMessage(Model model, int index, int type, Object data) {
1.693 + onMessage(model, index, type, data, new Object[0]);
1.694 + }
1.695 +
1.696 + /** Called to report results of asynchronous over-the-wire
1.697 + * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
1.698 + * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}.
1.699 + *
1.700 + * @param model the instance of the model class
1.701 + * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
1.702 + * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
1.703 + * not all messages are applicable to all communication protocols (JSON has only 1 and 2).
1.704 + * @param data <code>null</code> or string, number or a {@link Model} class
1.705 + * obtained to the server as a response
1.706 + * @param params extra parameters as passed for example to
1.707 + * {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}
1.708 + * method
1.709 + * @since 0.8.1
1.710 + */
1.711 + protected void onMessage(Model model, int index, int type, Object data, Object[] params) {
1.712 + onMessage(model, index, type, data);
1.713 + }
1.714 +
1.715 + //
1.716 + // Various support methods the generated classes use
1.717 + //
1.718 +
1.719 + /** Converts and array of raw JSON objects into an array of typed
1.720 + * Java {@link Model} classes.
1.721 + *
1.722 + * @param <T> the type of the destination array
1.723 + * @param context browser context to use
1.724 + * @param src array of raw JSON objects
1.725 + * @param destType type of the individual array elements
1.726 + * @param dest array to be filled with read type instances
1.727 + */
1.728 + public <T> void copyJSON(BrwsrCtx context, Object[] src, Class<T> destType, T[] dest) {
1.729 + for (int i = 0; i < src.length && i < dest.length; i++) {
1.730 + dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]);
1.731 + }
1.732 + }
1.733 +
1.734 + /** Compares two objects that can be converted to integers.
1.735 + * @param a first value
1.736 + * @param b second value
1.737 + * @return true if they are the same
1.738 + */
1.739 + public final boolean isSame(int a, int b) {
1.740 + return a == b;
1.741 + }
1.742 +
1.743 + /** Compares two objects that can be converted to (floating point)
1.744 + * numbers.
1.745 + * @param a first value
1.746 + * @param b second value
1.747 + * @return true if they are the same
1.748 + */
1.749 + public final boolean isSame(double a, double b) {
1.750 + return a == b;
1.751 + }
1.752 +
1.753 + /** Compares two objects for being the same - e.g. either <code>==</code>
1.754 + * or <code>equals</code>.
1.755 + * @param a first value
1.756 + * @param b second value
1.757 + * @return true if they are equals
1.758 + */
1.759 + public final boolean isSame(Object a, Object b) {
1.760 + if (a == b) {
1.761 + return true;
1.762 + }
1.763 + if (a == null || b == null) {
1.764 + return false;
1.765 + }
1.766 + return a.equals(b);
1.767 + }
1.768 +
1.769 + /** Cumulative hash function. Adds hashcode of the object to the
1.770 + * previous value.
1.771 + * @param o the object (or <code>null</code>)
1.772 + * @param h the previous value of the hash
1.773 + * @return new hash - the old one xor the object's one
1.774 + */
1.775 + public final int hashPlus(Object o, int h) {
1.776 + return o == null ? h : h ^ o.hashCode();
1.777 + }
1.778 +
1.779 + /** Converts an object to its JSON value.
1.780 + *
1.781 + * @param obj the object to convert
1.782 + * @return JSON representation of the object
1.783 + */
1.784 + public final String toJSON(Object obj) {
1.785 + return JSON.toJSON(obj);
1.786 + }
1.787 +
1.788 + /** Converts the value to string.
1.789 + *
1.790 + * @param val the value
1.791 + * @return the converted value
1.792 + */
1.793 + public final String stringValue(Object val) {
1.794 + return JSON.stringValue(val);
1.795 + }
1.796 +
1.797 + /** Converts the value to number.
1.798 + *
1.799 + * @param val the value
1.800 + * @return the converted value
1.801 + */
1.802 + public final Number numberValue(Object val) {
1.803 + return JSON.numberValue(val);
1.804 + }
1.805 +
1.806 + /** Converts the value to character.
1.807 + *
1.808 + * @param val the value
1.809 + * @return the converted value
1.810 + */
1.811 + public final Character charValue(Object val) {
1.812 + return JSON.charValue(val);
1.813 + }
1.814 +
1.815 + /** Converts the value to boolean.
1.816 + *
1.817 + * @param val the value
1.818 + * @return the converted value
1.819 + */
1.820 + public final Boolean boolValue(Object val) {
1.821 + return JSON.boolValue(val);
1.822 + }
1.823 +
1.824 + /** Extracts value of specific type from given object.
1.825 + *
1.826 + * @param <T> the type of object one is interested in
1.827 + * @param type the type
1.828 + * @param val the object to convert to type
1.829 + * @return the converted value
1.830 + */
1.831 + public final <T> T extractValue(Class<T> type, Object val) {
1.832 + if (Number.class.isAssignableFrom(type)) {
1.833 + val = numberValue(val);
1.834 + }
1.835 + if (Boolean.class == type) {
1.836 + val = boolValue(val);
1.837 + }
1.838 + if (String.class == type) {
1.839 + val = stringValue(val);
1.840 + }
1.841 + if (Character.class == type) {
1.842 + val = charValue(val);
1.843 + }
1.844 + if (Integer.class == type) {
1.845 + val = val instanceof Number ? ((Number) val).intValue() : 0;
1.846 + }
1.847 + if (Long.class == type) {
1.848 + val = val instanceof Number ? ((Number) val).longValue() : 0;
1.849 + }
1.850 + if (Short.class == type) {
1.851 + val = val instanceof Number ? ((Number) val).shortValue() : 0;
1.852 + }
1.853 + if (Byte.class == type) {
1.854 + val = val instanceof Number ? ((Number) val).byteValue() : 0;
1.855 + }
1.856 + if (Double.class == type) {
1.857 + val = val instanceof Number ? ((Number) val).doubleValue() : Double.NaN;
1.858 + }
1.859 + if (Float.class == type) {
1.860 + val = val instanceof Number ? ((Number) val).floatValue() : Float.NaN;
1.861 + }
1.862 + if (type.isEnum() && val instanceof String) {
1.863 + val = Enum.valueOf(type.asSubclass(Enum.class), (String)val);
1.864 + }
1.865 + return type.cast(val);
1.866 + }
1.867 +
1.868 + /** Special dealing with array & {@link List} values. This method
1.869 + * takes the provided collection, empties it and fills it again
1.870 + * with values extracted from <code>value</code> (which is supposed
1.871 + * to be an array).
1.872 + *
1.873 + * @param <T> the type of list elements
1.874 + * @param arr collection to fill with elements in value
1.875 + * @param type the type of elements in the collection
1.876 + * @param value array of elements to put into the collecition. If
1.877 + * value is not an array it is wrapped into array with only element
1.878 + * @since 1.0
1.879 + */
1.880 + public final <T> void replaceValue(Collection<? super T> arr, Class<T> type, Object value) {
1.881 + Object[] newArr;
1.882 + if (value instanceof Object[]) {
1.883 + newArr = (Object[]) value;
1.884 + } else {
1.885 + newArr = new Object[] { value };
1.886 + }
1.887 + List<T> tmp = new ArrayList<T>(newArr.length);
1.888 + for (Object e : newArr) {
1.889 + tmp.add(extractValue(type, e));
1.890 + }
1.891 + if (arr instanceof JSONList) {
1.892 + JSONList jsList = (JSONList) arr;
1.893 + jsList.fastReplace(tmp);
1.894 + } else {
1.895 + arr.clear();
1.896 + arr.addAll(tmp);
1.897 + }
1.898 + }
1.899 + }
1.900 +}