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.DataInputStream;
     20 import java.io.IOException;
     21 import java.io.InputStream;
     22 import java.io.InputStreamReader;
     23 
     24 import com.badlogic.gdx.Gdx;
     25 import com.badlogic.gdx.files.FileHandle;
     26 import com.badlogic.gdx.utils.JsonWriter.OutputType;
     27 
     28 /** Lightweight UBJSON parser.<br>
     29  * <br>
     30  * The default behavior is to parse the JSON into a DOM containing {@link JsonValue} objects. Extend this class and override
     31  * methods to perform event driven parsing. When this is done, the parse methods will return null. <br>
     32  * @author Xoppa */
     33 public class UBJsonReader implements BaseJsonReader {
     34 	public boolean oldFormat = true;
     35 
     36 	/** Parses the UBJSON from the given stream. <br>
     37 	 * For best performance you should provide buffered streams to this method! */
     38 	@Override
     39 	public JsonValue parse (InputStream input) {
     40 		DataInputStream din = null;
     41 		try {
     42 			din = new DataInputStream(input);
     43 			return parse(din);
     44 		} catch (IOException ex) {
     45 			throw new SerializationException(ex);
     46 		} finally {
     47 			StreamUtils.closeQuietly(din);
     48 		}
     49 	}
     50 
     51 	@Override
     52 	public JsonValue parse (FileHandle file) {
     53 		try {
     54 			return parse(file.read(8192));
     55 		} catch (Exception ex) {
     56 			throw new SerializationException("Error parsing file: " + file, ex);
     57 		}
     58 	}
     59 
     60 	public JsonValue parse (final DataInputStream din) throws IOException {
     61 		try {
     62 			return parse(din, din.readByte());
     63 		} finally {
     64 			StreamUtils.closeQuietly(din);
     65 		}
     66 	}
     67 
     68 	protected JsonValue parse (final DataInputStream din, final byte type) throws IOException {
     69 		if (type == '[')
     70 			return parseArray(din);
     71 		else if (type == '{')
     72 			return parseObject(din);
     73 		else if (type == 'Z')
     74 			return new JsonValue(JsonValue.ValueType.nullValue);
     75 		else if (type == 'T')
     76 			return new JsonValue(true);
     77 		else if (type == 'F')
     78 			return new JsonValue(false);
     79 		else if (type == 'B')
     80 			return new JsonValue((long)readUChar(din));
     81 		else if (type == 'U')
     82 			return new JsonValue((long)readUChar(din));
     83 		else if (type == 'i')
     84 			return new JsonValue(oldFormat ? (long)din.readShort() : (long)din.readByte());
     85 		else if (type == 'I')
     86 			return new JsonValue(oldFormat ? (long)din.readInt() : (long)din.readShort());
     87 		else if (type == 'l')
     88 			return new JsonValue((long)din.readInt());
     89 		else if (type == 'L')
     90 			return new JsonValue(din.readLong());
     91 		else if (type == 'd')
     92 			return new JsonValue(din.readFloat());
     93 		else if (type == 'D')
     94 			return new JsonValue(din.readDouble());
     95 		else if (type == 's' || type == 'S')
     96 			return new JsonValue(parseString(din, type));
     97 		else if (type == 'a' || type == 'A')
     98 			return parseData(din, type);
     99 		else if (type == 'C')
    100 			return new JsonValue(din.readChar());
    101 		else
    102 			throw new GdxRuntimeException("Unrecognized data type");
    103 	}
    104 
    105 	protected JsonValue parseArray (final DataInputStream din) throws IOException {
    106 		JsonValue result = new JsonValue(JsonValue.ValueType.array);
    107 		byte type = din.readByte();
    108 		byte valueType = 0;
    109 		if (type == '$') {
    110 			valueType = din.readByte();
    111 			type = din.readByte();
    112 		}
    113 		long size = -1;
    114 		if (type == '#') {
    115 			size = parseSize(din, false, -1);
    116 			if (size < 0) throw new GdxRuntimeException("Unrecognized data type");
    117 			if (size == 0) return result;
    118 			type = valueType == 0 ? din.readByte() : valueType;
    119 		}
    120 		JsonValue prev = null;
    121 		long c = 0;
    122 		while (din.available() > 0 && type != ']') {
    123 			final JsonValue val = parse(din, type);
    124 			val.parent = result;
    125 			if (prev != null) {
    126 				val.prev = prev;
    127 				prev.next = val;
    128 				result.size++;
    129 			} else {
    130 				result.child = val;
    131 				result.size = 1;
    132 			}
    133 			prev = val;
    134 			if (size > 0 && ++c >= size) break;
    135 			type = valueType == 0 ? din.readByte() : valueType;
    136 		}
    137 		return result;
    138 	}
    139 
    140 	protected JsonValue parseObject (final DataInputStream din) throws IOException {
    141 		JsonValue result = new JsonValue(JsonValue.ValueType.object);
    142 		byte type = din.readByte();
    143 		byte valueType = 0;
    144 		if (type == '$') {
    145 			valueType = din.readByte();
    146 			type = din.readByte();
    147 		}
    148 		long size = -1;
    149 		if (type == '#') {
    150 			size = parseSize(din, false, -1);
    151 			if (size < 0) throw new GdxRuntimeException("Unrecognized data type");
    152 			if (size == 0) return result;
    153 			type = din.readByte();
    154 		}
    155 		JsonValue prev = null;
    156 		long c = 0;
    157 		while (din.available() > 0 && type != '}') {
    158 			final String key = parseString(din, true, type);
    159 			final JsonValue child = parse(din, valueType == 0 ? din.readByte() : valueType);
    160 			child.setName(key);
    161 			child.parent = result;
    162 			if (prev != null) {
    163 				child.prev = prev;
    164 				prev.next = child;
    165 				result.size++;
    166 			} else {
    167 				result.child = child;
    168 				result.size = 1;
    169 			}
    170 			prev = child;
    171 			if (size > 0 && ++c >= size) break;
    172 			type = din.readByte();
    173 		}
    174 		return result;
    175 	}
    176 
    177 	protected JsonValue parseData (final DataInputStream din, final byte blockType) throws IOException {
    178 		// FIXME: a/A is currently not following the specs because it lacks strong typed, fixed sized containers,
    179 		// see: https://github.com/thebuzzmedia/universal-binary-json/issues/27
    180 		final byte dataType = din.readByte();
    181 		final long size = blockType == 'A' ? readUInt(din) : (long)readUChar(din);
    182 		final JsonValue result = new JsonValue(JsonValue.ValueType.array);
    183 		JsonValue prev = null;
    184 		for (long i = 0; i < size; i++) {
    185 			final JsonValue val = parse(din, dataType);
    186 			val.parent = result;
    187 			if (prev != null) {
    188 				prev.next = val;
    189 				result.size++;
    190 			} else {
    191 				result.child = val;
    192 				result.size = 1;
    193 			}
    194 			prev = val;
    195 		}
    196 		return result;
    197 	}
    198 
    199 	protected String parseString (final DataInputStream din, final byte type) throws IOException {
    200 		return parseString(din, false, type);
    201 	}
    202 
    203 	protected String parseString (final DataInputStream din, final boolean sOptional, final byte type) throws IOException {
    204 		long size = -1;
    205 		if (type == 'S') {
    206 			size = parseSize(din, true, -1);
    207 		} else if (type == 's')
    208 			size = (long)readUChar(din);
    209 		else if (sOptional) size = parseSize(din, type, false, -1);
    210 		if (size < 0) throw new GdxRuntimeException("Unrecognized data type, string expected");
    211 		return size > 0 ? readString(din, size) : "";
    212 	}
    213 
    214 	protected long parseSize (final DataInputStream din, final boolean useIntOnError, final long defaultValue) throws IOException {
    215 		return parseSize(din, din.readByte(), useIntOnError, defaultValue);
    216 	}
    217 
    218 	protected long parseSize (final DataInputStream din, final byte type, final boolean useIntOnError, final long defaultValue)
    219 		throws IOException {
    220 		if (type == 'i') return (long)readUChar(din);
    221 		if (type == 'I') return (long)readUShort(din);
    222 		if (type == 'l') return (long)readUInt(din);
    223 		if (type == 'L') return din.readLong();
    224 		if (useIntOnError) {
    225 			long result = (long)((short)type & 0xFF) << 24;
    226 			result |= (long)((short)din.readByte() & 0xFF) << 16;
    227 			result |= (long)((short)din.readByte() & 0xFF) << 8;
    228 			result |= (long)((short)din.readByte() & 0xFF);
    229 			return result;
    230 		}
    231 		return defaultValue;
    232 	}
    233 
    234 	protected short readUChar (final DataInputStream din) throws IOException {
    235 		return (short)((short)din.readByte() & 0xFF);
    236 	}
    237 
    238 	protected int readUShort (final DataInputStream din) throws IOException {
    239 		return ((int)din.readShort() & 0xFFFF);
    240 	}
    241 
    242 	protected long readUInt (final DataInputStream din) throws IOException {
    243 		return ((long)din.readInt() & 0xFFFFFFFF);
    244 	}
    245 
    246 	protected String readString (final DataInputStream din, final long size) throws IOException {
    247 		final byte data[] = new byte[(int)size];
    248 		din.readFully(data);
    249 		return new String(data, "UTF-8");
    250 	}
    251 }
    252