MetaschemaJsonReader.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.databind.io.json;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import gov.nist.secauto.metaschema.core.model.IBoundObject;
import gov.nist.secauto.metaschema.core.model.IMetaschemaData;
import gov.nist.secauto.metaschema.core.model.util.JsonUtil;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.databind.io.BindingException;
import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex;
import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue;
import gov.nist.secauto.metaschema.databind.model.IBoundInstance;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField;
import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
import gov.nist.secauto.metaschema.databind.model.IBoundProperty;
import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceReadHandler;
import gov.nist.secauto.metaschema.databind.model.info.IFeatureScalarItemValueHandler;
import gov.nist.secauto.metaschema.databind.model.info.IItemReadHandler;
import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jdt.annotation.NotOwning;
import java.io.IOException;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import nl.talsmasoftware.lazy4j.Lazy;
public class MetaschemaJsonReader
implements IJsonParsingContext, IItemReadHandler {
private static final Logger LOGGER = LogManager.getLogger(MetaschemaJsonReader.class);
@NonNull
private final Deque<JsonParser> parserStack = new LinkedList<>();
// @NonNull
// private final InstanceReader instanceReader = new InstanceReader();
@NonNull
private final IJsonProblemHandler problemHandler;
@NonNull
private final Lazy<ObjectMapper> objectMapper;
/**
* Construct a new Module-aware JSON parser using the default problem handler.
*
* @param parser
* the JSON parser to parse with
* @throws IOException
* if an error occurred while reading the JSON
* @see DefaultJsonProblemHandler
*/
@SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
public MetaschemaJsonReader(
@NonNull JsonParser parser) throws IOException {
this(parser, new DefaultJsonProblemHandler());
}
/**
* Construct a new Module-aware JSON parser.
*
* @param parser
* the JSON parser to parse with
* @param problemHandler
* the problem handler implementation to use
* @throws IOException
* if an error occurred while reading the JSON
*/
@SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
public MetaschemaJsonReader(
@NonNull JsonParser parser,
@NonNull IJsonProblemHandler problemHandler) throws IOException {
this.problemHandler = problemHandler;
this.objectMapper = ObjectUtils.notNull(Lazy.lazy(ObjectMapper::new));
push(parser);
}
@SuppressWarnings("resource")
@NotOwning
@Override
public JsonParser getReader() {
return ObjectUtils.notNull(parserStack.peek());
}
// protected void analyzeParserStack(@NonNull String action) throws IOException
// {
// StringBuilder builder = new StringBuilder()
// .append("------\n");
//
// for (JsonParser parser : parserStack) {
// JsonToken token = parser.getCurrentToken();
// if (token == null) {
// LOGGER.info(String.format("Advancing parser: %s", parser.hashCode()));
// token = parser.nextToken();
// }
//
// String name = parser.currentName();
// builder.append(String.format("%s: %d: %s(%s)%s\n",
// action,
// parser.hashCode(),
// token.name(),
// name == null ? "" : name,
// JsonUtil.generateLocationMessage(parser)));
// }
// LOGGER.info(builder.toString());
// }
@SuppressWarnings("resource")
public final void push(JsonParser parser) throws IOException {
assert !parser.equals(parserStack.peek());
if (parser.getCurrentToken() == null) {
parser.nextToken();
}
parserStack.push(parser);
}
@SuppressWarnings("resource")
@NonNull
public final JsonParser pop(@NonNull JsonParser parser) {
JsonParser old = parserStack.pop();
assert parser.equals(old);
return ObjectUtils.notNull(parserStack.peek());
}
@Override
public IJsonProblemHandler getProblemHandler() {
return problemHandler;
}
@NonNull
protected ObjectMapper getObjectMapper() {
return ObjectUtils.notNull(objectMapper.get());
}
@SuppressWarnings("unchecked")
@NonNull
public <T> T readObject(@NonNull IBoundDefinitionModelComplex definition) throws IOException {
T value = (T) definition.readItem(null, this);
if (value == null) {
throw new IOException(String.format("Failed to read object '%s'%s.",
definition.getDefinitionQName(),
JsonUtil.generateLocationMessage(getReader())));
}
return value;
}
@SuppressWarnings({ "unchecked" })
@NonNull
public <T> T readObjectRoot(
@NonNull IBoundDefinitionModelComplex definition,
@NonNull String expectedFieldName) throws IOException {
JsonParser parser = getReader();
boolean hasStartObject = JsonToken.START_OBJECT.equals(parser.currentToken());
if (hasStartObject) {
// advance past the start object
JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT);
}
T retval = null;
JsonToken token;
while (!JsonToken.END_OBJECT.equals(token = parser.currentToken()) && token != null) {
if (!JsonToken.FIELD_NAME.equals(token)) {
throw new IOException(String.format("Expected FIELD_NAME token, found '%s'", token.toString()));
}
String propertyName = ObjectUtils.notNull(parser.currentName());
if (expectedFieldName.equals(propertyName)) {
// process the object value, bound to the requested class
JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME);
// stop now, since we found the field
retval = (T) definition.readItem(null, this);
break;
}
if (!getProblemHandler().handleUnknownProperty(
definition,
null,
propertyName,
getReader())) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Skipping unhandled JSON field '{}'{}.", propertyName, JsonUtil.toString(parser));
}
JsonUtil.skipNextValue(parser);
}
}
if (hasStartObject) {
// advance past the end object
JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT);
}
if (retval == null) {
throw new IOException(String.format("Failed to find property with name '%s'%s.",
expectedFieldName,
JsonUtil.generateLocationMessage(parser)));
}
return retval;
}
// ================
// Instance readers
// ================
@Nullable
private Object readInstance(
@NonNull IBoundProperty<?> instance,
@NonNull IBoundObject parent) throws IOException {
return instance.readItem(parent, this);
}
@Nullable
private <T> Object readModelInstance(
@NonNull IBoundInstanceModel<T> instance,
@NonNull IBoundObject parent) throws IOException {
IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo();
return collectionInfo.readItems(new ModelInstanceReadHandler<>(instance, parent));
}
private Object readFieldValue(
@NonNull IBoundFieldValue instance,
@NonNull IBoundObject parent) throws IOException {
// handle the value key name case
return instance.readItem(parent, this);
}
@Nullable
private Object readObjectProperty(
@NonNull IBoundObject parent,
@NonNull IBoundProperty<?> property) throws IOException {
Object retval;
if (property instanceof IBoundInstanceModel) {
retval = readModelInstance((IBoundInstanceModel<?>) property, parent);
} else if (property instanceof IBoundInstance) {
retval = readInstance(property, parent);
} else { // IBoundFieldValue
retval = readFieldValue((IBoundFieldValue) property, parent);
}
return retval;
}
@Override
public Object readItemFlag(IBoundObject parentItem, IBoundInstanceFlag instance) throws IOException {
return readScalarItem(instance);
}
@Override
public Object readItemField(IBoundObject parentItem, IBoundInstanceModelFieldScalar instance) throws IOException {
return readScalarItem(instance);
}
@Override
public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelFieldComplex instance)
throws IOException {
return readFieldObject(
parentItem,
instance.getDefinition(),
instance.getJsonProperties(),
instance.getEffectiveJsonKey(),
getProblemHandler());
}
@Override
public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelGroupedField instance)
throws IOException {
IJsonProblemHandler problemHandler = new GroupedInstanceProblemHandler(instance, getProblemHandler());
IBoundDefinitionModelFieldComplex definition = instance.getDefinition();
IBoundInstanceFlag jsonValueKeyFlag = definition.getJsonValueKeyFlagInstance();
IJsonProblemHandler actualProblemHandler = jsonValueKeyFlag == null
? problemHandler
: new JsomValueKeyProblemHandler(problemHandler, jsonValueKeyFlag);
return readComplexDefinitionObject(
parentItem,
definition,
instance.getEffectiveJsonKey(),
new PropertyBodyHandler(instance.getJsonProperties()),
actualProblemHandler);
}
@Override
public IBoundObject readItemField(IBoundObject parentItem, IBoundDefinitionModelFieldComplex definition)
throws IOException {
return readFieldObject(
parentItem,
definition,
definition.getJsonProperties(),
null,
getProblemHandler());
}
@Override
public Object readItemFieldValue(IBoundObject parentItem, IBoundFieldValue fieldValue) throws IOException {
// read the field value's value
return checkMissingFieldValue(readScalarItem(fieldValue));
}
@Nullable
private Object checkMissingFieldValue(Object value) throws IOException {
if (value == null && LOGGER.isWarnEnabled()) {
LOGGER.atWarn().log("Missing property value{}",
JsonUtil.generateLocationMessage(getReader()));
}
// TODO: change nullness annotations to be @Nullable
return value;
}
@Override
public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelAssembly instance)
throws IOException {
IBoundInstanceFlag jsonKey = instance.getJsonKey();
IBoundDefinitionModelComplex definition = instance.getDefinition();
return readComplexDefinitionObject(
parentItem,
definition,
jsonKey,
new PropertyBodyHandler(instance.getJsonProperties()),
getProblemHandler());
}
@Override
public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelGroupedAssembly instance)
throws IOException {
return readComplexDefinitionObject(
parentItem,
instance.getDefinition(),
instance.getEffectiveJsonKey(),
new PropertyBodyHandler(instance.getJsonProperties()),
new GroupedInstanceProblemHandler(instance, getProblemHandler()));
}
@Override
public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundDefinitionModelAssembly definition)
throws IOException {
return readComplexDefinitionObject(
parentItem,
definition,
null,
new PropertyBodyHandler(definition.getJsonProperties()),
getProblemHandler());
}
@NonNull
private Object readScalarItem(@NonNull IFeatureScalarItemValueHandler handler)
throws IOException {
return handler.getJavaTypeAdapter().parse(getReader());
}
@NonNull
private IBoundObject readFieldObject(
@Nullable IBoundObject parentItem,
@NonNull IBoundDefinitionModelFieldComplex definition,
@NonNull Map<String, IBoundProperty<?>> jsonProperties,
@Nullable IBoundInstanceFlag jsonKey,
@NonNull IJsonProblemHandler problemHandler) throws IOException {
IBoundInstanceFlag jsonValueKey = definition.getJsonValueKeyFlagInstance();
IJsonProblemHandler actualProblemHandler = jsonValueKey == null
? problemHandler
: new JsomValueKeyProblemHandler(problemHandler, jsonValueKey);
IBoundObject retval;
if (jsonProperties.isEmpty() && jsonValueKey == null) {
retval = readComplexDefinitionObject(
parentItem,
definition,
jsonKey,
(def, parent, problem) -> {
IBoundFieldValue fieldValue = definition.getFieldValue();
Object item = readItemFieldValue(parent, fieldValue);
if (item != null) {
fieldValue.setValue(parent, item);
}
},
actualProblemHandler);
} else {
retval = readComplexDefinitionObject(
parentItem,
definition,
jsonKey,
new PropertyBodyHandler(jsonProperties),
actualProblemHandler);
}
return retval;
}
@NonNull
private IBoundObject readComplexDefinitionObject(
@Nullable IBoundObject parentItem,
@NonNull IBoundDefinitionModelComplex definition,
@Nullable IBoundInstanceFlag jsonKey,
@NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler,
@NonNull IJsonProblemHandler problemHandler) throws IOException {
DefinitionBodyHandler<IBoundDefinitionModelComplex> actualBodyHandler = jsonKey == null
? bodyHandler
: new JsonKeyBodyHandler(jsonKey, bodyHandler);
@SuppressWarnings("resource")
JsonLocation location = getReader().currentLocation();
// construct the item
IBoundObject item = definition.newInstance(
JsonLocation.NA.equals(location)
? null
: () -> new MetaschemaData(ObjectUtils.requireNonNull(location)));
try {
// call pre-parse initialization hook
definition.callBeforeDeserialize(item, parentItem);
// read the property values
actualBodyHandler.accept(definition, item, problemHandler);
// call post-parse initialization hook
definition.callAfterDeserialize(item, parentItem);
} catch (BindingException ex) {
throw new IOException(ex);
}
return item;
}
@SuppressWarnings("resource")
@Override
public IBoundObject readChoiceGroupItem(IBoundObject parentItem, IBoundInstanceModelChoiceGroup instance)
throws IOException {
JsonParser parser = getReader();
ObjectNode node = parser.readValueAsTree();
String discriminatorProperty = instance.getJsonDiscriminatorProperty();
JsonNode discriminatorNode = node.get(discriminatorProperty);
if (discriminatorNode == null) {
throw new IllegalArgumentException(String.format(
"Unable to find discriminator property '%s' for object at '%s'.",
discriminatorProperty,
JsonUtil.toString(parser)));
}
String discriminator = ObjectUtils.requireNonNull(discriminatorNode.asText());
IBoundInstanceModelGroupedNamed actualInstance = instance.getGroupedModelInstance(discriminator);
assert actualInstance != null;
IBoundObject retval;
try (JsonParser newParser = node.traverse(parser.getCodec())) {
push(newParser);
// get initial token
retval = actualInstance.readItem(parentItem, this);
assert newParser.currentToken() == null;
pop(newParser);
}
// advance the original parser to the next token
parser.nextToken();
return retval;
}
private final class JsonKeyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> {
@NonNull
private final IBoundInstanceFlag jsonKey;
@NonNull
private final DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler;
private JsonKeyBodyHandler(
@NonNull IBoundInstanceFlag jsonKey,
@NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler) {
this.jsonKey = jsonKey;
this.bodyHandler = bodyHandler;
}
@Override
public void accept(
IBoundDefinitionModelComplex definition,
IBoundObject parent,
IJsonProblemHandler problemHandler)
throws IOException {
@SuppressWarnings("resource")
JsonParser parser = getReader();
JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME);
// the field will be the JSON key
String key = ObjectUtils.notNull(parser.currentName());
try {
Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key);
jsonKey.setValue(parent, ObjectUtils.notNull(value.toString()));
} catch (IllegalArgumentException ex) {
throw new IOException(
String.format("Malformed data '%s'%s. %s",
key,
JsonUtil.generateLocationMessage(parser),
ex.getLocalizedMessage()),
ex);
}
// skip to the next token
parser.nextToken();
// JsonUtil.assertCurrent(parser, JsonToken.START_OBJECT);
// // advance past the JSON key's start object
// JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT);
// read the property values
bodyHandler.accept(definition, parent, problemHandler);
// // advance past the JSON key's end object
// JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT);
}
}
private final class PropertyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> {
@NonNull
private final Map<String, IBoundProperty<?>> jsonProperties;
private PropertyBodyHandler(@NonNull Map<String, IBoundProperty<?>> jsonProperties) {
this.jsonProperties = jsonProperties;
}
@Override
public void accept(
IBoundDefinitionModelComplex definition,
IBoundObject parent,
IJsonProblemHandler problemHandler)
throws IOException {
@SuppressWarnings("resource")
JsonParser parser = getReader();
// advance past the start object
JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT);
// make a copy, since we use the remaining values to initialize default values
Map<String, IBoundProperty<?>> remainingInstances = new HashMap<>(jsonProperties); // NOPMD not concurrent
// handle each property
while (JsonToken.FIELD_NAME.equals(parser.currentToken())) {
// the parser's current token should be the JSON field name
String propertyName = ObjectUtils.notNull(parser.currentName());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("reading property {}", propertyName);
}
IBoundProperty<?> property = remainingInstances.get(propertyName);
boolean handled = false;
if (property != null) {
// advance past the field name
parser.nextToken();
Object value = readObjectProperty(parent, property);
if (value != null) {
property.setValue(parent, value);
}
// mark handled
remainingInstances.remove(propertyName);
handled = true;
}
if (!handled && !problemHandler.handleUnknownProperty(
definition,
parent,
propertyName,
getReader())) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("Skipping unhandled JSON field '{}' {}.", propertyName, JsonUtil.toString(parser));
}
JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME);
JsonUtil.skipNextValue(parser);
}
// the current token will be either the next instance field name or the end of
// the parent object
JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT);
}
problemHandler.handleMissingInstances(
definition,
parent,
ObjectUtils.notNull(remainingInstances.values()));
// advance past the end object
JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT);
}
}
private final class GroupedInstanceProblemHandler implements IJsonProblemHandler {
@NonNull
private final IBoundInstanceModelGroupedNamed instance;
@NonNull
private final IJsonProblemHandler delegate;
private GroupedInstanceProblemHandler(
@NonNull IBoundInstanceModelGroupedNamed instance,
@NonNull IJsonProblemHandler delegate) {
this.instance = instance;
this.delegate = delegate;
}
@Override
public void handleMissingInstances(
IBoundDefinitionModelComplex parentDefinition,
IBoundObject targetObject,
Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException {
delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances);
}
@Override
public boolean handleUnknownProperty(
IBoundDefinitionModelComplex definition,
IBoundObject parentItem,
String fieldName,
JsonParser parser) throws IOException {
boolean retval;
if (instance.getParentContainer().getJsonDiscriminatorProperty().equals(fieldName)) {
JsonUtil.skipNextValue(parser);
retval = true;
} else {
retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, getReader());
}
return retval;
}
}
private final class JsomValueKeyProblemHandler implements IJsonProblemHandler {
@NonNull
private final IJsonProblemHandler delegate;
@NonNull
private final IBoundInstanceFlag jsonValueKeyFlag;
private boolean foundJsonValueKey; // false
private JsomValueKeyProblemHandler(
@NonNull IJsonProblemHandler delegate,
@NonNull IBoundInstanceFlag jsonValueKeyFlag) {
this.delegate = delegate;
this.jsonValueKeyFlag = jsonValueKeyFlag;
}
@Override
public void handleMissingInstances(
IBoundDefinitionModelComplex parentDefinition,
IBoundObject targetObject,
Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException {
delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances);
}
@Override
public boolean handleUnknownProperty(
IBoundDefinitionModelComplex definition,
IBoundObject parentItem,
String fieldName,
JsonParser parser) throws IOException {
boolean retval;
if (foundJsonValueKey) {
retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parser);
} else {
// handle JSON value key
String key = ObjectUtils.notNull(parser.currentName());
try {
Object keyValue = jsonValueKeyFlag.getJavaTypeAdapter().parse(key);
jsonValueKeyFlag.setValue(ObjectUtils.notNull(parentItem), keyValue);
} catch (IllegalArgumentException ex) {
throw new IOException(
String.format("Malformed data '%s'%s. %s",
key,
JsonUtil.generateLocationMessage(parser),
ex.getLocalizedMessage()),
ex);
}
// advance past the field name
JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME);
IBoundFieldValue fieldValue = ((IBoundDefinitionModelFieldComplex) definition).getFieldValue();
Object value = readItemFieldValue(ObjectUtils.notNull(parentItem), fieldValue);
if (value != null) {
fieldValue.setValue(ObjectUtils.notNull(parentItem), value);
}
retval = foundJsonValueKey = true;
}
return retval;
}
}
private class ModelInstanceReadHandler<ITEM>
extends AbstractModelInstanceReadHandler<ITEM> {
protected ModelInstanceReadHandler(
@NonNull IBoundInstanceModel<ITEM> instance,
@NonNull IBoundObject parentItem) {
super(instance, parentItem);
}
@Override
public List<ITEM> readList() throws IOException {
JsonParser parser = getReader();
List<ITEM> items = new LinkedList<>();
switch (parser.currentToken()) {
case START_ARRAY:
// this is an array, we need to parse the array wrapper then each item
JsonUtil.assertAndAdvance(parser, JsonToken.START_ARRAY);
// parse items
while (!JsonToken.END_ARRAY.equals(parser.currentToken())) {
items.add(readItem());
}
// this is the other side of the array wrapper, advance past it
JsonUtil.assertAndAdvance(parser, JsonToken.END_ARRAY);
break;
case VALUE_NULL:
JsonUtil.assertAndAdvance(parser, JsonToken.VALUE_NULL);
break;
default:
// this is a singleton, just parse the value as a single item
items.add(readItem());
break;
}
return items;
}
@Override
public Map<String, ITEM> readMap() throws IOException {
JsonParser parser = getReader();
IBoundInstanceModel<?> instance = getCollectionInfo().getInstance();
@SuppressWarnings("PMD.UseConcurrentHashMap")
Map<String, ITEM> items = new LinkedHashMap<>();
// A map value is always wrapped in a START_OBJECT, since fields are used for
// the keys
JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT);
// process all map items
while (!JsonToken.END_OBJECT.equals(parser.currentToken())) {
// a map item will always start with a FIELD_NAME, since this represents the key
JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME);
// get the object, since it must have a JSON key
ITEM item = readItem();
if (item == null) {
throw new IOException(String.format("Null object encountered'%s.",
JsonUtil.generateLocationMessage(parser)));
}
// lookup the key
IBoundInstanceFlag jsonKey = instance.getItemJsonKey(item);
assert jsonKey != null;
Object keyValue = jsonKey.getValue(item);
if (keyValue == null) {
throw new IOException(String.format("Null value for json-key for definition '%s'",
jsonKey.getContainingDefinition().toCoordinates()));
}
String key;
try {
key = jsonKey.getJavaTypeAdapter().asString(keyValue);
} catch (IllegalArgumentException ex) {
throw new IOException(
String.format("Malformed data '%s'%s. %s",
keyValue,
JsonUtil.generateLocationMessage(parser),
ex.getLocalizedMessage()),
ex);
}
items.put(key, item);
// the next item will be a FIELD_NAME, or we will encounter an END_OBJECT if all
// items have been
// read
JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT);
}
// A map value will always end with an end object, which needs to be consumed
JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT);
return items;
}
@Override
public ITEM readItem() throws IOException {
IBoundInstanceModel<ITEM> instance = getCollectionInfo().getInstance();
return instance.readItem(getParentObject(), MetaschemaJsonReader.this);
}
}
private static class MetaschemaData implements IMetaschemaData {
private final int line;
private final int column;
private final long charOffset;
private final long byteOffset;
public MetaschemaData(@NonNull JsonLocation location) {
this.line = location.getLineNr();
this.column = location.getColumnNr();
this.charOffset = location.getCharOffset();
this.byteOffset = location.getByteOffset();
}
@Override
public int getLine() {
return line;
}
@Override
public int getColumn() {
return column;
}
@Override
public long getCharOffset() {
return charOffset;
}
@Override
public long getByteOffset() {
return byteOffset;
}
}
@FunctionalInterface
private interface DefinitionBodyHandler<DEF extends IBoundDefinitionModelComplex> {
void accept(
@NonNull DEF definition,
@NonNull IBoundObject parent,
@NonNull IJsonProblemHandler problemHandler) throws IOException;
}
}