json/src/main/java/org/netbeans/html/json/spi/Proto.java
author Jaroslav Tulach <jtulach@netbeans.org>
Mon, 22 Feb 2016 06:09:33 +0100
branchNonMutable258088
changeset 1054 4c40ceb185e5
parent 1028 453e44c757ff
child 1055 c61d247f087a
permissions -rw-r--r--
#258088: Introducing @Property(mutable=false) and making sure the generated Java code yields exceptions when such properties are modified after their use by the technology
     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.propertyReadOnly[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 boolean[] propertyReadOnly;
   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.propertyReadOnly = new boolean[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             propertyReadOnly[index] = readOnly;
   592         }
   593 
   594         /** Registers function of given name at given index.
   595          *
   596          * @param name name of the function
   597          * @param index name of the type
   598          */
   599         protected final void registerFunction(String name, int index) {
   600             assert functions[index] == null;
   601             functions[index] = name;
   602         }
   603 
   604         /** Creates new proto-object for given {@link Model} class bound to
   605          * provided context.
   606          *
   607          * @param obj instance of appropriate {@link Model} class
   608          * @param context the browser context
   609          * @return new proto-object that the generated class can use for
   610          *   communication with the infrastructure
   611          */
   612         public Proto createProto(Object obj, BrwsrCtx context) {
   613             return new Proto(obj, this, context);
   614         }
   615 
   616         //
   617         // Implemented by subclasses
   618         //
   619 
   620         /** Sets value of a {@link #registerProperty(java.lang.String, int, boolean) registered property}
   621          * to new value.
   622          *
   623          * @param model the instance of {@link Model model class}
   624          * @param index index of the property used during registration
   625          * @param value the value to set the property to
   626          */
   627         protected abstract void setValue(Model model, int index, Object value);
   628 
   629         /** Obtains and returns value of a
   630          * {@link #registerProperty(java.lang.String, int, boolean) registered property}.
   631          *
   632          * @param model the instance of {@link Model model class}
   633          * @param index index of the property used during registration
   634          * @return current value of the property
   635          */
   636         protected abstract Object getValue(Model model, int index);
   637 
   638         /** Invokes a {@link #registerFunction(java.lang.String, int)} registered function
   639          * on given object.
   640          *
   641          * @param model the instance of {@link Model model class}
   642          * @param index index of the property used during registration
   643          * @param data the currently selected object the function is about to operate on
   644          * @param event the event that triggered the event
   645          * @throws Exception the method can throw exception which is then logged
   646          */
   647         protected abstract void call(Model model, int index, Object data, Object event)
   648         throws Exception;
   649 
   650         /** Re-binds the model object to new browser context.
   651          *
   652          * @param model the instance of {@link Model model class}
   653          * @param ctx browser context to clone the object to
   654          * @return new instance of the model suitable for new context
   655          */
   656         protected abstract Model cloneTo(Model model, BrwsrCtx ctx);
   657 
   658         /** Reads raw JSON data and converts them to our model class.
   659          *
   660          * @param c the browser context to work in
   661          * @param json raw JSON data to get values from
   662          * @return new instance of model class filled by the data
   663          */
   664         protected abstract Model read(BrwsrCtx c, Object json);
   665 
   666         /** Called when a {@link #registerProperty(java.lang.String, int, boolean) registered property}
   667          * changes its value.
   668          *
   669          * @param model the object that has the property
   670          * @param index the index of the property during registration
   671          */
   672         protected abstract void onChange(Model model, int index);
   673 
   674         /** Finds out if there is an associated proto-object for given
   675          * object.
   676          *
   677          * @param object an object, presumably (but not necessarily) instance of Model class
   678          * @return associated proto-object or <code>null</code>
   679          */
   680         protected abstract Proto protoFor(Object object);
   681 
   682         /** Called to report results of asynchronous over-the-wire
   683          * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
   684          * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}.
   685          *
   686          * @param model the instance of the model class
   687          * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
   688          * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
   689          *   not all messages are applicable to all communication protocols (JSON has only 1 and 2).
   690          * @param data <code>null</code> or string, number or a {@link Model} class
   691          *   obtained to the server as a response
   692          */
   693         protected void onMessage(Model model, int index, int type, Object data) {
   694             onMessage(model, index, type, data, new Object[0]);
   695         }
   696 
   697         /** Called to report results of asynchronous over-the-wire
   698          * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
   699          * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}.
   700          *
   701          * @param model the instance of the model class
   702          * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
   703          * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
   704          *   not all messages are applicable to all communication protocols (JSON has only 1 and 2).
   705          * @param data <code>null</code> or string, number or a {@link Model} class
   706          *   obtained to the server as a response
   707          * @param params extra parameters as passed for example to
   708          *   {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}
   709          *   method
   710          * @since 0.8.1
   711          */
   712         protected void onMessage(Model model, int index, int type, Object data, Object[] params) {
   713             onMessage(model, index, type, data);
   714         }
   715 
   716         //
   717         // Various support methods the generated classes use
   718         //
   719 
   720         /** Converts and array of raw JSON objects into an array of typed
   721          * Java {@link Model} classes.
   722          *
   723          * @param <T> the type of the destination array
   724          * @param context browser context to use
   725          * @param src array of raw JSON objects
   726          * @param destType type of the individual array elements
   727          * @param dest array to be filled with read type instances
   728          */
   729         public <T> void copyJSON(BrwsrCtx context, Object[] src, Class<T> destType, T[] dest) {
   730             for (int i = 0; i < src.length && i < dest.length; i++) {
   731                 dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]);
   732             }
   733         }
   734 
   735         /** Compares two objects that can be converted to integers.
   736          * @param a first value
   737          * @param b second value
   738          * @return true if they are the same
   739          */
   740         public final boolean isSame(int a, int b) {
   741             return a == b;
   742         }
   743 
   744         /** Compares two objects that can be converted to (floating point)
   745          * numbers.
   746          * @param a first value
   747          * @param b second value
   748          * @return  true if they are the same
   749          */
   750         public final boolean isSame(double a, double b) {
   751             return a == b;
   752         }
   753 
   754         /** Compares two objects for being the same - e.g. either <code>==</code>
   755          * or <code>equals</code>.
   756          * @param a first value
   757          * @param b second value
   758          * @return true if they are equals
   759          */
   760         public final boolean isSame(Object a, Object b) {
   761             if (a == b) {
   762                 return true;
   763             }
   764             if (a == null || b == null) {
   765                 return false;
   766             }
   767             return a.equals(b);
   768         }
   769 
   770         /** Cumulative hash function. Adds hashcode of the object to the
   771          * previous value.
   772          * @param o the object (or <code>null</code>)
   773          * @param h the previous value of the hash
   774          * @return new hash - the old one xor the object's one
   775          */
   776         public final int hashPlus(Object o, int h) {
   777             return o == null ? h : h ^ o.hashCode();
   778         }
   779 
   780         /** Converts an object to its JSON value.
   781          *
   782          * @param obj the object to convert
   783          * @return JSON representation of the object
   784          */
   785         public final String toJSON(Object obj) {
   786             return JSON.toJSON(obj);
   787         }
   788 
   789         /** Converts the value to string.
   790          *
   791          * @param val the value
   792          * @return the converted value
   793          */
   794         public final String stringValue(Object val) {
   795             return JSON.stringValue(val);
   796         }
   797 
   798         /** Converts the value to number.
   799          *
   800          * @param val the value
   801          * @return the converted value
   802          */
   803         public final Number numberValue(Object val) {
   804             return JSON.numberValue(val);
   805         }
   806 
   807         /** Converts the value to character.
   808          *
   809          * @param val the value
   810          * @return the converted value
   811          */
   812         public final Character charValue(Object val) {
   813             return JSON.charValue(val);
   814         }
   815 
   816         /** Converts the value to boolean.
   817          *
   818          * @param val the value
   819          * @return the converted value
   820          */
   821         public final Boolean boolValue(Object val) {
   822             return JSON.boolValue(val);
   823         }
   824 
   825         /** Extracts value of specific type from given object.
   826          *
   827          * @param <T> the type of object one is interested in
   828          * @param type the type
   829          * @param val the object to convert to type
   830          * @return the converted value
   831          */
   832         public final <T> T extractValue(Class<T> type, Object val) {
   833             if (Number.class.isAssignableFrom(type)) {
   834                 val = numberValue(val);
   835             }
   836             if (Boolean.class == type) {
   837                 val = boolValue(val);
   838             }
   839             if (String.class == type) {
   840                 val = stringValue(val);
   841             }
   842             if (Character.class == type) {
   843                 val = charValue(val);
   844             }
   845             if (Integer.class == type) {
   846                 val = val instanceof Number ? ((Number) val).intValue() : 0;
   847             }
   848             if (Long.class == type) {
   849                 val = val instanceof Number ? ((Number) val).longValue() : 0;
   850             }
   851             if (Short.class == type) {
   852                 val = val instanceof Number ? ((Number) val).shortValue() : 0;
   853             }
   854             if (Byte.class == type) {
   855                 val = val instanceof Number ? ((Number) val).byteValue() : 0;
   856             }
   857             if (Double.class == type) {
   858                 val = val instanceof Number ? ((Number) val).doubleValue() : Double.NaN;
   859             }
   860             if (Float.class == type) {
   861                 val = val instanceof Number ? ((Number) val).floatValue() : Float.NaN;
   862             }
   863             if (type.isEnum() && val instanceof String) {
   864                 val = Enum.valueOf(type.asSubclass(Enum.class), (String)val);
   865             }
   866             return type.cast(val);
   867         }
   868 
   869         /** Special dealing with array &amp; {@link List} values. This method
   870          * takes the provided collection, empties it and fills it again
   871          * with values extracted from <code>value</code> (which is supposed
   872          * to be an array).
   873          *
   874          * @param <T> the type of list elements
   875          * @param arr collection to fill with elements in value
   876          * @param type the type of elements in the collection
   877          * @param value array of elements to put into the collecition. If
   878          *   value is not an array it is wrapped into array with only element
   879          * @since 1.0
   880          */
   881         public final <T> void replaceValue(Collection<? super T> arr, Class<T> type, Object value) {
   882             List<T> tmp = new ArrayList<T>();
   883             if (value instanceof Object[]) {
   884                 for (Object e : (Object[]) value) {
   885                     tmp.add(extractValue(type, e));
   886                 }
   887             } else if (value instanceof byte[]) {
   888                 for (Object e : (byte[]) value) {
   889                     tmp.add(extractValue(type, e));
   890                 }
   891             } else if (value instanceof short[]) {
   892                 for (Object e : (short[]) value) {
   893                     tmp.add(extractValue(type, e));
   894                 }
   895             } else if (value instanceof int[]) {
   896                 for (Object e : (int[]) value) {
   897                     tmp.add(extractValue(type, e));
   898                 }
   899             } else if (value instanceof char[]) {
   900                 for (Object e : (char[]) value) {
   901                     tmp.add(extractValue(type, e));
   902                 }
   903             } else if (value instanceof long[]) {
   904                 for (Object e : (long[]) value) {
   905                     tmp.add(extractValue(type, e));
   906                 }
   907             } else if (value instanceof float[]) {
   908                 for (Object e : (float[]) value) {
   909                     tmp.add(extractValue(type, e));
   910                 }
   911             } else if (value instanceof double[]) {
   912                 for (Object e : (double[]) value) {
   913                     tmp.add(extractValue(type, e));
   914                 }
   915             } else {
   916                 tmp.add(extractValue(type, value));
   917             }
   918             if (arr instanceof JSONList) {
   919                 JSONList jsList = (JSONList) arr;
   920                 jsList.fastReplace(tmp);
   921             } else {
   922                 arr.clear();
   923                 arr.addAll(tmp);
   924             }
   925         }
   926     }
   927 }