[6756] | 1 | /*
|
---|
| 2 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
---|
| 3 | *
|
---|
| 4 | * Copyright (c) 2012-2013 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
|
---|
| 12 | * or packager/legal/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 packager/legal/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 |
|
---|
| 41 | package org.glassfish.json;
|
---|
| 42 |
|
---|
| 43 | import javax.json.*;
|
---|
| 44 | import javax.json.stream.JsonLocation;
|
---|
| 45 | import javax.json.stream.JsonParser;
|
---|
| 46 | import javax.json.stream.JsonParsingException;
|
---|
| 47 | import java.io.*;
|
---|
| 48 | import java.math.BigDecimal;
|
---|
| 49 | import java.nio.charset.Charset;
|
---|
| 50 | import java.util.*;
|
---|
| 51 |
|
---|
| 52 | import org.glassfish.json.JsonTokenizer.JsonToken;
|
---|
| 53 | import org.glassfish.json.api.BufferPool;
|
---|
| 54 |
|
---|
| 55 | /**
|
---|
| 56 | * JSON parser implementation. NoneContext, ArrayContext, ObjectContext is used
|
---|
| 57 | * to go to next parser state.
|
---|
| 58 | *
|
---|
| 59 | * @author Jitendra Kotamraju
|
---|
| 60 | */
|
---|
| 61 | public class JsonParserImpl implements JsonParser {
|
---|
| 62 |
|
---|
| 63 | private Context currentContext = new NoneContext();
|
---|
| 64 | private Event currentEvent;
|
---|
| 65 |
|
---|
| 66 | private final Stack stack = new Stack();
|
---|
| 67 | private final StateIterator stateIterator;
|
---|
| 68 | private final JsonTokenizer tokenizer;
|
---|
| 69 |
|
---|
| 70 | public JsonParserImpl(Reader reader, BufferPool bufferPool) {
|
---|
| 71 | tokenizer = new JsonTokenizer(reader, bufferPool);
|
---|
| 72 | stateIterator = new StateIterator();
|
---|
| 73 | }
|
---|
| 74 |
|
---|
| 75 | public JsonParserImpl(InputStream in, BufferPool bufferPool) {
|
---|
| 76 | UnicodeDetectingInputStream uin = new UnicodeDetectingInputStream(in);
|
---|
| 77 | tokenizer = new JsonTokenizer(new InputStreamReader(uin, uin.getCharset()), bufferPool);
|
---|
| 78 | stateIterator = new StateIterator();
|
---|
| 79 | }
|
---|
| 80 |
|
---|
| 81 | public JsonParserImpl(InputStream in, Charset encoding, BufferPool bufferPool) {
|
---|
| 82 | tokenizer = new JsonTokenizer(new InputStreamReader(in, encoding), bufferPool);
|
---|
| 83 | stateIterator = new StateIterator();
|
---|
| 84 | }
|
---|
| 85 |
|
---|
| 86 | public String getString() {
|
---|
| 87 | if (currentEvent == Event.KEY_NAME || currentEvent == Event.VALUE_STRING
|
---|
| 88 | || currentEvent == Event.VALUE_NUMBER) {
|
---|
| 89 | return tokenizer.getValue();
|
---|
| 90 | }
|
---|
| 91 | throw new IllegalStateException(
|
---|
| 92 | JsonMessages.PARSER_GETSTRING_ERR(currentEvent));
|
---|
| 93 | }
|
---|
| 94 |
|
---|
| 95 | @Override
|
---|
| 96 | public boolean isIntegralNumber() {
|
---|
| 97 | if (currentEvent != Event.VALUE_NUMBER) {
|
---|
| 98 | throw new IllegalStateException(
|
---|
| 99 | JsonMessages.PARSER_ISINTEGRALNUMBER_ERR(currentEvent));
|
---|
| 100 | }
|
---|
| 101 | return tokenizer.isIntegral();
|
---|
| 102 | }
|
---|
| 103 |
|
---|
| 104 | @Override
|
---|
| 105 | public int getInt() {
|
---|
| 106 | if (currentEvent != Event.VALUE_NUMBER) {
|
---|
| 107 | throw new IllegalStateException(
|
---|
| 108 | JsonMessages.PARSER_GETINT_ERR(currentEvent));
|
---|
| 109 | }
|
---|
| 110 | return tokenizer.getInt();
|
---|
| 111 | }
|
---|
| 112 |
|
---|
| 113 | boolean isDefinitelyInt() {
|
---|
| 114 | return tokenizer.isDefinitelyInt();
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | @Override
|
---|
| 118 | public long getLong() {
|
---|
| 119 | if (currentEvent != Event.VALUE_NUMBER) {
|
---|
| 120 | throw new IllegalStateException(
|
---|
| 121 | JsonMessages.PARSER_GETLONG_ERR(currentEvent));
|
---|
| 122 | }
|
---|
| 123 | return tokenizer.getBigDecimal().longValue();
|
---|
| 124 | }
|
---|
| 125 |
|
---|
| 126 | @Override
|
---|
| 127 | public BigDecimal getBigDecimal() {
|
---|
| 128 | if (currentEvent != Event.VALUE_NUMBER) {
|
---|
| 129 | throw new IllegalStateException(
|
---|
| 130 | JsonMessages.PARSER_GETBIGDECIMAL_ERR(currentEvent));
|
---|
| 131 | }
|
---|
| 132 | return tokenizer.getBigDecimal();
|
---|
| 133 | }
|
---|
| 134 |
|
---|
| 135 | @Override
|
---|
| 136 | public JsonLocation getLocation() {
|
---|
| 137 | return tokenizer.getLocation();
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | public JsonLocation getLastCharLocation() {
|
---|
| 141 | return tokenizer.getLastCharLocation();
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | public boolean hasNext() {
|
---|
| 145 | return stateIterator.hasNext();
|
---|
| 146 | }
|
---|
| 147 |
|
---|
| 148 | public Event next() {
|
---|
| 149 | return stateIterator.next();
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | private class StateIterator implements Iterator<JsonParser.Event> {
|
---|
| 153 |
|
---|
| 154 | @Override
|
---|
| 155 | public boolean hasNext() {
|
---|
| 156 | if (stack.isEmpty() && (currentEvent == Event.END_ARRAY || currentEvent == Event.END_OBJECT)) {
|
---|
| 157 | JsonToken token = tokenizer.nextToken();
|
---|
| 158 | if (token != JsonToken.EOF) {
|
---|
| 159 | throw new JsonParsingException(JsonMessages.PARSER_EXPECTED_EOF(token),
|
---|
| 160 | getLastCharLocation());
|
---|
| 161 | }
|
---|
| 162 | return false;
|
---|
| 163 | }
|
---|
| 164 | return true;
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | @Override
|
---|
| 168 | public JsonParser.Event next() {
|
---|
| 169 | if (!hasNext()) {
|
---|
| 170 | throw new NoSuchElementException();
|
---|
| 171 | }
|
---|
| 172 | return currentEvent = currentContext.getNextEvent();
|
---|
| 173 | }
|
---|
| 174 |
|
---|
| 175 | @Override
|
---|
| 176 | public void remove() {
|
---|
| 177 | throw new UnsupportedOperationException();
|
---|
| 178 | }
|
---|
| 179 | }
|
---|
| 180 |
|
---|
| 181 | public void close() {
|
---|
| 182 | try {
|
---|
| 183 | tokenizer.close();
|
---|
| 184 | } catch (IOException e) {
|
---|
| 185 | throw new JsonException(JsonMessages.PARSER_TOKENIZER_CLOSE_IO(), e);
|
---|
| 186 | }
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | // Using the optimized stack impl as we don't require other things
|
---|
| 190 | // like iterator etc.
|
---|
| 191 | private static final class Stack {
|
---|
| 192 | private Context head;
|
---|
| 193 |
|
---|
| 194 | private void push(Context context) {
|
---|
| 195 | context.next = head;
|
---|
| 196 | head = context;
|
---|
| 197 | }
|
---|
| 198 |
|
---|
| 199 | private Context pop() {
|
---|
| 200 | if (head == null) {
|
---|
| 201 | throw new NoSuchElementException();
|
---|
| 202 | }
|
---|
| 203 | Context temp = head;
|
---|
| 204 | head = head.next;
|
---|
| 205 | return temp;
|
---|
| 206 | }
|
---|
| 207 |
|
---|
| 208 | private boolean isEmpty() {
|
---|
| 209 | return head == null;
|
---|
| 210 | }
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | private abstract class Context {
|
---|
| 214 | Context next;
|
---|
| 215 | abstract Event getNextEvent();
|
---|
| 216 | }
|
---|
| 217 |
|
---|
| 218 | private final class NoneContext extends Context {
|
---|
| 219 | @Override
|
---|
| 220 | public Event getNextEvent() {
|
---|
| 221 | // Handle 1. { 2. [
|
---|
| 222 | JsonToken token = tokenizer.nextToken();
|
---|
| 223 | if (token == JsonToken.CURLYOPEN) {
|
---|
| 224 | stack.push(currentContext);
|
---|
| 225 | currentContext = new ObjectContext();
|
---|
| 226 | return Event.START_OBJECT;
|
---|
| 227 | } else if (token == JsonToken.SQUAREOPEN) {
|
---|
| 228 | stack.push(currentContext);
|
---|
| 229 | currentContext = new ArrayContext();
|
---|
| 230 | return Event.START_ARRAY;
|
---|
| 231 | }
|
---|
| 232 | throw parsingException(token, "[CURLYOPEN, SQUAREOPEN]");
|
---|
| 233 | }
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | private JsonParsingException parsingException(JsonToken token, String expectedTokens) {
|
---|
| 237 | JsonLocation location = getLastCharLocation();
|
---|
| 238 | return new JsonParsingException(
|
---|
| 239 | JsonMessages.PARSER_INVALID_TOKEN(token, location, expectedTokens), location);
|
---|
| 240 | }
|
---|
| 241 |
|
---|
| 242 | private final class ObjectContext extends Context {
|
---|
| 243 | private boolean firstValue = true;
|
---|
| 244 |
|
---|
| 245 | /*
|
---|
| 246 | * Some more things could be optimized. For example, instead
|
---|
| 247 | * tokenizer.nextToken(), one could use tokenizer.matchColonToken() to
|
---|
| 248 | * match ':'. That might optimize a bit, but will fragment nextToken().
|
---|
| 249 | * I think the current one is more readable.
|
---|
| 250 | *
|
---|
| 251 | */
|
---|
| 252 | @Override
|
---|
| 253 | public Event getNextEvent() {
|
---|
| 254 | // Handle 1. } 2. name:value 3. ,name:value
|
---|
| 255 | JsonToken token = tokenizer.nextToken();
|
---|
| 256 | if (currentEvent == Event.KEY_NAME) {
|
---|
| 257 | // Handle 1. :value
|
---|
| 258 | if (token != JsonToken.COLON) {
|
---|
| 259 | throw parsingException(token, "[COLON]");
|
---|
| 260 | }
|
---|
| 261 | token = tokenizer.nextToken();
|
---|
| 262 | if (token.isValue()) {
|
---|
| 263 | return token.getEvent();
|
---|
| 264 | } else if (token == JsonToken.CURLYOPEN) {
|
---|
| 265 | stack.push(currentContext);
|
---|
| 266 | currentContext = new ObjectContext();
|
---|
| 267 | return Event.START_OBJECT;
|
---|
| 268 | } else if (token == JsonToken.SQUAREOPEN) {
|
---|
| 269 | stack.push(currentContext);
|
---|
| 270 | currentContext = new ArrayContext();
|
---|
| 271 | return Event.START_ARRAY;
|
---|
| 272 | }
|
---|
| 273 | throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]");
|
---|
| 274 | } else {
|
---|
| 275 | // Handle 1. } 2. name 3. ,name
|
---|
| 276 | if (token == JsonToken.CURLYCLOSE) {
|
---|
| 277 | currentContext = stack.pop();
|
---|
| 278 | return Event.END_OBJECT;
|
---|
| 279 | }
|
---|
| 280 | if (firstValue) {
|
---|
| 281 | firstValue = false;
|
---|
| 282 | } else {
|
---|
| 283 | if (token != JsonToken.COMMA) {
|
---|
| 284 | throw parsingException(token, "[COMMA]");
|
---|
| 285 | }
|
---|
| 286 | token = tokenizer.nextToken();
|
---|
| 287 | }
|
---|
| 288 | if (token == JsonToken.STRING) {
|
---|
| 289 | return Event.KEY_NAME;
|
---|
| 290 | }
|
---|
| 291 | throw parsingException(token, "[STRING]");
|
---|
| 292 | }
|
---|
| 293 | }
|
---|
| 294 |
|
---|
| 295 | }
|
---|
| 296 |
|
---|
| 297 | private final class ArrayContext extends Context {
|
---|
| 298 | private boolean firstValue = true;
|
---|
| 299 |
|
---|
| 300 | // Handle 1. ] 2. value 3. ,value
|
---|
| 301 | @Override
|
---|
| 302 | public Event getNextEvent() {
|
---|
| 303 | JsonToken token = tokenizer.nextToken();
|
---|
| 304 | if (token == JsonToken.SQUARECLOSE) {
|
---|
| 305 | currentContext = stack.pop();
|
---|
| 306 | return Event.END_ARRAY;
|
---|
| 307 | }
|
---|
| 308 | if (firstValue) {
|
---|
| 309 | firstValue = false;
|
---|
| 310 | } else {
|
---|
| 311 | if (token != JsonToken.COMMA) {
|
---|
| 312 | throw parsingException(token, "[COMMA]");
|
---|
| 313 | }
|
---|
| 314 | token = tokenizer.nextToken();
|
---|
| 315 | }
|
---|
| 316 | if (token.isValue()) {
|
---|
| 317 | return token.getEvent();
|
---|
| 318 | } else if (token == JsonToken.CURLYOPEN) {
|
---|
| 319 | stack.push(currentContext);
|
---|
| 320 | currentContext = new ObjectContext();
|
---|
| 321 | return Event.START_OBJECT;
|
---|
| 322 | } else if (token == JsonToken.SQUAREOPEN) {
|
---|
| 323 | stack.push(currentContext);
|
---|
| 324 | currentContext = new ArrayContext();
|
---|
| 325 | return Event.START_ARRAY;
|
---|
| 326 | }
|
---|
| 327 | throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]");
|
---|
| 328 | }
|
---|
| 329 |
|
---|
| 330 | }
|
---|
| 331 |
|
---|
| 332 | }
|
---|