json/src/main/java/org/apidesign/html/json/spi/Proto.java
author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
Fri, 07 Feb 2014 07:44:34 +0100
changeset 551 7ca2253fa86d
parent 532 a9c8a1223895
child 567 83879118f17e
permissions -rw-r--r--
Updating copyright headers to mention current year
     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.apidesign.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 <jtulach@netbeans.org>
    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 boolean locked;
    72     private org.netbeans.html.json.impl.Bindings ko;
    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     /** Before doing modification of the model properties, the
    90      * generated code enters write lock by calling this method.
    91      * @throws IllegalStateException if already locked
    92      */
    93     public void acquireLock() throws IllegalStateException {
    94         if (locked) throw new IllegalStateException();
    95         locked = true;
    96     }
    97     
    98     /** Verifies the model is not locked otherwise throws an exception.
    99      * @throws IllegalStateException if the model is locked
   100      */
   101     public void verifyUnlocked() throws IllegalStateException {
   102         if (locked) throw new IllegalStateException();
   103     }
   104     
   105     /** When modifications are over, the model is switched into 
   106      * unlocked state by calling this method.
   107      */
   108     public void releaseLock() {
   109         locked = false;
   110     }
   111     
   112     /** Whenever model changes a property. It should notify the
   113      * associated technology by calling this method.
   114      * 
   115      *@param propName name of the changed property
   116      */
   117     public void valueHasMutated(String propName) {
   118         if (ko != null) {
   119             ko.valueHasMutated(propName);
   120         }
   121     }
   122     
   123     /** Initializes the associated model in the current {@link #getContext() context}.
   124      * In case of <em>knockout.js</em> technology, applies given bindings 
   125      * of the current model to the <em>body</em> element of the page.
   126      */
   127     public void applyBindings() {
   128         initBindings().applyBindings();
   129     }
   130     
   131     /** Invokes the provided runnable in the {@link #getContext() context}
   132      * of the browser. If the caller is already on the right thread, the
   133      * <code>run.run()</code> is invoked immediately and synchronously. 
   134      * Otherwise the method returns immediately and the <code>run()</code>
   135      * method is performed later
   136      * 
   137      * @param run the action to execute
   138      */
   139     public void runInBrowser(Runnable run) {
   140         JSON.runInBrowser(context, run);
   141     }
   142 
   143     /** Initializes the provided collection with a content of the <code>array</code>.
   144      * The initialization can only be done soon after the the collection 
   145      * is created, otherwise an exception is throw
   146      * 
   147      * @param to the collection to initialize (assumed to be empty)
   148      * @param array the array to add to the collection
   149      * @throws IllegalStateException if the system has already been initialized
   150      */
   151     public void initTo(Collection<?> to, Object array) {
   152         if (ko != null) {
   153             throw new IllegalStateException();
   154         }
   155         if (to instanceof JSONList) {
   156            ((JSONList)to).init(array);
   157         } else {
   158             JSONList.init(to, array);
   159         }
   160     }
   161 
   162     /** Takes an object representing JSON result and extract some of its
   163      * properties. It is assumed that the <code>props</code> and
   164      * <code>values</code> arrays have the same length.
   165      * 
   166      * @param json the JSON object (actual type depends on the associated
   167      *   {@link Technology})
   168      * @param props list of properties to extract
   169      * @param values array that will be filled with extracted values
   170      */
   171     public void extract(Object json, String[] props, Object[] values) {
   172         JSON.extract(context, json, props, values);
   173     }
   174 
   175     /** Converts raw JSON <code>data</code> into a Java {@link Model} class.
   176      * 
   177      * @param <T> type of the model class
   178      * @param modelClass the type of the class to create
   179      * @param data the raw JSON data
   180      * @return newly created instance of the model class
   181      */
   182     public <T> T read(Class<T> modelClass, Object data) {
   183         return JSON.read(context, modelClass, data);
   184     }
   185 
   186     /** Initializes asynchronous JSON connection to specified URL. The 
   187      * method returns immediately and later does callback later.
   188      * 
   189      * @param index the callback index to be used when a reply is received
   190      *   to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}.
   191      * 
   192      * @param urlBefore the part of the URL before JSON-P callback parameter
   193      * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used
   194      * @param method method to use for connection to the server
   195      * @param data string, number or a {@link Model} generated class to send to
   196      *    the server when doing a query
   197      */
   198     public void loadJSON(final int index, 
   199         String urlBefore, String urlAfter, String method,
   200         final Object data
   201     ) {
   202         class Rcvr extends RcvrJSON {
   203             @Override
   204             protected void onMessage(MsgEvnt msg) {
   205                 type.onMessage(obj, index, 1, msg.getValues());
   206             }
   207 
   208             @Override
   209             protected void onError(MsgEvnt msg) {
   210                 type.onMessage(obj, index, 2, msg.getException());
   211             }
   212         }
   213         JSON.loadJSON(context, new Rcvr(), urlBefore, urlAfter, method, data);
   214     }
   215     
   216     /** Opens new WebSocket connection to the specified URL. 
   217      * 
   218      * @param index the index to use later during callbacks to 
   219      *   {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}
   220      * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to
   221      * @param data data to send to server (usually <code>null</code>)
   222      * @return returns a non-null object representing the socket
   223      *   which can be used when calling {@link #wsSend(java.lang.Object, java.lang.String, java.lang.Object) }
   224      */
   225     public Object wsOpen(final int index, String url, Object data) {
   226         class WSrcvr extends RcvrJSON {
   227             @Override
   228             protected void onError(MsgEvnt msg) {
   229                 type.onMessage(obj, index, 2, msg.getException());
   230             }
   231             
   232             @Override
   233             protected void onMessage(MsgEvnt msg) {
   234                 type.onMessage(obj, index, 1, msg.getValues());
   235             }
   236             
   237             @Override
   238             protected void onClose(MsgEvnt msg) {
   239                 type.onMessage(obj, index, 3, null);
   240             }
   241 
   242             @Override
   243             protected void onOpen(MsgEvnt msg) {
   244                 type.onMessage(obj, index, 0, null);
   245             }
   246         }
   247         return JSON.openWS(context, new WSrcvr(), url, data);
   248     }
   249     
   250     /** Sends a message to existing socket.
   251      * 
   252      * @param webSocket the socket to send message to
   253      * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to,
   254      *    preferably the same as the one used when the socket was 
   255      *    {@link #wsOpen(int, java.lang.String, java.lang.Object) opened}
   256      * @param data the data to send or <code>null</code> if the socket is
   257      *    supposed to be closed
   258      */
   259     public void wsSend(Object webSocket, String url, Object data) {
   260         ((JSON.WS)webSocket).send(context, url, data);
   261     }
   262 
   263     /** Converts raw data (one of its properties) to string representation.
   264      * 
   265      * @param data the object
   266      * @param propName the name of object property or <code>null</code>
   267      *   if the whole object should be converted
   268      * @return the string representation of the object or its property
   269      */
   270     public String toString(Object data, String propName) {
   271         return JSON.toString(context, data, propName);
   272     }
   273     
   274     /** Converts raw data (one of its properties) to a number representation.
   275      * 
   276      * @param data the object
   277      * @param propName the name of object property or <code>null</code>
   278      *   if the whole object should be converted
   279      * @return the number representation of the object or its property
   280      */
   281     public Number toNumber(Object data, String propName) {
   282         return JSON.toNumber(context, data, propName);
   283     }
   284 
   285     /** Converts raw JSON data into a {@link Model} class representation.
   286      * 
   287      * @param <T> type of the model to create
   288      * @param type class of the model to create
   289      * @param data raw JSON data (depends on associated {@link Technology})
   290      * @return new instances of the model class filled with values from the
   291      *   <code>data</code> object
   292      */
   293     public <T> T toModel(Class<T> type, Object data) {
   294         return JSON.toModel(context, type, data, null);
   295     }
   296 
   297     /** Creates new JSON like observable list.
   298      * 
   299      * @param <T> the type of the list elements
   300      * @param propName name of a property this list is associated with
   301      * @param onChange index of the property to use when the list is modified
   302      *   during callback to {@link Type#onChange(java.lang.Object, int)}
   303      * @param dependingProps the array of {@link ComputedProperty derived properties}
   304      *   that depend on the value of the list
   305      * @return new, empty list associated with this proto-object and its model
   306      */
   307     public <T> List<T> createList(String propName, int onChange, String... dependingProps) {
   308         return new JSONList<T>(this, propName, onChange, dependingProps);
   309     }
   310 
   311     /** Copies content of one collection to another, re-assigning all its
   312      * elements from their current context to the new <code>ctx</code>.
   313      * 
   314      * @param <T> type of the collections
   315      * @param to the target collection to be filled with cloned values
   316      * @param ctx context for the new collection
   317      * @param from original collection with its data
   318      */
   319     public <T> void cloneList(Collection<T> to, BrwsrCtx ctx, Collection<T> from) {
   320         Boolean isModel = null;
   321         for (T t : from) {
   322             if (isModel == null) {
   323                 isModel = JSON.isModel(t.getClass());
   324             }
   325             if (isModel) {
   326                 to.add(JSON.bindTo(t, ctx));
   327             } else {
   328                 to.add(t);
   329             }
   330         }
   331     }
   332     
   333     //
   334     // internal state
   335     //
   336     
   337     
   338     final Bindings initBindings() {
   339         if (ko == null) {
   340             Bindings b = Bindings.apply(context, obj);
   341             PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length];
   342             for (int i = 0; i < pb.length; i++) {
   343                 pb[i] = b.registerProperty(
   344                     type.propertyNames[i], i, obj, type, type.propertyReadOnly[i]
   345                 );
   346             }
   347             FunctionBinding[] fb = new FunctionBinding[type.functions.length];
   348             for (int i = 0; i < fb.length; i++) {
   349                 fb[i] = FunctionBinding.registerFunction(
   350                     type.functions[i], i, obj, type
   351                 );
   352             }
   353             ko = b;
   354             b.finish(obj, pb, fb);
   355         }
   356         return ko;
   357     }
   358 
   359     final Bindings getBindings() {
   360         return ko;
   361     }
   362 
   363     final void onChange(int index) {
   364         type.onChange(obj, index);
   365     }
   366 
   367     /** Functionality used by the code generated by annotation
   368      * processor for the {@link net.java.html.json.Model} annotation.
   369      * 
   370      * @param <Model> the generated class
   371      * @since 0.7
   372      */
   373     public static abstract class Type<Model> {
   374         private final Class<Model> clazz;
   375         private final String[] propertyNames;
   376         private final boolean[] propertyReadOnly;
   377         private final String[] functions;
   378 
   379         /** Constructor for subclasses generated by the annotation processor
   380          * associated with {@link net.java.html.json.Model} annotation.
   381          * 
   382          * @param clazz the generated model class
   383          * @param modelFor the original class annotated by the {@link net.java.html.json.Model} annotation.
   384          * @param properties number of properties the class has
   385          * @param functions  number of functions the class has
   386          */
   387         protected Type(
   388             Class<Model> clazz, Class<?> modelFor, int properties, int functions
   389         ) {
   390             assert getClass().getName().endsWith("$Html4JavaType");
   391             try {
   392                 assert getClass().getDeclaringClass() == clazz;
   393             } catch (SecurityException ex) {
   394                 // OK, no check
   395             }
   396             this.clazz = clazz;
   397             this.propertyNames = new String[properties];
   398             this.propertyReadOnly = new boolean[properties];
   399             this.functions = new String[functions];
   400             JSON.register(clazz, this);
   401         }
   402 
   403         /** Registers property for the type. It is expected each index
   404          * is initialized only once.
   405          * 
   406          * @param name name of the property
   407          * @param index index of the property
   408          * @param readOnly is the property read only?
   409          */
   410         protected final void registerProperty(String name, int index, boolean readOnly) {
   411             assert propertyNames[index] == null;
   412             propertyNames[index] = name;
   413             propertyReadOnly[index] = readOnly;
   414         }
   415 
   416         /** Registers function of given name at given index.
   417          * 
   418          * @param name name of the function
   419          * @param index name of the type
   420          */
   421         protected final void registerFunction(String name, int index) {
   422             assert functions[index] == null;
   423             functions[index] = name;
   424         }
   425         
   426         /** Creates new proto-object for given {@link Model} class bound to
   427          * provided context.
   428          * 
   429          * @param obj instance of appropriate {@link Model} class
   430          * @param context the browser context
   431          * @return new proto-object that the generated class can use for
   432          *   communication with the infrastructure
   433          */
   434         public Proto createProto(Object obj, BrwsrCtx context) {
   435             return new Proto(obj, this, context);
   436         }
   437         
   438         //
   439         // Implemented by subclasses
   440         //
   441         
   442         /** Sets value of a {@link #registerProperty(java.lang.String, int, boolean) registered property}
   443          * to new value.
   444          * 
   445          * @param model the instance of {@link Model model class}
   446          * @param index index of the property used during registration
   447          * @param value the value to set the property to
   448          */
   449         protected abstract void setValue(Model model, int index, Object value);
   450         
   451         /** Obtains and returns value of a 
   452          * {@link #registerProperty(java.lang.String, int, boolean) registered property}.
   453          * 
   454          * @param model the instance of {@link Model model class}
   455          * @param index index of the property used during registration
   456          * @return current value of the property
   457          */
   458         protected abstract Object getValue(Model model, int index);
   459         
   460         /** Invokes a {@link #registerFunction(java.lang.String, int)} registered function
   461          * on given object.
   462          * 
   463          * @param model the instance of {@link Model model class}
   464          * @param index index of the property used during registration
   465          * @param data the currently selected object the function is about to operate on
   466          * @param event the event that triggered the event
   467          */
   468         protected abstract void call(Model model, int index, Object data, Object event);
   469         
   470         /** Re-binds the model object to new browser context.
   471          * 
   472          * @param model the instance of {@link Model model class}
   473          * @param ctx browser context to clone the object to
   474          * @return new instance of the model suitable for new context
   475          */
   476         protected abstract Model cloneTo(Model model, BrwsrCtx ctx);
   477         
   478         /** Reads raw JSON data and converts them to our model class.
   479          * 
   480          * @param c the browser context to work in
   481          * @param json raw JSON data to get values from
   482          * @return new instance of model class filled by the data
   483          */
   484         protected abstract Model read(BrwsrCtx c, Object json);
   485         
   486         /** Called when a {@link #registerProperty(java.lang.String, int, boolean) registered property}
   487          * changes its value.
   488          * 
   489          * @param model the object that has the property
   490          * @param index the index of the property during registration
   491          */
   492         protected abstract void onChange(Model model, int index);
   493         
   494         /** Finds out if there is an associated proto-object for given
   495          * object.
   496          * 
   497          * @param object an object, presumably (but not necessarily) instance of Model class
   498          * @return associated proto-object or <code>null</code>
   499          */
   500         protected abstract Proto protoFor(Object object);
   501 
   502         /** Called to report results of asynchronous over-the-wire 
   503          * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)}
   504          * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)}.
   505          * 
   506          * @param model the instance of the model class
   507          * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls)
   508          * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose -
   509          *   not all messages are applicable to all communication protocols (JSON has only 1 and 2).
   510          * @param data <code>null</code> or string, number or a {@link Model} class
   511          *   obtained to the server as a response
   512          */
   513         protected abstract void onMessage(Model model, int index, int type, Object data);
   514 
   515         //
   516         // Various support methods the generated classes use
   517         //
   518 
   519         /** Converts and array of raw JSON objects into an array of typed
   520          * Java {@link Model} classes.
   521          * 
   522          * @param <T> the type of the destination array
   523          * @param context browser context to use
   524          * @param src array of raw JSON objects
   525          * @param destType type of the individual array elements
   526          * @param dest array to be filled with read type instances
   527          */
   528         public <T> void copyJSON(BrwsrCtx context, Object[] src, Class<T> destType, T[] dest) {
   529             for (int i = 0; i < src.length && i < dest.length; i++) {
   530                 dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]);
   531             }
   532         }
   533         
   534         /** Compares two objects that can be converted to integers.
   535          * @param a first value
   536          * @param b second value
   537          * @return true if they are the same
   538          */
   539         public final boolean isSame(int a, int b) {
   540             return a == b;
   541         }
   542 
   543         /** Compares two objects that can be converted to (floating point)
   544          * numbers.
   545          * @param a first value
   546          * @param b second value
   547          * @return  true if they are the same
   548          */
   549         public final boolean isSame(double a, double b) {
   550             return a == b;
   551         }
   552 
   553         /** Compares two objects for being the same - e.g. either <code>==</code>
   554          * or <code>equals</code>.
   555          * @param a first value
   556          * @param b second value
   557          * @return true if they are equals
   558          */ 
   559         public final boolean isSame(Object a, Object b) {
   560             if (a == b) {
   561                 return true;
   562             }
   563             if (a == null || b == null) {
   564                 return false;
   565             }
   566             return a.equals(b);
   567         }
   568 
   569         /** Cumulative hash function. Adds hashcode of the object to the
   570          * previous value.
   571          * @param o the object (or <code>null</code>)
   572          * @param h the previous value of the hash
   573          * @return new hash - the old one xor the object's one
   574          */
   575         public final int hashPlus(Object o, int h) {
   576             return o == null ? h : h ^ o.hashCode();
   577         }
   578         
   579         /** Converts an object to its JSON value.
   580          * 
   581          * @param obj the object to convert
   582          * @return JSON representation of the object
   583          */
   584         public final String toJSON(Object obj) {
   585             return JSON.toJSON(obj);
   586         }
   587         
   588         /** Converts the value to string.
   589          * 
   590          * @param val the value
   591          * @return the converted value
   592          */
   593         public final String stringValue(Object val) {
   594             return JSON.stringValue(val);
   595         }
   596 
   597         /** Converts the value to number.
   598          * 
   599          * @param val the value
   600          * @return the converted value
   601          */
   602         public final Number numberValue(Object val) {
   603             return JSON.numberValue(val);
   604         }
   605 
   606         /** Converts the value to character.
   607          * 
   608          * @param val the value
   609          * @return the converted value
   610          */
   611         public final Character charValue(Object val) {
   612             return JSON.charValue(val);
   613         }
   614 
   615         /** Converts the value to boolean.
   616          * 
   617          * @param val the value
   618          * @return the converted value
   619          */
   620         public final Boolean boolValue(Object val) {
   621             return JSON.boolValue(val);
   622         }
   623         
   624         /** Extracts value of specific type from given object.
   625          * 
   626          * @param <T> the type of object one is interested in
   627          * @param type the type
   628          * @param val the object to convert to type
   629          * @return the converted value
   630          */
   631         public final <T> T extractValue(Class<T> type, Object val) {
   632             if (Number.class.isAssignableFrom(type)) {
   633                 val = numberValue(val);
   634             }
   635             if (Boolean.class == type) {
   636                 val = boolValue(val);
   637             }
   638             if (String.class == type) {
   639                 val = stringValue(val);
   640             }
   641             if (Character.class == type) {
   642                 val = charValue(val);
   643             }
   644             if (Integer.class == type) {
   645                 val = val instanceof Number ? ((Number) val).intValue() : 0;
   646             }
   647             if (Long.class == type) {
   648                 val = val instanceof Number ? ((Number) val).longValue() : 0;
   649             }
   650             if (Short.class == type) {
   651                 val = val instanceof Number ? ((Number) val).shortValue() : 0;
   652             }
   653             if (Byte.class == type) {
   654                 val = val instanceof Number ? ((Number) val).byteValue() : 0;
   655             }
   656             if (Double.class == type) {
   657                 val = val instanceof Number ? ((Number) val).doubleValue() : Double.NaN;
   658             }
   659             if (Float.class == type) {
   660                 val = val instanceof Number ? ((Number) val).floatValue() : Float.NaN;
   661             }
   662             return type.cast(val);
   663         }
   664 
   665     }
   666 }