Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1878 | jmachado | 1 | package com.owlike.genson.stream; |
2 | |||
3 | import java.io.IOException; |
||
4 | import java.io.Reader; |
||
5 | import java.io.StringReader; |
||
6 | import java.util.ArrayDeque; |
||
7 | import java.util.Arrays; |
||
8 | import java.util.Deque; |
||
9 | import java.util.HashMap; |
||
10 | import java.util.Map; |
||
11 | |||
12 | import static com.owlike.genson.stream.ValueType.*; |
||
13 | |||
14 | public class JsonReader implements ObjectReader { |
||
15 | |||
16 | protected final static int[] SKIPPED_TOKENS; |
||
17 | |||
18 | static { |
||
19 | SKIPPED_TOKENS = new int[128]; |
||
20 | SKIPPED_TOKENS['\t'] = 1; |
||
21 | SKIPPED_TOKENS['\b'] = 1; |
||
22 | SKIPPED_TOKENS['\n'] = 1; |
||
23 | SKIPPED_TOKENS['\r'] = 1; |
||
24 | SKIPPED_TOKENS['\f'] = 1; |
||
25 | SKIPPED_TOKENS[' '] = 1; |
||
26 | } |
||
27 | |||
28 | private final static boolean[] _NEXT_TOKEN = new boolean[128]; |
||
29 | |||
30 | static { |
||
31 | _NEXT_TOKEN[','] = true; |
||
32 | _NEXT_TOKEN['"'] = true; |
||
33 | _NEXT_TOKEN['{'] = true; |
||
34 | _NEXT_TOKEN['['] = true; |
||
35 | _NEXT_TOKEN['n'] = true; |
||
36 | _NEXT_TOKEN['N'] = true; |
||
37 | _NEXT_TOKEN['-'] = true; |
||
38 | _NEXT_TOKEN['t'] = true; |
||
39 | _NEXT_TOKEN['f'] = true; |
||
40 | _NEXT_TOKEN['T'] = true; |
||
41 | _NEXT_TOKEN['F'] = true; |
||
42 | |||
43 | for (int i = 48; i < 58; i++) |
||
44 | _NEXT_TOKEN[i] = true; |
||
45 | } |
||
46 | |||
47 | private final static char[] _END_OF_LINE = new char[]{'\n'}; |
||
48 | private final static char[] _END_OF_BLOCK_COMMENT = new char[]{'*', '/'}; |
||
49 | |||
50 | /* |
||
51 | * Recupere dans Jackson |
||
52 | */ |
||
53 | private final static int[] sHexValues = new int[128]; |
||
54 | |||
55 | static { |
||
56 | 1.5.0/docs/api/java/util/Arrays.html">Arrays.fill(sHexValues, -1); |
||
57 | for (int i = 0; i < 10; ++i) { |
||
58 | sHexValues['0' + i] = i; |
||
59 | } |
||
60 | for (int i = 0; i < 6; ++i) { |
||
61 | sHexValues['a' + i] = 10 + i; |
||
62 | sHexValues['A' + i] = 10 + i; |
||
63 | } |
||
64 | } |
||
65 | |||
66 | private final static double[] _POWS = new double[309]; |
||
67 | |||
68 | static { |
||
69 | for (int i = 0; i < _POWS.length; i++) |
||
70 | _POWS[i] = 1.5.0/docs/api/java/lang/Math.html">Math.pow(10, i); |
||
71 | } |
||
72 | |||
73 | private final 1.5.0/docs/api/java/io/Reader.html">Reader reader; |
||
74 | private final boolean strictDoubleParse; |
||
75 | private final boolean readMetadata; |
||
76 | private final char[] _buffer = new char[2048]; |
||
77 | private int _col; |
||
78 | private int _row; |
||
79 | private int _cursor; |
||
80 | private int _buflen; |
||
81 | |||
82 | private char[] _stringBuffer = new char[16]; |
||
83 | private int _stringBufferTail = 0; |
||
84 | private int _stringBufferLength = _stringBuffer.length; |
||
85 | |||
86 | private 1.5.0/docs/api/java/lang/String.html">String currentName; |
||
87 | private 1.5.0/docs/api/java/lang/String.html">String _stringValue; |
||
88 | protected long _intValue; |
||
89 | protected double _doubleValue; |
||
90 | private int _numberLen = 0; |
||
91 | private 1.5.0/docs/api/java/lang/Boolean.html">Boolean _booleanValue; |
||
92 | private ValueType valueType; |
||
93 | private boolean _first = true; |
||
94 | private boolean _metadata_readen = false; |
||
95 | private Map<String, String> _metadata = new HashMap<String, String>(5); |
||
96 | |||
97 | private final Deque<JsonType> _ctx = new ArrayDeque<JsonType>(10); |
||
98 | |||
99 | { |
||
100 | _ctx.push(JsonType.EMPTY); |
||
101 | } |
||
102 | |||
103 | public JsonReader(1.5.0/docs/api/java/lang/String.html">String source) { |
||
104 | this(new 1.5.0/docs/api/java/io/StringReader.html">StringReader(source), false, false); |
||
105 | } |
||
106 | |||
107 | public JsonReader(1.5.0/docs/api/java/io/Reader.html">Reader reader, boolean strictDoubleParse, boolean readMetadata) { |
||
108 | this.reader = reader; |
||
109 | this.strictDoubleParse = strictDoubleParse; |
||
110 | this.readMetadata = readMetadata; |
||
111 | |||
112 | char token = (char) readNextToken(false); |
||
113 | if ('[' == token) valueType = ARRAY; |
||
114 | else if ('{' == token) valueType = OBJECT; |
||
115 | else { |
||
116 | // ok lets try to read next |
||
117 | if (_buflen > 0) { |
||
118 | try { |
||
119 | valueType = consumeValue(); |
||
120 | } catch (JsonStreamException jse) { |
||
121 | try { |
||
122 | // we must cheat because consumeString attends the current token to be " |
||
123 | // and will increment the cursor |
||
124 | _cursor = -1; |
||
125 | _col = -1; |
||
126 | _stringValue = consumeString('"'); |
||
127 | valueType = STRING; |
||
128 | } catch (1.5.0/docs/api/java/lang/RuntimeException.html">RuntimeException re) { |
||
129 | throw re; |
||
130 | } |
||
131 | } |
||
132 | if (valueOf(valueType.name()) == null) |
||
133 | throw new JsonStreamException( |
||
134 | "Failed to instanciate reader, first character was " + (char) token |
||
135 | + " when possible characters are [ and {"); |
||
136 | } else valueType = NULL; |
||
137 | } |
||
138 | } |
||
139 | |||
140 | public void close() { |
||
141 | try { |
||
142 | reader.close(); |
||
143 | } catch (1.5.0/docs/api/java/io/IOException.html">IOException e) { |
||
144 | throw new JsonStreamException(e); |
||
145 | } |
||
146 | } |
||
147 | |||
148 | public ObjectReader beginArray() { |
||
149 | begin('[', JsonType.ARRAY); |
||
150 | valueType = ARRAY; |
||
151 | if (_metadata_readen) _metadata.clear(); |
||
152 | return this; |
||
153 | } |
||
154 | |||
155 | public ObjectReader beginObject() { |
||
156 | if (!_metadata_readen) { |
||
157 | begin('{', JsonType.OBJECT); |
||
158 | valueType = OBJECT; |
||
159 | if (readMetadata) { |
||
160 | _metadata.clear(); |
||
161 | readMetadata(); |
||
162 | } |
||
163 | } |
||
164 | return this; |
||
165 | } |
||
166 | |||
167 | public ObjectReader nextObjectMetadata() { |
||
168 | return beginObject(); |
||
169 | } |
||
170 | |||
171 | public ObjectReader endArray() { |
||
172 | end(']', JsonType.ARRAY); |
||
173 | return this; |
||
174 | } |
||
175 | |||
176 | public ObjectReader endObject() { |
||
177 | end('}', JsonType.OBJECT); |
||
178 | _metadata.clear(); |
||
179 | _metadata_readen = false; |
||
180 | return this; |
||
181 | } |
||
182 | |||
183 | public 1.5.0/docs/api/java/lang/String.html">String name() { |
||
184 | if (enclosingType() != JsonType.OBJECT) |
||
185 | throw new JsonStreamException("Only json objects have names, actual type is " |
||
186 | + valueType); |
||
187 | return currentName; |
||
188 | } |
||
189 | |||
190 | public 1.5.0/docs/api/java/lang/String.html">String valueAsString() { |
||
191 | if (STRING == valueType) return _stringValue; |
||
192 | if (INTEGER == valueType) return "" + _intValue; |
||
193 | if (DOUBLE == valueType) return "" + _doubleValue; |
||
194 | if (NULL == valueType) return null; |
||
195 | if (BOOLEAN == valueType) { |
||
196 | return _booleanValue.toString(); |
||
197 | } |
||
198 | throw new JsonStreamException("Readen value can not be converted to String"); |
||
199 | } |
||
200 | |||
201 | public int valueAsInt() { |
||
202 | if (INTEGER == valueType) { |
||
203 | int value = (int) _intValue; |
||
204 | if (value != _intValue) throwNumberFormatException("an int", "overflowing long value " + _intValue); |
||
205 | return value; |
||
206 | } else if (DOUBLE == valueType) { |
||
207 | int value = (int) _doubleValue; |
||
208 | long longValue = (long) _doubleValue; |
||
209 | // lets accept only if the integer part is the same and ignore the decimals |
||
210 | if (value != longValue) { |
||
211 | throwNumberFormatException("an int", "overflowing double value " + _doubleValue); |
||
212 | } |
||
213 | return value; |
||
214 | } else if (STRING == valueType) return 1.5.0/docs/api/java/lang/Integer.html">Integer.parseInt(_stringValue); |
||
215 | |||
216 | throw new JsonStreamException("Expected a int but value is of type " + valueType); |
||
217 | } |
||
218 | |||
219 | public long valueAsLong() { |
||
220 | if (INTEGER == valueType) { |
||
221 | return _intValue; |
||
222 | } else if (DOUBLE == valueType) { |
||
223 | if (1.5.0/docs/api/java/lang/Long.html">Long.MIN_VALUE > _doubleValue || _doubleValue > 1.5.0/docs/api/java/lang/Long.html">Long.MAX_VALUE) { |
||
224 | throwNumberFormatException("a long", "overflowing double value " + _doubleValue); |
||
225 | } |
||
226 | return (long) _doubleValue; |
||
227 | } else if (STRING == valueType) return 1.5.0/docs/api/java/lang/Long.html">Long.parseLong(_stringValue); |
||
228 | |||
229 | throw new JsonStreamException("Expected a long but value is of type " + valueType); |
||
230 | } |
||
231 | |||
232 | public double valueAsDouble() { |
||
233 | if (DOUBLE == valueType) { |
||
234 | return _doubleValue; |
||
235 | } else if (INTEGER == valueType) { |
||
236 | // for the moment lets do that even if there is some precision loss... |
||
237 | return 1.5.0/docs/api/java/lang/Long.html">Long.valueOf(_intValue).doubleValue(); |
||
238 | } else if (STRING == valueType) return 1.5.0/docs/api/java/lang/Double.html">Double.parseDouble(_stringValue); |
||
239 | |||
240 | throw new JsonStreamException("Expected a double but value is of type " + valueType); |
||
241 | } |
||
242 | |||
243 | public short valueAsShort() { |
||
244 | if (INTEGER == valueType) { |
||
245 | short value = (short) _intValue; |
||
246 | if (value != _intValue) throwNumberFormatException("a short", "overflowing long value " + _intValue); |
||
247 | return value; |
||
248 | } else if (DOUBLE == valueType) { |
||
249 | short value = (short) _doubleValue; |
||
250 | long longValue = (long) _doubleValue; |
||
251 | // lets accept only if the integer part is the same and ignore the decimals |
||
252 | if (value != longValue) { |
||
253 | throwNumberFormatException("a short", "overflowing double value " + _doubleValue); |
||
254 | } |
||
255 | return value; |
||
256 | } else if (STRING == valueType) return 1.5.0/docs/api/java/lang/Short.html">Short.parseShort(_stringValue); |
||
257 | |||
258 | throw new JsonStreamException("Expected a short but value is of type " + valueType); |
||
259 | } |
||
260 | |||
261 | public float valueAsFloat() { |
||
262 | if (DOUBLE == valueType) { |
||
263 | return (float) _doubleValue; |
||
264 | } else if (INTEGER == valueType) { |
||
265 | // same as for doubles, for the moment lets do that even if there is some precision |
||
266 | // loss... |
||
267 | return 1.5.0/docs/api/java/lang/Long.html">Long.valueOf(_intValue).floatValue(); |
||
268 | } else if (STRING == valueType) return 1.5.0/docs/api/java/lang/Float.html">Float.parseFloat(_stringValue); |
||
269 | |||
270 | throw new JsonStreamException("Expected a float but value is of type " + valueType); |
||
271 | } |
||
272 | |||
273 | public boolean valueAsBoolean() { |
||
274 | if (BOOLEAN == valueType) { |
||
275 | return _booleanValue; |
||
276 | } |
||
277 | if (STRING == valueType) return 1.5.0/docs/api/java/lang/Boolean.html">Boolean.parseBoolean(_stringValue); |
||
278 | |||
279 | throw new JsonStreamException("Readen value is not of type boolean"); |
||
280 | } |
||
281 | |||
282 | public byte[] valueAsByteArray() { |
||
283 | if (STRING == valueType) return Base64.decodeFast(_stringValue); |
||
284 | if (NULL == valueType) return null; |
||
285 | throw new JsonStreamException("Expected a String to convert to byte array found " |
||
286 | + valueType); |
||
287 | } |
||
288 | |||
289 | public 1.5.0/docs/api/java/lang/String.html">String metadata(1.5.0/docs/api/java/lang/String.html">String name) { |
||
290 | if (!_metadata_readen) nextObjectMetadata(); |
||
291 | return _metadata.get(name); |
||
292 | } |
||
293 | |||
294 | public ValueType getValueType() { |
||
295 | return valueType; |
||
296 | } |
||
297 | |||
298 | public ObjectReader skipValue() { |
||
299 | |||
300 | if (ARRAY == valueType || OBJECT == valueType) { |
||
301 | int balance = 0; |
||
302 | do { |
||
303 | if (ARRAY == valueType) { |
||
304 | beginArray(); |
||
305 | balance++; |
||
306 | } else if (OBJECT == valueType) { |
||
307 | beginObject(); |
||
308 | balance++; |
||
309 | } |
||
310 | |||
311 | while (hasNext()) { |
||
312 | next(); |
||
313 | skipValue(); |
||
314 | } |
||
315 | |||
316 | JsonType type = _ctx.peek(); |
||
317 | if (JsonType.ARRAY == type) { |
||
318 | endArray(); |
||
319 | balance--; |
||
320 | } else if (JsonType.OBJECT == type) { |
||
321 | endObject(); |
||
322 | balance--; |
||
323 | } |
||
324 | } while (balance > 0); |
||
325 | } |
||
326 | |||
327 | return this; |
||
328 | } |
||
329 | |||
330 | public boolean hasNext() { |
||
331 | int token = readNextToken(false); |
||
332 | if (token == -1) return false; |
||
333 | if (token < 128) { |
||
334 | if (_first || _ctx.size() == 1) return _NEXT_TOKEN[token]; |
||
335 | else if (token == ',') return true; |
||
336 | } |
||
337 | |||
338 | return false; |
||
339 | } |
||
340 | |||
341 | public ValueType next() { |
||
342 | _metadata_readen = false; |
||
343 | _first = false; |
||
344 | |||
345 | char ctoken = (char) readNextToken(false); |
||
346 | |||
347 | if (ctoken == ',') { |
||
348 | _cursor++; |
||
349 | ctoken = (char) readNextToken(false); |
||
350 | } else if (JsonType.ARRAY == _ctx.peek()) { |
||
351 | if (ctoken == '[') { |
||
352 | valueType = ARRAY; |
||
353 | return valueType; |
||
354 | } |
||
355 | if (ctoken == '{') { |
||
356 | valueType = OBJECT; |
||
357 | return valueType; |
||
358 | } |
||
359 | } |
||
360 | |||
361 | if (JsonType.OBJECT == _ctx.peek()) { |
||
362 | currentName = consumeString(ctoken); |
||
363 | if (readNextToken(true) != ':') newWrongTokenException(":", _cursor - 1); |
||
364 | } |
||
365 | |||
366 | valueType = consumeValue(); |
||
367 | return valueType; |
||
368 | } |
||
369 | |||
370 | @1.5.0/docs/api/java/lang/Override.html">Override |
||
371 | public JsonType enclosingType() { |
||
372 | return _ctx.peek(); |
||
373 | } |
||
374 | |||
375 | protected final ValueType consumeValue() { |
||
376 | char ctoken = (char) readNextToken(false); |
||
377 | if (ctoken == '"') { |
||
378 | _stringValue = consumeString(ctoken); |
||
379 | return STRING; |
||
380 | } else if (ctoken == '[') return ARRAY; |
||
381 | else if (ctoken == '{') return OBJECT; |
||
382 | else return consumeLiteral(); |
||
383 | } |
||
384 | |||
385 | protected final void readMetadata() { |
||
386 | _metadata_readen = true; |
||
387 | while (true) { |
||
388 | char ctoken = (char) readNextToken(false); |
||
389 | if ('"' != ctoken) return; |
||
390 | ensureBufferHas(2, true); |
||
391 | |||
392 | if ('@' == _buffer[_cursor + 1]) { |
||
393 | _cursor++; |
||
394 | // we cheat here... |
||
395 | 1.5.0/docs/api/java/lang/String.html">String key = consumeString(ctoken); |
||
396 | |||
397 | if (readNextToken(true) != ':') newWrongTokenException(":", _cursor - 1); |
||
398 | |||
399 | 1.5.0/docs/api/java/lang/String.html">String value = consumeString((char) readNextToken(false)); |
||
400 | _metadata.put(key, value); |
||
401 | if (readNextToken(false) == ',') { |
||
402 | _cursor++; |
||
403 | } |
||
404 | } else return; |
||
405 | } |
||
406 | } |
||
407 | |||
408 | protected final void begin(int character, JsonType type) { |
||
409 | int token = readNextToken(true); |
||
410 | // seems unecessary as it is done in readNextToken |
||
411 | // checkIllegalEnd(token); |
||
412 | if (character == token) { |
||
413 | _ctx.push(type); |
||
414 | } else newWrongTokenException("" + (char) character, _cursor - 1); |
||
415 | _first = true; |
||
416 | } |
||
417 | |||
418 | protected final void end(int character, JsonType type) { |
||
419 | int token = readNextToken(true); |
||
420 | // seems unecessary as it is done in readNextToken |
||
421 | // checkIllegalEnd(token); |
||
422 | if (character == token && type == _ctx.peek()) { |
||
423 | _ctx.pop(); |
||
424 | } else newWrongTokenException("" + (char) character, _cursor - 1); |
||
425 | _first = false; |
||
426 | } |
||
427 | |||
428 | protected final 1.5.0/docs/api/java/lang/String.html">String consumeString(int token) { |
||
429 | if (token != '"') newMisplacedTokenException(_cursor); |
||
430 | _cursor++; |
||
431 | boolean buffered = false; |
||
432 | while (true) { |
||
433 | if (fillBuffer(true) < 0) { |
||
434 | // TODO ugly to copy, by the way ensure we don't have the same problem elsewhere |
||
435 | 1.5.0/docs/api/java/lang/String.html">String name = new 1.5.0/docs/api/java/lang/String.html">String(_stringBuffer, 0, _stringBufferTail); |
||
436 | _stringBufferTail = 0; |
||
437 | return name; |
||
438 | } |
||
439 | |||
440 | int i = _cursor; |
||
441 | for (; i < _buflen; ) { |
||
442 | if (_buffer[i] == '"') { |
||
443 | if (buffered) { |
||
444 | writeToStringBuffer(_buffer, _cursor, i - _cursor); |
||
445 | _cursor = i + 1; |
||
446 | 1.5.0/docs/api/java/lang/String.html">String name = new 1.5.0/docs/api/java/lang/String.html">String(_stringBuffer, 0, _stringBufferTail); |
||
447 | _stringBufferTail = 0; |
||
448 | return name; |
||
449 | } else { |
||
450 | 1.5.0/docs/api/java/lang/String.html">String name = new 1.5.0/docs/api/java/lang/String.html">String(_buffer, _cursor, i - _cursor); |
||
451 | _cursor = i + 1; |
||
452 | return name; |
||
453 | } |
||
454 | } else if (_buffer[i] == '\\') { |
||
455 | buffered = true; |
||
456 | writeToStringBuffer(_buffer, _cursor, i - _cursor); |
||
457 | _cursor = i + 1; |
||
458 | if (_stringBufferLength <= (_stringBufferTail + 1)) expandStringBuffer(16); |
||
459 | _stringBuffer[_stringBufferTail++] = readEscaped(); |
||
460 | i = _cursor; |
||
461 | } else i++; |
||
462 | } |
||
463 | |||
464 | buffered = true; |
||
465 | writeToStringBuffer(_buffer, _cursor, i - _cursor); |
||
466 | _cursor = i + 1; |
||
467 | } |
||
468 | } |
||
469 | |||
470 | /** |
||
471 | * Reads the next literal value into _booleanValue, _doubleValue or _intValue and returns the |
||
472 | * type of the readed literal, possible values are : INTEGER, DOUBLE, BOOLEAN, NULL. When |
||
473 | * calling this method the _cursor must be positioned on the first character of the value in the |
||
474 | * _buffer, you can ensure that by calling {@link #readNextToken(boolean)}. |
||
475 | */ |
||
476 | protected final ValueType consumeLiteral() { |
||
477 | int token = _buffer[_cursor]; |
||
478 | |||
479 | if ((token > 47 && token < 58) || token == 45) { |
||
480 | return consumeNumber(); |
||
481 | } else { |
||
482 | ensureBufferHas(4, true); |
||
483 | |||
484 | if ((_buffer[_cursor] == 'N' || _buffer[_cursor] == 'n') |
||
485 | && (_buffer[_cursor + 1] == 'U' || _buffer[_cursor + 1] == 'u') |
||
486 | && (_buffer[_cursor + 2] == 'L' || _buffer[_cursor + 2] == 'l') |
||
487 | && (_buffer[_cursor + 3] == 'L' || _buffer[_cursor + 3] == 'l')) { |
||
488 | _cursor += 4; |
||
489 | return NULL; |
||
490 | } |
||
491 | |||
492 | if ((_buffer[_cursor] == 'T' || _buffer[_cursor] == 't') |
||
493 | && (_buffer[_cursor + 1] == 'R' || _buffer[_cursor + 1] == 'r') |
||
494 | && (_buffer[_cursor + 2] == 'U' || _buffer[_cursor + 2] == 'u') |
||
495 | && (_buffer[_cursor + 3] == 'E' || _buffer[_cursor + 3] == 'e')) { |
||
496 | _booleanValue = true; |
||
497 | _cursor += 4; |
||
498 | return BOOLEAN; |
||
499 | } |
||
500 | ensureBufferHas(5, true); |
||
501 | |||
502 | if ((_buffer[_cursor] == 'F' || _buffer[_cursor] == 'f') |
||
503 | && (_buffer[_cursor + 1] == 'A' || _buffer[_cursor + 1] == 'a') |
||
504 | && (_buffer[_cursor + 2] == 'L' || _buffer[_cursor + 2] == 'l') |
||
505 | && (_buffer[_cursor + 3] == 'S' || _buffer[_cursor + 3] == 's') |
||
506 | && (_buffer[_cursor + 4] == 'E' || _buffer[_cursor + 4] == 'e')) { |
||
507 | _booleanValue = false; |
||
508 | _cursor += 5; |
||
509 | return BOOLEAN; |
||
510 | } else { |
||
511 | throw new JsonStreamException.Builder().message( |
||
512 | "Illegal character around row " + _row + " and column " + (_cursor - _col) |
||
513 | + " awaited for literal (number, boolean or null) but read '" |
||
514 | + _buffer[_cursor] + "'!").create(); |
||
515 | } |
||
516 | } |
||
517 | } |
||
518 | |||
519 | private ValueType consumeNumber() { |
||
520 | // lets fill the buffer and handle differently overflowing values |
||
521 | if ((_buflen - _cursor) < 378) ensureBufferHas(_buflen, false); |
||
522 | |||
523 | int begin = _cursor; |
||
524 | int cur; |
||
525 | boolean negative; |
||
526 | // check the sign |
||
527 | if (_buffer[_cursor] == 45) { |
||
528 | negative = true; |
||
529 | _cursor++; |
||
530 | cur = _cursor; |
||
531 | } else { |
||
532 | negative = false; |
||
533 | cur = _cursor; |
||
534 | } |
||
535 | // just to handle invalid leading 0000 |
||
536 | for (; cur < _buflen && _buffer[cur] == 48; cur++) ; |
||
537 | // Careful we consume the '-' here, but also all the leading 0, even if it is of form 0.xxx |
||
538 | _cursor = cur; |
||
539 | |||
540 | int len = 1.5.0/docs/api/java/lang/Math.html">Math.min(_buflen, cur + 18); |
||
541 | int token; |
||
542 | |||
543 | long longValue = 0; |
||
544 | for (; cur < len; cur++) { |
||
545 | token = _buffer[cur]; |
||
546 | if (token < 48 || token > 57) { |
||
547 | break; |
||
548 | } |
||
549 | longValue = 10L * longValue + (token - 48); |
||
550 | } |
||
551 | |||
552 | if (cur < _buflen) { |
||
553 | // read the maximum we can to fill the long capacity, at max we can read 1 additional |
||
554 | // digit |
||
555 | token = _buffer[cur]; |
||
556 | if (token > 47 && token < 58) { |
||
557 | long newLongValue = 10L * longValue + (token - 48); |
||
558 | if (newLongValue > longValue) { |
||
559 | longValue = newLongValue; |
||
560 | cur++; |
||
561 | } |
||
562 | /* TODO we parse here Long.MIN_VALUE as a double, we had also pb in the writer |
||
563 | * the solution => instead of doing operations on positive numbers, do the inverse, use negative numbers |
||
564 | * as the min negative long holds the - max positive long. |
||
565 | */ |
||
566 | // else we exceed long capacity, just continue and parse it as a double |
||
567 | } |
||
568 | |||
569 | if (cur < _buflen |
||
570 | && ((token = _buffer[cur]) == 46 || token == 101 || token == 69 || (token > 47 && token < 58))) { |
||
571 | |||
572 | if (strictDoubleParse) { |
||
573 | _cursor = begin; |
||
574 | return consumeStrictNumber(cur); |
||
575 | } else return consumeDouble(cur, longValue, negative); |
||
576 | } |
||
577 | } |
||
578 | |||
579 | _intValue = negative ? -longValue : longValue; |
||
580 | _numberLen = cur - _cursor; |
||
581 | _cursor = cur; |
||
582 | return INTEGER; |
||
583 | } |
||
584 | |||
585 | // a custom implementation based on fast path principles |
||
586 | private ValueType consumeDouble(int cur, long longValue, boolean negative) { |
||
587 | int token; |
||
588 | |||
589 | // TODO a verifier : on ne veut compter dans les intDigit que ce qui n'est pas pris dans le |
||
590 | // longValue, idem pour les decimales?? |
||
591 | int intDigits = cur - _cursor; |
||
592 | int valueDigits = longValue > 0 ? cur - _cursor : 0; |
||
593 | |||
594 | // ok we have readen as many characters as a long can contain |
||
595 | // the next readen characters will serve for the digit count for large integer numbers |
||
596 | if (intDigits > 17) { |
||
597 | for (; cur < _buflen; cur++) { |
||
598 | if (_buffer[cur] < 48 || _buffer[cur] > 57) { |
||
599 | break; |
||
600 | } |
||
601 | } |
||
602 | // only if we advanced |
||
603 | if (intDigits != (cur - _cursor)) intDigits = (cur - _cursor) - intDigits; |
||
604 | } else |
||
605 | // TODO check ça m'a l'air bizarre comme cas... |
||
606 | intDigits = 0; |
||
607 | |||
608 | int decimalDigits = 0; |
||
609 | |||
610 | // next possible case is a dot |
||
611 | if (cur < _buflen && _buffer[cur] == 46) { |
||
612 | cur++; |
||
613 | int start = cur; |
||
614 | // if integer part value is zero we could use scientific notation and win in precision |
||
615 | if (longValue == 0) { |
||
616 | // reset the counter as we don't care of the leading zeros |
||
617 | intDigits = 0; |
||
618 | // now lets try to read as many consecutive zeros as available |
||
619 | for (; cur < _buflen && _buffer[cur] == 48; cur++) ; |
||
620 | } |
||
621 | // ok now we must read again into the longValue |
||
622 | int len = 1.5.0/docs/api/java/lang/Math.html">Math.min(_buflen, cur + (18 - valueDigits)); |
||
623 | |||
624 | for (; cur < len; cur++) { |
||
625 | token = _buffer[cur]; |
||
626 | if (token < 48 || token > 57) { |
||
627 | break; |
||
628 | } |
||
629 | longValue = 10L * longValue + (token - 48); |
||
630 | } |
||
631 | decimalDigits = cur - start; |
||
632 | |||
633 | start = cur; |
||
634 | // no need to count digits after the precision we support for decimals |
||
635 | // continue reading digits and just ignore the values, we will truncate |
||
636 | for (; cur < _buflen; cur++) { |
||
637 | if (_buffer[cur] < 48 || _buffer[cur] > 57) { |
||
638 | break; |
||
639 | } |
||
640 | } |
||
641 | } |
||
642 | |||
643 | // now try to read exponent E/e |
||
644 | if ((cur + 1) < _buflen && (_buffer[cur] == 101 || _buffer[cur] == 69)) { |
||
645 | token = _buffer[++cur]; |
||
646 | boolean negativeExp; |
||
647 | // check the sign |
||
648 | if (token == 45) { |
||
649 | negativeExp = true; |
||
650 | cur++; |
||
651 | } else { |
||
652 | if (token == 43) cur++; |
||
653 | negativeExp = false; |
||
654 | } |
||
655 | // read the power of ten |
||
656 | int powValue = 0; |
||
657 | for (; cur < _buflen; cur++) { |
||
658 | token = _buffer[cur]; |
||
659 | if (token < 48 || token > 57) { |
||
660 | break; |
||
661 | } |
||
662 | powValue = 10 * powValue + (token - 48); |
||
663 | } |
||
664 | |||
665 | // depending on the sign put it in the decimal digit counter or integer digit counter |
||
666 | if (negativeExp) decimalDigits += powValue; |
||
667 | else intDigits += powValue; |
||
668 | } |
||
669 | |||
670 | // and now make the difference so it balances well |
||
671 | decimalDigits = intDigits - decimalDigits; |
||
672 | |||
673 | if (decimalDigits < 0) { |
||
674 | /* |
||
675 | * This allows to handle also Double.MIN_VALUE and Double.MIN_NORMAL as |
||
676 | * Double.MIN_NORMAL has 16 decimal digits, 308+16=324, but 10^324 overflows the double |
||
677 | * capacity, its Infinity. So we use this little trick. |
||
678 | */ |
||
679 | if (decimalDigits < -308) { |
||
680 | if (decimalDigits < -325) { |
||
681 | _doubleValue = 0; |
||
682 | } else { |
||
683 | _doubleValue = longValue / _POWS[-decimalDigits - 308]; |
||
684 | _doubleValue = _doubleValue / _POWS[308]; |
||
685 | } |
||
686 | } else { |
||
687 | // better precision than multiplication |
||
688 | _doubleValue = longValue / _POWS[-decimalDigits]; |
||
689 | } |
||
690 | } else { |
||
691 | if (decimalDigits > 308) { |
||
692 | _doubleValue = 1.5.0/docs/api/java/lang/Double.html">Double.POSITIVE_INFINITY; |
||
693 | } else { |
||
694 | // we don't need to apply same technique as before as |
||
695 | // 308-16=292 so it's okay :) |
||
696 | _doubleValue = longValue * _POWS[decimalDigits]; |
||
697 | } |
||
698 | } |
||
699 | |||
700 | _doubleValue = negative ? -_doubleValue : _doubleValue; |
||
701 | _numberLen = cur - _cursor; |
||
702 | _cursor = cur; |
||
703 | return DOUBLE; |
||
704 | } |
||
705 | |||
706 | private final ValueType consumeStrictNumber(int localCursor) { |
||
707 | if (localCursor < _buflen) { |
||
708 | // consider all the remaining integer values as part of the double |
||
709 | for (; localCursor < _buflen; localCursor++) { |
||
710 | if (_buffer[localCursor] < 48 || _buffer[localCursor] > 57) { |
||
711 | break; |
||
712 | } |
||
713 | } |
||
714 | } |
||
715 | |||
716 | if (localCursor < _buflen) { |
||
717 | if (_buffer[localCursor] == '.') { |
||
718 | localCursor = advanceWhileNumeric(++localCursor); |
||
719 | } |
||
720 | } |
||
721 | |||
722 | if (localCursor + 1 < _buflen) { |
||
723 | char ctoken = _buffer[localCursor]; |
||
724 | if (ctoken == 'e' || ctoken == 'E') { |
||
725 | ctoken = _buffer[++localCursor]; |
||
726 | if (ctoken == '-' || ctoken == '+' || (ctoken > 47 && ctoken < 58)) { |
||
727 | localCursor = advanceWhileNumeric(++localCursor); |
||
728 | } else newWrongTokenException("'-' or '+' or '' (same as +)"); |
||
729 | } |
||
730 | } |
||
731 | |||
732 | // it may overflow of the max numeric value we accept to read, it should |
||
733 | if (localCursor >= _buflen) { |
||
734 | |||
735 | } |
||
736 | |||
737 | _numberLen = localCursor - _cursor; |
||
738 | _stringValue = new 1.5.0/docs/api/java/lang/String.html">String(_buffer, _cursor, _numberLen); |
||
739 | _doubleValue = 1.5.0/docs/api/java/lang/Double.html">Double.parseDouble(_stringValue); |
||
740 | _cursor = localCursor; |
||
741 | return DOUBLE; |
||
742 | } |
||
743 | |||
744 | private int advanceWhileNumeric(int cursor) { |
||
745 | for (; cursor < _buflen; cursor++) { |
||
746 | if ((_buffer[cursor] < 48 || _buffer[cursor] > 57)) { |
||
747 | return cursor; |
||
748 | } |
||
749 | } |
||
750 | return cursor; |
||
751 | } |
||
752 | |||
753 | protected final int readNextToken(boolean consume) { |
||
754 | while (true) { |
||
755 | if (_cursor >= _buflen) fillBuffer(true); |
||
756 | |||
757 | for (; _cursor < _buflen; _cursor++) { |
||
758 | int token = _buffer[_cursor]; |
||
759 | if (token < 128 && SKIPPED_TOKENS[token] == 0) { |
||
760 | if (token == '/') { |
||
761 | ensureBufferHas(2, true); |
||
762 | if (_buffer[_cursor + 1] == '*') { |
||
763 | _cursor += 2; |
||
764 | advanceAfter(_END_OF_BLOCK_COMMENT); |
||
765 | } else if (_buffer[_cursor + 1] == '/') { |
||
766 | _cursor += 2; |
||
767 | advanceAfter(_END_OF_LINE); |
||
768 | _row++; |
||
769 | _col = _cursor; |
||
770 | } else newWrongTokenException("start comment // or /*", _cursor); |
||
771 | // don't consume the token |
||
772 | _cursor--; |
||
773 | } else if (consume) { |
||
774 | return _buffer[_cursor++]; |
||
775 | } else return token; |
||
776 | } else if (_buffer[_cursor] == '\n') { |
||
777 | _row++; |
||
778 | _col = _cursor; |
||
779 | } |
||
780 | } |
||
781 | |||
782 | if (_buflen == -1) break; |
||
783 | } |
||
784 | |||
785 | return _cursor < _buflen ? _buffer[_cursor] : -1; |
||
786 | } |
||
787 | |||
788 | private final void advanceAfter(char[] str) { |
||
789 | int strPos = 0; |
||
790 | while (true) { |
||
791 | if (_cursor >= _buflen) fillBuffer(true); |
||
792 | |||
793 | for (; _cursor < _buflen && strPos < str.length; _cursor++) { |
||
794 | if (_buffer[_cursor] == str[strPos]) { |
||
795 | strPos++; |
||
796 | } else strPos = 0; |
||
797 | } |
||
798 | |||
799 | if (strPos == str.length) { |
||
800 | return; |
||
801 | } |
||
802 | if (_buflen == -1) break; |
||
803 | } |
||
804 | } |
||
805 | |||
806 | protected final char readEscaped() { |
||
807 | fillBuffer(true); |
||
808 | |||
809 | char token = _buffer[_cursor++]; |
||
810 | switch (token) { |
||
811 | case 'b': |
||
812 | return '\b'; |
||
813 | case 't': |
||
814 | return '\t'; |
||
815 | case 'n': |
||
816 | return '\n'; |
||
817 | case 'f': |
||
818 | return '\f'; |
||
819 | case 'r': |
||
820 | return '\r'; |
||
821 | case '"': |
||
822 | case '/': |
||
823 | case '\\': |
||
824 | return token; |
||
825 | |||
826 | case 'u': |
||
827 | break; |
||
828 | |||
829 | default: |
||
830 | newMisplacedTokenException(_cursor - 1); |
||
831 | } |
||
832 | |||
833 | int value = 0; |
||
834 | if (ensureBufferHas(4, false) < 0) { |
||
835 | throw new JsonStreamException("Expected 4 hex-digit for character escape sequence!"); |
||
836 | // System.arraycopy(buffer, cursor, buffer, 0, buflen-cursor); |
||
837 | // buflen = buflen - cursor + reader.read(buffer, buflen-cursor, cursor); |
||
838 | // cursor = 0; |
||
839 | } |
||
840 | for (int i = 0; i < 4; ++i) { |
||
841 | int ch = _buffer[_cursor++]; |
||
842 | int digit = (ch > 127) ? -1 : sHexValues[ch]; |
||
843 | if (digit < 0) { |
||
844 | throw new JsonStreamException("Wrong character '" + ch |
||
845 | + "' expected a hex-digit for character escape sequence!"); |
||
846 | } |
||
847 | value = (value << 4) | digit; |
||
848 | } |
||
849 | |||
850 | return (char) value; |
||
851 | } |
||
852 | |||
853 | private final void writeToStringBuffer(final char[] data, final int offset, final int length) { |
||
854 | if (_stringBufferLength <= (_stringBufferTail + length)) { |
||
855 | expandStringBuffer(length); |
||
856 | } |
||
857 | 1.5.0/docs/api/java/lang/System.html">System.arraycopy(data, offset, _stringBuffer, _stringBufferTail, length); |
||
858 | _stringBufferTail += length; |
||
859 | } |
||
860 | |||
861 | private final void expandStringBuffer(int length) { |
||
862 | char[] extendedStringBuffer = new char[_stringBufferLength * 2 + length]; |
||
863 | 1.5.0/docs/api/java/lang/System.html">System.arraycopy(_stringBuffer, 0, extendedStringBuffer, 0, _stringBufferTail); |
||
864 | _stringBuffer = extendedStringBuffer; |
||
865 | _stringBufferLength = extendedStringBuffer.length; |
||
866 | } |
||
867 | |||
868 | private final int fillBuffer(boolean doThrow) { |
||
869 | if (_cursor < _buflen) return _buflen; |
||
870 | try { |
||
871 | _buflen = reader.read(_buffer); |
||
872 | } catch (1.5.0/docs/api/java/io/IOException.html">IOException ioe) { |
||
873 | throw new JsonStreamException(ioe); |
||
874 | } |
||
875 | checkIllegalEnd(_buflen); |
||
876 | _cursor = 0; |
||
877 | _col = 0; |
||
878 | return _buflen; |
||
879 | } |
||
880 | |||
881 | private final int ensureBufferHas(int minLength, boolean doThrow) { |
||
882 | try { |
||
883 | int actualLen = _buflen - _cursor; |
||
884 | if (actualLen >= minLength) { |
||
885 | return actualLen; |
||
886 | } |
||
887 | |||
888 | 1.5.0/docs/api/java/lang/System.html">System.arraycopy(_buffer, _cursor, _buffer, 0, actualLen); |
||
889 | for (; actualLen < minLength; ) { |
||
890 | int len = reader.read(_buffer, actualLen, _buffer.length - actualLen); |
||
891 | if (len < 0) { |
||
892 | if (doThrow) throw new JsonStreamException( |
||
893 | "Encountered end of stream, incomplete json!"); |
||
894 | else { |
||
895 | _buflen = actualLen; |
||
896 | _col = 0; |
||
897 | _cursor = 0; |
||
898 | return len; |
||
899 | } |
||
900 | } |
||
901 | actualLen += len; |
||
902 | } |
||
903 | _buflen = actualLen; |
||
904 | _col = 0; |
||
905 | _cursor = 0; |
||
906 | return actualLen; |
||
907 | } catch (1.5.0/docs/api/java/io/IOException.html">IOException ioe) { |
||
908 | throw new JsonStreamException(ioe); |
||
909 | } |
||
910 | } |
||
911 | |||
912 | protected final boolean isEOF() { |
||
913 | return _buflen < 0 || fillBuffer(false) < 0; |
||
914 | } |
||
915 | |||
916 | private final void newWrongTokenException(1.5.0/docs/api/java/lang/String.html">String awaited) { |
||
917 | newWrongTokenException(awaited, _cursor); |
||
918 | } |
||
919 | |||
920 | public int column() { |
||
921 | int col = _cursor - _col; |
||
922 | return col < 0 ? 0 : col; |
||
923 | } |
||
924 | |||
925 | public int row() { |
||
926 | return _row; |
||
927 | } |
||
928 | |||
929 | private final void newWrongTokenException(1.5.0/docs/api/java/lang/String.html">String awaited, int cursor) { |
||
930 | // otherwise it fails when an error occurs on first character |
||
931 | if (cursor < 0) cursor = 0; |
||
932 | int pos = cursor - _col; |
||
933 | if (pos < 0) pos = 0; |
||
934 | |||
935 | if (_buflen < 0) throw new JsonStreamException( |
||
936 | "Incomplete data or malformed json : encoutered end of stream but expected " |
||
937 | + awaited).niceTrace(); |
||
938 | else throw new JsonStreamException.Builder() |
||
939 | .message( |
||
940 | "Illegal character at row " + _row + " and column " + pos + " expected " |
||
941 | + awaited + " but read '" + _buffer[cursor] + "' !") |
||
942 | .locate(_row, pos).create().niceTrace(); |
||
943 | } |
||
944 | |||
945 | private final void newMisplacedTokenException(int cursor) { |
||
946 | if (_buflen < 0) |
||
947 | throw JsonStreamException.niceTrace(new JsonStreamException( |
||
948 | "Incomplete data or malformed json : encoutered end of stream.")); |
||
949 | |||
950 | if (cursor < 0) cursor = 0; |
||
951 | int pos = cursor - _col; |
||
952 | if (pos < 0) pos = 0; |
||
953 | |||
954 | throw new JsonStreamException.Builder() |
||
955 | .message( |
||
956 | "Encountred misplaced character '" + _buffer[cursor] + "' around row " |
||
957 | + _row + " and column " + pos).locate(_row, pos).create().niceTrace(); |
||
958 | } |
||
959 | |||
960 | private final void checkIllegalEnd(int token) { |
||
961 | if (token == -1 && JsonType.EMPTY != _ctx.peek()) |
||
962 | throw new JsonStreamException( |
||
963 | "Incomplete data or malformed json : encoutered end of stream!").niceTrace(); |
||
964 | } |
||
965 | |||
966 | private void throwNumberFormatException(1.5.0/docs/api/java/lang/String.html">String expected, 1.5.0/docs/api/java/lang/String.html">String encoutered) { |
||
967 | int pos = _cursor - _col - _numberLen; |
||
968 | throw JsonStreamException.niceTrace(new 1.5.0/docs/api/java/lang/NumberFormatException.html">NumberFormatException("Wrong numeric type at row " + _row + " and column " + pos |
||
969 | + ", expected " + expected + " but encoutered " + encoutered)); |
||
970 | } |
||
971 | |||
972 | } |