/interpreter/tags/at2dist220411/src/edu/vub/at/trace/JSONWriter.java

http://ambienttalk.googlecode.com/ · Java · 424 lines · 267 code · 44 blank · 113 comment · 26 complexity · 6227e64283ebb0dfd2333b1e6dd1177c MD5 · raw file

  1. /**
  2. * AmbientTalk/2 Project
  3. * (c) Software Languages Lab, 2006 - 2009
  4. * Authors: Ambient Group at SOFT
  5. *
  6. * The source code in this file is based on source code from Tyler Close's
  7. * Waterken server, Copyright 2008 Waterken Inc. Waterken's code is published
  8. * under the MIT license.
  9. *
  10. * Permission is hereby granted, free of charge, to any person
  11. * obtaining a copy of this software and associated documentation
  12. * files (the "Software"), to deal in the Software without
  13. * restriction, including without limitation the rights to use,
  14. * copy, modify, merge, publish, distribute, sublicense, and/or
  15. * sell copies of the Software, and to permit persons to whom the
  16. * Software is furnished to do so, subject to the following
  17. * conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be
  20. * included in all copies or substantial portions of the Software.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  24. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  26. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  27. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  28. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  29. * OTHER DEALINGS IN THE SOFTWARE.
  30. */
  31. package edu.vub.at.trace;
  32. import java.io.IOException;
  33. import java.io.Writer;
  34. /**
  35. * A SAX-like API for generating JSON text.
  36. * <p>
  37. * A client can only output a syntactically correct JSON text, or leave the
  38. * {@link JSONWriter} in a {@linkplain #isWritten detectable} error state. The
  39. * implementation does <em>not</em> enforce the constraint that names within an
  40. * object SHOULD be unique.
  41. * </p>
  42. * <p>For example, to output the JSON text:</p>
  43. * <pre>
  44. * {
  45. * "title" : "I Can Has Cheezburger?",
  46. * "src" : { "@" : "http://www.example.com/image/481989943" },
  47. * "height" : 125,
  48. * "width" : 100,
  49. * "tags" : [ "lolcat", "food" ],
  50. * "score" : 9.5
  51. * }
  52. * </pre>
  53. * <p>, write code:</p>
  54. * <pre>
  55. * final Writer text = &hellip;
  56. * final JSONWriter top = JSONWriter.make(text);
  57. * final JSONWriter.ObjectWriter o = top.startObject();
  58. * o.startMember("title").writeString("I Can Has Cheezburger?");
  59. * o.startMember("src").writeLink("http://www.example.com/image/481989943");
  60. * o.startMember("height").writeInt(125);
  61. * o.startMember("width").writeInt(100);
  62. * final JSONWriter.ArrayWriter tags = o.startMember("tags").startArray();
  63. * tags.startElement().writeString("lolcat");
  64. * tags.startElement().writeString("food");
  65. * tags.finish();
  66. * o.startMember("score").writeDouble(9.5);
  67. * o.finish();
  68. * if (!top.isWritten()) { throw new NullPointerException(); }
  69. * text.flush();
  70. * text.close();
  71. * </pre>
  72. * <p>
  73. * An invalid sequence of calls to this API will cause a
  74. * {@link NullPointerException} to be thrown. For example, calling
  75. * writeString() twice in succession will throw a {@link NullPointerException}.
  76. * </p>
  77. */
  78. public final class JSONWriter {
  79. static private final String newLine = "\r\n";
  80. static private final String tab = " ";
  81. static private final class Prize {
  82. private Object value;
  83. protected Prize(final Object value) {
  84. this.value = value;
  85. }
  86. protected Object claim() {
  87. final Object r = value;
  88. value = null;
  89. return r;
  90. }
  91. }
  92. static private final class Milestone {
  93. private boolean marked;
  94. protected
  95. Milestone(final boolean marked) {
  96. this.marked = marked;
  97. }
  98. protected boolean
  99. is() { return marked; }
  100. protected void
  101. mark() { marked = true; }
  102. }
  103. private final boolean top; // Is this the top level JSON container?
  104. private final String indent; // indentation for this JSON value
  105. // Prize<Writer> output;
  106. private final Prize output; // claimed by 1st called output method
  107. private final Milestone written; // marked after output method is done
  108. private JSONWriter(final boolean top, final String indent, final Writer out) {
  109. this.top = top;
  110. this.indent = indent;
  111. output = new Prize(out);
  112. written = new Milestone(null == out);
  113. }
  114. /**
  115. * Constructs a JSON writer.
  116. * @param out UTF-8 output stream
  117. */
  118. static public JSONWriter make(final Writer out) { return new JSONWriter(true, "", out); }
  119. /**
  120. * @return <code>true</code> if a single JSON value was successfully
  121. * written, else <code>false</code>
  122. */
  123. public boolean isWritten() { return written.is(); }
  124. public ObjectWriter startObject() throws IOException {
  125. final Writer out = (Writer) output.claim();
  126. out.write('{');
  127. return new ObjectWriter(out);
  128. }
  129. /**
  130. * A JSON <em>object</em> writer.
  131. */
  132. public final class ObjectWriter {
  133. static private final String comma = "," + newLine;
  134. private final String inset; // indentation for each member
  135. private final Writer out;
  136. private String prefix; // current member separator prefix
  137. private JSONWriter member; // most recent member started, or
  138. // null if object is finished
  139. protected ObjectWriter(final Writer out) {
  140. inset = indent + tab;
  141. this.out = out;
  142. prefix = newLine;
  143. member = new JSONWriter(false, inset, null);
  144. }
  145. public void finish() throws IOException {
  146. if (!member.isWritten()) { throw new NullPointerException(); }
  147. member = null; // prevent future calls to this object
  148. out.write(newLine);
  149. out.write(indent);
  150. out.write('}');
  151. if (top) { out.write(newLine); }
  152. written.mark(); // mark the containing value successful
  153. }
  154. public JSONWriter startMember(final String name) throws IOException {
  155. if (!member.isWritten()) { throw new NullPointerException(); }
  156. member = new JSONWriter(false, inset, out); // prevent calls until
  157. // member is completed
  158. out.write(prefix);
  159. out.write(inset);
  160. writeStringTo(name, out);
  161. out.write(" : ");
  162. prefix = comma;
  163. return member;
  164. }
  165. /*
  166. * Every output method on a JSONWriter begins by deleting the output
  167. * stream from that writer. If the created JSON value is an object or an
  168. * array, the output stream is handed off to either an ObjectWriter or
  169. * an ArrayWriter. These JSON container writers hold onto the output
  170. * stream forever, but know whether or not they should be allowed to
  171. * write to it. Each container does this by remembering its most
  172. * recently created child value and only writing to the output stream if
  173. * that child has been written and the container itself has not been
  174. * finished. At any time, there may be multiple unfinished containers,
  175. * but only one of them could have a written child, since a JSON
  176. * structure is a tree and an unfinished container value is not marked
  177. * as written.
  178. */
  179. }
  180. public ArrayWriter startArray() throws IOException {
  181. final Writer out = (Writer) output.claim();
  182. out.write('[');
  183. return new ArrayWriter(out);
  184. }
  185. /**
  186. * A JSON <em>array</em> writer.
  187. */
  188. public final class ArrayWriter {
  189. static private final String comma = ", ";
  190. private final String inset; // indentation for each element
  191. private final Writer out;
  192. private String prefix; // current element separator prefix
  193. private JSONWriter element; // most recent element started, or
  194. // null if array is finished
  195. protected ArrayWriter(final Writer out) {
  196. inset = indent + tab;
  197. this.out = out;
  198. prefix = " ";
  199. element = new JSONWriter(false, inset, null);
  200. }
  201. public void finish() throws IOException {
  202. if (!element.isWritten()) { throw new NullPointerException(); }
  203. element = null; // prevent future calls to this object
  204. out.write(" ]");
  205. if (top) { out.write(newLine); }
  206. written.mark(); // mark the containing value successful
  207. }
  208. public JSONWriter
  209. startElement() throws IOException {
  210. if (!element.isWritten()) { throw new NullPointerException(); }
  211. element = new JSONWriter(false, inset, out); // prevent calls until
  212. // element is completed
  213. out.write(prefix);
  214. prefix = comma;
  215. return element;
  216. }
  217. }
  218. public void
  219. writeLink(final String URL) throws IOException {
  220. final Writer out = (Writer) output.claim();
  221. out.write("{ \"@\" : ");
  222. writeStringTo(URL, out);
  223. out.write(" }");
  224. if (top) { out.write(newLine); }
  225. written.mark();
  226. }
  227. public void
  228. writeNull() throws IOException {
  229. writePrimitive("null");
  230. }
  231. public void
  232. writeBoolean(final boolean value) throws IOException {
  233. writePrimitive(value ? "true" : "false");
  234. }
  235. public void
  236. writeInt(final int value) throws IOException {
  237. writePrimitive(Integer.toString(value));
  238. }
  239. /**
  240. * maximum magnitude of a Javascript number: {@value}
  241. */
  242. static public final long maxMagnitude = (1L << 53) - 1; // = 2^53 - 1
  243. public void
  244. writeLong(final long value) throws ArithmeticException, IOException {
  245. if (value > maxMagnitude) { throw new ArithmeticException(); }
  246. if (value < -maxMagnitude) { throw new ArithmeticException(); }
  247. writePrimitive(Long.toString(value));
  248. }
  249. public void
  250. writeFloat(final float value) throws ArithmeticException, IOException {
  251. if (Float.isNaN(value)) { throw new ArithmeticException(); }
  252. if (Float.isInfinite(value)) { throw new ArithmeticException(); }
  253. writePrimitive(Float.toString(value));
  254. }
  255. public void
  256. writeDouble(final double value) throws ArithmeticException, IOException {
  257. if (Double.isNaN(value)) { throw new ArithmeticException(); }
  258. if (Double.isInfinite(value)) { throw new ArithmeticException(); }
  259. writePrimitive(Double.toString(value));
  260. }
  261. private void
  262. writePrimitive(final String value) throws IOException {
  263. final Writer out = (Writer) output.claim();
  264. if (top) {
  265. out.write("{ \"=\" : ");
  266. out.write(value);
  267. out.write(" }");
  268. out.write(newLine);
  269. } else {
  270. out.write(value);
  271. }
  272. written.mark();
  273. }
  274. public void
  275. writeString(final String value) throws IOException {
  276. final Writer out = (Writer) output.claim();
  277. if (top) {
  278. out.write("{ \"=\" : ");
  279. writeStringTo(value, out);
  280. out.write(" }");
  281. out.write(newLine);
  282. } else {
  283. writeStringTo(value, out);
  284. }
  285. written.mark();
  286. }
  287. static private void
  288. writeStringTo(final String value, final Writer out) throws IOException {
  289. out.write('\"');
  290. char previous = '\0';
  291. final int len = value.length();
  292. for (int i = 0; i != len; ++i) {
  293. final char c = value.charAt(i);
  294. switch (c) {
  295. case '\"':
  296. out.write("\\\"");
  297. break;
  298. case '\\':
  299. out.write("\\\\");
  300. break;
  301. case '\b':
  302. out.write("\\b");
  303. break;
  304. case '\f':
  305. out.write("\\f");
  306. break;
  307. case '\n':
  308. out.write("\\n");
  309. break;
  310. case '\r':
  311. out.write("\\r");
  312. break;
  313. case '\t':
  314. out.write("\\t");
  315. break;
  316. // begin: HTML escaping
  317. case '/':
  318. if ('<' == previous) { out.write('\\'); }
  319. out.write(c);
  320. break;
  321. // need at least the above check, but paranoia demands more
  322. case '<':
  323. out.write("\\u003C");
  324. break;
  325. case '>':
  326. out.write("\\u003E");
  327. break;
  328. // end: HTML escaping
  329. case ' ':
  330. out.write(c);
  331. break;
  332. default:
  333. switch (Character.getType(c)) {
  334. case Character.UPPERCASE_LETTER:
  335. case Character.LOWERCASE_LETTER:
  336. case Character.TITLECASE_LETTER:
  337. case Character.MODIFIER_LETTER:
  338. case Character.OTHER_LETTER:
  339. case Character.NON_SPACING_MARK:
  340. case Character.ENCLOSING_MARK:
  341. case Character.COMBINING_SPACING_MARK:
  342. case Character.DECIMAL_DIGIT_NUMBER:
  343. case Character.LETTER_NUMBER:
  344. case Character.OTHER_NUMBER:
  345. case Character.DASH_PUNCTUATION:
  346. case Character.START_PUNCTUATION:
  347. case Character.END_PUNCTUATION:
  348. case Character.CONNECTOR_PUNCTUATION:
  349. case Character.OTHER_PUNCTUATION:
  350. case Character.MATH_SYMBOL:
  351. case Character.CURRENCY_SYMBOL:
  352. case Character.MODIFIER_SYMBOL:
  353. case Character.INITIAL_QUOTE_PUNCTUATION:
  354. case Character.FINAL_QUOTE_PUNCTUATION:
  355. out.write(c);
  356. break;
  357. case Character.UNASSIGNED:
  358. case Character.SPACE_SEPARATOR:
  359. case Character.LINE_SEPARATOR:
  360. case Character.PARAGRAPH_SEPARATOR:
  361. case Character.CONTROL: // includes '\u0085'
  362. case Character.FORMAT:
  363. case Character.PRIVATE_USE:
  364. case Character.SURROGATE:
  365. case Character.OTHER_SYMBOL:
  366. default:
  367. out.write("\\u");
  368. final int unicode = c;
  369. for (int shift = Character.SIZE; 0 != shift;) {
  370. shift -= 4;
  371. final int hex = (unicode >> shift) & 0x0F;
  372. out.write(hex < 10 ? '0' + hex : 'A' + (hex - 10));
  373. }
  374. }
  375. }
  376. previous = c;
  377. }
  378. out.write('\"');
  379. }
  380. }