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

com.badlogic.gdx.utils.UBJsonWriter Maven / Gradle / Ivy

There is a newer version: 1.13.1
Show newest version
/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.utils;

import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/** Builder style API for emitting UBJSON.
 * @author Justin Shapcott */
public class UBJsonWriter implements Closeable {

	final DataOutputStream out;

	private JsonObject current;
	private boolean named;
	private final Array stack = new Array();

	public UBJsonWriter (OutputStream out) {
		if (!(out instanceof DataOutputStream)) out = new DataOutputStream(out);
		this.out = (DataOutputStream)out;
	}

	/** Begins a new object container. To finish the object call {@link #pop()}.
	 * @return This writer, for chaining */
	public UBJsonWriter object () throws IOException {
		if (current != null) {
			if (!current.array) {
				if (!named) throw new IllegalStateException("Name must be set.");
				named = false;
			}
		}
		stack.add(current = new JsonObject(false));
		return this;
	}

	/** Begins a new named object container, having the given name. To finish the object call {@link #pop()}.
	 * @return This writer, for chaining */
	public UBJsonWriter object (String name) throws IOException {
		name(name).object();
		return this;
	}

	/** Begins a new array container. To finish the array call {@link #pop()}.
	 * @return this writer, for chaining. */
	public UBJsonWriter array () throws IOException {
		if (current != null) {
			if (!current.array) {
				if (!named) throw new IllegalStateException("Name must be set.");
				named = false;
			}
		}
		stack.add(current = new JsonObject(true));
		return this;
	}

	/** Begins a new named array container, having the given name. To finish the array call {@link #pop()}.
	 * @return this writer, for chaining. */
	public UBJsonWriter array (String name) throws IOException {
		name(name).array();
		return this;
	}

	/** Appends a name for the next object, array, or value.
	 * @return this writer, for chaining */
	public UBJsonWriter name (String name) throws IOException {
		if (current == null || current.array) throw new IllegalStateException("Current item must be an object.");
		byte[] bytes = name.getBytes("UTF-8");
		if (bytes.length <= Byte.MAX_VALUE) {
			out.writeByte('i');
			out.writeByte(bytes.length);
		} else if (bytes.length <= Short.MAX_VALUE) {
			out.writeByte('I');
			out.writeShort(bytes.length);
		} else {
			out.writeByte('l');
			out.writeInt(bytes.length);
		}
		out.write(bytes);
		named = true;
		return this;
	}

	/** Appends a {@code byte} value to the stream. This corresponds to the {@code int8} value type in the UBJSON specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (byte value) throws IOException {
		checkName();
		out.writeByte('i');
		out.writeByte(value);
		return this;
	}

	/** Appends a {@code short} value to the stream. This corresponds to the {@code int16} value type in the UBJSON specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (short value) throws IOException {
		checkName();
		out.writeByte('I');
		out.writeShort(value);
		return this;
	}

	/** Appends an {@code int} value to the stream. This corresponds to the {@code int32} value type in the UBJSON specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (int value) throws IOException {
		checkName();
		out.writeByte('l');
		out.writeInt(value);
		return this;
	}

	/** Appends a {@code long} value to the stream. This corresponds to the {@code int64} value type in the UBJSON specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (long value) throws IOException {
		checkName();
		out.writeByte('L');
		out.writeLong(value);
		return this;
	}

	/** Appends a {@code float} value to the stream. This corresponds to the {@code float32} value type in the UBJSON specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (float value) throws IOException {
		checkName();
		out.writeByte('d');
		out.writeFloat(value);
		return this;
	}

	/** Appends a {@code double} value to the stream. This corresponds to the {@code float64} value type in the UBJSON
	 * specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (double value) throws IOException {
		checkName();
		out.writeByte('D');
		out.writeDouble(value);
		return this;
	}

	/** Appends a {@code boolean} value to the stream. This corresponds to the {@code boolean} value type in the UBJSON
	 * specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (boolean value) throws IOException {
		checkName();
		out.writeByte(value ? 'T' : 'F');
		return this;
	}

	/** Appends a {@code char} value to the stream. Because, in Java, a {@code char} is 16 bytes, this corresponds to the
	 * {@code int16} value type in the UBJSON specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (char value) throws IOException {
		checkName();
		out.writeByte('I');
		out.writeChar(value);
		return this;
	}

	/** Appends a {@code String} value to the stream. This corresponds to the {@code string} value type in the UBJSON specification.
	 * @return this writer, for chaining */
	public UBJsonWriter value (String value) throws IOException {
		checkName();
		byte[] bytes = value.getBytes("UTF-8");
		out.writeByte('S');
		if (bytes.length <= Byte.MAX_VALUE) {
			out.writeByte('i');
			out.writeByte(bytes.length);
		} else if (bytes.length <= Short.MAX_VALUE) {
			out.writeByte('I');
			out.writeShort(bytes.length);
		} else {
			out.writeByte('l');
			out.writeInt(bytes.length);
		}
		out.write(bytes);
		return this;
	}

