All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dell.doradus.common.JSONEmitter Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Dell, Inc.
 * 
 * Licensed 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 com.dell.doradus.common;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;

/**
 * Creates JSON documents. The no-parameter constructor creates an internal StringWriter
 * and all JSON text is written to it. After calling link #endDocument()} call,
 * {@link #toString()} can be called to get the JSON text. The constructor that accepts
 * a Writer argument pushes all JSON text to the given writer. When {@link #endDocument()}
 * is called, flush() is called on the given writer (and toString() should not be called).
 * 
* Most methods return "this" to support call chaining. Example: *
 *         JSONEmitter json = new JSONEmitter();
 *         json.startDocument()
 *             .startGroup("doc")
 *             .startArray("fields")
 *             .addObject("Title", "Zen and the Art of Motorcycle Maintenance")
 *             .addObject("Amazon-Link", "http://www.amazon.com/")
 *             .addObject("Author", "Robert Pirsig")
 *             .addObject("Publisher", "Harper Perennial Modern Classics")
 *             .endArray()
 *             .endGroup()
 *             .endDocument();
 *         String text = json.toString();
 *         System.out.println(text);
 * 
*/ final public class JSONEmitter { // Maximum nested construct depth we support: private static final int MAX_STACK_DEPTH = 32; // Members: private final Writer m_writer; private final boolean[] m_commaStack = new boolean[MAX_STACK_DEPTH]; private int m_stackInx; private final int m_indent; private final StringBuilder m_buffer = new StringBuilder(); /** * Create a JSONEmitter that writes JSON text to an internal StringWriter. After * {@link #endDocument()} is called, the final JSON text an be obtained by calling * {@link #toString()}. */ public JSONEmitter() { m_writer = new StringWriter(); m_indent = 0; } // constructor /** * Create a JSONEmitter that writes JSON text to an internal StringWriter. After * {@link #endDocument()} is called, the final JSON text an be obtained by calling * {@link #toString()}. If the given indent value is > 0, the JSON formatting is * "pretty printed" using the given indentation. * * @param indent Indentation level for pretty printing. 0 disables pretty printing. */ public JSONEmitter(int indent) { m_writer = new StringWriter(); m_indent = indent; } // constructor /** * Create a JSONEmitter that writes JSON text to the given Writer. When * {@link #endDocument()} is called, flush() is called on the writer to ensure all * characters are flushed. * * @param writer Writer to which JSON text is written. */ public JSONEmitter(Writer writer) { m_writer = writer; m_indent = 0; } // constructor /** * Start a new document. This causes the opening '{' to be emitted. * * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter startDocument() { write('{'); push(); return this; } // startDocument /** * End the current document. This should be called only when the last object has been * ended. It causes the closing '}' to be emitted. * * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter endDocument() { pop('}'); try { m_writer.flush(); } catch (IOException e) { throw new RuntimeException(e); } return this; } // endDocument /** * Start a new group object. The group emits the JSON: *
     *      "[name]":{
     * 
* * @param name Name used for this group. * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter startGroup(String name) { checkComma(); write('"'); write(encodeString(name)); write("\":{"); push(); return this; } // startGroup /** * Start an unnamed object. This emits the JSON text '{'. * * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter startObject() { checkComma(); write('{'); push(); return this; } // startObject /** * End the current group object. This emits the group's closing '}'. * * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter endGroup() { // Remove this group from the stack and create a JsonRootNode that owns the // fields. pop('}'); return this; } // endGroup /** * Start a new array object using the given name. This emits the JSON text: *
     *      "[name]":[
     * 
* * @param name Name of new array object. * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter startArray(String name) { checkComma(); write('"'); write(encodeString(name)); write("\":["); push(); return this; } // startArray /** * End the current array object, which emits the closing ']'. * * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter endArray() { pop(']'); return this; } // endArray /** * End the current unnamed object, which emits a close '}'. * * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter endObject() { pop('}'); return this; } // endObject /** * Add a one-field object consisting of the given name and string value. This * generates the JSON text: *
     *      {"name":"value"}
     * 
* * @param name Name of new field. * @param value Value of new field as a string. * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter addObject(String name, String value) { checkComma(); write("{\""); write(encodeString(name)); write("\":\""); if (value != null) { write(encodeString(value)); } write("\"}"); return this; } // addObject /** * Add a named value. This generates the JSON text: *
     *      "name":"value"
     * 
* * @param name Name of new field. * @param value Value of new field as a string. * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter addValue(String name, String value) { checkComma(); write('"'); write(encodeString(name)); write("\":\""); if (value != null) { write(encodeString(value)); } write('"'); return this; } // addValue /** * Add a String value. * * @param value String value. * @return The same JSONEmitter object, which allows call chaining. */ public JSONEmitter addValue(String value) { checkComma(); write('"'); write(encodeString(value)); write('"'); return this; } // addValue /** * Return the JSON text for the JSON structure built by this JSONEmitter. The document * must have been finished via an {@link #endDocument()} call. * * @return JSON text built by this builder. */ @Override public String toString() { assert m_writer instanceof StringWriter; return m_writer.toString(); } // toString ///// Private methods private String encodeString(String str) { m_buffer.setLength(0); char ch; int inx = 0; while (inx < str.length()) { ch = str.charAt(inx++); switch (ch) { case '"': m_buffer.append("\\\""); break; case '\\': m_buffer.append("\\\\"); break; case '\b': m_buffer.append("\\b"); break; case '\f': m_buffer.append("\\f"); break; case '\n': m_buffer.append("\\n"); break; case '\r': m_buffer.append("\\r"); break; case '\t': m_buffer.append("\\t"); break; default: // All other chars are allowed as-is, despite JSON spec. m_buffer.append(ch); } } return m_buffer.toString(); } // encodeString // Add comma if needed, otherwise set need for one. private void checkComma() { if (m_commaStack[m_stackInx - 1]) { write(','); } else { m_commaStack[m_stackInx - 1] = true; } checkNewline(); } // checkComma // If we're indenting, emit a newline and indentation for next line. private void checkNewline() { if (m_indent > 0) { write("\n"); write(' ', m_stackInx * m_indent); } } // checkNewLine // Decrement tos by 1. private void pop(char closer) { assert m_stackInx > 0; m_stackInx--; if (m_commaStack[m_stackInx]) { checkNewline(); } write(closer); } // pop // Increment tos by 1 and set top-level need-comma to false. private void push() { assert m_stackInx < MAX_STACK_DEPTH; m_commaStack[m_stackInx++] = false; } // push // Write a character and turn any IOException caught into a RuntimeException private void write(char ch) { try { m_writer.write(ch); } catch (IOException ex) { throw new RuntimeException(ex); } } // write // Write a character repeat number of times and turn any IOException caught into a // RuntimeException. private void write(char ch, int repeat) { try { for (int i = 0; i < repeat; i++) { m_writer.write(ch); } } catch (IOException ex) { throw new RuntimeException(ex); } } // write // Write a string and turn any IOException caught into a RuntimeException private void write(String str) { try { m_writer.write(str); } catch (IOException ex) { throw new RuntimeException(ex); } } // write ///// Main (for testing/demo) public static void main(String args[]) { // Here's an example of how to use JSONEmitter. This produces the following JSON: // {"doc": { // "fields": [ // {"Title": "Zen and the Art of Motorcycle Maintenance"}, // {"Amazon-Link": "http://www.amazon.com/"}, // {"Author": "Robert Pirsig"}, // {"Publisher": "Harper Perennial Modern Classics"} // ] // }} // Note: Not necessarily correct Doradus syntax! JSONEmitter json = new JSONEmitter(); json.startDocument() .startGroup("doc") .startArray("fields") .addObject("Title", "Zen and the Art of Motorcycle Maintenance") .addObject("Amazon-Link", "http://www.amazon.com/") .addObject("Author", "Robert\tPirsig") .addObject("Publisher", "Harper Perennial Modern Classics\r\n") .endArray() // fields .endGroup() // doc .endDocument(); String text = json.toString(); System.out.println(text); } // main } // class JSONEmitter




© 2015 - 2025 Weber Informatics LLC | Privacy Policy