/*
* XXL: The eXtensible and fleXible Library for data processing
*
* Copyright (C) 2000-2014 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department
* of Mathematics and Computer Science University of Marburg Germany
*
* This library is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library;
* If not, see <http://www.gnu.org/licenses/>.
*
* http://code.google.com/p/xxl/
*/
package xxl.core.io.propertyList.json;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import xxl.core.io.propertyList.Property;
import xxl.core.io.propertyList.PropertyList;
import xxl.core.io.propertyList.PropertyList.PropertyListItem;
import xxl.core.io.propertyList.PropertyListPrinter;
/**
* The JSONPrinter is a class which takes a {@link PropertyList} instance and prints it to a given
* {@link OutputStream} in a format of a JSON file. <br/>
* <br/>
* <b>Example</b> In the example a property list is creates which contains a (simple) table dump of
* a student database table. At first the table name "Students" is set. After this two tuples of
* students where added. Each has two attributes which are ID and the students name. Finally the
* property list is printed with a <code>JSONPrinter</code> <br/>
* <code><pre>
* PropertyList myTable = new PropertyList();
* myTable.add(new Property("Table", "Students"));
*
* PropertyList myTuple1 = new PropertyList();
* myTuple1.add(new Property("ID", 202031));
* myTuple1.add(new Property("Name", "Mustermann"));
* myTuple1.add(new Property("Subjects", new Object[] { "Maths", "Physics" }));
*
* PropertyList myTuple2 = new PropertyList();
* myTuple2.add(new Property("ID", 405066));
* myTuple2.add(new Property("Name", "Musterfrau"));
* myTuple2.add(new Property("Subjects", new Object[] { "English", "Geography" }));
*
*
* myTable.add(new Property("Tuple1", myTuple1));
* myTable.add(new Property("Tuple2", myTuple2));
*
* JSONPrinter printer = new JSONPrinter(myTable);
* printer.print(System.out);
* </pre></code> This produces the following output on the console: <code><pre>
* {
* "Table": "Students"
* "Tuple1": {
* "ID": 202031
* "Name": "Mustermann"
* "Subjects": [ "Maths", "Physics" ]
* }
* "Tuple2": {
* "ID": 405066
* "Name": "Musterfrau"
* "Subjects": [ "English", "Geography" ]
* }
* }
* </pre></code> Another more elegant way to store multiple instances of an object, like the tuples
* in the example above, is to add a property which contains a array of property lists. Those nested
* property lists models the object. In the previous example replace <code><pre>
* myTable.add(new Property("Tuple1", myTuple1));
* myTable.add(new Property("Tuple2", myTuple2));</pre></code> with <code><pre>
* Object[] propertyListArray = new Object[] { myTuple1, myTuple2 };
* myTable.add(new Property("Tuples", propertyListArray));
* </pre></code> The output result is <code><pre>
* {
* "Table": "Students"
* "Tuples": [ {
* "ID": 202031
* "Name": "Mustermann"
* "Subjects": [ "Maths", "Physics" ]
* }, {
* "ID": 405066
* "Name": "Musterfrau"
* "Subjects": [ "English", "Geography" ]
* } ]
* }
* </pre></code>
*
* By default the printer adds automatically white spaces, tabs and line breaks. If you want to
* disable at least one of this features use {@link #print(OutputStream, int)}.
*
* <b>Note:</b> If you want to set a value assignment to <code>null</code> just leave the value out
* and use {@link Property#Property(String)} constructor, e.g.
* <code>new Property("ThisIsNull")</code> which leads to the JSON output
* <code>"ThisIsNull": null</code>. <br/>
* <br/>
*
* <b>Remark:</b> The printer supports the following primitive data types (or wrapper) for
* <ul>
* <li>String</li>
* <li>java.sql.Date</li>
* <li>java.sql.Time</li>
* <li>java.sql.Timestamp</li>
* <li>Integer</li>
* <li>Boolean</li>
* <li>Byte</li>
* <li>Double</li>
* <li>Float</li>
* <li>Long</li>
* <li>Short</li>
* </ul>
* or an array of <code>Object</code> which also contains the data types noted above. Also an empty
* <code>Object[]</code> is possible just as nested ones.<br/>
* <br/>
* If you insert a value which does <i>not</i> match the types above, data type it will be discarded
* and printed as <code>null</code>.
*
* @author Marcus Pinnecke (pinnecke@mathematik.uni-marburg.de)
*
* @see PropertyList a collection of Properties
* @see Property a single item for a PropertyList
* @see PropertyListPrinter the base class for all PropertyListPrinters
*
*/
public class JSONPrinter extends PropertyListPrinter {
/*
* JSON specific special characters like delimiter, assignment etc. which maybe extended by a
* additionally white space if the flag mFlagNoWhitespaces is not set.
*/
public static final String ARRAY_DELIMITER_LEFT = "[";
public static final String ARRAY_DELIMITER_RIGHT = "]";
public static final String ASSIGMENT_CHARACTER = ":";
public static final String LIST_DELIMITER = ",";
/*
* Control sequences
*/
public static final String NEWLINE_CHARACTER = "\n";
/*
* Options for formatting JSON output
*/
public final static int NO_LINEBREAKS = 0x01;
public final static int NO_TABS = 0x02;
public final static int NO_WHITESPACES = 0x04;
/*
* JSON specific null value
*/
public static final String NULL_VALUE = "null";
public static final String PROPERTY_LIST_DELIMITER_LEFT = "{";
public static final String PROPERTY_LIST_DELIMITER_RIGHT = "}";
public static final String STRING_LITERAL_QUOTE = "\"";
public static final String TAB_CHARACTER = "\t";
/*
* Format flags which controls if additionally breaks, tabs and spaces are added or not to the
* format output
*/
private boolean mFlagNoLineBreaks = false;
private boolean mFlagNoTabs = false;
private boolean mFlagNoWhitespaces = false;
/**
* Constructs a new instance of <code>JSONPrinter</code> to a given instance of
* {@link PropertyList}. Use {@link #print(OutputStream)} to print the content of the property
* list to several output streams.
*
* @param propertyList The property list which should be printed in JSON format
*/
public JSONPrinter(PropertyList propertyList) {
super(propertyList);
}
/*
* To prevent invalid output some input string have to be escaped. Precisely replace backslash,
* single and double quote, new line, carriage return and tab
*/
private String escapeString(String s) {
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
.replace("\r", "\\r").replace("\t", "\\t").replace("'", "\\'");
}
/**
* Prints the given {@link PropertyList} to the output.
*
* @param output Output stream
* @throws IOException If it fails to write into output stream
*
* @see #print(OutputStream, int) for format options
*/
public final void print(OutputStream output) throws IOException {
print(output, 0);
output.flush();
}
/**
* Prints the given {@link PropertyList} to the output.
*
* By default the printer adds automatically white spaces, tabs and line breaks. This method
* expects restrictions to this behavior. These are
* <ul>
* <li>NO_LINEBREAKS</li>
* <li>NO_TABS</li>
* <li>NO_WHITESPACES</li>
* </ul>
* Use logical <code>or</code> to combine this features.<br/>
* <br/>
* <b>Example</b> This code disables any extensions that are only for the better human
* readability. <code><pre>
* import static JSONPrinter.*;
*
* printer.print(System.out, NO_LINEBREAKS | NO_TABS | NO_WHITESPACES)
* </pre></code>
*
* @param output Output stream
* @param printerOptions A combination of NO_LINEBREAKS, NO_TABS, NO_WHITESPACES or zero
* @throws IOException If it fails to write into output stream
*/
public final void print(OutputStream output, int printerOptions)
throws IOException {
mFlagNoLineBreaks = (printerOptions & NO_LINEBREAKS) == NO_LINEBREAKS;
mFlagNoTabs = (printerOptions & NO_TABS) == NO_TABS;
mFlagNoWhitespaces = (printerOptions & NO_WHITESPACES) == NO_WHITESPACES;
print(mPropertyList, "", output);
}
/*
* Prints the body of an objects, which contains of a list of names and value assigments or nested
* objects.
*/
private void print(PropertyList l, String prefix, OutputStream output)
throws IOException {
String bodyStart =
PROPERTY_LIST_DELIMITER_LEFT
+ (mFlagNoLineBreaks ? "" : NEWLINE_CHARACTER);
output.write(bodyStart.getBytes());
for (PropertyListItem pl : l.listPropertyLists()) {
PropertyList v = pl.getPropertyList();
String assigment =
prefix + (mFlagNoTabs ? "" : TAB_CHARACTER) + STRING_LITERAL_QUOTE
+ escapeString(pl.getName()) + STRING_LITERAL_QUOTE
+ ASSIGMENT_CHARACTER + (mFlagNoWhitespaces ? "" : " ");
StringBuilder value = new StringBuilder();
output.write(assigment.getBytes());
print((PropertyList) v, prefix + (mFlagNoTabs ? "" : TAB_CHARACTER),
output);
}
for (Property p : l) {
Object v = (Object) p.getValue();
String assigment =
prefix + (mFlagNoTabs ? "" : TAB_CHARACTER) + STRING_LITERAL_QUOTE
+ escapeString(p.getName()) + STRING_LITERAL_QUOTE
+ ASSIGMENT_CHARACTER + (mFlagNoWhitespaces ? "" : " ");
StringBuilder value = new StringBuilder();
output.write(assigment.getBytes());
if (v instanceof String)
value.append(STRING_LITERAL_QUOTE + escapeString((String) v)
+ STRING_LITERAL_QUOTE);
else if (v instanceof Date || v instanceof Time || v instanceof Timestamp)
value.append(STRING_LITERAL_QUOTE + v + STRING_LITERAL_QUOTE);
else if (v instanceof Integer || v instanceof Float
|| v instanceof Boolean || v instanceof Double || v instanceof Byte
|| v instanceof Long || v instanceof Short)
value.append(v);
else if (v instanceof Object[])
value.append(printArray((Object[]) v));
else if (v instanceof PropertyList) {
print((PropertyList) v, prefix + (mFlagNoTabs ? "" : TAB_CHARACTER),
output);
} else
value.append(NULL_VALUE);
if (!mFlagNoLineBreaks) value.append(NEWLINE_CHARACTER);
output.write(value.toString().getBytes());
}
String bodyEnd =
prefix + PROPERTY_LIST_DELIMITER_RIGHT
+ ((mFlagNoLineBreaks) ? "" : NEWLINE_CHARACTER);
output.write(bodyEnd.getBytes());
}
/*
* If a component is an array of objects, just print it as enumeration of strings surround with [
* and ]. If the value is not a number (0-9 and .) it will be embedded into double quotes. Nested
* arrays are also possible which leads to a recursive call.
*/
private String printArray(Object[] array) throws IOException {
StringBuilder retval =
new StringBuilder(ARRAY_DELIMITER_LEFT
+ (mFlagNoWhitespaces ? "" : " "));
for (int i = 0; i < array.length; i++) {
Object o = array[i];
if (o instanceof Object[])
retval.append(printArray((Object[]) o));
else if (o instanceof PropertyList) {
ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream();
print((PropertyList) o, (mFlagNoTabs ? "" : TAB_CHARACTER
+ TAB_CHARACTER), byteArrayOutput);
retval.append(byteArrayOutput.toString());
} else if (o instanceof String || o instanceof Date || o instanceof Time
|| o instanceof Timestamp)
retval.append(STRING_LITERAL_QUOTE + o + STRING_LITERAL_QUOTE);
else if (o instanceof Integer || o instanceof Boolean
|| o instanceof Byte || o instanceof Double || o instanceof Float
|| o instanceof Long || o instanceof Short)
retval.append(o);
else
retval.append(NULL_VALUE);
if (i < array.length - 1)
retval.append(LIST_DELIMITER + (mFlagNoWhitespaces ? "" : " "));
}
retval.append((mFlagNoWhitespaces ? "" : " ") + ARRAY_DELIMITER_RIGHT);
return retval.toString();
}
}