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

com.zuunr.json.schema.validation.node.ValidationNode Maven / Gradle / Ivy

There is a newer version: 0.1.4
Show newest version
package com.zuunr.json.schema.validation.node;

import com.zuunr.json.JsonArray;
import com.zuunr.json.JsonObject;
import com.zuunr.json.JsonObjectBuilder;
import com.zuunr.json.JsonValue;
import com.zuunr.json.pointer.JsonPointer;
import com.zuunr.json.schema.JsonSchema;
import com.zuunr.json.schema.validation.OutputStructure;
import com.zuunr.json.schema.validation.ValidationContext;

import java.util.ArrayDeque;
import java.util.Deque;

/**
 * @author Niklas Eldberger
 */
public abstract class ValidationNode {

    private static final JsonObject VALID_FLAG = JsonObject.EMPTY.put("valid", true);
    private static final JsonObject INVALID_FLAG = JsonObject.EMPTY.put("valid", false);

    private final ValidationContext validationContext;
    protected final JsonValue rootInstance;
    protected final JsonValue instance;
    protected JsonValue filtrate;
    public final JsonSchema schema;
    protected ValidationNode parentNode;
    private boolean childNodesCompleted;
    public Location location;
    private Boolean valid;
    private JsonObject error;
    private ValidationNode nextSiblingNode;
    private ValidationNode firstChildNode;
    private int schemaKeywordIndex = -1;

    public ValidationNode(JsonValue instance, JsonSchema schema, int schemaKeywordIndex, ValidationContext validationContext, JsonValue rootInstance) {
        this.instance = instance;
        this.schema = schema;
        this.validationContext = validationContext;
        this.rootInstance = rootInstance;
        this.schemaKeywordIndex = schemaKeywordIndex;
    }

    public int keywordSchemaIndex() {
        return schemaKeywordIndex;
    }


    protected void childNodeCompleted(ValidationNode subnode) {

    }

    final void allChildNodesAreCompleted() {
        childNodesCompleted = true;
        doAfterAllChildNodesAreCompleted();
    }

    /**
     * A handle that is called when all subnodes are completed. If valid is not already set this method is
     * now able to do that.
     */
    protected abstract void doAfterAllChildNodesAreCompleted();

    public final boolean resultIsKnown() {
        return investigateIfValid() != null;
    }

    public final Boolean investigateIfValid() {
        if (valid == null) {
            valid = calculateValid();
        }
        return valid;
    }

    protected final void setValid(boolean valid) {
        if (this.valid != null && !this.valid.equals(valid)) {
            throw new RuntimeException("Valid is already set!");
        }
        this.valid = valid;
    }

    /**
     * Is called by getValidationResult() if there i no ValidationResult available yet.
     *
     * @return a ValidationResult if it can be calculated, otherwise null.
     */

    protected abstract Boolean calculateValid();

    protected ValidationNode firstChildNode() {
        if (firstChildNode == null) {
            firstChildNode = createFirstChildNode();
        }
        return firstChildNode;
    }

    /**
     * Creates the first nested subnode. This class is the parentnode of the childnode and the childnode is typically of
     * another class than this parentnode.
     *
     * @return the first childnode of this ValidationNode or null if there is no childnode
     */
    protected abstract ValidationNode createFirstChildNode();

    protected ValidationNode nextSiblingNode() {
        if (nextSiblingNode == null) {
            nextSiblingNode = createNextChildNodeOfParent();
        }
        return nextSiblingNode;
    }

    private ValidationNode nextErrorSiblingNode() {

        ValidationNode next = nextSiblingNode;
        if (next == null) {
            return null;
        }

        while (next.getValid() || validationContext().outputStructure() == OutputStructure.VERBOSE) {
            next = next.nextSiblingNode;
            if (next == null) {
                return null;
            }
        }
        return next;
    }

    /**
     * Creates the next childnode of the parentnode. The returned ValidationStep is typically of the same class as this ValidationStep
     *
     * @return
     */
    protected abstract ValidationNode createNextChildNodeOfParent();

    final ValidationNode parentNode() {
        return parentNode;
    }

    boolean isChildNodesCompleted() {
        return childNodesCompleted;
    }

