source: josm/trunk/src/org/glassfish/json/JsonParserImpl.java@ 13689

Last change on this file since 13689 was 13231, checked in by Don-vip, 6 years ago

see #15682 - upgrade to JSR 374 (JSON Processing) API 1.1.2

File size: 18.9 KB
Line 
1/*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can
10 * obtain a copy of the License at
11 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12 * or LICENSE.txt. See the License for the specific
13 * language governing permissions and limitations under the License.
14 *
15 * When distributing the software, include this License Header Notice in each
16 * file and include the License file at LICENSE.txt.
17 *
18 * GPL Classpath Exception:
19 * Oracle designates this particular file as subject to the "Classpath"
20 * exception as provided by Oracle in the GPL Version 2 section of the License
21 * file that accompanied this code.
22 *
23 * Modifications:
24 * If applicable, add the following below the License Header, with the fields
25 * enclosed by brackets [] replaced by your own identifying information:
26 * "Portions Copyright [year] [name of copyright owner]"
27 *
28 * Contributor(s):
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41package org.glassfish.json;
42
43import java.io.IOException;
44import java.io.InputStream;
45import java.io.InputStreamReader;
46import java.io.Reader;
47import java.math.BigDecimal;
48import java.nio.charset.Charset;
49import java.util.AbstractMap;
50import java.util.Map;
51import java.util.NoSuchElementException;
52import java.util.Spliterator;
53import java.util.Spliterators;
54import java.util.function.Consumer;
55import java.util.stream.Stream;
56import java.util.stream.StreamSupport;
57
58import javax.json.JsonArray;
59import javax.json.JsonArrayBuilder;
60import javax.json.JsonException;
61import javax.json.JsonObject;
62import javax.json.JsonObjectBuilder;
63import javax.json.JsonValue;
64import javax.json.stream.JsonLocation;
65import javax.json.stream.JsonParser;
66import javax.json.stream.JsonParser.Event;
67import javax.json.stream.JsonParsingException;
68
69import org.glassfish.json.JsonTokenizer.JsonToken;
70import org.glassfish.json.api.BufferPool;
71
72/**
73 * JSON parser implementation. NoneContext, ArrayContext, ObjectContext is used
74 * to go to next parser state.
75 *
76 * @author Jitendra Kotamraju
77 * @author Kin-man Chung
78 */
79public class JsonParserImpl implements JsonParser {
80
81 private final BufferPool bufferPool;
82 private Context currentContext = new NoneContext();
83 private Event currentEvent;
84
85 private final Stack stack = new Stack();
86 private final JsonTokenizer tokenizer;
87
88 public JsonParserImpl(Reader reader, BufferPool bufferPool) {
89 this.bufferPool = bufferPool;
90 tokenizer = new JsonTokenizer(reader, bufferPool);
91 }
92
93 public JsonParserImpl(InputStream in, BufferPool bufferPool) {
94 this.bufferPool = bufferPool;
95 UnicodeDetectingInputStream uin = new UnicodeDetectingInputStream(in);
96 tokenizer = new JsonTokenizer(new InputStreamReader(uin, uin.getCharset()), bufferPool);
97 }
98
99 public JsonParserImpl(InputStream in, Charset encoding, BufferPool bufferPool) {
100 this.bufferPool = bufferPool;
101 tokenizer = new JsonTokenizer(new InputStreamReader(in, encoding), bufferPool);
102 }
103
104 @Override
105 public String getString() {
106 if (currentEvent == Event.KEY_NAME || currentEvent == Event.VALUE_STRING
107 || currentEvent == Event.VALUE_NUMBER) {
108 return tokenizer.getValue();
109 }
110 throw new IllegalStateException(
111 JsonMessages.PARSER_GETSTRING_ERR(currentEvent));
112 }
113
114 @Override
115 public boolean isIntegralNumber() {
116 if (currentEvent != Event.VALUE_NUMBER) {
117 throw new IllegalStateException(
118 JsonMessages.PARSER_ISINTEGRALNUMBER_ERR(currentEvent));
119 }
120 return tokenizer.isIntegral();
121 }
122
123 @Override
124 public int getInt() {
125 if (currentEvent != Event.VALUE_NUMBER) {
126 throw new IllegalStateException(
127 JsonMessages.PARSER_GETINT_ERR(currentEvent));
128 }
129 return tokenizer.getInt();
130 }
131
132 boolean isDefinitelyInt() {
133 return tokenizer.isDefinitelyInt();
134 }
135
136 boolean isDefinitelyLong() {
137 return tokenizer.isDefinitelyLong();
138 }
139
140 @Override
141 public long getLong() {
142 if (currentEvent != Event.VALUE_NUMBER) {
143 throw new IllegalStateException(
144 JsonMessages.PARSER_GETLONG_ERR(currentEvent));
145 }
146 return tokenizer.getLong();
147 }
148
149 @Override
150 public BigDecimal getBigDecimal() {
151 if (currentEvent != Event.VALUE_NUMBER) {
152 throw new IllegalStateException(
153 JsonMessages.PARSER_GETBIGDECIMAL_ERR(currentEvent));
154 }
155 return tokenizer.getBigDecimal();
156 }
157
158 @Override
159 public JsonArray getArray() {
160 if (currentEvent != Event.START_ARRAY) {
161 throw new IllegalStateException(
162 JsonMessages.PARSER_GETARRAY_ERR(currentEvent));
163 }
164 return getArray(new JsonArrayBuilderImpl(bufferPool));
165 }
166
167 @Override
168 public JsonObject getObject() {
169 if (currentEvent != Event.START_OBJECT) {
170 throw new IllegalStateException(
171 JsonMessages.PARSER_GETOBJECT_ERR(currentEvent));
172 }
173 return getObject(new JsonObjectBuilderImpl(bufferPool));
174 }
175
176 @Override
177 public JsonValue getValue() {
178 switch (currentEvent) {
179 case START_ARRAY:
180 return getArray(new JsonArrayBuilderImpl(bufferPool));
181 case START_OBJECT:
182 return getObject(new JsonObjectBuilderImpl(bufferPool));
183 case KEY_NAME:
184 case VALUE_STRING:
185 return new JsonStringImpl(getString());
186 case VALUE_NUMBER:
187 if (isDefinitelyInt()) {
188 return JsonNumberImpl.getJsonNumber(getInt());
189 } else if (isDefinitelyLong()) {
190 return JsonNumberImpl.getJsonNumber(getLong());
191 }
192 return JsonNumberImpl.getJsonNumber(getBigDecimal());
193 case VALUE_TRUE:
194 return JsonValue.TRUE;
195 case VALUE_FALSE:
196 return JsonValue.FALSE;
197 case VALUE_NULL:
198 return JsonValue.NULL;
199 case END_ARRAY:
200 case END_OBJECT:
201 default:
202 throw new IllegalStateException(JsonMessages.PARSER_GETVALUE_ERR(currentEvent));
203 }
204 }
205
206 @Override
207 public Stream<JsonValue> getArrayStream() {
208 if (currentEvent != Event.START_ARRAY) {
209 throw new IllegalStateException(
210 JsonMessages.PARSER_GETARRAY_ERR(currentEvent));
211 }
212 Spliterator<JsonValue> spliterator =
213 new Spliterators.AbstractSpliterator<JsonValue>(Long.MAX_VALUE, Spliterator.ORDERED) {
214 @Override
215 public Spliterator<JsonValue> trySplit() {
216 return null;
217 }
218 @Override
219 public boolean tryAdvance(Consumer<? super JsonValue> action) {
220 if (action == null) {
221 throw new NullPointerException();
222 }
223 if (! hasNext()) {
224 return false;
225 }
226 if (next() == JsonParser.Event.END_ARRAY) {
227 return false;
228 }
229 action.accept(getValue());
230 return true;
231 }
232 };
233 return StreamSupport.stream(spliterator, false);
234 }
235
236 @Override
237 public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
238 if (currentEvent != Event.START_OBJECT) {
239 throw new IllegalStateException(
240 JsonMessages.PARSER_GETOBJECT_ERR(currentEvent));
241 }
242 Spliterator<Map.Entry<String, JsonValue>> spliterator =
243 new Spliterators.AbstractSpliterator<Map.Entry<String, JsonValue>>(Long.MAX_VALUE, Spliterator.ORDERED) {
244 @Override
245 public Spliterator<Map.Entry<String,JsonValue>> trySplit() {
246 return null;
247 }
248 @Override
249 public boolean tryAdvance(Consumer<? super Map.Entry<String, JsonValue>> action) {
250 if (action == null) {
251 throw new NullPointerException();
252 }
253 if (! hasNext()) {
254 return false;
255 }
256 JsonParser.Event e = next();
257 if (e == JsonParser.Event.END_OBJECT) {
258 return false;
259 }
260 if (e != JsonParser.Event.KEY_NAME) {
261 throw new JsonException(JsonMessages.INTERNAL_ERROR());
262 }
263 String key = getString();
264 if (! hasNext()) {
265 throw new JsonException(JsonMessages.INTERNAL_ERROR());
266 }
267 next();
268 JsonValue value = getValue();
269 action.accept(new AbstractMap.SimpleImmutableEntry<>(key, value));
270 return true;
271 }
272 };
273 return StreamSupport.stream(spliterator, false);
274 }
275
276 @Override
277 public Stream<JsonValue> getValueStream() {
278 if (! (currentContext instanceof NoneContext)) {
279 throw new IllegalStateException(
280 JsonMessages.PARSER_GETVALUESTREAM_ERR());
281 }
282 Spliterator<JsonValue> spliterator =
283 new Spliterators.AbstractSpliterator<JsonValue>(Long.MAX_VALUE, Spliterator.ORDERED) {
284 @Override
285 public Spliterator<JsonValue> trySplit() {
286 return null;
287 }
288 @Override
289 public boolean tryAdvance(Consumer<? super JsonValue> action) {
290 if (action == null) {
291 throw new NullPointerException();
292 }
293 if (! hasNext()) {
294 return false;
295 }
296 next();
297 action.accept(getValue());
298 return true;
299 }
300 };
301 return StreamSupport.stream(spliterator, false);
302 }
303
304 @Override
305 public void skipArray() {
306 if (currentEvent == Event.START_ARRAY) {
307 currentContext.skip();
308 currentContext = stack.pop();
309 }
310 }
311
312 @Override
313 public void skipObject() {
314 if (currentEvent == Event.START_OBJECT) {
315 currentContext.skip();
316 currentContext = stack.pop();
317 }
318 }
319
320 private JsonArray getArray(JsonArrayBuilder builder) {
321 while(hasNext()) {
322 JsonParser.Event e = next();
323 if (e == JsonParser.Event.END_ARRAY) {
324 return builder.build();
325 }
326 builder.add(getValue());
327 }
328 throw parsingException(JsonToken.EOF, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL, SQUARECLOSE]");
329 }
330
331 private JsonObject getObject(JsonObjectBuilder builder) {
332 while(hasNext()) {
333 JsonParser.Event e = next();
334 if (e == JsonParser.Event.END_OBJECT) {
335 return builder.build();
336 }
337 String key = getString();
338 next();
339 builder.add(key, getValue());
340 }
341 throw parsingException(JsonToken.EOF, "[STRING, CURLYCLOSE]");
342 }
343
344 @Override
345 public JsonLocation getLocation() {
346 return tokenizer.getLocation();
347 }
348
349 public JsonLocation getLastCharLocation() {
350 return tokenizer.getLastCharLocation();
351 }
352
353 @Override
354 public boolean hasNext() {
355 return tokenizer.hasNextToken();
356 }
357
358 @Override
359 public Event next() {
360 if (!hasNext()) {
361 throw new NoSuchElementException();
362 }
363 return currentEvent = currentContext.getNextEvent();
364 }
365
366 @Override
367 public void close() {
368 try {
369 tokenizer.close();
370 } catch (IOException e) {
371 throw new JsonException(JsonMessages.PARSER_TOKENIZER_CLOSE_IO(), e);
372 }
373 }
374
375 // Using the optimized stack impl as we don't require other things
376 // like iterator etc.
377 private static final class Stack {
378 private Context head;
379
380 private void push(Context context) {
381 context.next = head;
382 head = context;
383 }
384
385 private Context pop() {
386 if (head == null) {
387 throw new NoSuchElementException();
388 }
389 Context temp = head;
390 head = head.next;
391 return temp;
392 }
393
394 private Context peek() {
395 return head;
396 }
397
398 private boolean isEmpty() {
399 return head == null;
400 }
401 }
402
403 private abstract class Context {
404 Context next;
405 abstract Event getNextEvent();
406 abstract void skip();
407 }
408
409 private final class NoneContext extends Context {
410 @Override
411 public Event getNextEvent() {
412 // Handle 1. { 2. [ 3. value
413 JsonToken token = tokenizer.nextToken();
414 if (token == JsonToken.CURLYOPEN) {
415 stack.push(currentContext);
416 currentContext = new ObjectContext();
417 return Event.START_OBJECT;
418 } else if (token == JsonToken.SQUAREOPEN) {
419 stack.push(currentContext);
420 currentContext = new ArrayContext();
421 return Event.START_ARRAY;
422 } else if (token.isValue()) {
423 return token.getEvent();
424 }
425 throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]");
426 }
427
428 @Override
429 void skip() {
430 // no-op
431 }
432 }
433
434 private JsonParsingException parsingException(JsonToken token, String expectedTokens) {
435 JsonLocation location = getLastCharLocation();
436 return new JsonParsingException(
437 JsonMessages.PARSER_INVALID_TOKEN(token, location, expectedTokens), location);
438 }
439
440 private final class ObjectContext extends Context {
441 private boolean firstValue = true;
442
443 /*
444 * Some more things could be optimized. For example, instead
445 * tokenizer.nextToken(), one could use tokenizer.matchColonToken() to
446 * match ':'. That might optimize a bit, but will fragment nextToken().
447 * I think the current one is more readable.
448 *
449 */
450 @Override
451 public Event getNextEvent() {
452 // Handle 1. } 2. name:value 3. ,name:value
453 JsonToken token = tokenizer.nextToken();
454 if (currentEvent == Event.KEY_NAME) {
455 // Handle 1. :value
456 if (token != JsonToken.COLON) {
457 throw parsingException(token, "[COLON]");
458 }
459 token = tokenizer.nextToken();
460 if (token.isValue()) {
461 return token.getEvent();
462 } else if (token == JsonToken.CURLYOPEN) {
463 stack.push(currentContext);
464 currentContext = new ObjectContext();
465 return Event.START_OBJECT;
466 } else if (token == JsonToken.SQUAREOPEN) {
467 stack.push(currentContext);
468 currentContext = new ArrayContext();
469 return Event.START_ARRAY;
470 }
471 throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]");
472 } else {
473 // Handle 1. } 2. name 3. ,name
474 if (token == JsonToken.CURLYCLOSE) {
475 currentContext = stack.pop();
476 return Event.END_OBJECT;
477 }
478 if (firstValue) {
479 firstValue = false;
480 } else {
481 if (token != JsonToken.COMMA) {
482 throw parsingException(token, "[COMMA]");
483 }
484 token = tokenizer.nextToken();
485 }
486 if (token == JsonToken.STRING) {
487 return Event.KEY_NAME;
488 }
489 throw parsingException(token, "[STRING]");
490 }
491 }
492
493 @Override
494 void skip() {
495 JsonToken token;
496 int depth = 1;
497 do {
498 token = tokenizer.nextToken();
499 switch (token) {
500 case CURLYCLOSE:
501 depth--;
502 break;
503 case CURLYOPEN:
504 depth++;
505 break;
506 }
507 } while (!(token == JsonToken.CURLYCLOSE && depth == 0));
508 }
509
510 }
511
512 private final class ArrayContext extends Context {
513 private boolean firstValue = true;
514
515 // Handle 1. ] 2. value 3. ,value
516 @Override
517 public Event getNextEvent() {
518 JsonToken token = tokenizer.nextToken();
519 if (token == JsonToken.SQUARECLOSE) {
520 currentContext = stack.pop();
521 return Event.END_ARRAY;
522 }
523 if (firstValue) {
524 firstValue = false;
525 } else {
526 if (token != JsonToken.COMMA) {
527 throw parsingException(token, "[COMMA]");
528 }
529 token = tokenizer.nextToken();
530 }
531 if (token.isValue()) {
532 return token.getEvent();
533 } else if (token == JsonToken.CURLYOPEN) {
534 stack.push(currentContext);
535 currentContext = new ObjectContext();
536 return Event.START_OBJECT;
537 } else if (token == JsonToken.SQUAREOPEN) {
538 stack.push(currentContext);
539 currentContext = new ArrayContext();
540 return Event.START_ARRAY;
541 }
542 throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]");
543 }
544
545 @Override
546 void skip() {
547 JsonToken token;
548 int depth = 1;
549 do {
550 token = tokenizer.nextToken();
551 switch (token) {
552 case SQUARECLOSE:
553 depth--;
554 break;
555 case SQUAREOPEN:
556 depth++;
557 break;
558 }
559 } while (!(token == JsonToken.SQUARECLOSE && depth == 0));
560 }
561 }
562
563}
Note: See TracBrowser for help on using the repository browser.