json/src/main/java/org/netbeans/html/json/impl/JSON.java
author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
Fri, 07 Feb 2014 07:44:34 +0100
changeset 551 7ca2253fa86d
parent 453 5e90ec3cd61a
child 569 245637e6d8db
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.netbeans.html.json.impl;
    44 
    45 import java.io.IOException;
    46 import java.io.InputStream;
    47 import java.util.Collection;
    48 import java.util.HashMap;
    49 import java.util.Map;
    50 import net.java.html.BrwsrCtx;
    51 import org.apidesign.html.context.spi.Contexts;
    52 import org.apidesign.html.json.spi.FunctionBinding;
    53 import org.apidesign.html.json.spi.JSONCall;
    54 import org.apidesign.html.json.spi.PropertyBinding;
    55 import org.apidesign.html.json.spi.Proto;
    56 import org.apidesign.html.json.spi.Technology;
    57 import org.apidesign.html.json.spi.Transfer;
    58 import org.apidesign.html.json.spi.WSTransfer;
    59 
    60 /**
    61  *
    62  * @author Jaroslav Tulach <jtulach@netbeans.org>
    63  */
    64 public final class JSON {
    65     private JSON() {
    66     }
    67 
    68     static Technology<?> findTechnology(BrwsrCtx c) {
    69         Technology<?> t = Contexts.find(c, Technology.class);
    70         return t == null ? EmptyTech.EMPTY : t;
    71     }
    72 
    73     static Transfer findTransfer(BrwsrCtx c) {
    74         Transfer t = Contexts.find(c, Transfer.class);
    75         return t == null ? EmptyTech.EMPTY : t;
    76     }
    77 
    78     static WSTransfer<?> findWSTransfer(BrwsrCtx c) {
    79         WSTransfer<?> t = Contexts.find(c, WSTransfer.class);
    80         return t == null ? EmptyTech.EMPTY : t;
    81     }
    82     
    83     public static void runInBrowser(BrwsrCtx c, Runnable runnable) {
    84         findTechnology(c).runSafe(runnable);
    85     }
    86     
    87     public static void extract(BrwsrCtx c, Object value, String[] props, Object[] values) {
    88         Transfer t = findTransfer(c);
    89         t.extract(value, props, values);
    90     }
    91     
    92     private static Object getProperty(BrwsrCtx c, Object obj, String prop) {
    93         if (prop == null) return obj;
    94         
    95         String[] arr = { prop };
    96         Object[] val = { null };
    97         extract(c, obj, arr, val);
    98         return val[0];
    99     }
   100 
   101     public static String toJSON(Object value) {
   102         if (value == null) {
   103             return "null";
   104         }
   105         if (value instanceof Enum) {
   106             value = value.toString();
   107         }
   108         if (value instanceof String) {
   109             String s = (String)value;
   110             int len = s.length();
   111             StringBuilder sb = new StringBuilder(len + 10);
   112             sb.append('"');
   113             for (int i = 0; i < len; i++) {
   114                 char ch = s.charAt(i);
   115                 switch (ch) {
   116                     case '\"': sb.append("\\\""); break;
   117                     case '\n': sb.append("\\n"); break;
   118                     case '\r': sb.append("\\r"); break;
   119                     case '\t': sb.append("\\t"); break;
   120                     case '\\': sb.append("\\\\"); break;
   121                     default: sb.append(ch);
   122                 }
   123             }
   124             sb.append('"');
   125             return sb.toString();
   126         }
   127         return value.toString();
   128     }
   129 
   130     public static String toString(BrwsrCtx c, Object obj, String prop) {
   131         obj = getProperty(c, obj, prop);
   132         return obj instanceof String ? (String)obj : null;
   133     }
   134     public static Number toNumber(BrwsrCtx c, Object obj, String prop) {
   135         obj = getProperty(c, obj, prop);
   136         if (!(obj instanceof Number)) {
   137             obj = Double.NaN;
   138         }
   139         return (Number)obj;
   140     }
   141     public static <M> M toModel(BrwsrCtx c, Class<M> aClass, Object data, Object object) {
   142         Technology<?> t = findTechnology(c);
   143         Object o = t.toModel(aClass, data);
   144         return aClass.cast(o);
   145     }
   146     
   147     public static boolean isSame(int a, int b) {
   148         return a == b;
   149     }
   150     
   151     public static boolean isSame(double a, double b) {
   152         return a == b;
   153     }
   154     
   155     public static boolean isSame(Object a, Object b) {
   156         if (a == b) {
   157             return true;
   158         }
   159         if (a == null || b == null) {
   160             return false;
   161         }
   162         return a.equals(b);
   163     }
   164     
   165     public static int hashPlus(Object o, int h) {
   166         return o == null ? h : h ^ o.hashCode();
   167     }
   168 
   169     public static <T> T extractValue(Class<T> type, Object val) {
   170         if (Number.class.isAssignableFrom(type)) {
   171             val = numberValue(val);
   172         }
   173         if (Boolean.class == type) {
   174             val = boolValue(val);
   175         }
   176         if (String.class == type) {
   177             val = stringValue(val);
   178         }
   179         if (Character.class == type) {
   180             val = charValue(val);
   181         }
   182         if (Integer.class == type) {
   183             val = val instanceof Number ? ((Number)val).intValue() : 0;
   184         }
   185         if (Long.class == type) {
   186             val = val instanceof Number  ? ((Number)val).longValue() : 0;
   187         }
   188         if (Short.class == type) {
   189             val = val instanceof Number ? ((Number)val).shortValue() : 0;
   190         }
   191         if (Byte.class == type) {
   192             val = val instanceof Number ? ((Number)val).byteValue() : 0;
   193         }        
   194         if (Double.class == type) {
   195             val = val instanceof Number ? ((Number)val).doubleValue() : Double.NaN;
   196         }
   197         if (Float.class == type) {
   198             val = val instanceof Number ? ((Number)val).floatValue() : Float.NaN;
   199         }
   200         return type.cast(val);
   201     }
   202     
   203     static boolean isNumeric(Object val) {
   204         return ((val instanceof Integer) || (val instanceof Long) || (val instanceof Short) || (val instanceof Byte));
   205     }
   206     
   207     public static String stringValue(Object val) {
   208         if (val instanceof Boolean) {
   209             return ((Boolean)val ? "true" : "false");
   210         }
   211         if (isNumeric(val)) {
   212             return Long.toString(((Number)val).longValue());
   213         }
   214         if (val instanceof Float) {
   215             return Float.toString((Float)val);
   216         }
   217         if (val instanceof Double) {
   218             return Double.toString((Double)val);
   219         }
   220         return (String)val;
   221     }
   222     
   223     public static Number numberValue(Object val) {
   224         if (val instanceof String) {
   225             try {
   226                 return Double.valueOf((String)val);
   227             } catch (NumberFormatException ex) {
   228                 return Double.NaN;
   229             }
   230         }
   231         if (val instanceof Boolean) {
   232             return (Boolean)val ? 1 : 0;
   233         }
   234         return (Number)val;
   235     }
   236 
   237     public static Character charValue(Object val) {
   238         if (val instanceof Number) {
   239             return Character.toChars(numberValue(val).intValue())[0];
   240         }
   241         if (val instanceof Boolean) {
   242             return (Boolean)val ? (char)1 : (char)0;
   243         }
   244         if (val instanceof String) {
   245             String s = (String)val;
   246             return s.isEmpty() ? (char)0 : s.charAt(0);
   247         }
   248         return (Character)val;
   249     }
   250     
   251     public static Boolean boolValue(Object val) {
   252         if (val instanceof String) {
   253             return Boolean.parseBoolean((String)val);
   254         }
   255         if (val instanceof Number) {
   256             return numberValue(val).doubleValue() != 0.0;
   257         }
   258     
   259         return Boolean.TRUE.equals(val);
   260     }
   261     
   262     public static Object find(Object object, Bindings model) {
   263         if (object == null) {
   264             return null;
   265         }
   266         if (object instanceof JSONList) {
   267             return ((JSONList<?>) object).koData();
   268         }
   269         if (object instanceof Collection) {
   270             return JSONList.koData((Collection<?>) object, model);
   271         }
   272         Proto proto = findProto(object);
   273         if (proto == null) {
   274             return null;
   275         }
   276         final Bindings b = PropertyBindingAccessor.getBindings(proto, true);
   277         return b == null ? null : b.koData();
   278     }
   279     
   280     private static Proto findProto(Object object) {
   281         Proto.Type<?> type = JSON.findType(object.getClass());
   282         if (type == null) {
   283             return null;
   284         }
   285         final Proto proto = PropertyBindingAccessor.protoFor(type, object);
   286         return proto;
   287     }
   288 
   289     public static Object find(Object object) {
   290         return find(object, null);
   291     }
   292     
   293     public static void applyBindings(Object object) {
   294         final Proto proto = findProto(object);
   295         if (proto == null) {
   296             throw new IllegalArgumentException("Not a model: " + object.getClass());
   297         }
   298         proto.applyBindings();
   299     }
   300     
   301     public static void loadJSON(
   302         BrwsrCtx c, RcvrJSON callback,
   303         String urlBefore, String urlAfter, String method,
   304         Object data
   305     ) {
   306         JSONCall call = PropertyBindingAccessor.createCall(c, callback, urlBefore, urlAfter, method, data);
   307         Transfer t = findTransfer(c);
   308         t.loadJSON(call);
   309     }
   310     public static WS openWS(
   311         BrwsrCtx c, RcvrJSON r, String url, Object data
   312     ) {
   313         WS ws = WSImpl.create(findWSTransfer(c), r);
   314         ws.send(c, url, data);
   315         return ws;
   316     }
   317     
   318     public static abstract class WS {
   319         private WS() {
   320         }
   321         
   322         public abstract void send(BrwsrCtx ctx, String url, Object model);
   323     }
   324     
   325     private static final class WSImpl<Socket> extends WS {
   326 
   327         private final WSTransfer<Socket> trans;
   328         private final RcvrJSON rcvr;
   329         private Socket socket;
   330         private String prevURL;
   331 
   332         private WSImpl(WSTransfer<Socket> trans, RcvrJSON rcvr) {
   333             this.trans = trans;
   334             this.rcvr = rcvr;
   335         }
   336         
   337         static <Socket> WS create(WSTransfer<Socket> t, RcvrJSON r) {
   338             return new WSImpl<Socket>(t, r);
   339         }
   340 
   341         @Override
   342         public void send(BrwsrCtx ctx, String url, Object data) {
   343             Socket s = socket;
   344             if (s == null) {
   345                 if (data != null) {
   346                     throw new IllegalStateException("WebSocket is not opened yet. Call with null data, was: " + data);
   347                 }
   348                 JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, url, null, "WebSocket", null);
   349                 socket = trans.open(url, call);
   350                 prevURL = url;
   351                 return;
   352             }
   353             if (data == null) {
   354                 trans.close(s);
   355                 socket = null;
   356                 return;
   357             }
   358             if (!prevURL.equals(url)) {
   359                 throw new IllegalStateException(
   360                     "Can't call to different URL " + url + " was: " + prevURL + "!"
   361                     + " Close the socket by calling it will null data first!"
   362                 );
   363             }
   364             JSONCall call = PropertyBindingAccessor.createCall(ctx, rcvr, prevURL, null, "WebSocket", data);
   365             trans.send(s, call);
   366         }
   367         
   368     }
   369     
   370     private static final Map<Class,Proto.Type<?>> modelTypes;
   371     static {
   372         modelTypes = new HashMap<Class, Proto.Type<?>>();
   373     }
   374     public static void register(Class c, Proto.Type<?> type) {
   375         modelTypes.put(c, type);
   376     }
   377     
   378     public static boolean isModel(Class<?> clazz) {
   379         return findType(clazz) != null; 
   380     }
   381     
   382     static Proto.Type<?> findType(Class<?> clazz) {
   383         for (int i = 0; i < 2; i++) {
   384             Proto.Type<?> from = modelTypes.get(clazz);
   385             if (from == null) {
   386                 initClass(clazz);
   387             } else {
   388                 return from;
   389             }
   390         }
   391         return null;
   392     }
   393     
   394     public static <Model> Model bindTo(Model model, BrwsrCtx c) {
   395         Proto.Type<Model> from = (Proto.Type<Model>) findType(model.getClass());
   396         if (from == null) {
   397             throw new IllegalArgumentException();
   398         }
   399         return PropertyBindingAccessor.clone(from, model, c);
   400     }
   401     
   402     public static <T> T readStream(BrwsrCtx c, Class<T> modelClazz, InputStream data) 
   403     throws IOException {
   404         Transfer tr = findTransfer(c);
   405         return read(c, modelClazz, tr.toJSON((InputStream)data));
   406     }
   407     public static <T> T read(BrwsrCtx c, Class<T> modelClazz, Object data) {
   408         if (data == null) {
   409             return null;
   410         }
   411         if (modelClazz == String.class) {
   412             return modelClazz.cast(data.toString());
   413         }
   414         for (int i = 0; i < 2; i++) {
   415             Proto.Type<?> from = modelTypes.get(modelClazz);
   416             if (from == null) {
   417                 initClass(modelClazz);
   418             } else {
   419                 return modelClazz.cast(PropertyBindingAccessor.readFrom(from, c, data));
   420             }
   421         }
   422         throw new NullPointerException();
   423     }
   424     static void initClass(Class<?> modelClazz) {
   425         try {
   426             // try to resolve the class
   427             ClassLoader l;
   428             try {
   429                 l = modelClazz.getClassLoader();
   430             } catch (SecurityException ex) {
   431                 l = null;
   432             }
   433             if (l != null) {
   434                 Class.forName(modelClazz.getName(), true, l);
   435             }
   436             modelClazz.newInstance();
   437         } catch (Exception ex) {
   438             // ignore and try again
   439         }
   440     }
   441     
   442     private static final class EmptyTech
   443     implements Technology<Object>, Transfer, WSTransfer<Void> {
   444         private static final EmptyTech EMPTY = new EmptyTech();
   445 
   446         @Override
   447         public Object wrapModel(Object model) {
   448             return model;
   449         }
   450 
   451         @Override
   452         public void valueHasMutated(Object data, String propertyName) {
   453         }
   454 
   455         @Override
   456         public void bind(PropertyBinding b, Object model, Object data) {
   457         }
   458 
   459         @Override
   460         public void expose(FunctionBinding fb, Object model, Object d) {
   461         }
   462 
   463         @Override
   464         public void applyBindings(Object data) {
   465         }
   466 
   467         @Override
   468         public Object wrapArray(Object[] arr) {
   469             return arr;
   470         }
   471 
   472         @Override
   473         public void extract(Object obj, String[] props, Object[] values) {
   474             for (int i = 0; i < values.length; i++) {
   475                 values[i] = null;
   476             }
   477         }
   478 
   479         @Override
   480         public void loadJSON(JSONCall call) {
   481             call.notifyError(new UnsupportedOperationException());
   482         }
   483 
   484         @Override
   485         public <M> M toModel(Class<M> modelClass, Object data) {
   486             return modelClass.cast(data);
   487         }
   488 
   489         @Override
   490         public Object toJSON(InputStream is) throws IOException {
   491             throw new IOException("Not supported");
   492         }
   493 
   494         @Override
   495         public synchronized void runSafe(Runnable r) {
   496             r.run();
   497         }
   498 
   499         @Override
   500         public Void open(String url, JSONCall onReply) {
   501             onReply.notifyError(new UnsupportedOperationException("WebSockets not supported!"));
   502             return null;
   503         }
   504 
   505         @Override
   506         public void send(Void socket, JSONCall data) {
   507         }
   508 
   509         @Override
   510         public void close(Void socket) {
   511         }
   512     }
   513     
   514 }