All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thoughtworks.xstream.io.json.AbstractJsonWriter Maven / Gradle / Ivy

There is a newer version: 1.4.21
Show newest version
/*
 * Copyright (C) 2009, 2010, 2011, 2012, 2013 XStream Committers.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 *
 * Created on 20. August 2009 by Joerg Schaible
 */
package com.thoughtworks.xstream.io.json;

import java.io.Externalizable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.core.util.FastStack;
import com.thoughtworks.xstream.io.AbstractWriter;
import com.thoughtworks.xstream.io.naming.NameCoder;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.mapper.Mapper;


/**
 * An abstract implementation of a writer that calls abstract methods to build JSON structures.
 * Note, that XStream's implicit collection feature is only compatible with the syntax in
 * {@link #EXPLICIT_MODE}.
 * 
 * @author Jörg Schaible
 * @since 1.4
 */
public abstract class AbstractJsonWriter extends AbstractWriter {
    /**
     * DROP_ROOT_MODE drops the JSON root node.
     * 

* The root node is the first level of the JSON object i.e.

* *
     * { "person": {
     *     "name": "Joe"
     * }}
     * 
* *

will be written without root simply as

* *
     * {
     *     "name": "Joe"
     * }
     * 
* *

* Without a root node, the top level element might now also be an array. However, it is * possible to generate invalid JSON unless {@link #STRICT_MODE} is also set.

* * @since 1.3.1 */ public static final int DROP_ROOT_MODE = 1; /** * STRICT_MODE prevents invalid JSON for single value objects when dropping the root. *

* The mode is only useful in combination with the {@link #DROP_ROOT_MODE}. An object with a * single value as first node i.e.

* *
     * { "name": "Joe" }
     * 
* *

is simply written as

* *
     * "Joe"
     * 
* *

* However, this is no longer valid JSON. Therefore you can activate {@link #STRICT_MODE} * and a {@link ConversionException} is thrown instead.

* * @since 1.3.1 */ public static final int STRICT_MODE = 2; /** * EXPLICIT_MODE assures that all data has its explicit equivalent in the resulting JSON. *

* XStream is normally using attributes in XML that have no real equivalent in JSON. * Additionally it is essential in XML that the individual child elements of a tag keep * order and may have the same tag name. XStream's model relies on both characteristics. * However, properties of a JSON object do not have a defined order, but their names have to * be unique. Only a JSON array defines the order of its elements. *

*

* Therefore XStream uses in explicit mode a JSON format that supports the original * requirements at the expense of the simplicity of the JSON objects and arrays. Each Java * object will be represented by a JSON object with a single property representing the name * of the object and an array as value that contains two more arrays. The first one contains * a JSON object with all attributes, the second one the value of the Java object which can * be null, a string or integer value or again a new JSON object representing a Java object. * Here an example of an string array with one member, where the array and the string has an * additional attribute 'id':

* *
     * {"string-array":[[{"id":"1"}],[{"string":[[{"id":"2"}],["Joe"]]}]]}
     * 
* *

* This format can be used to always deserialize into Java again. *

*

* This mode cannot combined with {@link #STRICT_MODE} or {@link #DROP_ROOT_MODE}. *

* * @since 1.4 */ public static final int EXPLICIT_MODE = 4; /** * IEEE_754_MODE keeps precision of 64-bit integer values. *

* In JavaScript every number is expressed as 64-bit double value with a precision of 53 * bits following IEEE 754. Therefore it is not possible to represent the complete value * range of 64-bit integer values. Any integer value > 253 * (9007199254740992) or < -253 (-9007199254740992) will therefore be * written as string value. *

*

* CAUTION: A client must be aware that the element may contain a number or a string value. *

* * @since 1.4.5 * @see ECMA Specification: The Number Type */ public static final int IEEE_754_MODE = 8; public static class Type { public static Type NULL = new Type(); public static Type STRING = new Type(); public static Type NUMBER = new Type(); public static Type BOOLEAN = new Type(); } private static class StackElement { final Class type; int status; public StackElement(Class type, int status) { this.type = type; this.status = status; } } private static class IllegalWriterStateException extends IllegalStateException { public IllegalWriterStateException(int from, int to, String element) { super("Cannot turn from state " + getState(from) + " into state " + getState(to) + (element == null ? "" : " for property " + element)); } private static String getState(int state) { switch (state) { case STATE_ROOT: return "ROOT"; case STATE_END_OBJECT: return "END_OBJECT"; case STATE_START_OBJECT: return "START_OBJECT"; case STATE_START_ATTRIBUTES: return "START_ATTRIBUTES"; case STATE_NEXT_ATTRIBUTE: return "NEXT_ATTRIBUTE"; case STATE_END_ATTRIBUTES: return "END_ATTRIBUTES"; case STATE_START_ELEMENTS: return "START_ELEMENTS"; case STATE_NEXT_ELEMENT: return "NEXT_ELEMENT"; case STATE_END_ELEMENTS: return "END_ELEMENTS"; case STATE_SET_VALUE: return "SET_VALUE"; default: throw new IllegalArgumentException("Unknown state provided: " + state + ", cannot create message for IllegalWriterStateException"); } } } private static final int STATE_ROOT = 1 << 0; private static final int STATE_END_OBJECT = 1 << 1; private static final int STATE_START_OBJECT = 1 << 2; private static final int STATE_START_ATTRIBUTES = 1 << 3; private static final int STATE_NEXT_ATTRIBUTE = 1 << 4; private static final int STATE_END_ATTRIBUTES = 1 << 5; private static final int STATE_START_ELEMENTS = 1 << 6; private static final int STATE_NEXT_ELEMENT = 1 << 7; private static final int STATE_END_ELEMENTS = 1 << 8; private static final int STATE_SET_VALUE = 1 << 9; private static final Set NUMBER_TYPES = new HashSet(Arrays.asList(new Class[]{ byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class, BigInteger.class, BigDecimal.class})); private int mode; private FastStack stack = new FastStack(16); private int expectedStates; /** * Construct a JSON writer. * * @since 1.4 */ public AbstractJsonWriter() { this(new NoNameCoder()); } /** * Construct a JSON writer with a special mode. * * @param mode a bit mask of the mode constants * @since 1.4 */ public AbstractJsonWriter(int mode) { this(mode, new NoNameCoder()); } /** * Construct a JSON writer with a special name coder. * * @param nameCoder the name coder to use * @since 1.4 */ public AbstractJsonWriter(NameCoder nameCoder) { this(0, nameCoder); } /** * Construct a JSON writer with a special mode and name coder. * * @param mode a bit mask of the mode constants * @param nameCoder the name coder to use * @since 1.4 */ public AbstractJsonWriter(int mode, NameCoder nameCoder) { super(nameCoder); this.mode = (mode & EXPLICIT_MODE) > 0 ? EXPLICIT_MODE : mode; stack.push(new StackElement(null, STATE_ROOT)); expectedStates = STATE_START_OBJECT; } public void startNode(String name, Class clazz) { if (name == null) { throw new NullPointerException("name"); } stack.push(new StackElement(clazz, ((StackElement)stack.peek()).status)); handleCheckedStateTransition(STATE_START_OBJECT, name, null); expectedStates = STATE_SET_VALUE | STATE_NEXT_ATTRIBUTE | STATE_START_OBJECT | STATE_NEXT_ELEMENT | STATE_ROOT; } public void startNode(String name) { startNode(name, null); } public void addAttribute(String name, String value) { handleCheckedStateTransition(STATE_NEXT_ATTRIBUTE, name, value); expectedStates = STATE_SET_VALUE | STATE_NEXT_ATTRIBUTE | STATE_START_OBJECT | STATE_NEXT_ELEMENT | STATE_ROOT; } public void setValue(String text) { Class type = ((StackElement)stack.peek()).type; if ((type == Character.class || type == Character.TYPE) && "".equals(text)) { text = "\u0000"; } handleCheckedStateTransition(STATE_SET_VALUE, null, text); expectedStates = STATE_NEXT_ELEMENT | STATE_ROOT; } public void endNode() { int size = stack.size(); int nextState = size > 2 ? STATE_NEXT_ELEMENT : STATE_ROOT; handleCheckedStateTransition(nextState, null, null); stack.pop(); ((StackElement)stack.peek()).status = nextState; expectedStates = STATE_START_OBJECT; if (size > 2) { expectedStates |= STATE_NEXT_ELEMENT | STATE_ROOT; } } private void handleCheckedStateTransition(final int requiredState, final String elementToAdd, final String valueToAdd) { final StackElement stackElement = (StackElement)stack.peek(); if ((expectedStates & requiredState) == 0) { throw new IllegalWriterStateException(stackElement.status, requiredState, elementToAdd); } int currentState = handleStateTransition(stackElement.status, requiredState, elementToAdd, valueToAdd); stackElement.status = currentState; } private int handleStateTransition(int currentState, final int requiredState, final String elementToAdd, final String valueToAdd) { int size = stack.size(); Class currentType = ((StackElement)stack.peek()).type; boolean isArray = size > 1 && isArray(currentType); boolean isArrayElement = size > 1 && isArray(((StackElement)stack.get(size-2)).type); switch(currentState) { case STATE_ROOT: if (requiredState == STATE_START_OBJECT) { currentState = handleStateTransition(STATE_START_ELEMENTS, STATE_START_OBJECT, elementToAdd, null); return requiredState; } throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); case STATE_END_OBJECT: switch(requiredState) { case STATE_START_OBJECT: currentState = handleStateTransition(currentState, STATE_NEXT_ELEMENT, null, null); currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, null); return requiredState; case STATE_NEXT_ELEMENT: nextElement(); return requiredState; case STATE_ROOT: if (((mode & DROP_ROOT_MODE) == 0 || size > 2) && (mode & EXPLICIT_MODE) == 0) { endObject(); } return requiredState; default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } case STATE_START_OBJECT: switch(requiredState) { case STATE_SET_VALUE: case STATE_START_OBJECT: case STATE_ROOT: case STATE_NEXT_ELEMENT: if (!isArrayElement || (mode & EXPLICIT_MODE) != 0) { currentState = handleStateTransition(currentState, STATE_START_ATTRIBUTES, null, null); currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null); } currentState = STATE_START_ELEMENTS; switch(requiredState) { case STATE_SET_VALUE: currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, valueToAdd); break; case STATE_START_OBJECT: currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, null); break; case STATE_ROOT: case STATE_NEXT_ELEMENT: currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, null); currentState = handleStateTransition(currentState, requiredState, null, null); break; } return requiredState; case STATE_START_ATTRIBUTES: if ((mode & EXPLICIT_MODE) != 0) { startArray(); } return requiredState; case STATE_NEXT_ATTRIBUTE: if ((mode & EXPLICIT_MODE) != 0 || !isArray) { currentState = handleStateTransition(currentState, STATE_START_ATTRIBUTES, null, null); currentState = handleStateTransition(currentState, STATE_NEXT_ATTRIBUTE, elementToAdd, valueToAdd); return requiredState; } else { return STATE_START_OBJECT; } default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } case STATE_NEXT_ELEMENT: switch(requiredState) { case STATE_START_OBJECT: nextElement(); if (!isArrayElement && (mode & EXPLICIT_MODE) == 0) { addLabel(encodeNode(elementToAdd)); if ((mode & EXPLICIT_MODE) == 0 && isArray) { startArray(); } return requiredState; } break; case STATE_ROOT: currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); currentState = handleStateTransition(currentState, STATE_ROOT, null, null); return requiredState; case STATE_NEXT_ELEMENT: case STATE_END_OBJECT: currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, null, null); currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); if ((mode & EXPLICIT_MODE) == 0 && !isArray) { endObject(); } return requiredState; case STATE_END_ELEMENTS: if ((mode & EXPLICIT_MODE) == 0 && isArray) { endArray(); } return requiredState; default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } // fall through case STATE_START_ELEMENTS: switch(requiredState) { case STATE_START_OBJECT: if ((mode & DROP_ROOT_MODE) == 0 || size > 2) { if (!isArrayElement || (mode & EXPLICIT_MODE) != 0) { if (!"".equals(valueToAdd)) { startObject(); } addLabel(encodeNode(elementToAdd)); } if ((mode & EXPLICIT_MODE) != 0) { startArray(); } } if ((mode & EXPLICIT_MODE) == 0) { if (isArray) { startArray(); } } return requiredState; case STATE_SET_VALUE: if ((mode & STRICT_MODE) != 0 && size == 2) { throw new ConversionException("Single value cannot be root element"); } if (valueToAdd == null) { if (currentType == Mapper.Null.class) { addValue("null", Type.NULL); } else if ((mode & EXPLICIT_MODE) == 0 && !isArray) { startObject(); endObject(); } } else { if (((mode & IEEE_754_MODE) != 0) && (currentType == long.class || currentType == Long.class)) { long longValue = Long.parseLong(valueToAdd); // JavaScript supports a maximum of 2^53 if (longValue > 9007199254740992L || longValue < -9007199254740992L) { addValue(valueToAdd, Type.STRING); } else { addValue(valueToAdd, getType(currentType)); } } else { addValue(valueToAdd, getType(currentType)); } } return requiredState; case STATE_END_ELEMENTS: case STATE_NEXT_ELEMENT: if ((mode & EXPLICIT_MODE) == 0) { if (isArray) { endArray(); } else { endObject(); } } return requiredState; default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } case STATE_END_ELEMENTS: switch(requiredState) { case STATE_END_OBJECT: if ((mode & EXPLICIT_MODE) != 0) { endArray(); endArray(); endObject(); } return requiredState; default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } case STATE_START_ATTRIBUTES: switch(requiredState) { case STATE_NEXT_ATTRIBUTE: if (elementToAdd != null) { String name = ((mode & EXPLICIT_MODE) == 0 ? "@" : "" ) + elementToAdd; startObject(); addLabel(encodeAttribute(name)); addValue(valueToAdd, Type.STRING); } return requiredState; } // fall through case STATE_NEXT_ATTRIBUTE: switch(requiredState) { case STATE_END_ATTRIBUTES: if ((mode & EXPLICIT_MODE) != 0) { if (currentState == STATE_NEXT_ATTRIBUTE) { endObject(); } endArray(); nextElement(); startArray(); } return requiredState; case STATE_NEXT_ATTRIBUTE: if (!isArray || (mode & EXPLICIT_MODE) != 0) { nextElement(); String name = ((mode & EXPLICIT_MODE) == 0 ? "@" : "" ) + elementToAdd; addLabel(encodeAttribute(name)); addValue(valueToAdd, Type.STRING); } return requiredState; case STATE_SET_VALUE: case STATE_START_OBJECT: currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null); currentState = handleStateTransition(currentState, STATE_START_ELEMENTS, null, null); switch (requiredState) { case STATE_SET_VALUE: if ((mode & EXPLICIT_MODE) == 0) { addLabel(encodeNode("$")); } currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, valueToAdd); if ((mode & EXPLICIT_MODE) == 0) { endObject(); } break; case STATE_START_OBJECT: currentState = handleStateTransition(currentState, STATE_START_OBJECT, elementToAdd, (mode & EXPLICIT_MODE) == 0 ? "" : null); break; case STATE_END_OBJECT: currentState = handleStateTransition(currentState, STATE_SET_VALUE, null, null); currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); break; } return requiredState; case STATE_NEXT_ELEMENT: currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null); currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); return requiredState; case STATE_ROOT: currentState = handleStateTransition(currentState, STATE_END_ATTRIBUTES, null, null); currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); currentState = handleStateTransition(currentState, STATE_ROOT, null, null); return requiredState; default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } case STATE_END_ATTRIBUTES: switch(requiredState) { case STATE_START_ELEMENTS: if ((mode & EXPLICIT_MODE) == 0) { nextElement(); } break; case STATE_END_OBJECT: currentState = handleStateTransition(STATE_START_ELEMENTS, STATE_END_ELEMENTS, null, null); currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); break; default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } return requiredState; case STATE_SET_VALUE: switch(requiredState) { case STATE_END_ELEMENTS: if ((mode & EXPLICIT_MODE) == 0 && isArray) { endArray(); } return requiredState; case STATE_NEXT_ELEMENT: currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, null, null); currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); return requiredState; case STATE_ROOT: currentState = handleStateTransition(currentState, STATE_END_ELEMENTS, null, null); currentState = handleStateTransition(currentState, STATE_END_OBJECT, null, null); currentState = handleStateTransition(currentState, STATE_ROOT, null, null); return requiredState; default: throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } } throw new IllegalWriterStateException(currentState, requiredState, elementToAdd); } /** * Method to return the appropriate JSON type for a Java type. * * @param clazz the type * @return One of the {@link Type} instances * @since 1.4.4 */ protected Type getType(Class clazz) { return clazz == Mapper.Null.class ? Type.NULL : (clazz == Boolean.class || clazz == Boolean.TYPE) ? Type.BOOLEAN : NUMBER_TYPES.contains(clazz) ? Type.NUMBER : Type.STRING; } /** * Method to declare various Java types to be handles as JSON array. * * @param clazz the type * @return true if handles as array * @since 1.4 */ protected boolean isArray(Class clazz) { return clazz != null && (clazz.isArray() || Collection.class.isAssignableFrom(clazz) || Externalizable.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) || Map.Entry.class.isAssignableFrom(clazz)); } /** * Start a JSON object. * * @since 1.4 */ protected abstract void startObject(); /** * Add a label to a JSON object. * * @param name the label's name * @since 1.4 */ protected abstract void addLabel(String name); /** * Add a value to a JSON object's label or to an array. * * @param value the value itself * @param type the JSON type * @since 1.4 */ protected abstract void addValue(String value, Type type); /** * Start a JSON array. * * @since 1.4 */ protected abstract void startArray(); /** * Prepare a JSON object or array for another element. * * @since 1.4 */ protected abstract void nextElement(); /** * End the JSON array. * * @since 1.4 */ protected abstract void endArray(); /** * End the JSON object. * * @since 1.4 */ protected abstract void endObject(); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy