001: /*
002: * Copyright (C) 2006 Joe Walnes.
003: * Copyright (C) 2006, 2007, 2008 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 22. June 2006 by Mauro Talevi
011: */
012: package com.thoughtworks.xstream.io.json;
013:
014: import com.thoughtworks.xstream.core.util.FastStack;
015: import com.thoughtworks.xstream.core.util.Primitives;
016: import com.thoughtworks.xstream.core.util.QuickWriter;
017: import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
018: import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriter;
019:
020: import java.io.Writer;
021: import java.util.Collection;
022: import java.util.Map;
023:
024: /**
025: * A simple writer that outputs JSON in a pretty-printed indented stream. Arrays, Lists and Sets
026: * rely on you NOT using XStream.addImplicitCollection(..)
027: *
028: * @author Paul Hammant
029: * @author Jörg Schaible
030: * @since 1.2
031: */
032: public class JsonHierarchicalStreamWriter implements
033: ExtendedHierarchicalStreamWriter {
034:
035: private final QuickWriter writer;
036: private final FastStack elementStack = new FastStack(16);
037: private final char[] lineIndenter;
038:
039: private int depth;
040: private boolean readyForNewLine;
041: private boolean tagIsEmpty;
042: private String newLine;
043:
044: public JsonHierarchicalStreamWriter(Writer writer,
045: char[] lineIndenter, String newLine) {
046: this .writer = new QuickWriter(writer);
047: this .lineIndenter = lineIndenter;
048: this .newLine = newLine;
049: }
050:
051: public JsonHierarchicalStreamWriter(Writer writer,
052: char[] lineIndenter) {
053: this (writer, lineIndenter, "\n");
054: }
055:
056: public JsonHierarchicalStreamWriter(Writer writer,
057: String lineIndenter, String newLine) {
058: this (writer, lineIndenter.toCharArray(), newLine);
059: }
060:
061: public JsonHierarchicalStreamWriter(Writer writer,
062: String lineIndenter) {
063: this (writer, lineIndenter.toCharArray());
064: }
065:
066: public JsonHierarchicalStreamWriter(Writer writer) {
067: this (writer, new char[] { ' ', ' ' });
068: }
069:
070: /**
071: * @deprecated Use startNode(String name, Class clazz) instead.
072: */
073:
074: public void startNode(String name) {
075: startNode(name, null);
076: }
077:
078: public void startNode(String name, Class clazz) {
079: Node currNode = (Node) elementStack.peek();
080: if (currNode == null) {
081: writer.write("{");
082: }
083: if (currNode != null && currNode.fieldAlready) {
084: writer.write(",");
085: readyForNewLine = true;
086: }
087: tagIsEmpty = false;
088: finishTag();
089: if (currNode == null || currNode.clazz == null
090: || (currNode.clazz != null && !currNode.isCollection)) {
091: if (currNode != null && !currNode.fieldAlready) {
092: writer.write("{");
093: readyForNewLine = true;
094: finishTag();
095: }
096: writer.write("\"");
097: writer.write(name);
098: writer.write("\": ");
099: }
100: if (isCollection(clazz)) {
101: writer.write("[");
102: readyForNewLine = true;
103: }
104: if (currNode != null) {
105: currNode.fieldAlready = true;
106: }
107: elementStack.push(new Node(name, clazz));
108: depth++;
109: tagIsEmpty = true;
110: }
111:
112: public class Node {
113: public final String name;
114: public final Class clazz;
115: public boolean fieldAlready;
116: public boolean isCollection;
117:
118: public Node(String name, Class clazz) {
119: this .name = name;
120: this .clazz = clazz;
121: isCollection = isCollection(clazz);
122: }
123: }
124:
125: public void setValue(String text) {
126: readyForNewLine = false;
127: tagIsEmpty = false;
128: finishTag();
129: writeText(writer, text);
130: }
131:
132: public void addAttribute(String key, String value) {
133: Node currNode = (Node) elementStack.peek();
134: if (currNode == null || !currNode.isCollection) {
135: startNode('@' + key, String.class);
136: tagIsEmpty = false;
137: writeText(value, String.class);
138: endNode();
139: }
140: }
141:
142: protected void writeAttributeValue(QuickWriter writer, String text) {
143: writeText(text, null);
144: }
145:
146: protected void writeText(QuickWriter writer, String text) {
147: Node foo = (Node) elementStack.peek();
148:
149: writeText(text, foo.clazz);
150: }
151:
152: private void writeText(String text, Class clazz) {
153: if (needsQuotes(clazz)) {
154: writer.write("\"");
155: }
156: if ((clazz == Character.class || clazz == Character.TYPE)
157: && "".equals(text)) {
158: text = "\0";
159: }
160:
161: int length = text.length();
162: for (int i = 0; i < length; i++) {
163: char c = text.charAt(i);
164: switch (c) {
165: case '"':
166: this .writer.write("\\\"");
167: break;
168: case '\\':
169: this .writer.write("\\\\");
170: break;
171: default:
172: if (c > 0x1f) {
173: this .writer.write(c);
174: } else {
175: this .writer.write("\\u");
176: String hex = "000" + Integer.toHexString(c);
177: this .writer.write(hex.substring(hex.length() - 4));
178: }
179: }
180: }
181:
182: if (needsQuotes(clazz)) {
183: writer.write("\"");
184: }
185: }
186:
187: private boolean isCollection(Class clazz) {
188: return clazz != null
189: && (Collection.class.isAssignableFrom(clazz)
190: || clazz.isArray()
191: || Map.class.isAssignableFrom(clazz) || Map.Entry.class
192: .isAssignableFrom(clazz));
193: }
194:
195: private boolean needsQuotes(Class clazz) {
196: clazz = clazz != null && clazz.isPrimitive() ? clazz
197: : Primitives.unbox(clazz);
198: return clazz == null || clazz == Character.TYPE;
199: }
200:
201: public void endNode() {
202: depth--;
203: Node node = (Node) elementStack.pop();
204: if (node.clazz != null && node.isCollection) {
205: if (node.fieldAlready) {
206: readyForNewLine = true;
207: }
208: finishTag();
209: writer.write("]");
210: } else if (tagIsEmpty) {
211: readyForNewLine = false;
212: writer.write("{}");
213: finishTag();
214: } else {
215: finishTag();
216: if (node.fieldAlready) {
217: writer.write("}");
218: }
219: }
220: readyForNewLine = true;
221: if (depth == 0) {
222: writer.write("}");
223: writer.flush();
224: }
225: }
226:
227: private void finishTag() {
228: if (readyForNewLine) {
229: endOfLine();
230: }
231: readyForNewLine = false;
232: tagIsEmpty = false;
233: }
234:
235: protected void endOfLine() {
236: writer.write(newLine);
237: for (int i = 0; i < depth; i++) {
238: writer.write(lineIndenter);
239: }
240: }
241:
242: public void flush() {
243: writer.flush();
244: }
245:
246: public void close() {
247: writer.close();
248: }
249:
250: public HierarchicalStreamWriter underlyingWriter() {
251: return this;
252: }
253: }
|