json/src/main/java/org/netbeans/html/json/impl/JSON.java
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 26 Aug 2014 18:13:30 +0200
changeset 838 bdc3d696dd4a
parent 790 30f20d9c0986
child 920 117b732d42d0
permissions -rw-r--r--
During the API review process (bug 246133) the reviewers decided that in order to include html4j to NetBeans Platform, we need to stop using org.apidesign namespace and switch to NetBeans one. Repackaging all SPI packages into org.netbeans.html.smthng.spi.
     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.netbeans.html.context.spi.Contexts;
    53 import org.netbeans.html.json.spi.FunctionBinding;
    54 import org.netbeans.html.json.spi.JSONCall;
    55 import org.netbeans.html.json.spi.PropertyBinding;
    56 import org.netbeans.html.json.spi.Proto;
    57 import org.netbeans.html.json.spi.Technology;
    58 import org.netbeans.html.json.spi.Transfer;
    59 import org.netbeans.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 }