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