	/** Appends an optimized {@code byte array} value to the stream. As an optimized array, the {@code int8} value type marker and
	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (byte[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('i');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeByte(values[i]);
		}
		pop(true);
		return this;
	}

	/** Appends an optimized {@code short array} value to the stream. As an optimized array, the {@code int16} value type marker and
	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (short[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('I');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeShort(values[i]);
		}
		pop(true);
		return this;
	}

	/** Appends an optimized {@code int array} value to the stream. As an optimized array, the {@code int32} value type marker and
	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (int[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('l');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeInt(values[i]);
		}
		pop(true);
		return this;
	}

	/** Appends an optimized {@code long array} value to the stream. As an optimized array, the {@code int64} value type marker and
	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (long[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('I');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeLong(values[i]);
		}
		pop(true);
		return this;
	}

	/** Appends an optimized {@code float array} value to the stream. As an optimized array, the {@code float32} value type marker
	 * and element count are encoded once at the array marker instead of repeating the type marker for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (float[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('d');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeFloat(values[i]);
		}
		pop(true);
		return this;
	}

	/** Appends an optimized {@code double array} value to the stream. As an optimized array, the {@code float64} value type marker
	 * and element count are encoded once at the array marker instead of repeating the type marker for each element. element count
	 * are encoded once at the array marker instead of for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (double[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('d');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeDouble(values[i]);
		}
		pop(true);
		return this;
	}

	/** Appends a {@code boolean array} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter value (boolean[] values) throws IOException {
		array();
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeByte(values[i] ? 'T' : 'F');
		}
		pop();
		return this;
	}

	/** Appends an optimized {@code char array} value to the stream. As an optimized array, the {@code int16} value type marker and
	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (char[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('I');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			out.writeChar(values[i]);
		}
		pop(true);
		return this;
	}

	/** Appends an optimized {@code String array} value to the stream. As an optimized array, the {@code String} value type marker
	 * and element count are encoded once at the array marker instead of repeating the type marker for each element.
	 * @return this writer, for chaining */
	public UBJsonWriter value (String[] values) throws IOException {
		array();
		out.writeByte('$');
		out.writeByte('S');
		out.writeByte('#');
		value(values.length);
		for (int i = 0, n = values.length; i < n; i++) {
			byte[] bytes = values[i].getBytes("UTF-8");
			if (bytes.length <= Byte.MAX_VALUE) {
				out.writeByte('i');
				out.writeByte(bytes.length);
			} else if (bytes.length <= Short.MAX_VALUE) {
				out.writeByte('I');
				out.writeShort(bytes.length);
			} else {
				out.writeByte('l');
				out.writeInt(bytes.length);
			}
			out.write(bytes);
		}
		pop(true);
		return this;
	}

