co.elastic.clients.json.jackson.JacksonJsonpParser Maven / Gradle / Ivy
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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 co.elastic.clients.json.jackson;
import co.elastic.clients.json.BufferingJsonParser;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.json.LookAheadJsonParser;
import co.elastic.clients.json.UnexpectedJsonEventException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.util.JsonParserSequence;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonLocation;
import jakarta.json.stream.JsonParser;
import jakarta.json.stream.JsonParsingException;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.AbstractMap;
import java.util.EnumMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
/**
* A JSONP parser implementation on top of Jackson.
*
* Warning: this implementation isn't fully compliant with the JSONP specification: calling {@link #hasNext()}
* moves forward the underlying Jackson parser as Jackson doesn't provide an equivalent method. This means no value
* getter method (e.g. {@link #getInt()} or {@link #getString()} should be called until the next call to {@link #next()}.
* Such calls will throw an {@code IllegalStateException}.
*/
public class JacksonJsonpParser implements LookAheadJsonParser, BufferingJsonParser {
private final com.fasterxml.jackson.core.JsonParser parser;
private final JacksonJsonpMapper mapper;
private boolean hasNextWasCalled = false;
private static final EnumMap tokenToEvent;
static {
tokenToEvent = new EnumMap<>(JsonToken.class);
tokenToEvent.put(JsonToken.END_ARRAY, Event.END_ARRAY);
tokenToEvent.put(JsonToken.END_OBJECT, Event.END_OBJECT);
tokenToEvent.put(JsonToken.FIELD_NAME, Event.KEY_NAME);
tokenToEvent.put(JsonToken.START_ARRAY, Event.START_ARRAY);
tokenToEvent.put(JsonToken.START_OBJECT, Event.START_OBJECT);
tokenToEvent.put(JsonToken.VALUE_FALSE, Event.VALUE_FALSE);
tokenToEvent.put(JsonToken.VALUE_NULL, Event.VALUE_NULL);
tokenToEvent.put(JsonToken.VALUE_NUMBER_FLOAT, Event.VALUE_NUMBER);
tokenToEvent.put(JsonToken.VALUE_NUMBER_INT, Event.VALUE_NUMBER);
tokenToEvent.put(JsonToken.VALUE_STRING, Event.VALUE_STRING);
tokenToEvent.put(JsonToken.VALUE_TRUE, Event.VALUE_TRUE);
// No equivalent for
// - VALUE_EMBEDDED_OBJECT
// - NOT_AVAILABLE
}
public JacksonJsonpParser(com.fasterxml.jackson.core.JsonParser parser, JacksonJsonpMapper mapper) {
this.parser = parser;
this.mapper = mapper;
}
/**
* Returns the underlying Jackson parser.
*/
public com.fasterxml.jackson.core.JsonParser jacksonParser() {
return this.parser;
}
private JsonParsingException convertException(IOException ioe) {
return new JsonParsingException("Jackson exception: " + ioe.getMessage(), ioe, getLocation());
}
private JsonToken fetchNextToken() {
try {
return parser.nextToken();
} catch(IOException e) {
throw convertException(e);
}
}
private void ensureTokenIsCurrent() {
if (hasNextWasCalled) {
throw new IllegalStateException("Cannot get event data as parser as already been moved to the next event");
}
}
@Override
public boolean hasNext() {
if (hasNextWasCalled) {
return parser.currentToken() != null;
} else {
hasNextWasCalled = true;
return fetchNextToken() != null;
}
}
@Override
public Event next() {
JsonToken token;
if (hasNextWasCalled) {
token = parser.getCurrentToken();
hasNextWasCalled = false;
} else {
token = fetchNextToken();
}
if (token == null) {
throw new NoSuchElementException();
}
Event result = tokenToEvent.get(token);
if (result == null) {
throw new JsonParsingException("Unsupported Jackson event type '"+ token + "'", getLocation());
}
return result;
}
@Override
public String getString() {
ensureTokenIsCurrent();
try {
return parser.getValueAsString();
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public boolean isIntegralNumber() {
ensureTokenIsCurrent();
return parser.isExpectedNumberIntToken();
}
@Override
public int getInt() {
ensureTokenIsCurrent();
try {
return parser.getIntValue();
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public long getLong() {
ensureTokenIsCurrent();
try {
return parser.getLongValue();
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public BigDecimal getBigDecimal() {
ensureTokenIsCurrent();
try {
return parser.getDecimalValue();
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public JsonLocation getLocation() {
return new JacksonJsonpLocation(parser.getCurrentLocation());
}
@Override
public void close() {
try {
parser.close();
} catch (IOException e) {
throw convertException(e);
}
}
private JsonValueParser valueParser;
@Override
public JsonObject getObject() {
ensureTokenIsCurrent();
if (parser.currentToken() != JsonToken.START_OBJECT) {
throw new IllegalStateException("Unexpected event '" + parser.currentToken() +
"' at " + parser.getTokenLocation());
}
if (valueParser == null) {
valueParser = new JsonValueParser();
}
try {
return valueParser.parseObject(parser);
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public JsonArray getArray() {
ensureTokenIsCurrent();
if (valueParser == null) {
valueParser = new JsonValueParser();
}
if (parser.currentToken() != JsonToken.START_ARRAY) {
throw new IllegalStateException("Unexpected event '" + parser.currentToken() +
"' at " + parser.getTokenLocation());
}
try {
return valueParser.parseArray(parser);
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public JsonValue getValue() {
ensureTokenIsCurrent();
if (valueParser == null) {
valueParser = new JsonValueParser();
}
try {
return valueParser.parseValue(parser);
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public void skipObject() {
ensureTokenIsCurrent();
if (parser.currentToken() != JsonToken.START_OBJECT) {
return;
}
try {
int depth = 1;
JsonToken token;
do {
token = parser.nextToken();
switch (token) {
case START_OBJECT:
depth++;
break;
case END_OBJECT:
depth--;
break;
}
} while(!(token == JsonToken.END_OBJECT && depth == 0));
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public void skipArray() {
ensureTokenIsCurrent();
if (parser.currentToken() != JsonToken.START_ARRAY) {
return;
}
try {
int depth = 1;
JsonToken token;
do {
token = parser.nextToken();
switch (token) {
case START_ARRAY:
depth++;
break;
case END_ARRAY:
depth--;
break;
}
} while(!(token == JsonToken.END_ARRAY && depth == 0));
} catch (IOException e) {
throw convertException(e);
}
}
@Override
public Stream> getObjectStream() {
return getObject().entrySet().stream();
}
@Override
public Stream getArrayStream() {
return getArray().stream();
}
/**
* Not implemented.
*/
@Override
public Stream getValueStream() {
return LookAheadJsonParser.super.getValueStream();
}
//----- Look ahead methods
public Map.Entry lookAheadFieldValue(String name, String defaultValue) {
TokenBuffer tb = new TokenBuffer(parser, null);
try {
// The resulting parser must contain the full object, including START_EVENT
tb.copyCurrentEvent(parser);
while (parser.nextToken() != JsonToken.END_OBJECT) {
expectEvent(JsonToken.FIELD_NAME);
// Do not copy current event here, each branch will take care of it
String fieldName = parser.getCurrentName();
if (fieldName.equals(name)) {
// Found
tb.copyCurrentEvent(parser);
expectNextEvent(JsonToken.VALUE_STRING);
tb.copyCurrentEvent(parser);
return new AbstractMap.SimpleImmutableEntry<>(
parser.getText(),
new JacksonJsonpParser(
JsonParserSequence.createFlattened(false, tb.asParser(), parser),
mapper
)
);
} else {
tb.copyCurrentStructure(parser);
}
}
// Copy ending END_OBJECT
tb.copyCurrentEvent(parser);
} catch (IOException e) {
throw JacksonUtils.convertException(e);
}
// Field not found
return new AbstractMap.SimpleImmutableEntry<>(
defaultValue,
new JacksonJsonpParser(
JsonParserSequence.createFlattened(false, tb.asParser(), parser),
mapper
)
);
}
@Override
public Map.Entry findVariant(Map variants) {
// We're on a START_OBJECT event
TokenBuffer tb = new TokenBuffer(parser, null);
try {
if (parser.currentToken() != JsonToken.START_OBJECT) {
// Primitive value or array
tb.copyCurrentStructure(parser);
} else {
// The resulting parser must contain the full object, including START_EVENT
tb.copyCurrentEvent(parser);
while (parser.nextToken() != JsonToken.END_OBJECT) {
expectEvent(JsonToken.FIELD_NAME);
String fieldName = parser.getCurrentName();
Variant variant = variants.get(fieldName);
if (variant != null) {
tb.copyCurrentEvent(parser);
return new AbstractMap.SimpleImmutableEntry<>(
variant,
new JacksonJsonpParser(
JsonParserSequence.createFlattened(false, tb.asParser(), parser),
mapper
)
);
} else {
tb.copyCurrentStructure(parser);
}
}
// Copy ending END_OBJECT
tb.copyCurrentEvent(parser);
}
} catch (IOException e) {
throw JacksonUtils.convertException(e);
}
// No variant found: return the buffered parser and let the caller decide what to do.
return new AbstractMap.SimpleImmutableEntry<>(
null,
new JacksonJsonpParser(
JsonParserSequence.createFlattened(false, tb.asParser(), parser),
mapper
)
);
}
private void expectNextEvent(JsonToken expected) throws IOException {
JsonToken event = parser.nextToken();
if (event != expected) {
throw new UnexpectedJsonEventException(this, tokenToEvent.get(event), tokenToEvent.get(expected));
}
}
private void expectEvent(JsonToken expected) {
JsonToken event = parser.currentToken();
if (event != expected) {
throw new UnexpectedJsonEventException(this, tokenToEvent.get(event), tokenToEvent.get(expected));
}
}
//----- Buffering methods
@Override
public JsonData getJsonData() {
try {
TokenBuffer buffer = new TokenBuffer(parser);
buffer.copyCurrentStructure(parser);
return new JacksonJsonBuffer(buffer, mapper);
} catch (IOException e) {
throw JacksonUtils.convertException(e);
}
}
}