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