xhr4j/src/main/java/org/netbeans/html/xhr4j/LoadJSON.java
author Jaroslav Tulach <jtulach@netbeans.org>
Thu, 21 Jul 2016 11:21:58 +0200
changeset 1105 907bcc5dbb00
parent 1020 b5d5cbb44ce0
parent 1057 b547f8f663f5
permissions -rw-r--r--
Documenting the Truffle based presenter
     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.xhr4j;
    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.io.UnsupportedEncodingException;
    52 import java.net.HttpURLConnection;
    53 import java.net.URL;
    54 import java.net.URLConnection;
    55 import java.util.concurrent.Callable;
    56 import java.util.concurrent.Executor;
    57 import java.util.concurrent.Executors;
    58 import java.util.concurrent.ThreadFactory;
    59 import java.util.logging.Logger;
    60 import net.java.html.js.JavaScriptBody;
    61 import org.netbeans.html.json.spi.JSONCall;
    62 
    63 /** This is an implementation package - just
    64  * include its JAR on classpath and use official {@link Context} API
    65  * to access the functionality.
    66  *
    67  * @author Jaroslav Tulach
    68  */
    69 final class LoadJSON implements Runnable {
    70     private static final Logger LOG = Logger.getLogger(LoadJSON.class.getName());
    71     private static final Executor REQ = Executors.newCachedThreadPool(new ThreadFactory() {
    72         @Override
    73         public Thread newThread(Runnable runnable) {
    74             Thread thread = Executors.defaultThreadFactory().newThread(runnable);
    75             thread.setDaemon(true);
    76             thread.setName("xhr4j daemon");
    77             return thread;
    78         }
    79     });
    80 
    81     private final JSONCall call;
    82     private final URL base;
    83 
    84 
    85     private LoadJSON(JSONCall call) {
    86         this.call = call;
    87         this.base = null;
    88     }
    89 
    90     public static void loadJSON(JSONCall call) {
    91         assert !"WebSocket".equals(call.getMethod());
    92         REQ.execute(new LoadJSON((call)));
    93     }
    94 
    95     @Override
    96     public void run() {
    97         final String url;
    98         Throwable error = null;
    99         Object json = null;
   100 
   101         if (call.isJSONP()) {
   102             url = call.composeURL("dummy");
   103         } else {
   104             url = call.composeURL(null);
   105         }
   106         try {
   107             final URL u = new URL(base, url.replace(" ", "%20"));
   108             URLConnection conn = u.openConnection();
   109             if (call.isDoOutput()) {
   110                 conn.setDoOutput(true);
   111             }
   112             String h = call.getHeaders();
   113             if (h != null) {
   114                 int pos = 0;
   115                 while (pos < h.length()) {
   116                     int tagEnd = h.indexOf(':', pos);
   117                     if (tagEnd == -1) {
   118                         break;
   119                     }
   120                     int r = h.indexOf('\r', tagEnd);
   121                     int n = h.indexOf('\n', tagEnd);
   122                     if (r == -1) {
   123                         r = h.length();
   124                     }
   125                     if (n == -1) {
   126                         n = h.length();
   127                     }
   128                     String key = h.substring(pos, tagEnd).trim();
   129                     String val = h.substring(tagEnd + 1, Math.min(r, n)).trim();
   130                     conn.setRequestProperty(key, val);;
   131                     pos = Math.max(r, n);
   132                 }
   133             }
   134             if (call.getMethod() != null && conn instanceof HttpURLConnection) {
   135                 ((HttpURLConnection) conn).setRequestMethod(call.getMethod());
   136             }
   137             if (call.isDoOutput()) {
   138                 final OutputStream os = conn.getOutputStream();
   139                 call.writeData(os);
   140                 os.flush();
   141             }
   142             final PushbackInputStream is = new PushbackInputStream(
   143                 conn.getInputStream(), 1
   144             );
   145             boolean[] arrayOrString = { false, false };
   146             detectJSONType(call.isJSONP(), is, arrayOrString);
   147             String response = readStream(is);
   148             if (call.isJSONP()) {
   149                 response = '(' + response;
   150             }
   151             json = new Result(response, arrayOrString[0], arrayOrString[1]);
   152         } catch (IOException ex) {
   153             error = ex;
   154         } finally {
   155             if (error != null) {
   156                 call.notifyError(error);
   157             } else {
   158                 call.notifySuccess(json);
   159             }
   160         }
   161     }
   162 
   163     private static final class Result implements Callable<Object> {
   164         private final String response;
   165         private final boolean array;
   166         private final boolean plain;
   167 
   168         Result(String response, boolean array, boolean plain) {
   169             this.response = response;
   170             this.array = array;
   171             this.plain = plain;
   172         }
   173 
   174         @Override
   175         public Object call() throws Exception {
   176             if (plain) {
   177                 return response;
   178             } else {
   179                 if (array) {
   180                     Object r = parse(response);
   181                     Object[] arr = r instanceof Object[] ? (Object[])r : new Object[] { r };
   182                     for (int i = 0; i < arr.length; i++) {
   183                         arr[i] = new JSObjToStr(response, arr[i]);
   184                     }
   185                     return arr;
   186                 } else {
   187                     return new JSObjToStr(response, parse(response));
   188                 }
   189             }
   190         }
   191     }
   192     private static final class JSObjToStr {
   193         final String str;
   194         final Object obj;
   195 
   196         public JSObjToStr(Object str, Object obj) {
   197             this.str = str == null ? "" : str.toString();
   198             this.obj = obj;
   199         }
   200 
   201         @Override
   202         public String toString() {
   203             return str;
   204         }
   205     }
   206 
   207     static String readStream(InputStream is) throws IOException, UnsupportedEncodingException {
   208         Reader r = new InputStreamReader(is, "UTF-8");
   209         StringBuilder sb = new StringBuilder();
   210         char[] arr = new char[4096];
   211         for (;;) {
   212             int len = r.read(arr);
   213             if (len == -1) {
   214                 break;
   215             }
   216             sb.append(arr, 0, len);
   217         }
   218         return sb.toString();
   219     }
   220 
   221     private static void detectJSONType(boolean skipAnything, final PushbackInputStream is, boolean[] arrayOrString) throws IOException {
   222         for (;;) {
   223             int ch = is.read();
   224             if (ch == -1) {
   225                 arrayOrString[1] = true;
   226                 break;
   227             }
   228             if (Character.isWhitespace(ch)) {
   229                 continue;
   230             }
   231 
   232             if (ch == '[') {
   233                 is.unread(ch);
   234                 arrayOrString[0] = true;
   235                 break;
   236             }
   237             if (ch == '{') {
   238                 is.unread(ch);
   239                 break;
   240             }
   241             if (!skipAnything) {
   242                 is.unread(ch);
   243                 arrayOrString[1] = true;
   244                 break;
   245             }
   246         }
   247     }
   248 
   249     @JavaScriptBody(args = {"object", "property"}, body =
   250         "var ret;\n" + 
   251         "if (property === null) ret = object;\n" + 
   252         "else if (object === null) ret = null;\n" + 
   253         "else ret = object[property];\n" + 
   254         "return ret ? (typeof ko === 'undefined' ? ret : ko.utils.unwrapObservable(ret)) : null;"
   255     )
   256     private static Object getProperty(Object object, String property) {
   257         return null;
   258     }
   259 
   260     @JavaScriptBody(args = {"s"}, body = "return eval('(' + s + ')');")
   261     static Object parse(String s) {
   262         throw new IllegalStateException("No parser context for " + s);
   263     }
   264 
   265     static void extractJSON(Object js, String[] props, Object[] values) {
   266         if (js instanceof JSObjToStr) {
   267             js = ((JSObjToStr)js).obj;
   268         }
   269         for (int i = 0; i < props.length; i++) {
   270             values[i] = getProperty(js, props[i]);
   271         }
   272     }
   273 
   274 }