All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsonx.ArrayValidator Maven / Gradle / Ivy

Go to download

The JSON/Java Binding API is designed to bind JSON documents to Java objects. More specifically, the JSON/Java Binding API provides a way for JSON objects whose structure is expressed in the JSON Schema Definition Language to be parsed and marshaled, to and from Java objects of strongly-typed classes. The JSON/Java Binding API can also be used to validate JSON documents as they are parsed from text or marshaled from Java objects against a JSD. Thus, the JSON/Java Binding API is a reference implementation of the validation and binding functionalities of the JSON Schema Definition Language.

There is a newer version: 0.4.0
Show newest version
/* Copyright (c) 2018 Jsonx
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * You should have received a copy of The MIT License (MIT) along with this
 * program. If not, see .
 */

package org.jsonx;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;

import org.libj.util.Annotations;
import org.libj.util.DelegateList;
import org.libj.util.Numbers;
import org.libj.util.function.TriPredicate;

class ArrayValidator {
  static class Relation {
    final Object member;
    final Annotation annotation;

    Relation(final Object member, final Annotation annotation) {
      this.member = member;
      this.annotation = Objects.requireNonNull(annotation);
    }

    private Object deflate() {
      return member instanceof Relations ? ((Relations)member).deflate() : member;
    }

    @Override
    public String toString() {
      return String.valueOf(JsdUtil.getId(annotation));
    }
  }

  static class Relations extends DelegateList {
    Relations() {
      super(new ArrayList());
    }

    private Relations(final List target) {
      super(target);
    }

    @Override
    public Relation set(final int index, final Relation element) {
      if (index == size())
        super.add(element);
      else
        super.set(index, element);

      return element;
    }

    List deflate() {
      for (int i = 0; i < size(); ++i) {
        final Object member = get(i);
        target.set(i, member instanceof Relation ? ((Relation)member).deflate() : member);
      }

      return target;
    }

    @Override
    public Relations subList(final int fromIndex, final int toIndex) {
      return new Relations(target.subList(fromIndex, toIndex));
    }

    @Override
    public String toString() {
      if (size() == 0)
        return "[]";

      final StringBuilder builder = new StringBuilder("[");
      for (final Object relation : this)
        builder.append(relation).append(", ");

      builder.setLength(builder.length() - 2);
      return builder.append(']').toString();
    }
  }

  private static int getNextRequiredElement(final Annotation[] annotations, final int a, int occurrence) {
    for (int i = a; i < annotations.length; ++i) {
      final Annotation annotation = annotations[i];
      final int minOccurs = JsdUtil.getMinOccurs(annotation);
      if (occurrence <= minOccurs)
        return i;

      occurrence = 1;
    }

    return -1;
  }

