json/src/main/java/org/apidesign/html/json/spi/Proto.java
author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
Fri, 03 Jan 2014 08:16:39 +0100
branchosgi
changeset 386 2fc4100fcd32
parent 385 68012e8398a8
child 387 972a38bebd50
permissions -rw-r--r--
Making JSON calls work without references to non-public packages
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2013-2013 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-2013 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 org.netbeans.html.json.impl.Bindings;
    49 import org.netbeans.html.json.impl.JSON;
    50 import org.netbeans.html.json.impl.JSONList;
    51 import org.netbeans.html.json.impl.RcvrJSON;
    52 import org.netbeans.html.json.impl.RcvrJSON.MsgEvnt;
    53 
    54 /**
    55  *
    56  * @author Jaroslav Tulach <jtulach@netbeans.org>
    57  * @since 0.7
    58  */
    59 public final class Proto {
    60     private final Object obj;
    61     private final Type type;
    62     private final net.java.html.BrwsrCtx context;
    63     private boolean locked;
    64     private org.netbeans.html.json.impl.Bindings ko;
    65 
    66     Proto(Object obj, Type type, BrwsrCtx context) {
    67         this.obj = obj;
    68         this.type = type;
    69         this.context = context;
    70     }
    71 
    72     public BrwsrCtx getContext() {
    73         return context;
    74     }
    75     
    76     public void acquireLock() throws IllegalStateException {
    77         if (locked) throw new IllegalStateException();
    78         locked = true;
    79     }
    80     
    81     public void checkLock() throws IllegalStateException {
    82         if (locked) throw new IllegalStateException();
    83     }
    84     
    85     public void releaseLock() {
    86         locked = false;
    87     }
    88     
    89     public void valueHasMutated(String propName) {
    90         if (ko != null) {
    91             ko.valueHasMutated(propName);
    92         }
    93     }
    94     
    95     public void applyBindings() {
    96         initBindings().applyBindings();
    97     }
    98     
    99     public void runInBrowser(Runnable run) {
   100         JSON.runInBrowser(context, run);
   101     }
   102 
   103     public void initTo(Collection<?> to, Object array) {
   104         if (ko != null) {
   105             throw new IllegalStateException();
   106         }
   107         if (to instanceof JSONList) {
   108            ((JSONList)to).init(array);
   109         } else {
   110             JSONList.init(to, array);
   111         }
   112     }
   113     
   114     public void extract(Object json, String[] props, Object[] values) {
   115         JSON.extract(context, json, props, values);
   116     }
   117     
   118     public <T> T read(Class<T> modelClass, Object data) {
   119         return JSON.read(context, modelClass, data);
   120     }
   121     
   122     // XXX: Don't expose internal type
   123     public Bindings initBindings() {
   124         if (ko == null) {
   125             Bindings b = Bindings.apply(context, obj);
   126             PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length];
   127             for (int i = 0; i < pb.length; i++) {
   128                 pb[i] = b.registerProperty(
   129                     type.propertyNames[i], i, obj, type, type.propertyReadOnly[i]
   130                 );
   131             }
   132             FunctionBinding[] fb = new FunctionBinding[type.functions.length];
   133             for (int i = 0; i < fb.length; i++) {
   134                 fb[i] = FunctionBinding.registerFunction(
   135                     type.functions[i], i, obj, type
   136                 );
   137             }
   138             ko = b;
   139             b.finish(obj, pb, fb);
   140         }
   141         return ko;
   142     }
   143 
   144     // XXX: Don't expose internal type
   145     public Bindings getBindings() {
   146         return ko;
   147     }
   148     
   149     // XXX: Can be hidden too
   150     public void onChange(int index) {
   151         type.onChange(obj, index);
   152     }
   153 
   154     public void loadJSON(final int index, 
   155         String urlBefore, String urlAfter, String method,
   156         final Object data
   157     ) {
   158         class Rcvr extends RcvrJSON {
   159             @Override
   160             protected void onMessage(MsgEvnt msg) {
   161                 type.onMessage(obj, index, 1, msg.getValues());
   162             }
   163 
   164             @Override
   165             protected void onError(MsgEvnt msg) {
   166                 type.onMessage(obj, index, 2, msg.getException());
   167             }
   168         }
   169         JSON.loadJSON(context, new Rcvr(), urlBefore, urlAfter, method, data);
   170     }
   171     
   172     public String toString(Object data, String propName) {
   173         return JSON.toString(context, data, propName);
   174     }
   175     public Number toNumber(Object data, String propName) {
   176         return JSON.toNumber(context, data, propName);
   177     }
   178     
   179     public <T> T toModel(Class<T> type, Object data, String propName) {
   180         return JSON.toModel(context, type, data, propName);
   181     }
   182 
   183     public <T> List<T> createList(String propName, int onChange, String... dependingProps) {
   184         return new JSONList<T>(this, propName, onChange, dependingProps);
   185     }
   186     
   187     public <T> void cloneList(Collection<T> to, BrwsrCtx ctx, Collection<T> from) {
   188         Boolean isModel = null;
   189         for (T t : from) {
   190             if (isModel == null) {
   191                 isModel = JSON.isModel(t.getClass());
   192             }
   193             if (isModel) {
   194                 to.add(JSON.bindTo(t, ctx));
   195             } else {
   196                 to.add(t);
   197             }
   198         }
   199     }
   200 
   201     /** Functionality used by the code generated by annotation
   202      * processor for the {@link net.java.html.json.Model} annotation.
   203      * 
   204      * @param <Model> the generated class
   205      * @since 0.7
   206      */
   207     public static abstract class Type<Model> {
   208         private final Class<Model> clazz;
   209         private final String[] propertyNames;
   210         private final boolean[] propertyReadOnly;
   211         private final String[] functions;
   212 
   213         protected Type(
   214             Class<Model> clazz, Class<?> modelFor, int properties, int functions
   215         ) {
   216             assert getClass().getName().endsWith("$Html4JavaType");
   217             assert getClass().getDeclaringClass() == clazz;
   218             this.clazz = clazz;
   219             this.propertyNames = new String[properties];
   220             this.propertyReadOnly = new boolean[properties];
   221             this.functions = new String[functions];
   222             JSON.register(clazz, this);
   223         }
   224         
   225         protected final void registerProperty(String name, int index, boolean readOnly) {
   226             assert propertyNames[index] == null;
   227             propertyNames[index] = name;
   228             propertyReadOnly[index] = readOnly;
   229         }
   230         
   231         protected final void registerFunction(String name, int index) {
   232             assert functions[index] == null;
   233             functions[index] = name;
   234         }
   235         
   236         public Proto protoFor(Object obj, BrwsrCtx context) {
   237             return new Proto(obj, this, context);
   238         }
   239         
   240         // XXX: should be protected
   241         public abstract void setValue(Model model, int index, Object value);
   242         public abstract Object getValue(Model model, int index);
   243         public abstract void call(Model model, int index, Object data, Object event);
   244         public abstract Model cloneTo(Object model, BrwsrCtx ctx);
   245         public abstract Model read(BrwsrCtx c, Object json);
   246         public abstract void onChange(Model model, int index);
   247         public abstract Proto protoFor(Object object);
   248 
   249         /**
   250          * 
   251          * @param model
   252          * @param index
   253          * @param type 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose
   254          * @param data 
   255          */
   256         public abstract void onMessage(Model model, int index, int type, Object data);
   257 
   258         public <T> void copyJSON(BrwsrCtx context, Object[] src, Class<T> destType, T[] dest) {
   259             for (int i = 0; i < src.length && i < dest.length; i++) {
   260                 dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]);
   261             }
   262         }
   263         
   264         //
   265         // Various support methods the generated classes use
   266         //
   267         
   268         /** Compares two objects that can be converted to integers.
   269          * @return true if they are the same
   270          */
   271         public final boolean isSame(int a, int b) {
   272             return a == b;
   273         }
   274 
   275         /** Compares two objects that can be converted to (floating point)
   276          * numbers.
   277          * @return  true if they are the same
   278          */
   279         public final boolean isSame(double a, double b) {
   280             return a == b;
   281         }
   282 
   283         /** Compares two objects for being the same - e.g. either <code>==</code>
   284          * or <code>equals</code>.
   285          * @return true if they are equals
   286          */ 
   287         public final boolean isSame(Object a, Object b) {
   288             if (a == b) {
   289                 return true;
   290             }
   291             if (a == null || b == null) {
   292                 return false;
   293             }
   294             return a.equals(b);
   295         }
   296 
   297         /** Cumulative hash function. Adds hashcode of the object to the
   298          * previous value.
   299          * @param o the object (or <code>null</code>)
   300          * @param h the previous value of the hash
   301          * @return new hash - the old one xor the object's one
   302          */
   303         public final int hashPlus(Object o, int h) {
   304             return o == null ? h : h ^ o.hashCode();
   305         }
   306         
   307         /** Converts an object to its JSON value.
   308          * 
   309          * @param obj the object to convert
   310          * @return JSON representation of the object
   311          */
   312         public final String toJSON(Object obj) {
   313             return JSON.toJSON(obj);
   314         }
   315         
   316         /** Converts the value to string.
   317          * 
   318          * @param val the value
   319          * @return the converted value
   320          */
   321         public final String stringValue(Object val) {
   322             return JSON.stringValue(val);
   323         }
   324 
   325         /** Converts the value to number.
   326          * 
   327          * @param val the value
   328          * @return the converted value
   329          */
   330         public final Number numberValue(Object val) {
   331             return JSON.numberValue(val);
   332         }
   333 
   334         /** Converts the value to character.
   335          * 
   336          * @param val the value
   337          * @return the converted value
   338          */
   339         public final Character charValue(Object val) {
   340             return JSON.charValue(val);
   341         }
   342 
   343         /** Converts the value to boolean.
   344          * 
   345          * @param val the value
   346          * @return the converted value
   347          */
   348         public final Boolean boolValue(Object val) {
   349             return JSON.boolValue(val);
   350         }
   351         
   352         /** Extracts value of specific type from given object.
   353          * 
   354          * @param <T> the type of object one is interested in
   355          * @param type the type
   356          * @param val the object to convert to type
   357          * @return the converted value
   358          */
   359         public final <T> T extractValue(Class<T> type, Object val) {
   360             if (Number.class.isAssignableFrom(type)) {
   361                 val = numberValue(val);
   362             }
   363             if (Boolean.class == type) {
   364                 val = boolValue(val);
   365             }
   366             if (String.class == type) {
   367                 val = stringValue(val);
   368             }
   369             if (Character.class == type) {
   370                 val = charValue(val);
   371             }
   372             if (Integer.class == type) {
   373                 val = val instanceof Number ? ((Number) val).intValue() : 0;
   374             }
   375             if (Long.class == type) {
   376                 val = val instanceof Number ? ((Number) val).longValue() : 0;
   377             }
   378             if (Short.class == type) {
   379                 val = val instanceof Number ? ((Number) val).shortValue() : 0;
   380             }
   381             if (Byte.class == type) {
   382                 val = val instanceof Number ? ((Number) val).byteValue() : 0;
   383             }
   384             if (Double.class == type) {
   385                 val = val instanceof Number ? ((Number) val).doubleValue() : Double.NaN;
   386             }
   387             if (Float.class == type) {
   388                 val = val instanceof Number ? ((Number) val).floatValue() : Float.NaN;
   389             }
   390             return type.cast(val);
   391         }
   392 
   393     }
   394 }