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