  /**
   * Creates a signature of the start of an iteration of element attributes at
   * the specified member index, annotation index, and annotation occurrence.
   * 

* Note: For performance reasons, each of the arguments are * converted to an unsigned short, which reduces their max value to 65535. * This value represents the maximum number of members, annotations and * occurrences that are supported by the {@code ArrayValidator}. * * @param index The index of the member. * @param a The index of the annotation. * @param occurrence The occurrence of annotation. * @return a signature of the start of an iteration of element attributes at * the specified indices. */ private static long sign(final int index, final int a, final int occurrence) { return Numbers.Compound.encode((short)(Short.MIN_VALUE + index), (short)(Short.MIN_VALUE + a), (short)(Short.MIN_VALUE + occurrence), (short)0); } static Error validate(final ArrayIterator iterator, final int occurrence, final Annotation[] annotations, final int a, final int minIterate, final int maxIterate, final int iteration, final IdToElement idToElement, final Relations relations, final boolean validate, final TriPredicate onPropertyDecode, final long lastIterSig) throws IOException { final int index = iterator.nextIndex(); if (!iterator.hasNext()) { if (minIterate == 0 && iteration == 1 && index == 0) return null; int nextRequiredIndex = getNextRequiredElement(annotations, a, occurrence); if (nextRequiredIndex == -1) { if (iteration < minIterate) nextRequiredIndex = 0; else return null; } return index == 0 ? Error.INVALID_CONTENT_IN_EMPTY_NOT_COMPLETE(annotations[nextRequiredIndex]) : Error.INVALID_CONTENT_NOT_COMPLETE(index - 1, annotations[nextRequiredIndex]); } if (a == annotations.length) { if (iteration < maxIterate) { final long nextIterSig = sign(index, occurrence, a); if (nextIterSig == lastIterSig) return Error.LOOP; return validate(iterator, 1, annotations, 0, minIterate, maxIterate, iteration + 1, idToElement, relations, validate, onPropertyDecode, nextIterSig); } iterator.next(); final Object object = ArrayIterator.preview(iterator.current); iterator.previous(); return Error.INVALID_CONTENT_MEMBERS_NOT_EXPECTED(index, annotations[a - 1], object); } final int minOccurs; final int maxOccurs; final boolean nullable; final Class codecType; final Annotation annotation = annotations[a]; if (annotation instanceof AnyElement) { final AnyElement element = (AnyElement)annotation; minOccurs = element.minOccurs(); maxOccurs = element.maxOccurs(); nullable = element.nullable(); codecType = AnyCodec.class; } else if (annotation instanceof ArrayElement) { final ArrayElement element = (ArrayElement)annotation; minOccurs = element.minOccurs(); maxOccurs = element.maxOccurs(); nullable = element.nullable(); codecType = ArrayCodec.class; } else if (annotation instanceof BooleanElement) { final BooleanElement element = (BooleanElement)annotation; minOccurs = element.minOccurs(); maxOccurs = element.maxOccurs(); nullable = element.nullable(); codecType = BooleanCodec.class; } else if (annotation instanceof NumberElement) { final NumberElement element = (NumberElement)annotation; minOccurs = element.minOccurs(); maxOccurs = element.maxOccurs(); nullable = element.nullable(); codecType = NumberCodec.class; } else if (annotation instanceof ObjectElement) { final ObjectElement element = (ObjectElement)annotation; minOccurs = element.minOccurs(); maxOccurs = element.maxOccurs(); nullable = element.nullable(); codecType = ObjectCodec.class; } else if (annotation instanceof StringElement) { final StringElement element = (StringElement)annotation; minOccurs = element.minOccurs(); maxOccurs = element.maxOccurs(); nullable = element.nullable(); codecType = StringCodec.class; } else { throw new UnsupportedOperationException("Unsupported annotation type: " + annotation.annotationType().getName()); } if (minOccurs < 0) throw new ValidationException("minOccurs must be a non-negative integer: " + Annotations.toSortedString(annotation, JsdUtil.ATTRIBUTES)); if (maxOccurs < minOccurs) throw new ValidationException("minOccurs must be less than or equal to maxOccurs: " + Annotations.toSortedString(annotation, JsdUtil.ATTRIBUTES)); Error error; if (iterator.nextIsNull()) { if (nullable || !validate) { error = null; relations.set(index, iterator.currentRelate(annotation)); } else { error = Error.ILLEGAL_VALUE_NULL; } } else { error = iterator.validate(annotation, index, relations, idToElement, codecType, validate, onPropertyDecode); } // System.err.println("m[" + index + "], a[" + a + "], o(" + minOccurs + ", " + maxOccurs + ")[" + occurrence + "], i(" + minIterate + ", " + maxIterate + ")[" + iteration + "]"); // int before; if (error != null) { error = Error.INVALID_CONTENT_WAS_FOUND(index, annotation).append(error); } else if (occurrence < minOccurs) { // If `minOccurs` is not yet satisfied, require increment `occurrence` return rewind(iterator, iterator.nextIndex(), validate(iterator, occurrence + 1, annotations, a, minIterate, maxIterate, iteration, idToElement, relations, validate, onPropertyDecode, lastIterSig)); } else { // before = iterator.nextIndex(); // If `occurrence` is under `maxOccurs`, optional increment `occurrence`. if (occurrence < maxOccurs && (error = rewind(iterator, iterator.nextIndex(), validate(iterator, occurrence + 1, annotations, a, minIterate, maxIterate, iteration, idToElement, relations, validate, onPropertyDecode, lastIterSig))) == null) return null; // assertIndex(before, iterator); } // If `minOccurs` has already been satisfied, then let's first try to skip the next member if (minOccurs < occurrence) { // before = iterator.nextIndex(); iterator.previous(); final Relation rollback = error != null ? null : relations.get(index); if (rewind(iterator, iterator.nextIndex(), validate(iterator, 1, annotations, a + 1, minIterate, maxIterate, iteration, idToElement, relations, validate, onPropertyDecode, lastIterSig)) == null) return null; if (rollback != null) relations.set(index, rollback); iterator.next(); // assertIndex(before, iterator); } return error != null ? error : rewind(iterator, iterator.nextIndex(), validate(iterator, 1, annotations, a + 1, minIterate, maxIterate, iteration, idToElement, relations, validate, onPropertyDecode, lastIterSig)); } private static Error rewind(final ArrayIterator iterator, final int index, final Error error) throws IOException { if (error == null) return null; for (int i = iterator.nextIndex(); i > index; --i) iterator.previous(); return error; } // private static void assertIndex(final int before, final ArrayIterator iterator) throws IOException { // if (iterator.nextIndex() != before) // throw new IllegalStateException(iterator.nextIndex() + " != " + before); // } @SuppressWarnings("unchecked") static Error validate(final List members, final IdToElement idToElement, final int[] elementIds, final Relations relations, final boolean validate, final TriPredicate onPropertyDecode) { final Annotation[] annotations = idToElement.get(elementIds); try { return validate(new ArrayEncodeIterator((ListIterator)members.listIterator()), 1, annotations, 0, idToElement.getMinIterate(), idToElement.getMaxIterate(), 1, idToElement, relations, validate, onPropertyDecode, -1); } catch (final IOException e) { throw new IllegalStateException("Should not happen, as this method is only called for encode", e); } } static Error validate(final Class annotationType, final List members, final Relations relations, final boolean validate, final TriPredicate onPropertyDecode) { final IdToElement idToElement = new IdToElement(); final int[] elementIds = JsdUtil.digest(annotationType.getAnnotations(), annotationType.getName(), idToElement); return validate(members, idToElement, elementIds, relations, validate, onPropertyDecode); } private ArrayValidator() { } }