1 /*
2  * Copyright (C) 2009, 2010, 2011, 2012, 2013 XStream Committers.
3  * All rights reserved.
4  *
5  * The software in this package is published under the terms of the BSD
6  * style license a copy of which has been included with this distribution in
7  * the LICENSE.txt file.
8  *
9  * Created on 20. August 2009 by Joerg Schaible
10  */

11 package com.thoughtworks.xstream.io.json;
12
13 import java.io.Externalizable;
14 import java.math.BigDecimal;
15 import java.math.BigInteger;
16 import java.util.Arrays;
17 import java.util.Collection;
18 import java.util.HashSet;
19 import java.util.Map;
20 import java.util.Set;
21
22 import com.thoughtworks.xstream.converters.ConversionException;
23 import com.thoughtworks.xstream.core.util.FastStack;
24 import com.thoughtworks.xstream.io.AbstractWriter;
25 import com.thoughtworks.xstream.io.naming.NameCoder;
26 import com.thoughtworks.xstream.io.naming.NoNameCoder;
27 import com.thoughtworks.xstream.mapper.Mapper;
28
29
30 /**
31  * An abstract implementation of a writer that calls abstract methods to build JSON structures.
32  * Note, that XStream's implicit collection feature is only compatible with the syntax in
33  * {@link #EXPLICIT_MODE}.
34  * 
35  * @author Jörg Schaible
36  * @since 1.4
37  */

