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