001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.war;
034:
035: import com.flexive.shared.exceptions.FxInvalidParameterException;
036: import com.flexive.shared.exceptions.FxInvalidStateException;
037: import org.apache.commons.lang.StringUtils;
038:
039: import java.io.IOException;
040: import java.io.Writer;
041: import java.text.DateFormat;
042: import java.util.Collection;
043: import java.util.Date;
044: import java.util.Locale;
045: import java.util.Stack;
046:
047: /**
048: * A simple abstraction for JSON writers, including trivial
049: * output format checks.
050: *
051: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
052: */
053: public class JsonWriter {
054: /**
055: * Current stack of "open" parentheses ([ and {)
056: */
057: private Stack<Character> openSet = new Stack<Character>();
058: /**
059: * Tracks number of written elements per level
060: */
061: private Stack<Integer> numElements = new Stack<Integer>();
062: private final Writer out;
063: private boolean attributeValueNeeded = false;
064: private final DateFormat dateFormat;
065:
066: /**
067: * not completely JSON-compliant, but makes encoding XHTML much easier
068: */
069: private boolean singleQuotesForStrings = true;
070:
071: public JsonWriter(Writer out) {
072: this (out, Locale.getDefault());
073: }
074:
075: public JsonWriter(Writer out, Locale locale) {
076: this .out = out;
077: this .dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM,
078: locale);
079: }
080:
081: public JsonWriter startArray() throws IOException {
082: writeSeparator();
083: out.write("[");
084: pushElement('[');
085: attributeValueNeeded = false; // array may be value of an attribute
086: return this ;
087: }
088:
089: public JsonWriter closeArray() throws IOException {
090: checkClosingElement(']');
091: out.write("]");
092: popElement();
093: return this ;
094: }
095:
096: public JsonWriter startMap() throws IOException {
097: writeSeparator();
098: out.write("{");
099: pushElement('{');
100: attributeValueNeeded = false; // map may be value of an attribute
101: return this ;
102: }
103:
104: public JsonWriter closeMap() throws IOException {
105: checkClosingElement('}');
106: out.write("}");
107: popElement();
108: return this ;
109: }
110:
111: public JsonWriter startAttribute(String name) throws IOException {
112: checkInMap();
113: writeSeparator();
114: out.write("\"" + name + "\":");
115: attributeValueNeeded = true;
116: return this ;
117: }
118:
119: public JsonWriter writeAttributeValue(Object value, boolean escape)
120: throws IOException {
121: if (!attributeValueNeeded) {
122: throw new FxInvalidStateException(
123: "ex.json.writer.attribute.value")
124: .asRuntimeException();
125: }
126: writeValue(value, escape);
127: attributeValueNeeded = false;
128: return this ;
129: }
130:
131: public JsonWriter writeAttributeValue(Object value)
132: throws IOException {
133: writeAttributeValue(value, true);
134: return this ;
135: }
136:
137: public JsonWriter writeAttribute(String name, Object value)
138: throws IOException {
139: startAttribute(name);
140: writeAttributeValue(value);
141: return this ;
142: }
143:
144: public JsonWriter writeAttribute(String name, Object value,
145: boolean escape) throws IOException {
146: startAttribute(name);
147: writeAttributeValue(value, escape);
148: return this ;
149: }
150:
151: public JsonWriter writeLiteral(Object value) throws IOException {
152: writeLiteral(value, true);
153: return this ;
154: }
155:
156: public JsonWriter writeLiteral(Object value, boolean escapeValue)
157: throws IOException {
158: checkInArray();
159: writeSeparator();
160: writeValue(value, escapeValue);
161: return this ;
162: }
163:
164: private void writeValue(Object value, boolean escapeValue)
165: throws IOException {
166: if (value instanceof String && escapeValue) {
167: writeStringValue((String) value);
168: } else if (value instanceof Collection) {
169: startArray();
170: for (Object item : (Collection<?>) value) {
171: writeLiteral(item, escapeValue);
172: }
173: closeArray();
174: } else if (value instanceof Number) {
175: out.write(value.toString());
176: } else if (value instanceof Date) {
177: writeStringValue(dateFormat.format((Date) value));
178: } else {
179: out.write(String.valueOf(value));
180: }
181: }
182:
183: private void writeStringValue(String value) throws IOException {
184: out.write(singleQuotesForStrings ? "'"
185: + StringUtils.replace(value, "'", "\\'") + "'" : "\""
186: + StringUtils.replace(value, "\"", "\\\"") + "\"");
187: }
188:
189: /**
190: * Close the response, checking if the response is valid.
191: * @return this
192: */
193: public JsonWriter finishResponse() {
194: if (openSet.size() > 0) {
195: throw new FxInvalidStateException("ex.json.writer.state",
196: StringUtils.join(openSet.iterator(), ','))
197: .asRuntimeException();
198: }
199: return this ;
200: }
201:
202: /**
203: * Enable/disable the usage of single quotes (') for string values (default:true).
204: * Using single quotes makes encoding of XHTML content much easier, since
205: * XHTML attribute values have to use double quotes (").
206: *
207: * @param singleQuotesForStrings true to enable the usage of single quotes (') for string values
208: * @return this
209: */
210: public JsonWriter setSingleQuotesForStrings(
211: boolean singleQuotesForStrings) {
212: this .singleQuotesForStrings = singleQuotesForStrings;
213: return this ;
214: }
215:
216: private void writeSeparator() throws IOException {
217: if (numElements.size() > 0) {
218: if (numElements.lastElement() > 0 && !attributeValueNeeded) {
219: out.write(",");
220: }
221: numElements.set(numElements.size() - 1, numElements
222: .lastElement() + 1);
223: }
224: }
225:
226: private void pushElement(Character element) throws IOException {
227: openSet.push(element);
228: numElements.push(0);
229: }
230:
231: private void popElement() {
232: openSet.pop();
233: numElements.pop();
234: }
235:
236: private void checkClosingElement(Character element) {
237: if (element != ']' && element != '}') {
238: throw new FxInvalidParameterException("ELEMENT",
239: "ex.json.writer.openBracketExp")
240: .asRuntimeException();
241: }
242: if (attributeValueNeeded) {
243: throw new FxInvalidStateException(
244: "ex.json.writer.attribute.close")
245: .asRuntimeException();
246: }
247: if (openSet.size() == 0
248: || (element == ']' && openSet.lastElement() != '[')
249: || (element == '}' && openSet.lastElement() != '{')) {
250: throw new FxInvalidStateException(
251: "ex.json.writer.expected", element, openSet
252: .lastElement()).asRuntimeException();
253: }
254: }
255:
256: private void checkInMap() {
257: if (openSet.size() == 0 || openSet.lastElement() != '{') {
258: throw new FxInvalidStateException(
259: "ex.json.writer.attribute.map")
260: .asRuntimeException();
261: }
262: }
263:
264: private void checkInArray() {
265: if (openSet.size() == 0 || openSet.lastElement() != '[') {
266: throw new FxInvalidStateException(
267: "ex.json.writer.attribute.array")
268: .asRuntimeException();
269: }
270: }
271:
272: }
|