/******************************************************************************* * Copyright (c) 2012, 2016, 2017 PDT Extension Group and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * PDT Extension Group - initial API and implementation * Kaloyan Raev - [501269] externalize strings *******************************************************************************/ package org.eclipse.php.composer.api.json; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.google.gson.Gson; public class JsonFormatter { public static String format(final Object object) { final JsonVisitor visitor = new JsonVisitor(1, '\t'); visitor.visit(object, 0); return JsonFormatter.postProcessing(visitor.toString()); } private static String postProcessing(String json) { json = json.replace("[\n{", "[{"); //$NON-NLS-1$ //$NON-NLS-2$ json = json.replace("},\n{", "}, {"); //$NON-NLS-1$ //$NON-NLS-2$ return json; } private static class JsonVisitor { private Gson gson = new Gson(); private final StringBuilder builder = new StringBuilder(); private final int indentationSize; private final char indentationChar; public JsonVisitor(final int indentationSize, final char indentationChar) { this.indentationSize = indentationSize; this.indentationChar = indentationChar; } private void visit(final List<Object> array, final int indent) { final int length = array.size(); if (length == 0) { write("[]", 0); //$NON-NLS-1$ } else { writeln("[", 0); //$NON-NLS-1$ for (int i = 0; i < length; i++) { visit(array.get(i), indent + 1); if (i < length - 1) { writeln(",", 0); //$NON-NLS-1$ } } writeln("", 0); //$NON-NLS-1$ write("]", indent); //$NON-NLS-1$ } } private void visit(final Map<String, Object> obj, int indent) { final int length = obj.size(); if (length == 0) { write("{}", 0); //$NON-NLS-1$ } else { writeln("{", 0); //$NON-NLS-1$ final Iterator<String> keys = ((Set<String>) obj.keySet()).iterator(); while (keys.hasNext()) { final String key = keys.next(); write("\"" + escape(key) + "\" : ", indent + 1); //$NON-NLS-1$ //$NON-NLS-2$ visit(obj.get(key), indent + 1); if (keys.hasNext()) { writeln(",", 0); //$NON-NLS-1$ } } writeln("", 0); //$NON-NLS-1$ write("}", indent); //$NON-NLS-1$ } } @SuppressWarnings("unchecked") private void visit(final Object object, int indent) { if (object instanceof List) { visit((List<Object>) object, indent); } else if (object instanceof Map) { visit((Map<String, Object>) object, indent); } else { if (builder.charAt(builder.length() - 1) != '\n') { indent = 0; } if (object instanceof String) { write("\"" + escape(String.valueOf(object)) + "\"", indent); //$NON-NLS-1$ //$NON-NLS-2$ } else if (object instanceof Boolean || object instanceof Number) { write(String.valueOf(object), indent); } else { write(gson.toJson(String.valueOf(object)), indent); } } } private void writeln(final String data, final int indent) { write(data, indent); builder.append('\n'); } private void write(final String data, final int indent) { for (int i = 0; i < (indent * indentationSize); i++) { builder.append(indentationChar); } builder.append(data); } @Override public String toString() { return builder.toString(); } private static String escape(String s) { if (s == null || s.isEmpty()) { return ""; //$NON-NLS-1$ } StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); switch (ch) { case '"': sb.append("\\\""); //$NON-NLS-1$ break; case '\\': sb.append("\\\\"); //$NON-NLS-1$ break; case '\b': sb.append("\\b"); //$NON-NLS-1$ break; case '\f': sb.append("\\f"); //$NON-NLS-1$ break; case '\n': sb.append("\\n"); //$NON-NLS-1$ break; case '\r': sb.append("\\r"); //$NON-NLS-1$ break; case '\t': sb.append("\\t"); //$NON-NLS-1$ break; // see // https://github.com/pulse00/Composer-Eclipse-Plugin/issues/51 /* * case '/': sb.append("\\/"); break; */ default: // Reference: http://www.unicode.org/versions/Unicode5.1.0/ if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) { String ss = Integer.toHexString(ch); sb.append("\\u"); //$NON-NLS-1$ for (int k = 0; k < 4 - ss.length(); k++) { sb.append('0'); } sb.append(ss.toUpperCase()); } else { sb.append(ch); } } } return sb.toString(); } } }