json/src/main/java/org/netbeans/html/json/spi/Proto.java
author Jaroslav Tulach <jtulach@netbeans.org>
Mon, 22 Feb 2016 19:58:32 +0100
branchNonMutable258088
changeset 1055 c61d247f087a
parent 1054 4c40ceb185e5
permissions -rw-r--r--
#258088: Non-mutable values aren't wrapped into ko.observable, but rather stored as primitive values
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     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]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    31  *
    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.
    42  */
    43 package org.netbeans.html.json.spi;
    44 
    45 import java.util.ArrayList;
    46 import java.util.Collection;
    47 import java.util.List;
    48 import net.java.html.BrwsrCtx;
    49 import net.java.html.json.ComputedProperty;
    50 import net.java.html.json.Model;
    51 import net.java.html.json.Property;
    52 import org.netbeans.html.json.impl.Bindings;
    53 import org.netbeans.html.json.impl.JSON;
    54 import org.netbeans.html.json.impl.JSON.WS;
    55 import org.netbeans.html.json.impl.JSONList;
    56 import org.netbeans.html.json.impl.PropertyBindingAccessor;
    57 import org.netbeans.html.json.impl.RcvrJSON;
    58 import org.netbeans.html.json.impl.RcvrJSON.MsgEvnt;
    59 
    60 /** Object associated with one instance of a model generated by the
    61  * {@link Model} annotation. Contains methods the generated class can
    62  * use to communicate with behind the scene associated {@link Technology}.
    63  * Each {@link Proto} object is associated with <a href="http://wiki.apidesign.org/wiki/Singletonizer">
    64  * singletonizer</a>-like interface {@link Type} which provides the
    65  * associated {@link Technology} the necessary information about the
    66  * generated {@link Model} class.
    67  *
    68  * @author Jaroslav Tulach
    69  * @since 0.7
    70  */
    71 public final class Proto {
    72     private final Object obj;
    73     private final Type type;
    74     private final net.java.html.BrwsrCtx context;
    75     private org.netbeans.html.json.impl.Bindings ko;
    76     private Observers observers;
    77 
    78     Proto(Object obj, Type type, BrwsrCtx context) {
    79         this.obj = obj;
    80         this.type = type;
    81         this.context = context;
    82     }
    83 
    84     /** Browser context this proto object and its associated model
    85      * are operating-in.
    86      *
    87      * @return the associated context
    88      */
    89     public BrwsrCtx getContext() {
    90         return context;
    91     }
    92 
    93     /** Acquires global lock to compute a {@link ComputedProperty derived property}
    94      * on this proto object. This proto object must not be locked yet. No
    95      * dependency tracking is performed.
    96      *
    97      * @throws IllegalStateException if already locked
    98      */
    99     public void acquireLock() throws IllegalStateException {
   100         acquireLock(null);
   101     }
   102 
   103     /** Acquires global lock to compute a {@link ComputedProperty derived property}
   104      * on this proto object. This proto object must not be locked yet. The
   105      * name of the property is used to track dependencies on own
   106      * properties of other proto objects - when they are changed, this
   107      * {@link #valueHasMutated(java.lang.String) property is changed too}.
   108      *
   109      * @param propName name of property we are about to compute
   110      * @throws IllegalStateException thrown when there is a cyclic
   111      *   call is detected
   112      * @since 0.9
   113      */
   114     public void acquireLock(String propName) throws IllegalStateException {
   115         Observers.beginComputing(this, propName);
   116     }
   117 
   118     /** A property on this proto object is about to be accessed. Verifies
   119      * whether this proto object is accessible - e.g. it has not been
   120      * {@link #acquireLock() locked yet}. If everything is OK, the
   121      * <code>propName</code> is recorded in the chain of dependencies
   122      * tracked by {@link #acquireLock(java.lang.String)} and watched by
   123      * {@link #valueHasMutated(java.lang.String)}.
   124      *
   125      * @param propName name of the property that is requested
   126      * @throws IllegalStateException if the model is locked
   127      * @since 0.9
   128      */
   129     public void accessProperty(String propName) throws IllegalStateException {
   130         Observers.accessingValue(this, propName);
   131     }
   132 
   133     /** Verifies the model is not locked otherwise throws an exception.
   134      * @throws IllegalStateException if the model is locked
   135      */
   136     public void verifyUnlocked() throws IllegalStateException {
   137         Observers.verifyUnlocked(this);
   138     }
   139 
   140     /** When modifications are over, the model is switched into
   141      * unlocked state by calling this method.
   142      */
   143     public void releaseLock() {
   144         Observers.finishComputing(this);
   145     }
   146 
   147     /** Whenever model changes a property. It should notify the
   148      * associated technology by calling this method.
   149      * Since 0.8.3: This method may be called by any thread - it reschedules
   150      * its actual execution into appropriate one by using
   151      * {@link BrwsrCtx#execute(java.lang.Runnable)}.
   152      *
   153      * @param propName name of the changed property
   154      */
   155     public void valueHasMutated(final String propName) {
   156         context.execute(new Runnable() {
   157             @Override
   158             public void run() {
   159                 if (ko != null) {
   160                     ko.valueHasMutated(propName, null, null);
   161                 }
   162                 Observers.valueHasMutated(Proto.this, propName);
   163             }
   164         });
   165     }
   166 
   167     /** Whenever model changes a propertyit should notify the
   168      * associated technology. Either by calling this method
   169      * (if the new value is known and different to the old one) or
   170      * via (slightly ineffective) {@link #valueHasMutated(java.lang.String)}
   171      * method.
   172      * Since 0.8.3: This method may be called by any thread - it reschedules
   173      * its actual execution into appropriate one by using
   174      * {@link BrwsrCtx#execute(java.lang.Runnable)}.
   175      *
   176      * @param propName name of the changed property
   177      * @param oldValue provides previous value of the property
   178      * @param newValue provides new value of the property
   179      * @since 0.7.6
   180      */
   181     public void valueHasMutated(
   182         final String propName, final Object oldValue, final Object newValue
   183     ) {
   184         context.execute(new Runnable() {
   185             @Override
   186             public void run() {
   187                 if (ko != null) {
   188                     ko.valueHasMutated(propName, oldValue, newValue);
   189                 }
   190                 Observers.valueHasMutated(Proto.this, propName);
   191             }
   192         });
   193     }
   194 
   195     /** Initializes the associated model in the current {@link #getContext() context}.
   196      * In case of <em>knockout.js</em> technology, applies given bindings
   197      * of the current model to the <em>body</em> element of the page.
   198      */
   199     public void applyBindings() {
   200         initBindings(null).applyBindings(null);
   201     }
   202 
   203     /** Initializes the associated model to the specified element's subtree.
   204      * The technology is taken from the current {@link #getContext() context} and
   205      * in case of <em>knockout.js</em> applies given bindings
   206      * of the current model to the element of the page with 'id' attribute
   207      * set to the specified <code>id</code> value.
   208      *
   209      * @param id the id of element to apply the binding to
   210      * @since 1.1
   211      * @see Technology.ApplyId
   212      */
   213     public void applyBindings(String id) {
   214         initBindings(null).applyBindings(id);
   215     }
   216 
   217     /** Invokes the provided runnable in the {@link #getContext() context}
   218      * of the browser. If the caller is already on the right thread, the
   219      * <code>run.run()</code> is invoked immediately and synchronously.
   220      * Otherwise the method returns immediately and the <code>run()</code>
   221      * method is performed later
   222      *
   223      * @param run the action to execute
   224      */
   225     public void runInBrowser(Runnable run) {
   226         context.execute(run);
   227     }
   228 
   229     /** Invokes the specified function index in the {@link #getContext() context}
   230      * of the browser. If the caller is already on the right thread, the
   231      * index-th function is invoked immediately and synchronously.
   232      * Otherwise the method returns immediately and the function is invoked
   233      * later.
   234      *
   235      * @param index the index of the function as will be passed to
   236      *   {@link Type#call(java.lang.Object, int, java.lang.Object, java.lang.Object)}
   237      *   method
   238      * @param args array of arguments that will be passed as
   239      *   <code>data</code> argument of the <code>call</code> method.
   240      * @since 0.7.6
   241      */
   242     public void runInBrowser(final int index, final Object... args) {
   243         context.execute(new Runnable() {
   244             @Override
   245             public void run() {
   246                 try {
   247                     type.call(obj, index, args, null);
   248                 } catch (Exception ex) {
   249                     ex.printStackTrace();
   250                 }
   251             }
   252         });
   253     }
   254 
   255     /** Initializes the provided collection with a content of the <code>array</code>.
   256      * The initialization can only be done soon after the the collection
   257      * is created, otherwise an exception is throw
   258      *
   259      * @param to the collection to initialize (assumed to be empty)
   260      * @param array the array to add to the collection
   261      * @throws IllegalStateException if the system has already been initialized
   262      */
   263     public void initTo(Collection<?> to, Object array) {
   264         if (ko != null) {
   265             throw new IllegalStateException();
   266         }
   267         if (to instanceof JSONList) {
   268            ((JSONList)to).init(array);
   269         } else {
   270             JSONList.init(to, array);
   271         }
   272     }
   273 
   274     /** Takes an object representing JSON result and extract some of its
   275      * properties. It is assumed that the <code>props</code> and
   276      * <code>values</code> arrays have the same length.
   277      *
   278      * @param json the JSON object (actual type depends on the associated
   279      *   {@link Technology})
   280      * @param props list of properties to extract
   281      * @param values array that will be filled with extracted values
   282      */
   283     public void extract(Object json, String[] props, Object[] values) {
   284         JSON.extract(context, json, props, values);
   285     }
   286 
   287     /** Converts raw JSON <code>data</code> into a Java {@link Model} class.
   288      *
   289      * @param <T> type of the model class
   290      * @param modelClass the type of the class to create
   291      * @param data the raw JSON data
   292      * @return newly created instance of the model class
   293      */
   294     public <T> T read(Class<T> modelClass, Object data) {
   295         return JSON.read(context, modelClass, data);
   296     }
   297 
   298     /** Initializes asynchronous JSON connection to specified URL. Delegates
   299      * to {@link #loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...) }
   300      * with no extra parameters.
   301      *
   302      * @param index the callback index to be used when a reply is received
   303      *   to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
   304      *
   305      * @param urlBefore the part of the URL before JSON-P callback parameter
   306      * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
   307      * @param method method to use for connection to the server
   308      * @param data string, number or a {@link Model} generated class to send to
   309      *    the server when doing a query
   310      */
   311     public void loadJSON(final int index,
   312         String urlBefore, String urlAfter, String method,
   313         final Object data
   314     ) {
   315         loadJSON(index, urlBefore, urlAfter, method, data, new Object[0]);
   316     }
   317 
   318     /** Initializes asynchronous JSON connection to specified URL. The
   319      * method returns immediately and later does callback later.
   320      *
   321      * @param index the callback index to be used when a reply is received
   322      *   to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
   323      *
   324      * @param urlBefore the part of the URL before JSON-P callback parameter
   325      * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
   326      * @param method method to use for connection to the server
   327      * @param data string, number or a {@link Model} generated class to send to
   328      *    the server when doing a query
   329      * @param params extra params to pass back when calling
   330      *   {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])}
   331      * @since 0.8.1
   332      */
   333     public void loadJSON(final int index,
   334         String urlBefore, String urlAfter, String method,
   335         final Object data, final Object... params
   336     ) {
   337         loadJSONWithHeaders(index, null, urlBefore, urlAfter, method, data, params);
   338     }
   339 
   340     /** Initializes asynchronous JSON connection to specified URL. The
   341      * method returns immediately and later does callback later.
   342      *
   343      * @param index the callback index to be used when a reply is received
   344      *   to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
   345      *
   346      * @param headers headers to use for the request or <code>null</code> to use default ones
   347      * @param urlBefore the part of the URL before JSON-P callback parameter
   348      * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
   349      * @param method method to use for connection to the server
   350      * @param data string, number or a {@link Model} generated class to send to
   351      *    the server when doing a query
   352      * @param params extra params to pass back when calling
   353      *   {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])}
   354      * @since 1.2
   355      */
   356     public void loadJSONWithHeaders(final int index,
   357         String headers,
   358         String urlBefore, String urlAfter, String method,
   359         final Object data, final Object... params
   360     ) {
   361         class Rcvr extends RcvrJSON {
   362             @Override
   363             protected void onMessage(MsgEvnt msg) {
   364                 type.onMessage(obj, index, 1, msg.getValues(), params);
   365             }
   366 
   367             @Override
   368             protected void onError(MsgEvnt msg) {
   369                 type.onMessage(obj, index, 2, msg.getException(), params);
   370             }
   371         }
   372         JSONCall call = PropertyBindingAccessor.createCall(
   373             context, new Rcvr(), headers, urlBefore, urlAfter, method, data
   374         );
   375         Transfer t = JSON.findTransfer(context);
   376         t.loadJSON(call);
   377     }
   378 
   379     /** Opens new WebSocket connection to the specified URL.
   380      *
   381      * @param index the index to use later during callbacks to
   382      *   {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}
   383      * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to
   384      * @param data data to send to server (usually <code>null</code>)
   385      * @return returns a non-null object representing the socket
   386      *   which can be used when calling {@link #wsSend(java.lang.Object, java.lang.String, java.lang.Object) }
   387      */
   388     public Object wsOpen(final int index, String url, Object data) {
   389         class WSrcvr extends RcvrJSON {
   390             @Override
   391             protected void onError(MsgEvnt msg) {
   392                 type.onMessage(obj, index, 2, msg.getException());
   393             }
   394 
   395             @Override
   396             protected void onMessage(MsgEvnt msg) {
   397                 type.onMessage(obj, index, 1, msg.getValues());
   398             }
   399 
   400             @Override
   401             protected void onClose(MsgEvnt msg) {
   402                 type.onMessage(obj, index, 3, null);
   403             }
   404 
   405             @Override
   406             protected void onOpen(MsgEvnt msg) {
   407                 type.onMessage(obj, index, 0, null);
   408             }
   409         }
   410         WS ws = WS.create(JSON.findWSTransfer(context), new WSrcvr());
   411         ws.send(context, null, url, data);
   412         return ws;
   413     }
   414 
   415     /** Sends a message to existing socket.
   416      *
   417      * @param webSocket the socket to send message to
   418      * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to,
   419      *    preferably the same as the one used when the socket was
   420      *    {@link #wsOpen(int, java.lang.String, java.lang.Object) opened}
   421      * @param data the data to send or <code>null</code> if the socket is
   422      *    supposed to be closed
   423      */
   424     public void wsSend(Object webSocket, String url, Object data) {
   425         ((JSON.WS)webSocket).send(context, null, url, data);
   426     }
   427 
   428     /** Converts raw data (one of its properties) to string representation.
   429      *
   430      * @param data the object
   431      * @param propName the name of object property or <code>null</code>
   432      *   if the whole object should be converted
   433      * @return the string representation of the object or its property
   434      */
   435     public String toString(Object data, String propName) {
   436         return JSON.toString(context, data, propName);
   437     }
   438 
   439     /** Converts raw data (one of its properties) to a number representation.
   440      *
   441      * @param data the object
   442      * @param propName the name of object property or <code>null</code>
   443      *   if the whole object should be converted
   444      * @return the number representation of the object or its property
   445      */
   446     public Number toNumber(Object data, String propName) {
   447         return JSON.toNumber(context, data, propName);
   448     }
   449 
   450     /** Converts raw JSON data into a {@link Model} class representation.
   451      *
   452      * @param <T> type of the model to create
   453      * @param type class of the model to create
   454      * @param data raw JSON data (depends on associated {@link Technology})
   455      * @return new instances of the model class filled with values from the
   456      *   <code>data</code> object
   457      */
   458     public <T> T toModel(Class<T> type, Object data) {
   459         return JSON.toModel(context, type, data, null);
   460     }
   461 
   462     /** Creates new JSON like observable list.
   463      *
   464      * @param <T> the type of the list elements
   465      * @param propName name of a property this list is associated with
   466      * @param onChange index of the property to use when the list is modified
   467      *   during callback to {@link Type#onChange(java.lang.Object, int)}.
   468      *   If the value is {@link Integer#MIN_VALUE}, then the list is
   469      *   not fully {@link Property#mutable()} and throws {@link UnsupportedOperationException}
   470      *   on such attempts.
   471      * @param dependingProps the array of {@link ComputedProperty derived properties}
   472      *   that depend on the value of the list
   473      * @return new, empty list associated with this proto-object and its model
   474      */
   475     public <T> List<T> createList(String propName, int onChange, String... dependingProps) {
   476         return new JSONList<T>(this, propName, onChange, dependingProps);
   477     }
   478 
   479     /** Copies content of one collection to another, re-assigning all its
   480      * elements from their current context to the new <code>ctx</code>.
   481      *
   482      * @param <T> type of the collections
   483      * @param to the target collection to be filled with cloned values
   484      * @param ctx context for the new collection
   485      * @param from original collection with its data
   486      */
   487     public <T> void cloneList(Collection<T> to, BrwsrCtx ctx, Collection<T> from) {
   488         Boolean isModel = null;
   489         for (T t : from) {
   490             if (isModel == null) {
   491                 isModel = JSON.isModel(t.getClass());
   492             }
   493             if (isModel) {
   494                 to.add(JSON.bindTo(t, ctx));
   495             } else {
   496                 to.add(t);
   497             }
   498         }
   499     }
   500 
   501     //
   502     // internal state
   503     //
   504 
   505     final String toStr() {
   506         return "Proto[" + obj + "]@" + Integer.toHexString(System.identityHashCode(this));
   507     }
   508 
   509     final Bindings initBindings(Object originalObject) {
   510         if (ko == null) {
   511             Bindings b = Bindings.apply(context);
   512             PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length];
   513             for (int i = 0; i < pb.length; i++) {
   514                 pb[i] = b.registerProperty(
   515                     type.propertyNames[i], i, obj, type, type.propertyType[i]
   516                 );
   517             }
   518             FunctionBinding[] fb = new FunctionBinding[type.functions.length];
   519             for (int i = 0; i < fb.length; i++) {
   520                 fb[i] = FunctionBinding.registerFunction(
   521                     type.functions[i], i, obj, type
   522                 );
   523             }
   524             ko = b;
   525             b.finish(obj, originalObject, pb, fb);
   526         }
   527         return ko;
   528     }
   529 
   530     final Bindings getBindings() {
   531         return ko;
   532     }
   533 
   534     final void onChange(int index) {
   535         type.onChange(obj, index);
   536     }
   537 
   538     final Observers observers(boolean create) {
   539         if (create && observers == null) {
   540             observers = new Observers();
   541         }
   542         return observers;
   543     }
   544 
   545     /** Functionality used by the code generated by annotation
   546      * processor for the {@link net.java.html.json.Model} annotation.
   547      *
   548      * @param <Model> the generated class
   549      * @since 0.7
   550      */
   551     public static abstract class Type<Model> {
   552         private final Class<Model> clazz;
   553         private final String[] propertyNames;
   554         private final byte[] propertyType;
   555         private final String[] functions;
   556 
   557         /** Constructor for subclasses generated by the annotation processor
   558          * associated with {@link net.java.html.json.Model} annotation.
   559          *
   560          * @param clazz the generated model class
   561          * @param modelFor the original class annotated by the {@link net.java.html.json.Model} annotation.
   562          * @param properties number of properties the class has
   563          * @param functions  number of functions the class has
   564          */
   565         protected Type(
   566             Class<Model> clazz, Class<?> modelFor, int properties, int functions
   567         ) {
   568             assert getClass().getName().endsWith("$Html4JavaType");
   569             try {
   570                 assert getClass().getDeclaringClass() == clazz;
   571             } catch (SecurityException ex) {
   572                 // OK, no check
   573             }
   574             this.clazz = clazz;
   575             this.propertyNames = new String[properties];
   576             this.propertyType = new byte[properties];
   577             this.functions = new String[functions];
   578             JSON.register(clazz, this);
   579         }
   580 
   581         /** Registers property for the type. It is expected each index
   582          * is initialized only once.
   583          *
   584          * @param name name of the property
   585          * @param index index of the property
   586          * @param readOnly is the property read only?
   587          */
   588         protected final void registerProperty(String name, int index, boolean readOnly) {
   589             assert propertyNames[index] == null;
   590             propertyNames[index] = name;
   591             propertyType[index] = (byte) (readOnly ? 1 : 0);
   592         }
   593 
   594         /** Registers property for the type. It is expected each index
   595          * is initialized only once. The difference between <code>readOnly</code>
   596          * and <code>constant</code> is: The <code>constant</code> value is
   597          * assigned only at the beginning and never changed then - like the
   598          * {@link Property#mutable() non-mutable} property. On the other
   599          * hand, a <code>readOnly</code> property can change its value,
   600          * but not via a setter - just like {@link ComputedProperty}.
   601          *
   602          * @param name name of the property
   603          * @param index index of the property
   604          * @param readOnly is the property read only?
   605          * @param constant is the property assigned once and never changed again?
   606          * @since 1.3
   607          */
   608         protected final void registerProperty(
   609             String name, int index, boolean readOnly, boolean constant
   610         ) {
   611             assert propertyNames[index] == null;
   612             propertyNames[index] = name;
   613             propertyType[index] = (byte) ((readOnly ? 1 : 0) | (constant ? 2 : 0));
   614         }
   615 
   616         /** Registers function of given name at given index.
   617          *
   618          * @param name name of the function
   619          * @param index name of the type
   620          */
   621         protected final void registerFunction(String name, int index) {
   622             assert functions[index] == null;
   623             functions[index] = name;
   624         }
   625 
   626         /** Creates new proto-object for given {@link Model} class bound to
   627          * provided context.
   628          *
   629          * @param obj instance of appropriate {@link Model} class
   630          * @param context the browser context
   631          * @return new proto-object that the generated class can use for
   632          *   communication with the infrastructure
   633          */
   634         public Proto createProto(Object obj, BrwsrCtx context) {
   635             return new Proto(obj, this, context);
   636         }
   637 
   638         //
   639         // Implemented by subclasses
   640         //
   641 
   642         /** Sets value of a {@link #registerProperty(java.lang.String, int, boolean) registered property}
   643          * to new value.
   644          *
   645          * @param model the instance of {@link Model model class}
   646          * @param index index of the property used during registration
   647          * @param value the value to set the property to
   648          */
   649         protected abstract void setValue(Model model, int index, Object value);
   650 
   651         /** Obtains and returns value of a
   652          * {@link #registerProperty(java.lang.String, int, boolean) registered property}.
   653          *
   654          * @param model the instance of {@link Model model class}
   655          * @param index index of the property used during registration
   656          * @return current value of the property
   657          */
   658         protected abstract Object getValue(Model model, int index);
   659 
   660         /** Invokes a {@link #registerFunction(java.lang.String, int)} registered function
   661          * on given object.
   662          *
   663          * @param model the instance of {@link Model model class}
   664          * @param index index of the property used during registration
   665          * @param data the currently selected object the function is about to operate on
   666          * @param event the event that triggered the event
   667          * @throws Exception the method can throw exception which is then logged
   668          */
   669         protected abstract void call(Model model, int index, Object data, Object event)
   670         throws Exception;
   671 
   672         /** Re-binds the model object to new browser context.
   673          *
   674          * @param model the instance of {@link Model model class}
   675          * @param ctx browser context to clone the object to
   676          * @return new instance of the model suitable for new context
   677          */
   678         protected abstract Model cloneTo(Model model, BrwsrCtx ctx);
   679 
   680         /** Reads raw JSON data and converts them to our model class.
   681          *
   682          * @param c the browser context to work in
   683          * @param json raw JSON data to get values from
   684          * @return new instance of model class filled by the data
   685          */
   686         protected abstract Model read(BrwsrCtx c, Object json);
   687 
   688         /** Called when a {@link #registerProperty(java.lang.String, int, boolean) registered property}
   689          * changes its value.
   690          *
   691          * @param model the object that has the property
   692          * @param index the index of the property during registration
   693          */
   694         protected abstract void onChange(Model model, int index);
   695 
   696         /** Finds out if there is an associated proto-object for given
   697          * object.
   698          *
   699          * @param object an object, presumably (but not necessarily) instance of Model class
   700          * @return associated proto-object or <code>null</code>
   701          */
   702         protected abstract Proto protoFor(Object object);
   703 
   704         /** Called to report results of asynchronous over-the-wire
   705          * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
   706          * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}.
   707          *
   708          * @param model the instance of the model class
   709          * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
   710          * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
   711          *   not all messages are applicable to all communication protocols (JSON has only 1 and 2).
   712          * @param data <code>null</code> or string, number or a {@link Model} class
   713          *   obtained to the server as a response
   714          */
   715         protected void onMessage(Model model, int index, int type, Object data) {
   716             onMessage(model, index, type, data, new Object[0]);
   717         }
   718 
   719         /** Called to report results of asynchronous over-the-wire
   720          * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
   721          * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}.
   722          *
   723          * @param model the instance of the model class
   724          * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
   725          * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
   726          *   not all messages are applicable to all communication protocols (JSON has only 1 and 2).
   727          * @param data <code>null</code> or string, number or a {@link Model} class
   728          *   obtained to the server as a response
   729          * @param params extra parameters as passed for example to
   730          *   {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}
   731          *   method
   732          * @since 0.8.1
   733          */
   734         protected void onMessage(Model model, int index, int type, Object data, Object[] params) {
   735             onMessage(model, index, type, data);
   736         }
   737 
   738         //
   739         // Various support methods the generated classes use
   740         //
   741 
   742         /** Converts and array of raw JSON objects into an array of typed
   743          * Java {@link Model} classes.
   744          *
   745          * @param <T> the type of the destination array
   746          * @param context browser context to use
   747          * @param src array of raw JSON objects
   748          * @param destType type of the individual array elements
   749          * @param dest array to be filled with read type instances
   750          */
   751         public <T> void copyJSON(BrwsrCtx context, Object[] src, Class<T> destType, T[] dest) {
   752             for (int i = 0; i < src.length && i < dest.length; i++) {
   753                 dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]);
   754             }
   755         }
   756 
   757         /** Compares two objects that can be converted to integers.
   758          * @param a first value
   759          * @param b second value
   760          * @return true if they are the same
   761          */
   762         public final boolean isSame(int a, int b) {
   763             return a == b;
   764         }
   765 
   766         /** Compares two objects that can be converted to (floating point)
   767          * numbers.
   768          * @param a first value
   769          * @param b second value
   770          * @return  true if they are the same
   771          */
   772         public final boolean isSame(double a, double b) {
   773             return a == b;
   774         }
   775 
   776         /** Compares two objects for being the same - e.g. either <code>==</code>
   777          * or <code>equals</code>.
   778          * @param a first value
   779          * @param b second value
   780          * @return true if they are equals
   781          */
   782         public final boolean isSame(Object a, Object b) {
   783             if (a == b) {
   784                 return true;
   785             }
   786             if (a == null || b == null) {
   787                 return false;
   788             }
   789             return a.equals(b);
   790         }
   791 
   792         /** Cumulative hash function. Adds hashcode of the object to the
   793          * previous value.
   794          * @param o the object (or <code>null</code>)
   795          * @param h the previous value of the hash
   796          * @return new hash - the old one xor the object's one
   797          */
   798         public final int hashPlus(Object o, int h) {
   799             return o == null ? h : h ^ o.hashCode();
   800         }
   801 
   802         /** Converts an object to its JSON value.
   803          *
   804          * @param obj the object to convert
   805          * @return JSON representation of the object
   806          */
   807         public final String toJSON(Object obj) {
   808             return JSON.toJSON(obj);
   809         }
   810 
   811         /** Converts the value to string.
   812          *
   813          * @param val the value
   814          * @return the converted value
   815          */
   816         public final String stringValue(Object val) {
   817             return JSON.stringValue(val);
   818         }
   819 
   820         /** Converts the value to number.
   821          *
   822          * @param val the value
   823          * @return the converted value
   824          */
   825         public final Number numberValue(Object val) {
   826             return JSON.numberValue(val);
   827         }
   828 
   829         /** Converts the value to character.
   830          *
   831          * @param val the value
   832          * @return the converted value
   833          */
   834         public final Character charValue(Object val) {
   835             return JSON.charValue(val);
   836         }
   837 
   838         /** Converts the value to boolean.
   839          *
   840          * @param val the value
   841          * @return the converted value
   842          */
   843         public final Boolean boolValue(Object val) {
   844             return JSON.boolValue(val);
   845         }
   846 
   847         /** Extracts value of specific type from given object.
   848          *
   849          * @param <T> the type of object one is interested in
   850          * @param type the type
   851          * @param val the object to convert to type
   852          * @return the converted value
   853          */
   854         public final <T> T extractValue(Class<T> type, Object val) {
   855             if (Number.class.isAssignableFrom(type)) {
   856                 val = numberValue(val);
   857             }
   858             if (Boolean.class == type) {
   859                 val = boolValue(val);
   860             }
   861             if (String.class == type) {
   862                 val = stringValue(val);
   863             }
   864             if (Character.class == type) {
   865                 val = charValue(val);
   866             }
   867             if (Integer.class == type) {
   868                 val = val instanceof Number ? ((Number) val).intValue() : 0;
   869             }
   870             if (Long.class == type) {
   871                 val = val instanceof Number ? ((Number) val).longValue() : 0;
   872             }
   873             if (Short.class == type) {
   874                 val = val instanceof Number ? ((Number) val).shortValue() : 0;
   875             }
   876             if (Byte.class == type) {
   877                 val = val instanceof Number ? ((Number) val).byteValue() : 0;
   878             }
   879             if (Double.class == type) {
   880                 val = val instanceof Number ? ((Number) val).doubleValue() : Double.NaN;
   881             }
   882             if (Float.class == type) {
   883                 val = val instanceof Number ? ((Number) val).floatValue() : Float.NaN;
   884             }
   885             if (type.isEnum() && val instanceof String) {
   886                 val = Enum.valueOf(type.asSubclass(Enum.class), (String)val);
   887             }
   888             return type.cast(val);
   889         }
   890 
   891         /** Special dealing with array &amp; {@link List} values. This method
   892          * takes the provided collection, empties it and fills it again
   893          * with values extracted from <code>value</code> (which is supposed
   894          * to be an array).
   895          *
   896          * @param <T> the type of list elements
   897          * @param arr collection to fill with elements in value
   898          * @param type the type of elements in the collection
   899          * @param value array of elements to put into the collecition. If
   900          *   value is not an array it is wrapped into array with only element
   901          * @since 1.0
   902          */
   903         public final <T> void replaceValue(Collection<? super T> arr, Class<T> type, Object value) {
   904             List<T> tmp = new ArrayList<T>();
   905             if (value instanceof Object[]) {
   906                 for (Object e : (Object[]) value) {
   907                     tmp.add(extractValue(type, e));
   908                 }
   909             } else if (value instanceof byte[]) {
   910                 for (Object e : (byte[]) value) {
   911                     tmp.add(extractValue(type, e));
   912                 }
   913             } else if (value instanceof short[]) {
   914                 for (Object e : (short[]) value) {
   915                     tmp.add(extractValue(type, e));
   916                 }
   917             } else if (value instanceof int[]) {
   918                 for (Object e : (int[]) value) {
   919                     tmp.add(extractValue(type, e));
   920                 }
   921             } else if (value instanceof char[]) {
   922                 for (Object e : (char[]) value) {
   923                     tmp.add(extractValue(type, e));
   924                 }
   925             } else if (value instanceof long[]) {
   926                 for (Object e : (long[]) value) {
   927                     tmp.add(extractValue(type, e));
   928                 }
   929             } else if (value instanceof float[]) {
   930                 for (Object e : (float[]) value) {
   931                     tmp.add(extractValue(type, e));
   932                 }
   933             } else if (value instanceof double[]) {
   934                 for (Object e : (double[]) value) {
   935                     tmp.add(extractValue(type, e));
   936                 }
   937             } else {
   938                 tmp.add(extractValue(type, value));
   939             }
   940             if (arr instanceof JSONList) {
   941                 JSONList jsList = (JSONList) arr;
   942                 jsList.fastReplace(tmp);
   943             } else {
   944                 arr.clear();
   945                 arr.addAll(tmp);
   946             }
   947         }
   948     }
   949 }