38 public abstract class AbstractJsonWriter extends AbstractWriter {
39     /**
40      * DROP_ROOT_MODE drops the JSON root node.
41      * <p>
42      * The root node is the first level of the JSON object i.e.</p>
43      * 
44      * <pre>
45      * { &quot;person&quot;: {
46      *     &quot;name&quot;: &quot;Joe&quot;
47      * }}
48      * </pre>
49      * 
50      * <p>will be written without root simply as</p>
51      * 
52      * <pre>
53      * {
54      *     &quot;name&quot;: &quot;Joe&quot;
55      * }
56      * </pre>
57      * 
58      * <p>
59      * Without a root node, the top level element might now also be an array. However, it is
60      * possible to generate invalid JSON unless {@link #STRICT_MODE} is also set.</p>
61      * 
62      * @since 1.3.1
63      */

64     public static final int DROP_ROOT_MODE = 1;
65     /**
66      * STRICT_MODE prevents invalid JSON for single value objects when dropping the root.
67      * <p>
68      * The mode is only useful in combination with the {@link #DROP_ROOT_MODE}. An object with a
69      * single value as first node i.e.</p>
70      * 
71      * <pre>
72      * { &quot;name&quot;: &quot;Joe&quot; }
73      * </pre>
74      * 
75      * <p>is simply written as</p>
76      * 
77      * <pre>
78      * &quot;Joe&quot;
79      * </pre>
80      * 
81      * <p>
82      * However, this is no longer valid JSON. Therefore you can activate {@link #STRICT_MODE}
83      * and a {@link ConversionException} is thrown instead.</p>
84      * 
85      * @since 1.3.1
86      */

87     public static final int STRICT_MODE = 2;
88     /**
89      * EXPLICIT_MODE assures that all data has its explicit equivalent in the resulting JSON.
90      * <p>
91      * XStream is normally using attributes in XML that have no real equivalent in JSON.
92      * Additionally it is essential in XML that the individual child elements of a tag keep
93      * order and may have the same tag name. XStream's model relies on both characteristics.
94      * However, properties of a JSON object do not have a defined order, but their names have to
95      * be unique. Only a JSON array defines the order of its elements.
96      * </p>
97      * <p>
98      * Therefore XStream uses in explicit mode a JSON format that supports the original
99      * requirements at the expense of the simplicity of the JSON objects and arrays. Each Java
100      * object will be represented by a JSON object with a single property representing the name
101      * of the object and an array as value that contains two more arrays. The first one contains
102      * a JSON object with all attributes, the second one the value of the Java object which can
103      * be null, a string or integer value or again a new JSON object representing a Java object.
104      * Here an example of an string array with one member, where the array and the string has an
105      * additional attribute 'id':</p>
106      * 
107      * <pre>
108      * {&quot;string-array&quot;:[[{&quot;id&quot;:&quot;1&quot;}],[{&quot;string&quot;:[[{&quot;id&quot;:&quot;2&quot;}],[&quot;Joe&quot;]]}]]}
109      * </pre>
110      * 
111      * <p>
112      * This format can be used to always deserialize into Java again.
113      * </p>
114      * <p>
115      * This mode cannot combined with {@link #STRICT_MODE} or {@link #DROP_ROOT_MODE}.
116      * </p>
117      * 
118      * @since 1.4
119      */

120     public static final int EXPLICIT_MODE = 4;
121     /**
122      * IEEE_754_MODE keeps precision of 64-bit integer values.
123      * <p>
124      * In JavaScript every number is expressed as 64-bit double value with a precision of 53
125      * bits following IEEE 754.  Therefore it is not possible to represent the complete value
126      * range of 64-bit integer values.  Any integer value &gt; 2<sup>53</sup>
127      * (9007199254740992) or &lt; -2<sup>53</sup> (-9007199254740992) will therefore be
128      * written as string value.
129      * </p>
130      * <p>
131      * CAUTION: A client must be aware that the element may contain a number or a string value.
132      * </p>
133      * 
134      * @since 1.4.5
135      * @see <a href="http://ecma262-5.com/ELS5_HTML.htm#Section_8.5">ECMA Specification: The Number Type</a>
136      */

137     public static final int IEEE_754_MODE = 8;
138
139     public static class Type {
140         public static Type NULL = new Type();
141         public static Type STRING = new Type();
142         public static Type NUMBER = new Type();
143         public static Type BOOLEAN = new Type();
144     }
145     
146     private static class StackElement {
147         final Class type;
148         int status;
149         public StackElement(Class type, int status) {
150             this.type = type;
151             this.status = status;
152         }
153     }
154     
155     private static class IllegalWriterStateException extends IllegalStateException {
156         public IllegalWriterStateException(int from, int to, String element) {
157             super("Cannot turn from state " + getState(from) + " into state " + getState(to)
158                 + (element ==  null ? "" : for property " + element));
159         }
160         private static String getState(int state) {
161             switch (state) {
162             case STATE_ROOT: return "ROOT";
163             case STATE_END_OBJECT: return "END_OBJECT";
164             case STATE_START_OBJECT: return "START_OBJECT";
165             case STATE_START_ATTRIBUTES: return "START_ATTRIBUTES";
166             case STATE_NEXT_ATTRIBUTE: return "NEXT_ATTRIBUTE";
167             case STATE_END_ATTRIBUTES: return "END_ATTRIBUTES";
168             case STATE_START_ELEMENTS: return "START_ELEMENTS";
169             case STATE_NEXT_ELEMENT: return "NEXT_ELEMENT";
170             case STATE_END_ELEMENTS: return "END_ELEMENTS";
171             case STATE_SET_VALUE: return "SET_VALUE";
172             defaultthrow new IllegalArgumentException("Unknown state provided: " + state
173                 + ", cannot create message for IllegalWriterStateException");
174             }
175         }
176     }
177
178     private static final int STATE_ROOT = 1 << 0;
179     private static final int STATE_END_OBJECT = 1 << 1;
180     private static final int STATE_START_OBJECT = 1 << 2;
181     private static final int STATE_START_ATTRIBUTES = 1 << 3;
182     private static final int STATE_NEXT_ATTRIBUTE = 1 << 4;
183     private static final int STATE_END_ATTRIBUTES = 1 << 5;
184     private static final int STATE_START_ELEMENTS = 1 << 6;
185     private static final int STATE_NEXT_ELEMENT = 1 << 7;
186     private static final int STATE_END_ELEMENTS = 1 << 8;
187     private static final int STATE_SET_VALUE = 1 << 9;
188
189     private static final Set NUMBER_TYPES = new HashSet(Arrays.asList(new Class[]{
190         byte.class, Byte.classshort.class, Short.classint.class, Integer.classlong.class,
191         Long.classfloat.class, Float.classdouble.class, Double.class, BigInteger.class,
192         BigDecimal.class}));
193     private int mode;
194     private FastStack stack = new FastStack(16);
195     private int expectedStates;
196
197     /**
198      * Construct a JSON writer.
199      * 
200      * @since 1.4
201      */

202     public AbstractJsonWriter() {
203         this(new NoNameCoder());
204     }
205
206     /**
207      * Construct a JSON writer with a special mode.
208      * 
209      * @param mode a bit mask of the mode constants
210      * @since 1.4
211      */

212     public AbstractJsonWriter(int mode) {
213         this(mode, new NoNameCoder());
214     }
215
216     /**
217      * Construct a JSON writer with a special name coder.
218      * 
219      * @param nameCoder the name coder to use
220      * @since 1.4
221      */

222     public AbstractJsonWriter(NameCoder nameCoder) {
223         this(0, nameCoder);
224     }
225
226     /**
227      * Construct a JSON writer with a special mode and name coder.
228      * 
229      * @param mode a bit mask of the mode constants
230      * @param nameCoder the name coder to use
231      * @since 1.4
232      */

233     public AbstractJsonWriter(int mode, NameCoder nameCoder) {
234         super(nameCoder);
235         this.mode = (mode & EXPLICIT_MODE) > 0 ? EXPLICIT_MODE : mode;
236         stack.push(new StackElement(null, STATE_ROOT));
237         expectedStates = STATE_START_OBJECT;
238     }
239
240     public void startNode(String name, Class clazz) {
241         if (name == null) {
242             throw new NullPointerException("name");
243         }
244         stack.push(new StackElement(clazz, ((StackElement)stack.peek()).status));
245         handleCheckedStateTransition(STATE_START_OBJECT, name, null);
246         expectedStates = STATE_SET_VALUE | STATE_NEXT_ATTRIBUTE | STATE_START_OBJECT | STATE_NEXT_ELEMENT | STATE_ROOT;
247     }
248
249     public void startNode(String name) {
250         startNode(name, null);
251     }
252
253     public void addAttribute(String name, String value) {
254         handleCheckedStateTransition(STATE_NEXT_ATTRIBUTE, name, value);
255         expectedStates = STATE_SET_VALUE | STATE_NEXT_ATTRIBUTE | STATE_START_OBJECT | STATE_NEXT_ELEMENT | STATE_ROOT;
256     }
257
258     public void setValue(String text) {
259         Class type = ((StackElement)stack.peek()).type;
260         if ((type == Character.class || type == Character.TYPE) && "".equals(text)) {
261             text = "\u0000";
262         }
263         handleCheckedStateTransition(STATE_SET_VALUE, null, text);
264         expectedStates = STATE_NEXT_ELEMENT | STATE_ROOT;
265     }
266
267     public void endNode() {
268         int size = stack.size();
269         int nextState = size > 2 ? STATE_NEXT_ELEMENT : STATE_ROOT;
270         handleCheckedStateTransition(nextState, nullnull);
271         stack.pop();
272         ((StackElement)stack.peek()).status = nextState;
273         expectedStates = STATE_START_OBJECT;
274         if (size > 2) { 
275             expectedStates |= STATE_NEXT_ELEMENT | STATE_ROOT;
276         }
277     }
278
279     private void handleCheckedStateTransition(final int requiredState, final String elementToAdd, final String valueToAdd)
280     {
281         final StackElement stackElement = (StackElement)stack.peek();
282         if ((expectedStates & requiredState) == 0) {
283             throw new IllegalWriterStateException(stackElement.status, requiredState, elementToAdd);
284         }
285         int currentState = handleStateTransition(stackElement.status, requiredState, elementToAdd, valueToAdd);
286         stackElement.status = currentState;
287     }
288
289     private int handleStateTransition(int currentState, final int requiredState, final String elementToAdd, final String valueToAdd)
290     {
291         int size = stack.size();
292         Class currentType = ((StackElement)stack.peek()).type;
293         boolean isArray = size > 1 && isArray(currentType); 
294         boolean isArrayElement = size > 1 && isArray(((StackElement)stack.get(size-2)).type); 
295         switch(currentState) {
296         case STATE_ROOT:
297             if (requiredState == STATE_START_OBJECT) {
298                 currentState = handleStateTransition(STATE_START_ELEMENTS, STATE_START_OBJECT, elementToAdd, null);
299                 return requiredState;
300             }
301             throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
302             
303         case STATE_END_OBJECT:
304             switch(requiredState) {
305             case STATE_START_OBJECT:
306                 currentState = handleStateTransition(currentState, STATE_NEXT_ELEMENT, nullnull);
307                 currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, null);
308                 return requiredState;
309             case STATE_NEXT_ELEMENT:
310                 nextElement();
311                 return requiredState;
312             case STATE_ROOT:
313                 if (((mode & DROP_ROOT_MODE) == 0 || size > 2) && (mode & EXPLICIT_MODE) == 0) {
314                     endObject();
315                 }
316                 return requiredState;
317             default:
318                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
319             }
320             
321         case STATE_START_OBJECT:
322             switch(requiredState) {
323             case STATE_SET_VALUE:
324             case STATE_START_OBJECT:
325             case STATE_ROOT:
326             case STATE_NEXT_ELEMENT:
327                 if (!isArrayElement || (mode & EXPLICIT_MODE) != 0) {
328                     currentState = handleStateTransition(currentState, STATE_START_ATTRIBUTES, nullnull);
329                     currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, nullnull);
330                 }
331                 currentState = STATE_START_ELEMENTS;
332                 
333                 switch(requiredState) {
334                 case STATE_SET_VALUE:
335                     currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, valueToAdd);
336                     break;
337                 case STATE_START_OBJECT:
338                     currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, null);
339                     break;
340                 case STATE_ROOT:
341                 case STATE_NEXT_ELEMENT:
342                     currentState = handleStateTransition(currentState, STATE_SET_VALUE, nullnull);
343                     currentState = handleStateTransition(currentState, requiredState, nullnull);
344                     break;
345                 }
346                 return requiredState;
347             case STATE_START_ATTRIBUTES:
348                 if ((mode & EXPLICIT_MODE) != 0) {
349                     startArray();
350                 }
351                 return requiredState;
352             case STATE_NEXT_ATTRIBUTE:
353                 if ((mode & EXPLICIT_MODE) != 0 || !isArray) {
354                     currentState = handleStateTransition(currentState, STATE_START_ATTRIBUTES, nullnull);
355                     currentState = handleStateTransition(currentState, STATE_NEXT_ATTRIBUTE, elementToAdd, valueToAdd);
356                     return requiredState;
357                 } else {
358                     return STATE_START_OBJECT;
359                 }
360             default:
361                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
362             }
363             
364         case STATE_NEXT_ELEMENT:
365             switch(requiredState) {
366             case STATE_START_OBJECT:
367                 nextElement();
368                 if (!isArrayElement && (mode & EXPLICIT_MODE) == 0) {
369                     addLabel(encodeNode(elementToAdd));
370                     if ((mode & EXPLICIT_MODE) == 0 && isArray) {
371                         startArray();
372                     }
373                     return requiredState;
374                 }
375                 break;
376             case STATE_ROOT:
377                 currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
378                 currentState = handleStateTransition(currentState, STATE_ROOT, nullnull);
379                 return requiredState;
380             case STATE_NEXT_ELEMENT:
381             case STATE_END_OBJECT:
382                 currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, nullnull);
383                 currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
384                 if ((mode & EXPLICIT_MODE) == 0 && !isArray) {
385                     endObject();
386                 }
387                 return requiredState;
388             case STATE_END_ELEMENTS:
389                 if ((mode & EXPLICIT_MODE) == 0 && isArray) {
390                     endArray();
391                 }
392                 return requiredState;
393             default:
394                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
395             }
396             // fall through
397         case STATE_START_ELEMENTS:
398             switch(requiredState) {
399             case STATE_START_OBJECT:
400                 if ((mode & DROP_ROOT_MODE) == 0 || size > 2) {
401                     if (!isArrayElement || (mode & EXPLICIT_MODE) != 0) {
402                         if (!"".equals(valueToAdd)) {
403                             startObject();
404                         }
405                         addLabel(encodeNode(elementToAdd));
406                     }
407                     if ((mode & EXPLICIT_MODE) != 0) {
408                         startArray();
409                     }
410                 }
411                 if ((mode & EXPLICIT_MODE) == 0) {
412                     if (isArray) {
413                         startArray();
414                     }
415                 }
416                 return requiredState;
417             case STATE_SET_VALUE:
418                 if ((mode & STRICT_MODE) != 0 && size == 2) {
419                     throw new ConversionException("Single value cannot be root element");
420                 }
421                 if (valueToAdd == null) {
422                     if (currentType == Mapper.Null.class) {
423                        addValue("null", Type.NULL);
424                     } else if ((mode & EXPLICIT_MODE) == 0 && !isArray) {
425                         startObject();
426                         endObject();
427                     }
428                 } else {
429                     if (((mode & IEEE_754_MODE) != 0)
430                         && (currentType == long.class || currentType == Long.class)) {
431                         long longValue = Long.parseLong(valueToAdd);
432                         // JavaScript supports a maximum of 2^53
433                         if (longValue > 9007199254740992L || longValue < -9007199254740992L) {
434                             addValue(valueToAdd, Type.STRING);
435                         } else {
436                             addValue(valueToAdd, getType(currentType));
437                         }
438                     } else {
439                         addValue(valueToAdd, getType(currentType));
440                     }
441                 }
442                 return requiredState;
443             case STATE_END_ELEMENTS:
444             case STATE_NEXT_ELEMENT:
445                 if ((mode & EXPLICIT_MODE) == 0) {
446                     if (isArray) {
447                         endArray();
448                     } else {
449                         endObject();
450                     }
451                 }
452                 return requiredState;
453             default:
454                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
455             }
456
457         case STATE_END_ELEMENTS:
458             switch(requiredState) {
459             case STATE_END_OBJECT:
460                 if ((mode & EXPLICIT_MODE) != 0) {
461                     endArray();
462                     endArray();
463                     endObject();
464                 }
465                 return requiredState;
466             default:
467                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
468             }
469
470         case STATE_START_ATTRIBUTES:
471             switch(requiredState) {
472             case STATE_NEXT_ATTRIBUTE:
473                 if (elementToAdd != null) {
474                     String name = ((mode & EXPLICIT_MODE) == 0 ? "@" : "" ) + elementToAdd;
475                     startObject();
476                     addLabel(encodeAttribute(name));
477                     addValue(valueToAdd, Type.STRING);
478                 }
479                 return requiredState;
480             }
481             // fall through
482         case STATE_NEXT_ATTRIBUTE:
483             switch(requiredState) {
484             case STATE_END_ATTRIBUTES:
485                 if ((mode & EXPLICIT_MODE) != 0) {
486                     if (currentState == STATE_NEXT_ATTRIBUTE) {
487                         endObject();
488                     }
489                     endArray();
490                     nextElement();
491                     startArray();
492                 }
493                 return requiredState;
494             case STATE_NEXT_ATTRIBUTE:
495                 if (!isArray || (mode & EXPLICIT_MODE) != 0) {
496                     nextElement();
497                     String name = ((mode & EXPLICIT_MODE) == 0 ? "@" : "" ) + elementToAdd;
498                     addLabel(encodeAttribute(name));
499                     addValue(valueToAdd, Type.STRING);
500                 }
501                 return requiredState;
502             case STATE_SET_VALUE:
503             case STATE_START_OBJECT:
504                 currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, nullnull);
505                 currentState = handleStateTransition(currentState, STATE_START_ELEMENTS, nullnull);
506                 switch (requiredState) {
507                 case STATE_SET_VALUE:
508                     if ((mode & EXPLICIT_MODE) == 0) {
509                         addLabel(encodeNode("$"));
510                     }
511                     currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, valueToAdd);
512                     if ((mode & EXPLICIT_MODE) == 0) {
513                         endObject();
514                     }
515                     break;
516                 case STATE_START_OBJECT:
517                     currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, (mode & EXPLICIT_MODE) == 0 ? "" : null);
518                     break;
519                 case STATE_END_OBJECT:
520                     currentState = handleStateTransition(currentState, STATE_SET_VALUE, nullnull);
521                     currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
522                     break;
523                 }
524                 return requiredState;
525             case STATE_NEXT_ELEMENT:
526                 currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, nullnull);
527                 currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
528                 return requiredState;
529             case STATE_ROOT:
530                 currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, nullnull);
531                 currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
532                 currentState = handleStateTransition(currentState, STATE_ROOT, nullnull);
533                 return requiredState;
534             default:
535                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
536             }
537
538         case STATE_END_ATTRIBUTES:
539             switch(requiredState) {
540             case STATE_START_ELEMENTS:
541                 if ((mode & EXPLICIT_MODE) == 0) {
542                     nextElement();
543                 }
544                 break;
545             case STATE_END_OBJECT:
546                 currentState = handleStateTransition(STATE_START_ELEMENTS, STATE_END_ELEMENTS, nullnull);
547                 currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
548                 break;
549             default:
550                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
551             }
552             return requiredState;
553             
554         case STATE_SET_VALUE:
555             switch(requiredState) {
556             case STATE_END_ELEMENTS:
557                 if ((mode & EXPLICIT_MODE) == 0 && isArray) {
558                     endArray();
559                 }
560                 return requiredState;
561             case STATE_NEXT_ELEMENT:
562                 currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, nullnull);
563                 currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
564                 return requiredState;
565             case STATE_ROOT:
566                 currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, nullnull);
567                 currentState = handleStateTransition(currentState, STATE_END_OBJECT, nullnull);
568                 currentState = handleStateTransition(currentState, STATE_ROOT, nullnull);
569                 return requiredState;
570             default:
571                 throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
572             }
573         }
574
575         throw new IllegalWriterStateException(currentState, requiredState, elementToAdd);
576     }
577
578     /**
579      * Method to return the appropriate JSON type for a Java type.
580      * 
581      * @param clazz the type
582      * @return One of the {@link Type} instances
583      * @since 1.4.4
584      */

