json/src/main/java/org/netbeans/html/json/spi/Proto.java
branchbeans
changeset 1036 05139f7b3629
parent 795 8c3e0f4aee77
parent 970 b686eead871e
     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 &amp; {@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 +}