/******************************************************************************* * 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.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.JsonWriter.OutputType; /** Lightweight UBJSON parser.<br> * <br> * The default behavior is to parse the JSON into a DOM containing {@link JsonValue} objects. Extend this class and override * methods to perform event driven parsing. When this is done, the parse methods will return null. <br> * @author Xoppa */ public class UBJsonReader implements BaseJsonReader { public boolean oldFormat = true; /** Parses the UBJSON from the given stream. <br> * For best performance you should provide buffered streams to this method! */ @Override public JsonValue parse (InputStream input) { DataInputStream din = null; try { din = new DataInputStream(input); return parse(din); } catch (IOException ex) { throw new SerializationException(ex); } finally { StreamUtils.closeQuietly(din); } } @Override public JsonValue parse (FileHandle file) { try { return parse(file.read(8192)); } catch (Exception ex) { throw new SerializationException("Error parsing file: " + file, ex); } } public JsonValue parse (final DataInputStream din) throws IOException { try { return parse(din, din.readByte()); } finally { StreamUtils.closeQuietly(din); } } protected JsonValue parse (final DataInputStream din, final byte type) throws IOException { if (type == '[') return parseArray(din); else if (type == '{') return parseObject(din); else if (type == 'Z') return new JsonValue(JsonValue.ValueType.nullValue); else if (type == 'T') return new JsonValue(true); else if (type == 'F') return new JsonValue(false); else if (type == 'B') return new JsonValue((long)readUChar(din)); else if (type == 'U') return new JsonValue((long)readUChar(din)); else if (type == 'i') return new JsonValue(oldFormat ? (long)din.readShort() : (long)din.readByte()); else if (type == 'I') return new JsonValue(oldFormat ? (long)din.readInt() : (long)din.readShort()); else if (type == 'l') return new JsonValue((long)din.readInt()); else if (type == 'L') return new JsonValue(din.readLong()); else if (type == 'd') return new JsonValue(din.readFloat()); else if (type == 'D') return new JsonValue(din.readDouble()); else if (type == 's' || type == 'S') return new JsonValue(parseString(din, type)); else if (type == 'a' || type == 'A') return parseData(din, type); else if (type == 'C') return new JsonValue(din.readChar()); else throw new GdxRuntimeException("Unrecognized data type"); } protected JsonValue parseArray (final DataInputStream din) throws IOException { JsonValue result = new JsonValue(JsonValue.ValueType.array); byte type = din.readByte(); byte valueType = 0; if (type == '$') { valueType = din.readByte(); type = din.readByte(); } long size = -1; if (type == '#') { size = parseSize(din, false, -1); if (size < 0) throw new GdxRuntimeException("Unrecognized data type"); if (size == 0) return result; type = valueType == 0 ? din.readByte() : valueType; } JsonValue prev = null; long c = 0; while (din.available() > 0 && type != ']') { final JsonValue val = parse(din, type); val.parent = result; if (prev != null) { val.prev = prev; prev.next = val; result.size++; } else { result.child = val; result.size = 1; } prev = val; if (size > 0 && ++c >= size) break; type = valueType == 0 ? din.readByte() : valueType; } return result; } protected JsonValue parseObject (final DataInputStream din) throws IOException { JsonValue result = new JsonValue(JsonValue.ValueType.object); byte type = din.readByte(); byte valueType = 0; if (type == '$') { valueType = din.readByte(); type = din.readByte(); } long size = -1; if (type == '#') { size = parseSize(din, false, -1); if (size < 0) throw new GdxRuntimeException("Unrecognized data type"); if (size == 0) return result; type = din.readByte(); } JsonValue prev = null; long c = 0; while (din.available() > 0 && type != '}') { final String key = parseString(din, true, type); final JsonValue child = parse(din, valueType == 0 ? din.readByte() : valueType); child.setName(key); child.parent = result; if (prev != null) { child.prev = prev; prev.next = child; result.size++; } else { result.child = child; result.size = 1; } prev = child; if (size > 0 && ++c >= size) break; type = din.readByte(); } return result; } protected JsonValue parseData (final DataInputStream din, final byte blockType) throws IOException { // FIXME: a/A is currently not following the specs because it lacks strong typed, fixed sized containers, // see: https://github.com/thebuzzmedia/universal-binary-json/issues/27 final byte dataType = din.readByte(); final long size = blockType == 'A' ? readUInt(din) : (long)readUChar(din); final JsonValue result = new JsonValue(JsonValue.ValueType.array); JsonValue prev = null; for (long i = 0; i < size; i++) { final JsonValue val = parse(din, dataType); val.parent = result; if (prev != null) { prev.next = val; result.size++; } else { result.child = val; result.size = 1; } prev = val; } return result; } protected String parseString (final DataInputStream din, final byte type) throws IOException { return parseString(din, false, type); } protected String parseString (final DataInputStream din, final boolean sOptional, final byte type) throws IOException { long size = -1; if (type == 'S') { size = parseSize(din, true, -1); } else if (type == 's') size = (long)readUChar(din); else if (sOptional) size = parseSize(din, type, false, -1); if (size < 0) throw new GdxRuntimeException("Unrecognized data type, string expected"); return size > 0 ? readString(din, size) : ""; } protected long parseSize (final DataInputStream din, final boolean useIntOnError, final long defaultValue) throws IOException { return parseSize(din, din.readByte(), useIntOnError, defaultValue); } protected long parseSize (final DataInputStream din, final byte type, final boolean useIntOnError, final long defaultValue) throws IOException { if (type == 'i') return (long)readUChar(din); if (type == 'I') return (long)readUShort(din); if (type == 'l') return (long)readUInt(din); if (type == 'L') return din.readLong(); if (useIntOnError) { long result = (long)((short)type & 0xFF) << 24; result |= (long)((short)din.readByte() & 0xFF) << 16; result |= (long)((short)din.readByte() & 0xFF) << 8; result |= (long)((short)din.readByte() & 0xFF); return result; } return defaultValue; } protected short readUChar (final DataInputStream din) throws IOException { return (short)((short)din.readByte() & 0xFF); } protected int readUShort (final DataInputStream din) throws IOException { return ((int)din.readShort() & 0xFFFF); } protected long readUInt (final DataInputStream din) throws IOException { return ((long)din.readInt() & 0xFFFFFFFF); } protected String readString (final DataInputStream din, final long size) throws IOException { final byte data[] = new byte[(int)size]; din.readFully(data); return new String(data, "UTF-8"); } }