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 java.awt.Color;
015: import java.io.InputStream;
016: import java.io.Reader;
017: import java.net.MalformedURLException;
018: import java.net.URL;
019: import java.util.ArrayList;
020: import java.util.Calendar;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Properties;
027: import java.util.Set;
028: import java.util.TimeZone;
029: import java.util.TreeMap;
030:
031: import com.thoughtworks.xstream.XStream;
032:
033: import junit.framework.TestCase;
034:
035: /**
036: * Some of these test cases are taken from example JSON listed at
037: * http://www.json.org/example.html
038: *
039: * @author Paul Hammant
040: * @author Jörg Schaible
041: */
042: public class JsonHierarchicalStreamDriverTest extends TestCase {
043: private XStream xstream;
044:
045: /**
046: * @see junit.framework.TestCase#setUp()
047: */
048: protected void setUp() throws Exception {
049: super .setUp();
050: xstream = new XStream(new JsonHierarchicalStreamDriver());
051: }
052:
053: public void testDoesNotSupportReader() {
054: try {
055: new JsonHierarchicalStreamDriver()
056: .createReader((Reader) null);
057: fail("should have barfed");
058: } catch (UnsupportedOperationException uoe) {
059: // expected
060: }
061: }
062:
063: public void testDoesNotSupportInputStream() {
064: try {
065: new JsonHierarchicalStreamDriver()
066: .createReader((InputStream) null);
067: fail("should have barfed");
068: } catch (UnsupportedOperationException uoe) {
069: // expected
070: }
071: }
072:
073: public void testCanMarshalSimpleTypes() {
074:
075: String expected = ("{'innerMessage': {\n" + " 'long1': 5,\n"
076: + " 'long2': 42,\n" + " 'greeting': 'hello',\n"
077: + " 'num1': 2,\n" + " 'num2': 3,\n"
078: + " 'bool': true,\n" + " 'bool2': true,\n"
079: + " 'char1': 'A',\n" + " 'char2': 'B',\n"
080: + " 'innerMessage': {\n" + " 'long1': 0,\n"
081: + " 'greeting': 'bonjour',\n" + " 'num1': 3,\n"
082: + " 'bool': false,\n" + " 'char1': '\\u0000'\n"
083: + " }\n" + "}}").replace('\'', '"');
084:
085: xstream.alias("innerMessage", Message.class);
086:
087: Message message = new Message("hello");
088: message.long1 = 5L;
089: message.long2 = new Long(42);
090: message.num1 = 2;
091: message.num2 = new Integer(3);
092: message.bool = true;
093: message.bool2 = Boolean.TRUE;
094: message.char1 = 'A';
095: message.char2 = new Character('B');
096:
097: Message message2 = new Message("bonjour");
098: message2.num1 = 3;
099:
100: message.innerMessage = message2;
101:
102: assertEquals(expected, xstream.toXML(message));
103: }
104:
105: public static class Message {
106: long long1;
107: Long long2;
108: String greeting;
109: int num1;
110: Integer num2;
111: boolean bool;
112: Boolean bool2;
113: char char1;
114: Character char2;
115: Message innerMessage;
116:
117: public Message(String greeting) {
118: this .greeting = greeting;
119: }
120: }
121:
122: String expectedMenuStart = "" + "{'menu': {\n"
123: + " 'id': 'file',\n" + " 'value': 'File:',\n"
124: + " 'popup': {\n" + " 'menuitem': [";
125: String expectedNew = "" + " {\n" + " 'value': 'New',\n"
126: + " 'onclick': 'CreateNewDoc()'\n" + " }";
127: String expectedOpen = "" + " {\n"
128: + " 'value': 'Open',\n"
129: + " 'onclick': 'OpenDoc()'\n" + " }";
130: String expectedClose = "" + " {\n"
131: + " 'value': 'Close',\n"
132: + " 'onclick': 'CloseDoc()'\n" + " }";
133: String expectedMenuEnd = "" + " ]\n" + " }\n" + "}}";
134: String expected = (expectedMenuStart + "\n" + expectedNew + ",\n"
135: + expectedOpen + ",\n" + expectedClose + "\n" + expectedMenuEnd)
136: .replace('\'', '"');
137:
138: public void testCanMarshalLists() {
139:
140: // This from http://www.json.org/example.html
141:
142: xstream.alias("menu", MenuWithList.class);
143: xstream.alias("menuitem", MenuItem.class);
144:
145: MenuWithList menu = new MenuWithList();
146:
147: assertEquals(expected, xstream.toXML(menu));
148: }
149:
150: public void testCanMarshalArrays() {
151:
152: xstream.alias("menu", MenuWithArray.class);
153: xstream.alias("menuitem", MenuItem.class);
154:
155: MenuWithArray menu = new MenuWithArray();
156:
157: assertEquals(expected, xstream.toXML(menu));
158: }
159:
160: public void testCanMarshalSets() {
161:
162: // This from http://www.json.org/example.html
163:
164: xstream.alias("menu", MenuWithSet.class);
165: xstream.alias("menuitem", MenuItem.class);
166:
167: MenuWithSet menu = new MenuWithSet();
168:
169: String json = xstream.toXML(menu);
170: assertTrue(json
171: .startsWith(expectedMenuStart.replace('\'', '"')));
172: assertTrue(json.indexOf(expectedNew.replace('\'', '"')) > 0);
173: assertTrue(json.indexOf(expectedOpen.replace('\'', '"')) > 0);
174: assertTrue(json.indexOf(expectedClose.replace('\'', '"')) > 0);
175: assertTrue(json.endsWith(expectedMenuEnd.replace('\'', '"')));
176: }
177:
178: public static class MenuWithList {
179: String id = "file";
180: String value = "File:";
181: PopupWithList popup = new PopupWithList();
182: }
183:
184: public static class PopupWithList {
185: List menuitem;
186: {
187: menuitem = new ArrayList();
188: menuitem.add(new MenuItem("New", "CreateNewDoc()"));
189: menuitem.add(new MenuItem("Open", "OpenDoc()"));
190: menuitem.add(new MenuItem("Close", "CloseDoc()"));
191: }
192: }
193:
194: public static class MenuWithArray {
195: String id = "file";
196: String value = "File:";
197: PopupWithArray popup = new PopupWithArray();
198: }
199:
200: public static class PopupWithArray {
201: MenuItem[] menuitem = new MenuItem[] {
202: new MenuItem("New", "CreateNewDoc()"),
203: new MenuItem("Open", "OpenDoc()"),
204: new MenuItem("Close", "CloseDoc()") };
205: }
206:
207: public static class MenuWithSet {
208: String id = "file";
209: String value = "File:";
210: PopupWithSet popup = new PopupWithSet();
211: }
212:
213: public static class PopupWithSet {
214: Set menuitem;
215: {
216: menuitem = new HashSet();
217: menuitem.add(new MenuItem("New", "CreateNewDoc()"));
218: menuitem.add(new MenuItem("Open", "OpenDoc()"));
219: menuitem.add(new MenuItem("Close", "CloseDoc()"));
220: }
221:
222: }
223:
224: public static class MenuItem {
225: public String value; // assume unique
226: public String onclick;
227:
228: public MenuItem(String value, String onclick) {
229: this .value = value;
230: this .onclick = onclick;
231: }
232:
233: public int hashCode() {
234: return value.hashCode();
235: }
236:
237: }
238:
239: public void testCanMarshalTypesWithPrimitives() {
240:
241: // This also from http://www.expected.org/example.html
242:
243: String expected = ("{'widget': {\n"
244: + " 'debug': 'on',\n"
245: + " 'window': {\n"
246: + " 'title': 'Sample Konfabulator Widget',\n"
247: + " 'name': 'main_window',\n"
248: + " 'width': 500,\n"
249: + " 'height': 500\n"
250: + " },\n"
251: + " 'image': {\n"
252: + " 'src': 'Images/Sun.png',\n"
253: + " 'name': 'sun1',\n"
254: + " 'hOffset': 250,\n"
255: + " 'vOffset': 250,\n"
256: + " 'alignment': 'center'\n"
257: + " },\n"
258: + " 'text': {\n"
259: + " 'data': 'Click Here',\n"
260: + " 'size': 36,\n"
261: + " 'style': 'bold',\n"
262: + " 'name': 'text1',\n"
263: + " 'hOffset': 250,\n"
264: + " 'vOffset': 100,\n"
265: + " 'alignment': 'center',\n"
266: + " 'onMouseUp': 'sun1.opacity = (sun1.opacity / 100) * 90;'\n"
267: + " }\n" + "}}").replace('\'', '"');
268:
269: xstream.alias("widget", Widget.class);
270: xstream.alias("window", Window.class);
271: xstream.alias("image", Image.class);
272: xstream.alias("text", Text.class);
273:
274: Widget widget = new Widget();
275:
276: assertEquals(expected, xstream.toXML(widget));
277:
278: }
279:
280: public static class Widget {
281: String debug = "on";
282: Window window = new Window();
283: Image image = new Image();
284: Text text = new Text();
285: }
286:
287: public static class Window {
288: String title = "Sample Konfabulator Widget";
289: String name = "main_window";
290: int width = 500;
291: int height = 500;
292: }
293:
294: public static class Image {
295: String src = "Images/Sun.png";
296: String name = "sun1";
297: int hOffset = 250;
298: int vOffset = 250;
299: String alignment = "center";
300: }
301:
302: public static class Text {
303: String data = "Click Here";
304: int size = 36;
305: String style = "bold";
306: String name = "text1";
307: int hOffset = 250;
308: int vOffset = 100;
309: String alignment = "center";
310: String onMouseUp = "sun1.opacity = (sun1.opacity / 100) * 90;";
311: }
312:
313: public void testColor() {
314: Color color = Color.black;
315: String expected = ("{'awt-color': {\n" + " 'red': 0,\n"
316: + " 'green': 0,\n" + " 'blue': 0,\n"
317: + " 'alpha': 255\n" + "}}").replace('\'', '"');
318: assertEquals(expected, xstream.toXML(color));
319: }
320:
321: public void testDoesHandleQuotesAndEscapes() {
322: String[] strings = new String[] { "last\"", "\"first",
323: "\"between\"", "around \"\" it", "back\\slash", };
324: String expected = ("" + "{#string-array#: [\n"
325: + " #last\\\"#,\n" + " #\\\"first#,\n"
326: + " #\\\"between\\\"#,\n"
327: + " #around \\\"\\\" it#,\n" + " #back\\\\slash#\n"
328: + "]}").replace('#', '"');
329: assertEquals(expected, xstream.toXML(strings));
330: }
331:
332: public void testCanMarshalSimpleTypesWithNullMembers() {
333: Msg message = new Msg("hello");
334: Msg message2 = new Msg(null);
335: message.innerMessage = message2;
336:
337: xstream.alias("innerMessage", Msg.class);
338:
339: String expected = ("" + "{'innerMessage': {\n"
340: + " 'greeting': 'hello',\n" + " 'innerMessage': {}\n"
341: + "}}").replace('\'', '"');
342: assertEquals(expected, xstream.toXML(message));
343: }
344:
345: public static class Msg {
346: String greeting;
347: Msg innerMessage;
348:
349: public Msg(String greeting) {
350: this .greeting = greeting;
351: }
352: }
353:
354: public void testCanMarshalElementWithEmptyArray() {
355: xstream.alias("element", ElementWithEmptyArray.class);
356:
357: String expected = ("" + "{'element': {\n" + " 'array': [\n"
358: + " ]\n" + "}}").replace('\'', '"');
359: assertEquals(expected, xstream
360: .toXML(new ElementWithEmptyArray()));
361: }
362:
363: public static class ElementWithEmptyArray {
364: String[] array = new String[0];
365: }
366:
367: public void testCanMarshalJavaMap() {
368: String entry1 = "" // entry 1
369: + " [\n" + " 'one',\n" + " 1\n" + " ]";
370: String entry2 = "" // entry 2
371: + " [\n" + " 'two',\n" + " 2\n" + " ]";
372:
373: final Map map = new HashMap();
374: map.put("one", new Integer(1));
375: map.put("two", new Integer(2));
376: String actual = xstream.toXML(map);
377: int idx1 = actual.indexOf("one");
378: int idx2 = actual.indexOf("two");
379:
380: String expected = ("" + "{'map': [\n"
381: + ((idx1 < idx2 ? entry1 : entry2) + ",\n")
382: + ((idx1 < idx2 ? entry2 : entry1) + "\n") // no comma
383: + "]}").replace('\'', '"');
384: assertEquals(expected, actual);
385: }
386:
387: public void testCanMarshalProperties() {
388: String entry1 = "" // entry 1
389: + " {\n"
390: + " '@name': 'one',\n"
391: + " '@value': '1'\n" + " }";
392: String entry2 = "" // entry 2
393: + " {\n"
394: + " '@name': 'two',\n"
395: + " '@value': '2'\n" + " }";
396:
397: final Properties properties = new Properties();
398: properties.setProperty("one", "1");
399: properties.setProperty("two", "2");
400: String actual = xstream.toXML(properties);
401: int idx1 = actual.indexOf("one");
402: int idx2 = actual.indexOf("two");
403:
404: String expected = ("" + "{'properties': [\n"
405: + ((idx1 < idx2 ? entry1 : entry2) + ",\n")
406: + ((idx1 < idx2 ? entry2 : entry1) + "\n") // no comma
407: + "]}").replace('\'', '"');
408: assertEquals(expected, actual);
409: }
410:
411: final static class MapHolder {
412: private Map map = new HashMap();
413: }
414:
415: public void testCanMarshalNestedMap() {
416: xstream.alias("holder", MapHolder.class);
417: String entry1 = "" // entry 1
418: + " [\n" + " 'one',\n" + " 1\n" + " ]";
419: String entry2 = "" // entry 2
420: + " [\n" + " 'two',\n" + " 2\n" + " ]";
421:
422: final MapHolder holder = new MapHolder();
423: holder.map.put("one", new Integer(1));
424: holder.map.put("two", new Integer(2));
425: String actual = xstream.toXML(holder);
426: int idx1 = actual.indexOf("one");
427: int idx2 = actual.indexOf("two");
428:
429: String expected = ("" + "{'holder': {\n" + " 'map': [\n"
430: + ((idx1 < idx2 ? entry1 : entry2) + ",\n")
431: + ((idx1 < idx2 ? entry2 : entry1) + "\n") + " ]\n" // no comma
432: + "}}").replace('\'', '"');
433: assertEquals(expected, actual);
434: }
435:
436: static class CollectionKeeper {
437: Collection coll = new ArrayList();
438: }
439:
440: public void testIgnoresAttributeForCollectionMember() {
441: xstream.alias("keeper", CollectionKeeper.class);
442: String expected = ("" //
443: + "{'keeper': {\n" + " 'coll': [\n"
444: + " 'one',\n"
445: + " 'two'\n" + " ]\n" + "}}").replace('\'', '"');
446:
447: final CollectionKeeper holder = new CollectionKeeper();
448: holder.coll.add("one");
449: holder.coll.add("two");
450: assertEquals(expected, xstream.toXML(holder));
451: }
452:
453: // Writing attributes, the writer has no clue about their original type.
454: public void testDoesWriteAttributesAsStringValues() {
455: xstream.alias("window", Window.class);
456: xstream.useAttributeFor("width", int.class);
457: xstream.useAttributeFor("height", int.class);
458: String expected = ("" + "{'window': {\n"
459: + " '@width': '500',\n" + " '@height': '500',\n"
460: + " 'title': 'JUnit'\n" + "}}").replace('\'', '"');
461:
462: final Window window = new Window();
463: window.title = "JUnit";
464: window.name = null;
465: assertEquals(expected, xstream.toXML(window));
466: }
467:
468: static class Person {
469: String firstName;
470: String lastName;
471: Calendar dateOfBirth;
472: Map titles = new TreeMap();
473: }
474:
475: public void testCanWriteEmbeddedCalendar() {
476: xstream.alias("person", Person.class);
477: String expected = ("" + "{'list': [\n" + " {\n"
478: + " 'firstName': 'Joe',\n"
479: + " 'lastName': 'Walnes',\n"
480: + " 'dateOfBirth': {\n"
481: + " 'time': -2177539200000,\n"
482: + " 'timezone': 'Europe/London'\n" + " },\n"
483: + " 'titles': [\n"
484: + " {},\n" // no-comparator element
485: + " [\n" + " '1',\n" + " 'Mr'\n"
486: + " ]\n" + " ]\n" + " }\n" + "]}").replace(
487: '\'', '"');
488:
489: Person person = new Person();
490: person.firstName = "Joe";
491: person.lastName = "Walnes";
492: person.dateOfBirth = Calendar.getInstance(TimeZone
493: .getTimeZone("Europe/London"));
494: person.dateOfBirth.clear();
495: person.dateOfBirth.set(1900, Calendar.DECEMBER, 31);
496: person.titles.put("1", "Mr");
497: List list = new ArrayList();
498: list.add(person);
499: assertEquals(expected, xstream.toXML(list));
500: }
501:
502: public void testDoesEscapeValuesAccordingRfc4627() {
503: String expected = "{'string': '\\u0000\\u0001\\u001f \uffee'}"
504: .replace('\'', '"');
505: assertEquals(expected, xstream
506: .toXML("\u0000\u0001\u001f\u0020\uffee"));
507: }
508:
509: static class SingleValue {
510: long l;
511: URL url;
512: }
513:
514: public void testSupportsAllConvertersWithASingleValue()
515: throws MalformedURLException {
516: xstream.alias("sv", SingleValue.class);
517: String expected = ("" + "{'sv': {\n" + " 'l': 4711,\n"
518: + " 'url': 'http://localhost:8888'\n" + "}}").replace(
519: '\'', '"');
520:
521: SingleValue value = new SingleValue();
522: value.l = 4711;
523: value.url = new URL("http://localhost:8888");
524: assertEquals(expected, xstream.toXML(value));
525: }
526: }
|