    public JsonValue filtrate() {
        if (filtrate == null) {
            if (investigateIfValid()) {
                filtrate = instance;
            }
        }
        return filtrate;
    }

    public ValidationContext validationContext() {
        return validationContext;
    }

    void cleanCompletedChildNodesList() {

    }

    public abstract Location location();

    public Boolean getValid() {
        return valid;
    }

    public JsonObject validationResult() {
        return buildValidationResult(this, validationContext);
    }

    //TODO: Refactor this method
    static JsonObject buildValidationResult(ValidationNode schemaNode, ValidationContext validationContext) {

        if (schemaNode.getValid()) {
            return VALID_FLAG;
        }

        if (validationContext.outputStructure() == OutputStructure.FLAG) {
            return INVALID_FLAG;
        }

        Deque stack = new ArrayDeque();

        ValidationNode top = schemaNode;
        top.location();
        stack.push(schemaNode);

        while (!stack.isEmpty()) {

            top = stack.peek();

            if (top.firstChildNode == null) {

                top.location();
                top.error(buildError(top, null));
                stack.pop();
            } else {

                JsonArray nestedErrorsBuilder = null; // TODO This should be a builder

                ValidationNode errorSubnode = top.firstChildNode();

                boolean allErrorsAboveInStackHandled = true;// = false;
                while (errorSubnode != null) {
                    if (errorSubnode.error() == null) {
                        if (OutputStructure.VERBOSE == validationContext.outputStructure() || !errorSubnode.getValid()) {
                            errorSubnode.location();
                            stack.push(errorSubnode);
                            allErrorsAboveInStackHandled = false;
                        }
                    } else {
                        int errorsSize = errorSubnode.error().get("errors", JsonArray.EMPTY).getJsonArray().size();

                        if (errorsSize == 1 && OutputStructure.VERBOSE != validationContext.outputStructure()) {
                            if (nestedErrorsBuilder == null) {
                                nestedErrorsBuilder = JsonArray.EMPTY;
                            }
                            nestedErrorsBuilder = nestedErrorsBuilder.add(errorSubnode.error().get("errors").get(0));
                        } else {
                            if (nestedErrorsBuilder == null) {
                                nestedErrorsBuilder = JsonArray.EMPTY;
                            }
                            nestedErrorsBuilder = nestedErrorsBuilder.add(errorSubnode.error());
                        }
                    }
                    errorSubnode = errorSubnode.nextErrorSiblingNode();
                }

                top.location();
                top.error(buildError(top, nestedErrorsBuilder));


                if (allErrorsAboveInStackHandled) {
                    stack.pop();
                }
            }
        }
        return top.error();
    }

    private static JsonObject buildError(ValidationNode validationNode, JsonArray nestedErrors) {
        JsonObjectBuilder errorBuilder = JsonObject.EMPTY.builder();

        errorBuilder
                .put("instanceLocation", validationNode.location.instance.as(JsonPointer.class).getJsonPointerString())
                .put("keywordLocation", validationNode.location.keyword.as(JsonPointer.class).getJsonPointerString())
                .put("valid", validationNode.valid);

        if (validationNode.location.instanceProperty != null) {
            errorBuilder = errorBuilder.put("instanceProperty", validationNode.location.instanceProperty);
        }

        if (validationNode.location.keywordValue != null) {
            errorBuilder = errorBuilder.put("keywordValue", validationNode.location.keywordValue);
        }

        if (nestedErrors != null) {
            errorBuilder = errorBuilder.put("errors", nestedErrors);
        }

        return errorBuilder.build();
    }

    private JsonObject error() {
        return error;
    }

    private void error(JsonObject error) {
        this.error = error;
    }

    protected abstract JsonValue keyword();

    public static class Location {
        public JsonArray instance;
        public String instanceProperty;
        public JsonArray keyword;
        public JsonValue keywordValue;

        public Location() {
        }

        public Location(JsonArray instance, String instanceProperty, JsonArray keyword, JsonValue keywordValue) {
            this.instance = instance;
            this.instanceProperty = instanceProperty;
            this.keyword = keyword;
            this.keywordValue = keywordValue;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy