1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.salver.serialize;
  24 
  25 import java.io.IOException;
  26 import java.util.List;
  27 import java.util.Map;
  28 
  29 import org.graalvm.compiler.salver.writer.DumpWriter;
  30 
  31 public class JSONSerializer extends AbstractSerializer {
  32 
  33     public static final String MEDIA_TYPE = "application/json";
  34     public static final String FILE_EXTENSION = "json";
  35 
  36     public JSONSerializer() {
  37     }
  38 
  39     public JSONSerializer(DumpWriter writer) {
  40         super(writer);
  41     }
  42 
  43     @Override
  44     public JSONSerializer serialize(Object obj) throws IOException {
  45         if (writer != null) {
  46             writer.write(appendValue(new StringBuilder(), obj).append('\n'));
  47         }
  48         return this;
  49     }
  50 
  51     public static StringBuilder stringify(StringBuilder sb, Object obj) {
  52         return appendValue(sb, obj);
  53     }
  54 
  55     public static String stringify(Object obj) {
  56         return appendValue(new StringBuilder(), obj).toString();
  57     }
  58 
  59     public static String getMediaType() {
  60         return MEDIA_TYPE;
  61     }
  62 
  63     public static String getFileExtension() {
  64         return FILE_EXTENSION;
  65     }
  66 
  67     @SuppressWarnings("unchecked")
  68     private static StringBuilder appendValue(StringBuilder sb, Object val) {
  69         if (val instanceof Map<?, ?>) {
  70             return appendDict(sb, (Map<Object, Object>) val);
  71         }
  72         if (val instanceof List<?>) {
  73             return appendList(sb, (List<Object>) val);
  74         }
  75         if (val instanceof byte[]) {
  76             return appendByteArray(sb, (byte[]) val);
  77         }
  78         if (val instanceof Number) {
  79             return sb.append(val);
  80         }
  81         if (val instanceof Boolean) {
  82             return sb.append(val);
  83         }
  84         if (val == null) {
  85             return sb.append("null");
  86         }
  87         return appendString(sb, String.valueOf(val));
  88     }
  89 
  90     private static StringBuilder appendDict(StringBuilder sb, Map<Object, Object> dict) {
  91         sb.append('{');
  92         boolean comma = false;
  93         for (Map.Entry<Object, Object> entry : dict.entrySet()) {
  94             if (comma) {
  95                 sb.append(',');
  96             } else {
  97                 comma = true;
  98             }
  99             appendString(sb, String.valueOf(entry.getKey()));
 100             sb.append(':');
 101             appendValue(sb, entry.getValue());
 102         }
 103         return sb.append('}');
 104     }
 105 
 106     private static StringBuilder appendList(StringBuilder sb, List<Object> list) {
 107         sb.append('[');
 108         boolean comma = false;
 109         for (Object val : list) {
 110             if (comma) {
 111                 sb.append(',');
 112             } else {
 113                 comma = true;
 114             }
 115             appendValue(sb, val);
 116         }
 117         return sb.append(']');
 118     }
 119 
 120     private static StringBuilder appendString(StringBuilder sb, String str) {
 121         sb.append('"');
 122         for (int i = 0; i < str.length(); i++) {
 123             char c = str.charAt(i);
 124             switch (c) {
 125                 case '"':
 126                     sb.append("\\\"");
 127                     break;
 128                 case '\\':
 129                     sb.append("\\\\");
 130                     break;
 131                 case '\b':
 132                     sb.append("\\b");
 133                     break;
 134                 case '\f':
 135                     sb.append("\\f");
 136                     break;
 137                 case '\n':
 138                     sb.append("\\n");
 139                     break;
 140                 case '\r':
 141                     sb.append("\\r");
 142                     break;
 143                 case '\t':
 144                     sb.append("\\t");
 145                     break;
 146                 default: {
 147                     if (Character.isISOControl(c)) {
 148                         sb.append("\\u00");
 149                         sb.append(Character.forDigit((c >> 4) & 0xF, 16));
 150                         sb.append(Character.forDigit(c & 0xF, 16));
 151                     } else {
 152                         sb.append(c);
 153                     }
 154                 }
 155             }
 156         }
 157         return sb.append('"');
 158     }
 159 
 160     private static StringBuilder appendByteArray(StringBuilder sb, byte[] arr) {
 161         if (arr.length > 0) {
 162             sb.append("0x");
 163             for (byte b : arr) {
 164                 sb.append(String.format("%02x", b));
 165             }
 166             return sb;
 167         }
 168         return sb.append("null");
 169     }
 170 }