Home | History | Annotate | Download | only in utils
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  ******************************************************************************/
     16 
     17 package com.badlogic.gdx.utils;
     18 
     19 import java.io.Closeable;
     20 import java.io.DataOutputStream;
     21 import java.io.IOException;
     22 import java.io.OutputStream;
     23 
     24 /** Builder style API for emitting UBJSON.
     25  * @author Justin Shapcott */
     26 public class UBJsonWriter implements Closeable {
     27 
     28 	final DataOutputStream out;
     29 
     30 	private JsonObject current;
     31 	private boolean named;
     32 	private final Array<JsonObject> stack = new Array();
     33 
     34 	public UBJsonWriter (OutputStream out) {
     35 		if (!(out instanceof DataOutputStream)) out = new DataOutputStream(out);
     36 		this.out = (DataOutputStream)out;
     37 	}
     38 
     39 	/** Begins a new object container. To finish the object call {@link #pop()}.
     40 	 * @return This writer, for chaining */
     41 	public UBJsonWriter object () throws IOException {
     42 		if (current != null) {
     43 			if (!current.array) {
     44 				if (!named) throw new IllegalStateException("Name must be set.");
     45 				named = false;
     46 			}
     47 		}
     48 		stack.add(current = new JsonObject(false));
     49 		return this;
     50 	}
     51 
     52 	/** Begins a new named object container, having the given name. To finish the object call {@link #pop()}.
     53 	 * @return This writer, for chaining */
     54 	public UBJsonWriter object (String name) throws IOException {
     55 		name(name).object();
     56 		return this;
     57 	}
     58 
     59 	/** Begins a new array container. To finish the array call {@link #pop()}.
     60 	 * @return this writer, for chaining. */
     61 	public UBJsonWriter array () throws IOException {
     62 		if (current != null) {
     63 			if (!current.array) {
     64 				if (!named) throw new IllegalStateException("Name must be set.");
     65 				named = false;
     66 			}
     67 		}
     68 		stack.add(current = new JsonObject(true));
     69 		return this;
     70 	}
     71 
     72 	/** Begins a new named array container, having the given name. To finish the array call {@link #pop()}.
     73 	 * @return this writer, for chaining. */
     74 	public UBJsonWriter array (String name) throws IOException {
     75 		name(name).array();
     76 		return this;
     77 	}
     78 
     79 	/** Appends a name for the next object, array, or value.
     80 	 * @return this writer, for chaining */
     81 	public UBJsonWriter name (String name) throws IOException {
     82 		if (current == null || current.array) throw new IllegalStateException("Current item must be an object.");
     83 		byte[] bytes = name.getBytes("UTF-8");
     84 		if (bytes.length <= Byte.MAX_VALUE) {
     85 			out.writeByte('i');
     86 			out.writeByte(bytes.length);
     87 		} else if (bytes.length <= Short.MAX_VALUE) {
     88 			out.writeByte('I');
     89 			out.writeShort(bytes.length);
     90 		} else {
     91 			out.writeByte('l');
     92 			out.writeInt(bytes.length);
     93 		}
     94 		out.write(bytes);
     95 		named = true;
     96 		return this;
     97 	}
     98 
     99 	/** Appends a {@code byte} value to the stream. This corresponds to the {@code int8} value type in the UBJSON specification.
    100 	 * @return this writer, for chaining */
    101 	public UBJsonWriter value (byte value) throws IOException {
    102 		checkName();
    103 		out.writeByte('i');
    104 		out.writeByte(value);
    105 		return this;
    106 	}
    107 
    108 	/** Appends a {@code short} value to the stream. This corresponds to the {@code int16} value type in the UBJSON specification.
    109 	 * @return this writer, for chaining */
    110 	public UBJsonWriter value (short value) throws IOException {
    111 		checkName();
    112 		out.writeByte('I');
    113 		out.writeShort(value);
    114 		return this;
    115 	}
    116 
    117 	/** Appends an {@code int} value to the stream. This corresponds to the {@code int32} value type in the UBJSON specification.
    118 	 * @return this writer, for chaining */
    119 	public UBJsonWriter value (int value) throws IOException {
    120 		checkName();
    121 		out.writeByte('l');
    122 		out.writeInt(value);
    123 		return this;
    124 	}
    125 
    126 	/** Appends a {@code long} value to the stream. This corresponds to the {@code int64} value type in the UBJSON specification.
    127 	 * @return this writer, for chaining */
    128 	public UBJsonWriter value (long value) throws IOException {
    129 		checkName();
    130 		out.writeByte('L');
    131 		out.writeLong(value);
    132 		return this;
    133 	}
    134 
    135 	/** Appends a {@code float} value to the stream. This corresponds to the {@code float32} value type in the UBJSON specification.
    136 	 * @return this writer, for chaining */
    137 	public UBJsonWriter value (float value) throws IOException {
    138 		checkName();
    139 		out.writeByte('d');
    140 		out.writeFloat(value);
    141 		return this;
    142 	}
    143 
    144 	/** Appends a {@code double} value to the stream. This corresponds to the {@code float64} value type in the UBJSON
    145 	 * specification.
    146 	 * @return this writer, for chaining */
    147 	public UBJsonWriter value (double value) throws IOException {
    148 		checkName();
    149 		out.writeByte('D');
    150 		out.writeDouble(value);
    151 		return this;
    152 	}
    153 
    154 	/** Appends a {@code boolean} value to the stream. This corresponds to the {@code boolean} value type in the UBJSON
    155 	 * specification.
    156 	 * @return this writer, for chaining */
    157 	public UBJsonWriter value (boolean value) throws IOException {
    158 		checkName();
    159 		out.writeByte(value ? 'T' : 'F');
    160 		return this;
    161 	}
    162 
    163 	/** Appends a {@code char} value to the stream. Because, in Java, a {@code char} is 16 bytes, this corresponds to the
    164 	 * {@code int16} value type in the UBJSON specification.
    165 	 * @return this writer, for chaining */
    166 	public UBJsonWriter value (char value) throws IOException {
    167 		checkName();
    168 		out.writeByte('I');
    169 		out.writeChar(value);
    170 		return this;
    171 	}
    172 
    173 	/** Appends a {@code String} value to the stream. This corresponds to the {@code string} value type in the UBJSON specification.
    174 	 * @return this writer, for chaining */
    175 	public UBJsonWriter value (String value) throws IOException {
    176 		checkName();
    177 		byte[] bytes = value.getBytes("UTF-8");
    178 		out.writeByte('S');
    179 		if (bytes.length <= Byte.MAX_VALUE) {
    180 			out.writeByte('i');
    181 			out.writeByte(bytes.length);
    182 		} else if (bytes.length <= Short.MAX_VALUE) {
    183 			out.writeByte('I');
    184 			out.writeShort(bytes.length);
    185 		} else {
    186 			out.writeByte('l');
    187 			out.writeInt(bytes.length);
    188 		}
    189 		out.write(bytes);
    190 		return this;
    191 	}
    192 
    193 	/** Appends an optimized {@code byte array} value to the stream. As an optimized array, the {@code int8} value type marker and
    194 	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
    195 	 * @return this writer, for chaining */
    196 	public UBJsonWriter value (byte[] values) throws IOException {
    197 		array();
    198 		out.writeByte('$');
    199 		out.writeByte('i');
    200 		out.writeByte('#');
    201 		value(values.length);
    202 		for (int i = 0, n = values.length; i < n; i++) {
    203 			out.writeByte(values[i]);
    204 		}
    205 		pop(true);
    206 		return this;
    207 	}
    208 
    209 	/** Appends an optimized {@code short array} value to the stream. As an optimized array, the {@code int16} value type marker and
    210 	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
    211 	 * @return this writer, for chaining */
    212 	public UBJsonWriter value (short[] values) throws IOException {
    213 		array();
    214 		out.writeByte('$');
    215 		out.writeByte('I');
    216 		out.writeByte('#');
    217 		value(values.length);
    218 		for (int i = 0, n = values.length; i < n; i++) {
    219 			out.writeShort(values[i]);
    220 		}
    221 		pop(true);
    222 		return this;
    223 	}
    224 
    225 	/** Appends an optimized {@code int array} value to the stream. As an optimized array, the {@code int32} value type marker and
    226 	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
    227 	 * @return this writer, for chaining */
    228 	public UBJsonWriter value (int[] values) throws IOException {
    229 		array();
    230 		out.writeByte('$');
    231 		out.writeByte('l');
    232 		out.writeByte('#');
    233 		value(values.length);
    234 		for (int i = 0, n = values.length; i < n; i++) {
    235 			out.writeInt(values[i]);
    236 		}
    237 		pop(true);
    238 		return this;
    239 	}
    240 
    241 	/** Appends an optimized {@code long array} value to the stream. As an optimized array, the {@code int64} value type marker and
    242 	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
    243 	 * @return this writer, for chaining */
    244 	public UBJsonWriter value (long[] values) throws IOException {
    245 		array();
    246 		out.writeByte('$');
    247 		out.writeByte('L');
    248 		out.writeByte('#');
    249 		value(values.length);
    250 		for (int i = 0, n = values.length; i < n; i++) {
    251 			out.writeLong(values[i]);
    252 		}
    253 		pop(true);
    254 		return this;
    255 	}
    256 
    257 	/** Appends an optimized {@code float array} value to the stream. As an optimized array, the {@code float32} value type marker
    258 	 * and element count are encoded once at the array marker instead of repeating the type marker for each element.
    259 	 * @return this writer, for chaining */
    260 	public UBJsonWriter value (float[] values) throws IOException {
    261 		array();
    262 		out.writeByte('$');
    263 		out.writeByte('d');
    264 		out.writeByte('#');
    265 		value(values.length);
    266 		for (int i = 0, n = values.length; i < n; i++) {
    267 			out.writeFloat(values[i]);
    268 		}
    269 		pop(true);
    270 		return this;
    271 	}
    272 
    273 	/** Appends an optimized {@code double array} value to the stream. As an optimized array, the {@code float64} value type marker
    274 	 * and element count are encoded once at the array marker instead of repeating the type marker for each element. element count
    275 	 * are encoded once at the array marker instead of for each element.
    276 	 * @return this writer, for chaining */
    277 	public UBJsonWriter value (double[] values) throws IOException {
    278 		array();
    279 		out.writeByte('$');
    280 		out.writeByte('D');
    281 		out.writeByte('#');
    282 		value(values.length);
    283 		for (int i = 0, n = values.length; i < n; i++) {
    284 			out.writeDouble(values[i]);
    285 		}
    286 		pop(true);
    287 		return this;
    288 	}
    289 
    290 	/** Appends a {@code boolean array} value to the stream.
    291 	 * @return this writer, for chaining */
    292 	public UBJsonWriter value (boolean[] values) throws IOException {
    293 		array();
    294 		for (int i = 0, n = values.length; i < n; i++) {
    295 			out.writeByte(values[i] ? 'T' : 'F');
    296 		}
    297 		pop();
    298 		return this;
    299 	}
    300 
    301 	/** Appends an optimized {@code char array} value to the stream. As an optimized array, the {@code int16} value type marker and
    302 	 * element count are encoded once at the array marker instead of repeating the type marker for each element.
    303 	 * @return this writer, for chaining */
    304 	public UBJsonWriter value (char[] values) throws IOException {
    305 		array();
    306 		out.writeByte('$');
    307 		out.writeByte('C');
    308 		out.writeByte('#');
    309 		value(values.length);
    310 		for (int i = 0, n = values.length; i < n; i++) {
    311 			out.writeChar(values[i]);
    312 		}
    313 		pop(true);
    314 		return this;
    315 	}
    316 
    317 	/** Appends an optimized {@code String array} value to the stream. As an optimized array, the {@code String} value type marker
    318 	 * and element count are encoded once at the array marker instead of repeating the type marker for each element.
    319 	 * @return this writer, for chaining */
    320 	public UBJsonWriter value (String[] values) throws IOException {
    321 		array();
    322 		out.writeByte('$');
    323 		out.writeByte('S');
    324 		out.writeByte('#');
    325 		value(values.length);
    326 		for (int i = 0, n = values.length; i < n; i++) {
    327 			byte[] bytes = values[i].getBytes("UTF-8");
    328 			if (bytes.length <= Byte.MAX_VALUE) {
    329 				out.writeByte('i');
    330 				out.writeByte(bytes.length);
    331 			} else if (bytes.length <= Short.MAX_VALUE) {
    332 				out.writeByte('I');
    333 				out.writeShort(bytes.length);
    334 			} else {
    335 				out.writeByte('l');
    336 				out.writeInt(bytes.length);
    337 			}
    338 			out.write(bytes);
    339 		}
    340 		pop(true);
    341 		return this;
    342 	}
    343 
    344 	/** Appends the given JsonValue, including all its fields recursively, to the stream.
    345 	 * @return this writer, for chaining */
    346 	public UBJsonWriter value (JsonValue value) throws IOException {
    347 		if (value.isObject()) {
    348 			if (value.name != null)
    349 				object(value.name);
    350 			else
    351 				object();
    352 			for (JsonValue child = value.child; child != null; child = child.next)
    353 				value(child);
    354 			pop();
    355 		} else if (value.isArray()) {
    356 			if (value.name != null)
    357 				array(value.name);
    358 			else
    359 				array();
    360 			for (JsonValue child = value.child; child != null; child = child.next)
    361 				value(child);
    362 			pop();
    363 		} else if (value.isBoolean()) {
    364 			if (value.name != null) name(value.name);
    365 			value(value.asBoolean());
    366 		} else if (value.isDouble()) {
    367 			if (value.name != null) name(value.name);
    368 			value(value.asDouble());
    369 		} else if (value.isLong()) {
    370 			if (value.name != null) name(value.name);
    371 			value(value.asLong());
    372 		} else if (value.isString()) {
    373 			if (value.name != null) name(value.name);
    374 			value(value.asString());
    375 		} else if (value.isNull()) {
    376 			if (value.name != null) name(value.name);
    377 			value();
    378 		} else {
    379 			throw new IOException("Unhandled JsonValue type");
    380 		}
    381 		return this;
    382 	}
    383 
    384 	/** Appends the object to the stream, if it is a known value type. This is a convenience method that calls through to the
    385 	 * appropriate value method.
    386 	 * @return this writer, for chaining */
    387 	public UBJsonWriter value (Object object) throws IOException {
    388 		if (object == null) {
    389 			return value();
    390 		} else if (object instanceof Number) {
    391 			Number number = (Number)object;
    392 			if (object instanceof Byte) return value(number.byteValue());
    393 			if (object instanceof Short) return value(number.shortValue());
    394 			if (object instanceof Integer) return value(number.intValue());
    395 			if (object instanceof Long) return value(number.longValue());
    396 			if (object instanceof Float) return value(number.floatValue());
    397 			if (object instanceof Double) return value(number.doubleValue());
    398 		} else if (object instanceof Character) {
    399 			return value(((Character)object).charValue());
    400 		} else if (object instanceof CharSequence) {
    401 			return value(object.toString());
    402 		} else
    403 			throw new IOException("Unknown object type.");
    404 
    405 		return this;
    406 	}
    407 
    408 	/** Appends a {@code null} value to the stream.
    409 	 * @return this writer, for chaining */
    410 	public UBJsonWriter value () throws IOException {
    411 		checkName();
    412 		out.writeByte('Z');
    413 		return this;
    414 	}
    415 
    416 	/** Appends a named {@code byte} value to the stream.
    417 	 * @return this writer, for chaining */
    418 	public UBJsonWriter set (String name, byte value) throws IOException {
    419 		return name(name).value(value);
    420 	}
    421 
    422 	/** Appends a named {@code short} value to the stream.
    423 	 * @return this writer, for chaining */
    424 	public UBJsonWriter set (String name, short value) throws IOException {
    425 		return name(name).value(value);
    426 	}
    427 
    428 	/** Appends a named {@code int} value to the stream.
    429 	 * @return this writer, for chaining */
    430 	public UBJsonWriter set (String name, int value) throws IOException {
    431 		return name(name).value(value);
    432 	}
    433 
    434 	/** Appends a named {@code long} value to the stream.
    435 	 * @return this writer, for chaining */
    436 	public UBJsonWriter set (String name, long value) throws IOException {
    437 		return name(name).value(value);
    438 	}
    439 
    440 	/** Appends a named {@code float} value to the stream.
    441 	 * @return this writer, for chaining */
    442 	public UBJsonWriter set (String name, float value) throws IOException {
    443 		return name(name).value(value);
    444 	}
    445 
    446 	/** Appends a named {@code double} value to the stream.
    447 	 * @return this writer, for chaining */
    448 	public UBJsonWriter set (String name, double value) throws IOException {
    449 		return name(name).value(value);
    450 	}
    451 
    452 	/** Appends a named {@code boolean} value to the stream.
    453 	 * @return this writer, for chaining */
    454 	public UBJsonWriter set (String name, boolean value) throws IOException {
    455 		return name(name).value(value);
    456 	}
    457 
    458 	/** Appends a named {@code char} value to the stream.
    459 	 * @return this writer, for chaining */
    460 	public UBJsonWriter set (String name, char value) throws IOException {
    461 		return name(name).value(value);
    462 	}
    463 
    464 	/** Appends a named {@code String} value to the stream.
    465 	 * @return this writer, for chaining */
    466 	public UBJsonWriter set (String name, String value) throws IOException {
    467 		return name(name).value(value);
    468 	}
    469 
    470 	/** Appends a named {@code byte} array value to the stream.
    471 	 * @return this writer, for chaining */
    472 	public UBJsonWriter set (String name, byte[] value) throws IOException {
    473 		return name(name).value(value);
    474 	}
    475 
    476 	/** Appends a named {@code short} array value to the stream.
    477 	 * @return this writer, for chaining */
    478 	public UBJsonWriter set (String name, short[] value) throws IOException {
    479 		return name(name).value(value);
    480 	}
    481 
    482 	/** Appends a named {@code int} array value to the stream.
    483 	 * @return this writer, for chaining */
    484 	public UBJsonWriter set (String name, int[] value) throws IOException {
    485 		return name(name).value(value);
    486 	}
    487 
    488 	/** Appends a named {@code long} array value to the stream.
    489 	 * @return this writer, for chaining */
    490 	public UBJsonWriter set (String name, long[] value) throws IOException {
    491 		return name(name).value(value);
    492 	}
    493 
    494 	/** Appends a named {@code float} array value to the stream.
    495 	 * @return this writer, for chaining */
    496 	public UBJsonWriter set (String name, float[] value) throws IOException {
    497 		return name(name).value(value);
    498 	}
    499 
    500 	/** Appends a named {@code double} array value to the stream.
    501 	 * @return this writer, for chaining */
    502 	public UBJsonWriter set (String name, double[] value) throws IOException {
    503 		return name(name).value(value);
    504 	}
    505 
    506 	/** Appends a named {@code boolean} array value to the stream.
    507 	 * @return this writer, for chaining */
    508 	public UBJsonWriter set (String name, boolean[] value) throws IOException {
    509 		return name(name).value(value);
    510 	}
    511 
    512 	/** Appends a named {@code char} array value to the stream.
    513 	 * @return this writer, for chaining */
    514 	public UBJsonWriter set (String name, char[] value) throws IOException {
    515 		return name(name).value(value);
    516 	}
    517 
    518 	/** Appends a named {@code String} array value to the stream.
    519 	 * @return this writer, for chaining */
    520 	public UBJsonWriter set (String name, String[] value) throws IOException {
    521 		return name(name).value(value);
    522 	}
    523 
    524 	/** Appends a named {@code null} array value to the stream.
    525 	 * @return this writer, for chaining */
    526 	public UBJsonWriter set (String name) throws IOException {
    527 		return name(name).value();
    528 	}
    529 
    530 	private void checkName () {
    531 		if (current != null) {
    532 			if (!current.array) {
    533 				if (!named) throw new IllegalStateException("Name must be set.");
    534 				named = false;
    535 			}
    536 		}
    537 	}
    538 
    539 	/** Ends the current object or array and pops it off of the element stack.
    540 	 * @return This writer, for chaining */
    541 	public UBJsonWriter pop () throws IOException {
    542 		return pop(false);
    543 	}
    544 
    545 	protected UBJsonWriter pop (boolean silent) throws IOException {
    546 		if (named) throw new IllegalStateException("Expected an object, array, or value since a name was set.");
    547 		if (silent)
    548 			stack.pop();
    549 		else
    550 			stack.pop().close();
    551 		current = stack.size == 0 ? null : stack.peek();
    552 		return this;
    553 	}
    554 
    555 	/** Flushes the underlying stream. This forces any buffered output bytes to be written out to the stream. */
    556 	public void flush () throws IOException {
    557 		out.flush();
    558 	}
    559 
    560 	/** Closes the underlying output stream and releases any system resources associated with the stream. */
    561 	public void close () throws IOException {
    562 		while (stack.size > 0)
    563 			pop();
    564 		out.close();
    565 	}
    566 
    567 	private class JsonObject {
    568 		final boolean array;
    569 
    570 		JsonObject (boolean array) throws IOException {
    571 			this.array = array;
    572 			out.writeByte(array ? '[' : '{');
    573 		}
    574 
    575 		void close () throws IOException {
    576 			out.writeByte(array ? ']' : '}');
    577 		}
    578 	}
    579 
    580 }
    581