| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.sling.servlets.get.impl.util; |
| |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import javax.json.Json; |
| import javax.json.JsonArray; |
| import javax.json.JsonArrayBuilder; |
| import javax.json.JsonObject; |
| import javax.json.JsonObjectBuilder; |
| import javax.json.JsonValue; |
| |
| public class JsonToText |
| { |
| /** Rendering options */ |
| static public class Options { |
| int indent; |
| private boolean indentIsPositive; |
| int initialIndent; |
| boolean arraysForChildren; |
| |
| public static final String DEFAULT_CHILDREN_KEY = "__children__"; |
| public static final String DEFAULT_CHILD_NAME_KEY = "__name__"; |
| |
| String childrenKey = DEFAULT_CHILDREN_KEY; |
| String childNameKey = DEFAULT_CHILD_NAME_KEY; |
| |
| /** Clients use JSONRenderer.options() to create objects */ |
| private Options() { |
| } |
| |
| Options(Options opt) { |
| this.indent = opt.indent; |
| this.indentIsPositive = opt.indentIsPositive; |
| this.initialIndent = opt.initialIndent; |
| this.arraysForChildren = opt.arraysForChildren; |
| } |
| |
| public Options withIndent(int n) { |
| indent = n; |
| indentIsPositive = indent > 0; |
| return this; |
| } |
| |
| public Options withInitialIndent(int n) { |
| initialIndent = n; |
| return this; |
| } |
| |
| public Options withArraysForChildren(boolean b) { |
| arraysForChildren = b; |
| return this; |
| } |
| |
| public Options withChildNameKey(String key) { |
| childNameKey = key; |
| return this; |
| } |
| |
| public Options withChildrenKey(String key) { |
| childrenKey = key; |
| return this; |
| } |
| |
| boolean hasIndent() { |
| return indentIsPositive; |
| } |
| } |
| |
| /** Return an Options object with default values */ |
| public Options options() { |
| return new Options(); |
| } |
| |
| /** Write N spaces to sb for indentation */ |
| private void indent(StringBuilder sb, int howMuch) { |
| for (int i=0; i < howMuch; i++) { |
| sb.append(' '); |
| } |
| } |
| |
| /** Quote the supplied string for JSON */ |
| private String quote(String string) { |
| if (string == null || string.length() == 0) { |
| return "\"\""; |
| } |
| |
| char b; |
| char c = 0; |
| int i; |
| int len = string.length(); |
| StringBuilder sb = new StringBuilder(len + 2); |
| String t; |
| |
| sb.append('"'); |
| for (i = 0; i < len; i += 1) { |
| b = c; |
| c = string.charAt(i); |
| switch (c) { |
| case '\\': |
| case '"': |
| sb.append('\\'); |
| sb.append(c); |
| break; |
| case '/': |
| if (b == '<') { |
| sb.append('\\'); |
| } |
| sb.append(c); |
| break; |
| case '\b': |
| sb.append("\\b"); |
| break; |
| case '\t': |
| sb.append("\\t"); |
| break; |
| case '\n': |
| sb.append("\\n"); |
| break; |
| case '\f': |
| sb.append("\\f"); |
| break; |
| case '\r': |
| sb.append("\\r"); |
| break; |
| default: |
| if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || |
| (c >= '\u2000' && c < '\u2100')) { |
| t = "000" + Integer.toHexString(c); |
| sb.append("\\u").append(t.substring(t.length() - 4)); |
| } else { |
| sb.append(c); |
| } |
| } |
| } |
| sb.append('"'); |
| return sb.toString(); |
| } |
| |
| |
| |
| /** Make a JSON String of an Object value, with rendering options |
| */ |
| private String valueToString(JsonValue value, Options opt) { |
| if (value instanceof JsonObject) { |
| return prettyPrint((JsonObject)value, opt); |
| } else if (value instanceof JsonArray) { |
| return prettyPrint((JsonArray)value, opt); |
| } |
| return value.toString(); |
| } |
| |
| /** Decide whether o must be skipped and added to a, when rendering a JSONObject */ |
| private boolean skipChildObject(JsonArrayBuilder a, Options opt, String key, Object value) { |
| if(opt.arraysForChildren && (value instanceof JsonObject)) { |
| JsonObjectBuilder builder = Json.createObjectBuilder(); |
| builder.add(opt.childNameKey, key); |
| for (Map.Entry<String, JsonValue> entry : ((JsonObject) value).entrySet()) { |
| builder.add(entry.getKey(), entry.getValue()); |
| } |
| a.add(builder); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Make a prettyprinted JSON text of this JSONObject. |
| * <p> |
| * Warning: This method assumes that the data structure is acyclical. |
| * @return a printable, displayable, transmittable |
| * representation of the object, beginning |
| * with <code>{</code> <small>(left brace)</small> and ending |
| * with <code>}</code> <small>(right brace)</small>. |
| * @throws IllegalArgumentException If the object contains an invalid number. |
| */ |
| public String prettyPrint(JsonObject jo, Options opt) { |
| int n = jo.size(); |
| if (n == 0) { |
| return "{}"; |
| } |
| final JsonArrayBuilder children = Json.createArrayBuilder(); |
| Iterator<String> keys = jo.keySet().iterator(); |
| StringBuilder sb = new StringBuilder("{"); |
| int newindent = opt.initialIndent + opt.indent; |
| String o; |
| if (n == 1) { |
| o = keys.next(); |
| final JsonValue v = jo.get(o); |
| if(!skipChildObject(children, opt, o, v)) { |
| sb.append(quote(o)); |
| sb.append(": "); |
| sb.append(valueToString(v, opt)); |
| } |
| } else { |
| while (keys.hasNext()) { |
| o = keys.next(); |
| final JsonValue v = jo.get(o); |
| if(skipChildObject(children, opt, o, v)) { |
| continue; |
| } |
| if (sb.length() > 1) { |
| sb.append(",\n"); |
| } else { |
| sb.append('\n'); |
| } |
| indent(sb, newindent); |
| sb.append(quote(o.toString())); |
| sb.append(": "); |
| sb.append(valueToString(v, |
| options().withIndent(opt.indent).withInitialIndent(newindent))); |
| } |
| if (sb.length() > 1) { |
| sb.append('\n'); |
| indent(sb, newindent); |
| } |
| } |
| |
| /** Render children if any were skipped (in "children in arrays" mode) */ |
| JsonArray childrenArray = children.build(); |
| if(childrenArray.size() > 0) { |
| if (sb.length() > 1) { |
| sb.append(",\n"); |
| } else { |
| sb.append('\n'); |
| } |
| final Options childOpt = new Options(opt); |
| childOpt.withInitialIndent(childOpt.initialIndent + newindent); |
| indent(sb, childOpt.initialIndent); |
| sb.append(quote(opt.childrenKey)).append(":"); |
| sb.append(prettyPrint(childrenArray, childOpt)); |
| } |
| |
| sb.append('}'); |
| return sb.toString(); |
| } |
| |
| /** Pretty-print a JSONArray */ |
| public String prettyPrint(JsonArray ja, Options opt) { |
| int len = ja.size(); |
| if (len == 0) { |
| return "[]"; |
| } |
| int i; |
| StringBuilder sb = new StringBuilder("["); |
| if (len == 1) { |
| sb.append(valueToString(ja.get(0), opt)); |
| } else { |
| final int newindent = opt.initialIndent + opt.indent; |
| if(opt.hasIndent()) { |
| sb.append('\n'); |
| } |
| for (i = 0; i < len; i += 1) { |
| if (i > 0) { |
| sb.append(','); |
| if(opt.hasIndent()) { |
| sb.append('\n'); |
| } |
| } |
| indent(sb, newindent); |
| sb.append(valueToString(ja.get(i), opt)); |
| } |
| if(opt.hasIndent()) { |
| sb.append('\n'); |
| } |
| indent(sb, opt.initialIndent); |
| } |
| sb.append(']'); |
| return sb.toString(); |
| } |
| |
| } |