585     protected Type getType(Class clazz) {
586         return clazz == Mapper.Null.class
587             ? Type.NULL
588             : (clazz == Boolean.class || clazz == Boolean.TYPE) 
589                 ? Type.BOOLEAN 
590                 : NUMBER_TYPES.contains(clazz) 
591                     ? Type.NUMBER 
592                     : Type.STRING;
593     }
594
595     /**
596      * Method to declare various Java types to be handles as JSON array.
597      * 
598      * @param clazz the type
599      * @return <code>true</code> if handles as array
600      * @since 1.4
601      */

602     protected boolean isArray(Class clazz) {
603         return clazz != null && (clazz.isArray()
604             || Collection.class.isAssignableFrom(clazz)
605             || Externalizable.class.isAssignableFrom(clazz)
606             || Map.class.isAssignableFrom(clazz)
607             || Map.Entry.class.isAssignableFrom(clazz));
608     }
609
610     /**
611      * Start a JSON object.
612      * 
613      * @since 1.4
614      */

615     protected abstract void startObject();
616
617     /**
618      * Add a label to a JSON object.
619      * 
620      * @param name the label's name
621      * @since 1.4
622      */

623     protected abstract void addLabel(String name);
624
625     /**
626      * Add a value to a JSON object's label or to an array.
627      * 
628      * @param value the value itself
629      * @param type the JSON type
630      * @since 1.4
631      */

632     protected abstract void addValue(String value, Type type);
633
634     /**
635      * Start a JSON array.
636      * 
637      * @since 1.4
638      */

639     protected abstract void startArray();
640
641     /**
642      * Prepare a JSON object or array for another element.
643      * 
644      * @since 1.4
645      */

646     protected abstract void nextElement();
647
648     /**
649      * End the JSON array.
650      * 
651      * @since 1.4
652      */

653     protected abstract void endArray();
654
655     /**
656      * End the JSON object.
657      * 
658      * @since 1.4
659      */

660     protected abstract void endObject();
661 }
662