ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java
author Jaroslav Tulach <jtulach@netbeans.org>
Thu, 24 Jul 2014 16:18:37 +0200
branchJsonArray
changeset 750 166bc67759d0
parent 749 18514ccc3ed1
child 760 785fe0c29404
permissions -rw-r--r--
Handle null elements in arrays
     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.wstyrus;
    44 
    45 import java.io.IOException;
    46 import java.io.InputStream;
    47 import java.io.InputStreamReader;
    48 import java.io.OutputStream;
    49 import java.io.PushbackInputStream;
    50 import java.io.Reader;
    51 import java.net.HttpURLConnection;
    52 import java.net.URL;
    53 import java.net.URLConnection;
    54 import java.util.Iterator;
    55 import java.util.concurrent.Executor;
    56 import java.util.concurrent.Executors;
    57 import java.util.concurrent.ThreadFactory;
    58 import java.util.logging.Level;
    59 import java.util.logging.Logger;
    60 import net.java.html.js.JavaScriptBody;
    61 import org.apidesign.html.json.spi.JSONCall;
    62 import org.json.JSONArray;
    63 import org.json.JSONException;
    64 import org.json.JSONObject;
    65 import org.json.JSONTokener;
    66 
    67 /** This is an implementation package - just
    68  * include its JAR on classpath and use official {@link Context} API
    69  * to access the functionality.
    70  *
    71  * @author Jaroslav Tulach <jtulach@netbeans.org>
    72  */
    73 final class LoadJSON implements Runnable {
    74     private static final Logger LOG = Logger.getLogger(LoadJSON.class.getName());
    75     private static final Executor REQ = Executors.newCachedThreadPool(new ThreadFactory() {
    76         @Override
    77         public Thread newThread(Runnable runnable) {
    78             Thread thread = Executors.defaultThreadFactory().newThread(runnable);
    79             thread.setDaemon(true);
    80             return thread;
    81         }
    82     });
    83 
    84     private final JSONCall call;
    85     private final URL base;
    86 
    87 
    88     private LoadJSON(JSONCall call) {
    89         this.call = call;
    90         this.base = null;
    91     }
    92 
    93     public static void loadJSON(JSONCall call) {
    94         assert !"WebSocket".equals(call.getMethod());
    95         REQ.execute(new LoadJSON((call)));
    96     }
    97 
    98     @Override
    99     public void run() {
   100         final String url;
   101         Throwable error = null;
   102         Object json = null;
   103         
   104         if (call.isJSONP()) {
   105             url = call.composeURL("dummy");
   106         } else {
   107             url = call.composeURL(null);
   108         }
   109         try {
   110             final URL u = new URL(base, url.replace(" ", "%20"));
   111             URLConnection conn = u.openConnection();
   112             if (call.isDoOutput()) {
   113                 conn.setDoOutput(true);
   114             }
   115             if (call.getMethod() != null && conn instanceof HttpURLConnection) {
   116                 ((HttpURLConnection) conn).setRequestMethod(call.getMethod());
   117             }
   118             if (call.isDoOutput()) {
   119                 final OutputStream os = conn.getOutputStream();
   120                 call.writeData(os);
   121                 os.flush();
   122             }
   123             final PushbackInputStream is = new PushbackInputStream(
   124                 conn.getInputStream(), 1
   125             );
   126             boolean[] arrayOrString = { false, false };
   127             detectJSONType(call.isJSONP(), is, arrayOrString);
   128             try {
   129                 if (arrayOrString[1]) {
   130                     throw new JSONException("");
   131                 }
   132                 JSONTokener tok = createTokener(is);
   133                 Object obj;
   134                 obj = arrayOrString[0] ? new JSONArray(tok) : new JSONObject(tok);
   135                 json = convertToArray(obj);
   136             } catch (JSONException ex) {
   137                 Reader r = new InputStreamReader(is, "UTF-8");
   138                 StringBuilder sb = new StringBuilder();
   139                 for (;;) {
   140                     int ch = r.read();
   141                     if (ch == -1) {
   142                         break;
   143                     }
   144                     sb.append((char)ch);
   145                 }
   146                 json = sb.toString();
   147             }
   148         } catch (IOException ex) {
   149             error = ex;
   150         } finally {
   151             if (error != null) {
   152                 call.notifyError(error);
   153             } else {
   154                 call.notifySuccess(json);
   155             }
   156         }
   157     }
   158 
   159     private static void detectJSONType(boolean skipAnything, final PushbackInputStream is, boolean[] arrayOrString) throws IOException {
   160         for (;;) {
   161             int ch = is.read();
   162             if (ch == -1) {
   163                 arrayOrString[1] = true;
   164                 break;
   165             }
   166             if (Character.isWhitespace(ch)) {
   167                 continue;
   168             }
   169 
   170             if (ch == '[') {
   171                 is.unread(ch);
   172                 arrayOrString[0] = true;
   173                 break;
   174             }
   175             if (ch == '{') {
   176                 is.unread(ch);
   177                 break;
   178             }
   179             if (!skipAnything) {
   180                 is.unread(ch);
   181                 arrayOrString[1] = true;
   182                 break;
   183             }
   184         }
   185     }
   186 
   187     private static JSONTokener createTokener(InputStream is) throws IOException {
   188         Reader r = new InputStreamReader(is, "UTF-8");
   189         try {
   190             return new JSONTokener(r);
   191         } catch (LinkageError ex) {
   192             // phones may carry outdated version of JSONTokener
   193             StringBuilder sb = new StringBuilder();
   194             for (;;) {
   195                 int ch = r.read();
   196                 if (ch == -1) {
   197                     break;
   198                 }
   199                 sb.append((char)ch);
   200             }
   201             return new JSONTokener(sb.toString());
   202         }
   203     }
   204 
   205     static Object convertToArray(Object o) throws JSONException {
   206         if (o instanceof JSONArray) {
   207             JSONArray ja = (JSONArray)o;
   208             Object[] arr = new Object[ja.length()];
   209             for (int i = 0; i < arr.length; i++) {
   210                 arr[i] = convertToArray(ja.get(i));
   211             }
   212             return arr;
   213         } else if (o instanceof JSONObject) {
   214             JSONObject obj = (JSONObject)o;
   215             Iterator it = obj.keys();
   216             while (it.hasNext()) {
   217                 String key = (String)it.next();
   218                 obj.put(key, convertToArray(obj.get(key)));
   219             }
   220             return obj;
   221         } else if (o == JSONObject.NULL) {
   222             return null;
   223         } else {
   224             return o;
   225         }
   226     }
   227     
   228     public static void extractJSON(Object jsonObject, String[] props, Object[] values) {
   229         if (jsonObject instanceof JSONObject) {
   230             JSONObject obj = (JSONObject)jsonObject;
   231             for (int i = 0; i < props.length; i++) {
   232                 Object val = obj.opt(props[i]);
   233                 if (val == JSONObject.NULL) {
   234                     val = null;
   235                 }
   236                 values[i] = val;
   237             }
   238             return;
   239         }
   240         for (int i = 0; i < props.length; i++) {
   241             values[i] = getProperty(jsonObject, props[i]);
   242         }
   243     }
   244     
   245     @JavaScriptBody(args = {"object", "property"},
   246             body
   247             = "if (property === null) return object;\n"
   248             + "if (object === null) return null;\n"
   249             + "var p = object[property]; return p ? p : null;"
   250     )
   251     private static Object getProperty(Object object, String property) {
   252         return null;
   253     }
   254     
   255     public static Object parse(InputStream is) throws IOException {
   256         try {
   257             PushbackInputStream push = new PushbackInputStream(is, 1);
   258             boolean[] arrayOrString = { false, false };
   259             detectJSONType(false, push, arrayOrString);
   260             JSONTokener t = createTokener(push);
   261             Object obj = arrayOrString[0] ? new JSONArray(t) : new JSONObject(t);
   262             return convertToArray(obj);
   263         } catch (JSONException ex) {
   264             throw new IOException(ex);
   265         }
   266     }
   267 }