001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind.io.json; 007 008import com.fasterxml.jackson.core.JsonLocation; 009import com.fasterxml.jackson.core.JsonParser; 010import com.fasterxml.jackson.core.JsonToken; 011import com.fasterxml.jackson.databind.JsonNode; 012import com.fasterxml.jackson.databind.ObjectMapper; 013import com.fasterxml.jackson.databind.node.ObjectNode; 014 015import gov.nist.secauto.metaschema.core.model.IBoundObject; 016import gov.nist.secauto.metaschema.core.model.IMetaschemaData; 017import gov.nist.secauto.metaschema.core.model.util.JsonUtil; 018import gov.nist.secauto.metaschema.core.util.ObjectUtils; 019import gov.nist.secauto.metaschema.databind.io.BindingException; 020import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 021import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex; 022import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex; 023import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue; 024import gov.nist.secauto.metaschema.databind.model.IBoundInstance; 025import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag; 026import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel; 027import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly; 028import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup; 029import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex; 030import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar; 031import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly; 032import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField; 033import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed; 034import gov.nist.secauto.metaschema.databind.model.IBoundProperty; 035import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceReadHandler; 036import gov.nist.secauto.metaschema.databind.model.info.IFeatureScalarItemValueHandler; 037import gov.nist.secauto.metaschema.databind.model.info.IItemReadHandler; 038import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo; 039 040import org.apache.logging.log4j.LogManager; 041import org.apache.logging.log4j.Logger; 042 043import java.io.IOException; 044import java.util.Collection; 045import java.util.Deque; 046import java.util.HashMap; 047import java.util.LinkedHashMap; 048import java.util.LinkedList; 049import java.util.List; 050import java.util.Map; 051 052import edu.umd.cs.findbugs.annotations.NonNull; 053import edu.umd.cs.findbugs.annotations.Nullable; 054import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 055import nl.talsmasoftware.lazy4j.Lazy; 056 057public class MetaschemaJsonReader 058 implements IJsonParsingContext, IItemReadHandler { 059 private static final Logger LOGGER = LogManager.getLogger(MetaschemaJsonReader.class); 060 061 @NonNull 062 private final Deque<JsonParser> parserStack = new LinkedList<>(); 063 // @NonNull 064 // private final InstanceReader instanceReader = new InstanceReader(); 065 066 @NonNull 067 private final IJsonProblemHandler problemHandler; 068 @NonNull 069 private final Lazy<ObjectMapper> objectMapper; 070 071 /** 072 * Construct a new Module-aware JSON parser using the default problem handler. 073 * 074 * @param parser 075 * the JSON parser to parse with 076 * @throws IOException 077 * if an error occurred while reading the JSON 078 * @see DefaultJsonProblemHandler 079 */ 080 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields") 081 public MetaschemaJsonReader( 082 @NonNull JsonParser parser) throws IOException { 083 this(parser, new DefaultJsonProblemHandler()); 084 } 085 086 /** 087 * Construct a new Module-aware JSON parser. 088 * 089 * @param parser 090 * the JSON parser to parse with 091 * @param problemHandler 092 * the problem handler implementation to use 093 * @throws IOException 094 * if an error occurred while reading the JSON 095 */ 096 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields") 097 public MetaschemaJsonReader( 098 @NonNull JsonParser parser, 099 @NonNull IJsonProblemHandler problemHandler) throws IOException { 100 this.problemHandler = problemHandler; 101 this.objectMapper = ObjectUtils.notNull(Lazy.lazy(ObjectMapper::new)); 102 push(parser); 103 } 104 105 @SuppressWarnings("resource") 106 @Override 107 public JsonParser getReader() { 108 return ObjectUtils.notNull(parserStack.peek()); 109 } 110 111 // protected void analyzeParserStack(@NonNull String action) throws IOException 112 // { 113 // StringBuilder builder = new StringBuilder() 114 // .append("------\n"); 115 // 116 // for (JsonParser parser : parserStack) { 117 // JsonToken token = parser.getCurrentToken(); 118 // if (token == null) { 119 // LOGGER.info(String.format("Advancing parser: %s", parser.hashCode())); 120 // token = parser.nextToken(); 121 // } 122 // 123 // String name = parser.currentName(); 124 // builder.append(String.format("%s: %d: %s(%s)%s\n", 125 // action, 126 // parser.hashCode(), 127 // token.name(), 128 // name == null ? "" : name, 129 // JsonUtil.generateLocationMessage(parser))); 130 // } 131 // LOGGER.info(builder.toString()); 132 // } 133 134 @SuppressWarnings("resource") 135 public final void push(JsonParser parser) throws IOException { 136 assert !parser.equals(parserStack.peek()); 137 if (parser.getCurrentToken() == null) { 138 parser.nextToken(); 139 } 140 parserStack.push(parser); 141 } 142 143 @SuppressWarnings("resource") 144 @NonNull 145 public final JsonParser pop(@NonNull JsonParser parser) { 146 JsonParser old = parserStack.pop(); 147 assert parser.equals(old); 148 return ObjectUtils.notNull(parserStack.peek()); 149 } 150 151 @Override 152 public IJsonProblemHandler getProblemHandler() { 153 return problemHandler; 154 } 155 156 @NonNull 157 protected ObjectMapper getObjectMapper() { 158 return ObjectUtils.notNull(objectMapper.get()); 159 } 160 161 @SuppressWarnings("unchecked") 162 @NonNull 163 public <T> T readObject(@NonNull IBoundDefinitionModelComplex definition) throws IOException { 164 return (T) definition.readItem(null, this); 165 } 166 167 @SuppressWarnings({ "unchecked", "resource" }) 168 @NonNull 169 public <T> T readObjectRoot( 170 @NonNull IBoundDefinitionModelComplex definition, 171 @NonNull String expectedFieldName) throws IOException { 172 JsonParser parser = getReader(); 173 174 boolean hasStartObject = JsonToken.START_OBJECT.equals(parser.currentToken()); 175 if (hasStartObject) { 176 // advance past the start object 177 JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); 178 } 179 180 T retval = null; 181 JsonToken token; 182 while (!JsonToken.END_OBJECT.equals(token = parser.currentToken()) && token != null) { 183 if (!JsonToken.FIELD_NAME.equals(token)) { 184 throw new IOException(String.format("Expected FIELD_NAME token, found '%s'", token.toString())); 185 } 186 187 String propertyName = ObjectUtils.notNull(parser.currentName()); 188 if (expectedFieldName.equals(propertyName)) { 189 // process the object value, bound to the requested class 190 JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); 191 192 // stop now, since we found the field 193 retval = (T) definition.readItem(null, this); 194 break; 195 } 196 197 if (!getProblemHandler().handleUnknownProperty( 198 definition, 199 null, 200 propertyName, 201 getReader())) { 202 if (LOGGER.isWarnEnabled()) { 203 LOGGER.warn("Skipping unhandled JSON field '{}'{}.", propertyName, JsonUtil.toString(parser)); 204 } 205 JsonUtil.skipNextValue(parser); 206 } 207 } 208 209 if (hasStartObject) { 210 // advance past the end object 211 JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); 212 } 213 214 if (retval == null) { 215 throw new IOException(String.format("Failed to find property with name '%s'%s.", 216 expectedFieldName, 217 JsonUtil.generateLocationMessage(parser))); 218 } 219 return retval; 220 } 221 222 // ================ 223 // Instance readers 224 // ================ 225 226 @NonNull 227 private Object readInstance( 228 @NonNull IBoundProperty<?> instance, 229 @NonNull IBoundObject parent) throws IOException { 230 return instance.readItem(parent, this); 231 } 232 233 @NonNull 234 private <T> Object readModelInstance( 235 @NonNull IBoundInstanceModel<T> instance, 236 @NonNull IBoundObject parent) throws IOException { 237 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo(); 238 return collectionInfo.readItems(new ModelInstanceReadHandler<>(instance, parent)); 239 } 240 241 private Object readFieldValue( 242 @NonNull IBoundFieldValue instance, 243 @NonNull IBoundObject parent) throws IOException { 244 // handle the value key name case 245 return instance.readItem(parent, this); 246 } 247 248 private Object readObjectProperty( 249 @NonNull IBoundObject parent, 250 @NonNull IBoundProperty<?> property) throws IOException { 251 Object retval; 252 if (property instanceof IBoundInstanceModel) { 253 retval = readModelInstance((IBoundInstanceModel<?>) property, parent); 254 } else if (property instanceof IBoundInstance) { 255 retval = readInstance(property, parent); 256 } else { // IBoundFieldValue 257 retval = readFieldValue((IBoundFieldValue) property, parent); 258 } 259 return retval; 260 } 261 262 @Override 263 public Object readItemFlag(IBoundObject parentItem, IBoundInstanceFlag instance) throws IOException { 264 return readScalarItem(instance); 265 } 266 267 @Override 268 public Object readItemField(IBoundObject parentItem, IBoundInstanceModelFieldScalar instance) throws IOException { 269 return readScalarItem(instance); 270 } 271 272 @Override 273 public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelFieldComplex instance) 274 throws IOException { 275 return readFieldObject( 276 parentItem, 277 instance.getDefinition(), 278 instance.getJsonProperties(), 279 instance.getEffectiveJsonKey(), 280 getProblemHandler()); 281 } 282 283 @Override 284 public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelGroupedField instance) 285 throws IOException { 286 IJsonProblemHandler problemHandler = new GroupedInstanceProblemHandler(instance, getProblemHandler()); 287 IBoundDefinitionModelFieldComplex definition = instance.getDefinition(); 288 IBoundInstanceFlag jsonValueKeyFlag = definition.getJsonValueKeyFlagInstance(); 289 290 IJsonProblemHandler actualProblemHandler = jsonValueKeyFlag == null 291 ? problemHandler 292 : new JsomValueKeyProblemHandler(problemHandler, jsonValueKeyFlag); 293 294 return readComplexDefinitionObject( 295 parentItem, 296 definition, 297 instance.getEffectiveJsonKey(), 298 new PropertyBodyHandler(instance.getJsonProperties()), 299 actualProblemHandler); 300 } 301 302 @Override 303 public IBoundObject readItemField(IBoundObject parentItem, IBoundDefinitionModelFieldComplex definition) 304 throws IOException { 305 return readFieldObject( 306 parentItem, 307 definition, 308 definition.getJsonProperties(), 309 null, 310 getProblemHandler()); 311 } 312 313 @Override 314 public Object readItemFieldValue(IBoundObject parentItem, IBoundFieldValue fieldValue) throws IOException { 315 // read the field value's value 316 return readScalarItem(fieldValue); 317 } 318 319 @Override 320 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelAssembly instance) 321 throws IOException { 322 IBoundInstanceFlag jsonKey = instance.getJsonKey(); 323 IBoundDefinitionModelComplex definition = instance.getDefinition(); 324 return readComplexDefinitionObject( 325 parentItem, 326 definition, 327 jsonKey, 328 new PropertyBodyHandler(instance.getJsonProperties()), 329 getProblemHandler()); 330 } 331 332 @Override 333 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelGroupedAssembly instance) 334 throws IOException { 335 return readComplexDefinitionObject( 336 parentItem, 337 instance.getDefinition(), 338 instance.getEffectiveJsonKey(), 339 new PropertyBodyHandler(instance.getJsonProperties()), 340 new GroupedInstanceProblemHandler(instance, getProblemHandler())); 341 } 342 343 @Override 344 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundDefinitionModelAssembly definition) 345 throws IOException { 346 return readComplexDefinitionObject( 347 parentItem, 348 definition, 349 null, 350 new PropertyBodyHandler(definition.getJsonProperties()), 351 getProblemHandler()); 352 } 353 354 @SuppressWarnings("resource") 355 @NonNull 356 private Object readScalarItem(@NonNull IFeatureScalarItemValueHandler handler) 357 throws IOException { 358 return handler.getJavaTypeAdapter().parse(getReader()); 359 } 360 361 @NonNull 362 private IBoundObject readFieldObject( 363 @Nullable IBoundObject parentItem, 364 @NonNull IBoundDefinitionModelFieldComplex definition, 365 @NonNull Map<String, IBoundProperty<?>> jsonProperties, 366 @Nullable IBoundInstanceFlag jsonKey, 367 @NonNull IJsonProblemHandler problemHandler) throws IOException { 368 IBoundInstanceFlag jsonValueKey = definition.getJsonValueKeyFlagInstance(); 369 IJsonProblemHandler actualProblemHandler = jsonValueKey == null 370 ? problemHandler 371 : new JsomValueKeyProblemHandler(problemHandler, jsonValueKey); 372 373 IBoundObject retval; 374 if (jsonProperties.isEmpty() && jsonValueKey == null) { 375 retval = readComplexDefinitionObject( 376 parentItem, 377 definition, 378 jsonKey, 379 (def, parent, problem) -> { 380 IBoundFieldValue fieldValue = definition.getFieldValue(); 381 Object item = readItemFieldValue(parent, fieldValue); 382 fieldValue.setValue(parent, item); 383 }, 384 actualProblemHandler); 385 386 } else { 387 retval = readComplexDefinitionObject( 388 parentItem, 389 definition, 390 jsonKey, 391 new PropertyBodyHandler(jsonProperties), 392 actualProblemHandler); 393 } 394 return retval; 395 } 396 397 @NonNull 398 private IBoundObject readComplexDefinitionObject( 399 @Nullable IBoundObject parentItem, 400 @NonNull IBoundDefinitionModelComplex definition, 401 @Nullable IBoundInstanceFlag jsonKey, 402 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler, 403 @NonNull IJsonProblemHandler problemHandler) throws IOException { 404 DefinitionBodyHandler<IBoundDefinitionModelComplex> actualBodyHandler = jsonKey == null 405 ? bodyHandler 406 : new JsonKeyBodyHandler(jsonKey, bodyHandler); 407 408 JsonLocation location = getReader().currentLocation(); 409 410 // construct the item 411 IBoundObject item = definition.newInstance( 412 JsonLocation.NA.equals(location) 413 ? null 414 : () -> new MetaschemaData(location)); 415 416 try { 417 // call pre-parse initialization hook 418 definition.callBeforeDeserialize(item, parentItem); 419 420 // read the property values 421 actualBodyHandler.accept(definition, item, problemHandler); 422 423 // call post-parse initialization hook 424 definition.callAfterDeserialize(item, parentItem); 425 } catch (BindingException ex) { 426 throw new IOException(ex); 427 } 428 429 return item; 430 } 431 432 @SuppressWarnings("resource") 433 @Override 434 public IBoundObject readChoiceGroupItem(IBoundObject parentItem, IBoundInstanceModelChoiceGroup instance) 435 throws IOException { 436 JsonParser parser = getReader(); 437 ObjectNode node = parser.readValueAsTree(); 438 439 String discriminatorProperty = instance.getJsonDiscriminatorProperty(); 440 JsonNode discriminatorNode = node.get(discriminatorProperty); 441 if (discriminatorNode == null) { 442 throw new IllegalArgumentException(String.format( 443 "Unable to find discriminator property '%s' for object at '%s'.", 444 discriminatorProperty, 445 JsonUtil.toString(parser))); 446 } 447 String discriminator = ObjectUtils.requireNonNull(discriminatorNode.asText()); 448 449 IBoundInstanceModelGroupedNamed actualInstance = instance.getGroupedModelInstance(discriminator); 450 assert actualInstance != null; 451 452 IBoundObject retval; 453 try (JsonParser newParser = node.traverse(parser.getCodec())) { 454 push(newParser); 455 456 // get initial token 457 retval = actualInstance.readItem(parentItem, this); 458 assert newParser.currentToken() == null; 459 pop(newParser); 460 } 461 462 // advance the original parser to the next token 463 parser.nextToken(); 464 465 return retval; 466 } 467 468 private final class JsonKeyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> { 469 @NonNull 470 private final IBoundInstanceFlag jsonKey; 471 @NonNull 472 private final DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler; 473 474 private JsonKeyBodyHandler( 475 @NonNull IBoundInstanceFlag jsonKey, 476 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler) { 477 this.jsonKey = jsonKey; 478 this.bodyHandler = bodyHandler; 479 } 480 481 @Override 482 public void accept( 483 IBoundDefinitionModelComplex definition, 484 IBoundObject parent, 485 IJsonProblemHandler problemHandler) 486 throws IOException { 487 @SuppressWarnings("resource") JsonParser parser = getReader(); 488 JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME); 489 490 // the field will be the JSON key 491 String key = ObjectUtils.notNull(parser.currentName()); 492 Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key); 493 jsonKey.setValue(parent, ObjectUtils.notNull(value.toString())); 494 495 // skip to the next token 496 parser.nextToken(); 497 // JsonUtil.assertCurrent(parser, JsonToken.START_OBJECT); 498 499 // // advance past the JSON key's start object 500 // JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); 501 502 // read the property values 503 bodyHandler.accept(definition, parent, problemHandler); 504 505 // // advance past the JSON key's end object 506 // JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); 507 } 508 } 509 510 private final class PropertyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> { 511 @NonNull 512 private final Map<String, IBoundProperty<?>> jsonProperties; 513 514 private PropertyBodyHandler(@NonNull Map<String, IBoundProperty<?>> jsonProperties) { 515 this.jsonProperties = jsonProperties; 516 } 517 518 @Override 519 public void accept( 520 IBoundDefinitionModelComplex definition, 521 IBoundObject parent, 522 IJsonProblemHandler problemHandler) 523 throws IOException { 524 @SuppressWarnings("resource") JsonParser parser = getReader(); 525 526 // advance past the start object 527 JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); 528 529 // make a copy, since we use the remaining values to initialize default values 530 Map<String, IBoundProperty<?>> remainingInstances = new HashMap<>(jsonProperties); // NOPMD not concurrent 531 532 // handle each property 533 while (JsonToken.FIELD_NAME.equals(parser.currentToken())) { 534 535 // the parser's current token should be the JSON field name 536 String propertyName = ObjectUtils.notNull(parser.currentName()); 537 if (LOGGER.isTraceEnabled()) { 538 LOGGER.trace("reading property {}", propertyName); 539 } 540 541 IBoundProperty<?> property = remainingInstances.get(propertyName); 542 543 boolean handled = false; 544 if (property != null) { 545 // advance past the field name 546 parser.nextToken(); 547 548 Object value = readObjectProperty(parent, property); 549 property.setValue(parent, value); 550 551 // mark handled 552 remainingInstances.remove(propertyName); 553 handled = true; 554 } 555 556 if (!handled && !problemHandler.handleUnknownProperty( 557 definition, 558 parent, 559 propertyName, 560 getReader())) { 561 if (LOGGER.isWarnEnabled()) { 562 LOGGER.warn("Skipping unhandled JSON field '{}' {}.", propertyName, JsonUtil.toString(parser)); 563 } 564 JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); 565 JsonUtil.skipNextValue(parser); 566 } 567 568 // the current token will be either the next instance field name or the end of 569 // the parent object 570 JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); 571 } 572 573 problemHandler.handleMissingInstances( 574 definition, 575 parent, 576 ObjectUtils.notNull(remainingInstances.values())); 577 578 // advance past the end object 579 JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); 580 } 581 } 582 583 private final class GroupedInstanceProblemHandler implements IJsonProblemHandler { 584 @NonNull 585 private final IBoundInstanceModelGroupedNamed instance; 586 @NonNull 587 private final IJsonProblemHandler delegate; 588 589 private GroupedInstanceProblemHandler( 590 @NonNull IBoundInstanceModelGroupedNamed instance, 591 @NonNull IJsonProblemHandler delegate) { 592 this.instance = instance; 593 this.delegate = delegate; 594 } 595 596 @Override 597 public void handleMissingInstances( 598 IBoundDefinitionModelComplex parentDefinition, 599 IBoundObject targetObject, 600 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException { 601 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances); 602 } 603 604 @SuppressWarnings("resource") 605 @Override 606 public boolean handleUnknownProperty( 607 IBoundDefinitionModelComplex definition, 608 IBoundObject parentItem, 609 String fieldName, 610 JsonParser parser) throws IOException { 611 boolean retval; 612 if (instance.getParentContainer().getJsonDiscriminatorProperty().equals(fieldName)) { 613 JsonUtil.skipNextValue(parser); 614 retval = true; 615 } else { 616 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, getReader()); 617 } 618 return retval; 619 } 620 } 621 622 private final class JsomValueKeyProblemHandler implements IJsonProblemHandler { 623 @NonNull 624 private final IJsonProblemHandler delegate; 625 @NonNull 626 private final IBoundInstanceFlag jsonValueKyeFlag; 627 private boolean foundJsonValueKey; // false 628 629 private JsomValueKeyProblemHandler( 630 @NonNull IJsonProblemHandler delegate, 631 @NonNull IBoundInstanceFlag jsonValueKyeFlag) { 632 this.delegate = delegate; 633 this.jsonValueKyeFlag = jsonValueKyeFlag; 634 } 635 636 @Override 637 public void handleMissingInstances( 638 IBoundDefinitionModelComplex parentDefinition, 639 IBoundObject targetObject, 640 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException { 641 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances); 642 } 643 644 @Override 645 public boolean handleUnknownProperty( 646 IBoundDefinitionModelComplex definition, 647 IBoundObject parentItem, 648 String fieldName, 649 JsonParser parser) throws IOException { 650 boolean retval; 651 if (foundJsonValueKey) { 652 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parser); 653 } else { 654 // handle JSON value key 655 String key = ObjectUtils.notNull(parser.currentName()); 656 Object keyValue = jsonValueKyeFlag.getJavaTypeAdapter().parse(key); 657 jsonValueKyeFlag.setValue(ObjectUtils.notNull(parentItem), keyValue); 658 659 // advance past the field name 660 JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME); 661 662 IBoundFieldValue fieldValue = ((IBoundDefinitionModelFieldComplex) definition).getFieldValue(); 663 Object value = readItemFieldValue( 664 ObjectUtils.notNull(parentItem), 665 fieldValue); 666 fieldValue.setValue(ObjectUtils.notNull(parentItem), value); 667 668 retval = foundJsonValueKey = true; 669 } 670 return retval; 671 } 672 673 } 674 675 private class ModelInstanceReadHandler<ITEM> 676 extends AbstractModelInstanceReadHandler<ITEM> { 677 678 protected ModelInstanceReadHandler( 679 @NonNull IBoundInstanceModel<ITEM> instance, 680 @NonNull IBoundObject parentItem) { 681 super(instance, parentItem); 682 } 683 684 @SuppressWarnings("resource") // no need to close parser 685 @Override 686 public List<ITEM> readList() throws IOException { 687 JsonParser parser = getReader(); 688 689 List<ITEM> items = new LinkedList<>(); 690 switch (parser.currentToken()) { 691 case START_ARRAY: 692 // this is an array, we need to parse the array wrapper then each item 693 JsonUtil.assertAndAdvance(parser, JsonToken.START_ARRAY); 694 695 // parse items 696 while (!JsonToken.END_ARRAY.equals(parser.currentToken())) { 697 items.add(readItem()); 698 } 699 700 // this is the other side of the array wrapper, advance past it 701 JsonUtil.assertAndAdvance(parser, JsonToken.END_ARRAY); 702 break; 703 case VALUE_NULL: 704 JsonUtil.assertAndAdvance(parser, JsonToken.VALUE_NULL); 705 break; 706 default: 707 // this is a singleton, just parse the value as a single item 708 items.add(readItem()); 709 break; 710 } 711 return items; 712 } 713 714 @SuppressWarnings("resource") // no need to close parser 715 @Override 716 public Map<String, ITEM> readMap() throws IOException { 717 JsonParser parser = getReader(); 718 719 IBoundInstanceModel<?> instance = getCollectionInfo().getInstance(); 720 721 @SuppressWarnings("PMD.UseConcurrentHashMap") Map<String, ITEM> items = new LinkedHashMap<>(); 722 723 // A map value is always wrapped in a START_OBJECT, since fields are used for 724 // the keys 725 JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); 726 727 // process all map items 728 while (!JsonToken.END_OBJECT.equals(parser.currentToken())) { 729 730 // a map item will always start with a FIELD_NAME, since this represents the key 731 JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME); 732 733 ITEM item = readItem(); 734 735 // lookup the key 736 IBoundInstanceFlag jsonKey = instance.getItemJsonKey(item); 737 assert jsonKey != null; 738 739 Object keyValue = jsonKey.getValue(item); 740 if (keyValue == null) { 741 throw new IOException(String.format("Null value for json-key for definition '%s'", 742 jsonKey.getContainingDefinition().toCoordinates())); 743 } 744 String key = jsonKey.getJavaTypeAdapter().asString(keyValue); 745 items.put(key, item); 746 747 // the next item will be a FIELD_NAME, or we will encounter an END_OBJECT if all 748 // items have been 749 // read 750 JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); 751 } 752 753 // A map value will always end with an end object, which needs to be consumed 754 JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); 755 756 return items; 757 } 758 759 @Override 760 @NonNull 761 public ITEM readItem() throws IOException { 762 IBoundInstanceModel<ITEM> instance = getCollectionInfo().getInstance(); 763 return instance.readItem(getParentObject(), MetaschemaJsonReader.this); 764 } 765 } 766 767 private static class MetaschemaData implements IMetaschemaData { 768 private final int line; 769 private final int column; 770 private final long charOffset; 771 private final long byteOffset; 772 773 public MetaschemaData(@NonNull JsonLocation location) { 774 this.line = location.getLineNr(); 775 this.column = location.getColumnNr(); 776 this.charOffset = location.getCharOffset(); 777 this.byteOffset = location.getByteOffset(); 778 } 779 780 @Override 781 public int getLine() { 782 return line; 783 } 784 785 @Override 786 public int getColumn() { 787 return column; 788 } 789 790 @Override 791 public long getCharOffset() { 792 return charOffset; 793 } 794 795 @Override 796 public long getByteOffset() { 797 return byteOffset; 798 } 799 } 800 801 @FunctionalInterface 802 private interface DefinitionBodyHandler<DEF extends IBoundDefinitionModelComplex> { 803 void accept( 804 @NonNull DEF definition, 805 @NonNull IBoundObject parent, 806 @NonNull IJsonProblemHandler problemHandler) throws IOException; 807 } 808}