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 parse(InputStream input)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 parse(FileHandle file)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 parse(final DataInputStream din)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 parse(final DataInputStream din, final byte type)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 parseArray(final DataInputStream din)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 parseObject(final DataInputStream din)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 parseData(final DataInputStream din, final byte blockType)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 parseString(final DataInputStream din, final byte type)199 protected String parseString (final DataInputStream din, final byte type) throws IOException { 200 return parseString(din, false, type); 201 } 202 parseString(final DataInputStream din, final boolean sOptional, final byte type)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 parseSize(final DataInputStream din, final boolean useIntOnError, final long defaultValue)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 parseSize(final DataInputStream din, final byte type, final boolean useIntOnError, final long defaultValue)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 readUChar(final DataInputStream din)234 protected short readUChar (final DataInputStream din) throws IOException { 235 return (short)((short)din.readByte() & 0xFF); 236 } 237 readUShort(final DataInputStream din)238 protected int readUShort (final DataInputStream din) throws IOException { 239 return ((int)din.readShort() & 0xFFFF); 240 } 241 readUInt(final DataInputStream din)242 protected long readUInt (final DataInputStream din) throws IOException { 243 return ((long)din.readInt() & 0xFFFFFFFF); 244 } 245 readString(final DataInputStream din, final long size)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