blob: a8c70ee973a9066a6a59931dd0e3b8ba9706c8c9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.johnzon.core;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonReader;
import jakarta.json.JsonStructure;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonParser;
import jakarta.json.stream.JsonParsingException;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
public class JsonReaderImpl implements JsonReader {
private final JohnzonJsonParser parser;
private final BufferStrategy.BufferProvider<char[]> bufferProvider;
private JsonProviderImpl provider;
private final DuplicateKeysMode duplicateKeysMode;
private boolean closed = false;
private boolean subStreamReader;
public JsonReaderImpl(final JsonParser parser, final BufferStrategy.BufferProvider<char[]> bufferProvider,
final DuplicateKeysMode duplicateKeysMode, final JsonProviderImpl provider) {
this(parser, false, bufferProvider, duplicateKeysMode, provider);
}
/**
* @param parser json parser.
* @param subStreamReader {@code true} if the Stream already got started and the first
* operation should not be next() but {@link JohnzonJsonParser#current()} instead.
* @param bufferProvider buffer provider for toString of created instances.
*/
public JsonReaderImpl(final JsonParser parser, boolean subStreamReader,
final BufferStrategy.BufferProvider<char[]> bufferProvider,
final DuplicateKeysMode rejectDuplicateKeys, final JsonProviderImpl provider) {
this.bufferProvider = bufferProvider;
this.provider = provider;
if (parser instanceof JohnzonJsonParser) {
this.parser = (JohnzonJsonParser) parser;
} else {
this.parser = new JohnzonJsonParser.JohnzonJsonParserWrapper(parser);
}
this.subStreamReader = subStreamReader;
this.duplicateKeysMode = rejectDuplicateKeys;
}
@Override
public JsonStructure read() {
return JsonStructure.class.cast(readValue());
}
@Override
public JsonValue readValue() {
checkClosed();
if (!parser.hasNext()) {
throw new NothingToRead();
}
final JsonParser.Event next;
if (subStreamReader) {
next = parser.current();
} else {
next = parser.next();
}
switch (next) {
case START_OBJECT:
final JsonObjectBuilder objectBuilder = new JsonObjectBuilderImpl(emptyMap(), bufferProvider, duplicateKeysMode, provider);
parseObject(objectBuilder);
if (!subStreamReader && parser.hasNext()) {
throw new JsonParsingException("Expected end of file", parser.getLocation());
}
return objectBuilder.build();
case START_ARRAY:
final JsonArrayBuilder arrayBuilder = new JsonArrayBuilderImpl(emptyList(), bufferProvider, duplicateKeysMode, provider);
parseArray(arrayBuilder);
if (!subStreamReader && parser.hasNext()) {
throw new JsonParsingException("Expected end of file", parser.getLocation());
}
return arrayBuilder.build();
case VALUE_STRING:
final JsonStringImpl string = new JsonStringImpl(parser.getString());
if (!subStreamReader && parser.hasNext()) {
throw new JsonParsingException("Expected end of file", parser.getLocation());
}
return string;
case VALUE_FALSE:
if (!subStreamReader && parser.hasNext()) {
throw new JsonParsingException("Expected end of file", parser.getLocation());
}
return JsonValue.FALSE;
case VALUE_TRUE:
if (!subStreamReader && parser.hasNext()) {
throw new JsonParsingException("Expected end of file", parser.getLocation());
}
return JsonValue.TRUE;
case VALUE_NULL:
if (!subStreamReader && parser.hasNext()) {
throw new JsonParsingException("Expected end of file", parser.getLocation());
}
return JsonValue.NULL;
case VALUE_NUMBER:
final JsonNumber number;
if (parser.isFitLong()) {
number = new JsonLongImpl(parser.getLong());
} else {
number = new JsonNumberImpl(parser.getBigDecimal(), provider::checkBigDecimalScale);
}
if (!subStreamReader && parser.hasNext()) {
throw new JsonParsingException("Expected end of file", parser.getLocation());
}
return number;
default:
close();
throw new JsonParsingException("Unknown structure: " + next, parser.getLocation());
}
}
@Override
public JsonObject readObject() {
final JsonStructure read = read();
checkType(JsonObject.class, read);
return JsonObject.class.cast(read);
}
@Override
public JsonArray readArray() {
final JsonStructure read = read();
checkType(JsonArray.class, read);
return JsonArray.class.cast(read);
}
private void checkType(final Class<?> expected, final JsonStructure read) {
if (!expected.isInstance(read)) {
throw new JsonParsingException("Expecting " + expected + " but got " + read, parser.getLocation());
}
}
@Override
public void close() {
if (!closed) {
closed = true;
parser.close();
}
}
private void parseObject(final JsonObjectBuilder builder) {
String key = null;
while (parser.hasNext()) {
final JsonParser.Event next = parser.next();
switch (next) {
case KEY_NAME:
key = parser.getString();
break;
case VALUE_STRING:
builder.add(key, new JsonStringImpl(parser.getString()));
break;
case START_OBJECT:
JsonObjectBuilder subObject = new JsonObjectBuilderImpl(emptyMap(), bufferProvider, duplicateKeysMode, provider);
parseObject(subObject);
builder.add(key, subObject);
break;
case START_ARRAY:
JsonArrayBuilder subArray = new JsonArrayBuilderImpl(emptyList(), bufferProvider, duplicateKeysMode, provider);
parseArray(subArray);
builder.add(key, subArray);
break;
case VALUE_NUMBER:
if (parser.isIntegralNumber() && parser.isNotTooLong()) {
builder.add(key, new JsonLongImpl(parser.getLong()));
} else {
builder.add(key, new JsonNumberImpl(parser.getBigDecimal(), provider::checkBigDecimalScale));
}
break;
case VALUE_NULL:
builder.addNull(key);
break;
case VALUE_TRUE:
builder.add(key, true);
break;
case VALUE_FALSE:
builder.add(key, false);
break;
case END_OBJECT:
return;
case END_ARRAY:
throw new JsonParsingException("']', shouldn't occur", parser.getLocation());
default:
throw new JsonParsingException(next.name() + ", shouldn't occur", parser.getLocation());
}
}
}
private void parseArray(final JsonArrayBuilder builder) {
while (parser.hasNext()) {
final JsonParser.Event next = parser.next();
switch (next) {
case VALUE_STRING:
builder.add(new JsonStringImpl(parser.getString()));
break;
case VALUE_NUMBER:
if (parser.isFitLong()) {
builder.add(new JsonLongImpl(parser.getLong()));
} else {
builder.add(new JsonNumberImpl(parser.getBigDecimal(), provider::checkBigDecimalScale));
}
break;
case START_OBJECT:
JsonObjectBuilder subObject = new JsonObjectBuilderImpl(emptyMap(), bufferProvider, duplicateKeysMode, provider);
parseObject(subObject);
builder.add(subObject);
break;
case START_ARRAY:
JsonArrayBuilder subArray = null;
parseArray(subArray = new JsonArrayBuilderImpl(emptyList(), bufferProvider, duplicateKeysMode, provider));
builder.add(subArray);
break;
case END_ARRAY:
return;
case VALUE_NULL:
builder.addNull();
break;
case VALUE_TRUE:
builder.add(true);
break;
case VALUE_FALSE:
builder.add(false);
break;
case KEY_NAME:
throw new JsonParsingException("array doesn't have keys", parser.getLocation());
case END_OBJECT:
throw new JsonParsingException("'}', shouldn't occur", parser.getLocation());
default:
throw new JsonParsingException(next.name() + ", shouldn't occur", parser.getLocation());
}
}
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException("read(), readObject(), readArray() or close() method was already called");
}
}
public static class NothingToRead extends IllegalStateException {
public NothingToRead() {
super("Nothing to read");
}
}
}