	/** Appends the given JsonValue, including all its fields recursively, to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter value (JsonValue value) throws IOException {
		if (value.isObject()) {
			if (value.name != null)
				object(value.name);
			else
				object();
			for (JsonValue child = value.child; child != null; child = child.next)
				value(child);
			pop();
		} else if (value.isArray()) {
			if (value.name != null)
				array(value.name);
			else
				array();
			for (JsonValue child = value.child; child != null; child = child.next)
				value(child);
			pop();
		} else if (value.isBoolean()) {
			if (value.name != null) name(value.name);
			value(value.asBoolean());
		} else if (value.isDouble()) {
			if (value.name != null) name(value.name);
			value(value.asDouble());
		} else if (value.isLong()) {
			if (value.name != null) name(value.name);
			value(value.asLong());
		} else if (value.isString()) {
			if (value.name != null) name(value.name);
			value(value.asString());
		} else if (value.isNull()) {
			if (value.name != null) name(value.name);
			value();
		} else {
			throw new IOException("Unhandled JsonValue type");
		}
		return this;
	}

	/** Appends the object to the stream, if it is a known value type. This is a convenience method that calls through to the
	 * appropriate value method.
	 * @return this writer, for chaining */
	public UBJsonWriter value (Object object) throws IOException {
		if (object == null) {
			return value();
		} else if (object instanceof Number) {
			Number number = (Number)object;
			if (object instanceof Byte) return value(number.byteValue());
			if (object instanceof Short) return value(number.shortValue());
			if (object instanceof Integer) return value(number.intValue());
			if (object instanceof Long) return value(number.longValue());
			if (object instanceof Float) return value(number.floatValue());
			if (object instanceof Double) return value(number.doubleValue());
		} else if (object instanceof Character) {
			return value(((Character)object).charValue());
		} else if (object instanceof CharSequence) {
			return value(object.toString());
		} else
			throw new IOException("Unknown object type.");

		return this;
	}

	/** Appends a {@code null} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter value () throws IOException {
		checkName();
		out.writeByte('Z');
		return this;
	}

	/** Appends a named {@code byte} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, byte value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code short} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, short value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code int} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, int value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code long} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, long value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code float} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, float value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code double} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, double value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code boolean} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, boolean value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code char} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, char value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code String} value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, String value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code byte} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, byte[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code short} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, short[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code int} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, int[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code long} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, long[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code float} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, float[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code double} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, double[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code boolean} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, boolean[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code char} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, char[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code String} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name, String[] value) throws IOException {
		return name(name).value(value);
	}

	/** Appends a named {@code null} array value to the stream.
	 * @return this writer, for chaining */
	public UBJsonWriter set (String name) throws IOException {
		return name(name).value();
	}

	private void checkName () {
		if (current != null) {
			if (!current.array) {
				if (!named) throw new IllegalStateException("Name must be set.");
				named = false;
			}
		}
	}

	/** Ends the current object or array and pops it off of the element stack.
	 * @return This writer, for chaining */
	public UBJsonWriter pop () throws IOException {
		return pop(false);
	}

	protected UBJsonWriter pop (boolean silent) throws IOException {
		if (named) throw new IllegalStateException("Expected an object, array, or value since a name was set.");
		if (silent)
			stack.pop();
		else
			stack.pop().close();
		current = stack.size == 0 ? null : stack.peek();
		return this;
	}

	/** Flushes the underlying stream. This forces any buffered output bytes to be written out to the stream. */
	public void flush () throws IOException {
		out.flush();
	}

	/** Closes the underlying output stream and releases any system resources associated with the stream. */
	public void close () throws IOException {
		while (stack.size > 0)
			pop();
		out.close();
	}

	private class JsonObject {
		final boolean array;

		JsonObject (boolean array) throws IOException {
			this.array = array;
			out.writeByte(array ? '[' : '{');
		}

		void close () throws IOException {
			out.writeByte(array ? ']' : '}');
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy