com.github.fge.jsonschema.core.tree.BaseSchemaTree.java Source code

Java tutorial

Introduction

Here is the source code for com.github.fge.jsonschema.core.tree.BaseSchemaTree.java

Source

/*
 * Copyright (c) 2014, Francis Galiegue (fgaliegue@gmail.com)
 *
 * This software is dual-licensed under:
 *
 * - the Lesser General Public License (LGPL) version 3.0 or, at your option, any
 *   later version;
 * - the Apache Software License (ASL) version 2.0.
 *
 * The text of this file and of both licenses is available at the root of this
 * project or, if you have the jar distribution, in directory META-INF/, under
 * the names LGPL-3.0.txt and ASL-2.0.txt respectively.
 *
 * Direct link to the sources:
 *
 * - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt
 * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt
 */

package com.github.fge.jsonschema.core.tree;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.JacksonUtils;
import com.github.fge.jackson.jsonpointer.JsonPointer;
import com.github.fge.jackson.jsonpointer.TokenResolver;
import com.github.fge.jsonschema.core.exceptions.JsonReferenceException;
import com.github.fge.jsonschema.core.ref.JsonRef;
import com.github.fge.jsonschema.core.tree.key.SchemaKey;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;

/**
 * Base implementation of a {@link SchemaTree}
 *
 * @see CanonicalSchemaTree
 * @see InlineSchemaTree
 */
@Immutable
@ParametersAreNonnullByDefault
public abstract class BaseSchemaTree implements SchemaTree {
    private static final JsonNodeFactory FACTORY = JacksonUtils.nodeFactory();

    protected final SchemaKey key;

    /**
     * The contents of {@code $schema} for that schema
     *
     * <p>Note that it is required that if it is present, it be an absolute
     * JSON Reference. If no suitable {@code $schema} is found, an empty ref
     * is returned.</p>
     */
    private final JsonRef dollarSchema;

    /**
     * The initial node
     */
    protected final JsonNode baseNode;

    /**
     * The current JSON Pointer into the node. Starts empty.
     */
    protected final JsonPointer pointer;

    /**
     * The current node.
     */
    private final JsonNode node;

    /**
     * The JSON Reference representing the context at the root of the schema
     *
     * <p>If there is an {@code id} at the top level, this will take precedence
     * over {@link SchemaKey#getLoadingRef()} (provided the id is well
     * formed).</p>
     */
    private final JsonRef startingRef;

    /**
     * The current resolution context
     */
    private final JsonRef currentRef;

    protected BaseSchemaTree(final SchemaKey key, final JsonNode baseNode, final JsonPointer pointer) {
        Preconditions.checkNotNull(key);
        Preconditions.checkNotNull(baseNode);
        Preconditions.checkNotNull(pointer);

        this.key = key;
        dollarSchema = extractDollarSchema(baseNode);

        this.baseNode = baseNode;
        this.pointer = pointer;
        node = pointer.path(baseNode);

        final JsonRef loadingRef = key.getLoadingRef();
        final JsonRef ref = idFromNode(baseNode);

        startingRef = ref == null ? loadingRef : loadingRef.resolve(ref);
        currentRef = nextRef(startingRef, pointer, baseNode);
    }

    @Deprecated
    protected BaseSchemaTree(final JsonRef loadingRef, final JsonNode baseNode, final JsonPointer pointer) {
        dollarSchema = extractDollarSchema(baseNode);

        key = loadingRef.equals(JsonRef.emptyRef()) ? SchemaKey.anonymousKey() : SchemaKey.forJsonRef(loadingRef);

        this.baseNode = baseNode;
        this.pointer = pointer;
        node = pointer.path(baseNode);

        final JsonRef ref = idFromNode(baseNode);

        startingRef = ref == null ? loadingRef : loadingRef.resolve(ref);

        currentRef = nextRef(startingRef, pointer, baseNode);
    }

    protected BaseSchemaTree(final BaseSchemaTree other, final JsonPointer newPointer) {
        key = other.key;

        dollarSchema = other.dollarSchema;
        baseNode = other.baseNode;

        pointer = newPointer;
        node = newPointer.get(baseNode);

        startingRef = other.startingRef;
        currentRef = nextRef(startingRef, newPointer, baseNode);
    }

    @Override
    @Deprecated
    public final long getId() {
        return key.getId();
    }

    @Override
    public final JsonNode getBaseNode() {
        return baseNode;
    }

    @Override
    public final JsonPointer getPointer() {
        return pointer;
    }

    @Override
    public final JsonNode getNode() {
        return node;
    }

    /**
     * Resolve a JSON Reference against the current resolution context
     *
     * @param other the JSON Reference to resolve
     * @return the resolved reference
     * @see JsonRef#resolve(JsonRef)
     */
    @Override
    public final JsonRef resolve(final JsonRef other) {
        return currentRef.resolve(other);
    }

    @Override
    public final JsonRef getDollarSchema() {
        return dollarSchema;
    }

    /**
     * Get the loading URI for that schema
     *
     * @return the loading URI as a {@link JsonRef}
     */
    @Override
    public final JsonRef getLoadingRef() {
        return key.getLoadingRef();
    }

    /**
     * Get the current resolution context
     *
     * @return the context as a {@link JsonRef}
     */
    @Override
    public final JsonRef getContext() {
        return currentRef;
    }

    @Override
    public final JsonNode asJson() {
        final ObjectNode ret = FACTORY.objectNode();

        ret.put("loadingURI", FACTORY.textNode(key.getLoadingRef().toString()));
        ret.put("pointer", FACTORY.textNode(pointer.toString()));

        return ret;
    }

    @Nonnull
    @Override
    public final String toString() {
        return Objects.toStringHelper(this).add("key", key).add("pointer", pointer).add("URI context", currentRef)
                .toString();
    }

    @Override
    public final int hashCode() {
        return key.hashCode() ^ pointer.hashCode();
    }

    @Override
    public final boolean equals(@Nullable final Object obj) {
        if (obj == null)
            return false;
        if (this == obj)
            return true;
        if (getClass() != obj.getClass())
            return false;
        final BaseSchemaTree other = (BaseSchemaTree) obj;
        return key.equals(other.key) && pointer.equals(other.pointer);
    }

    /**
     * Build a JSON Reference from a node
     *
     * <p>This will return {@code null} if the reference could not be built. The
     * conditions for a successful build are as follows:</p>
     *
     * <ul>
     *     <li>the node is an object;</li>
     *     <li>it has a member named {@code id};</li>
     *     <li>the value of this member is a string;</li>
     *     <li>this string is a valid URI.</li>
     * </ul>
     *
     * @param node the node
     * @return a JSON Reference, or {@code null}
     */
    @Nullable
    protected static JsonRef idFromNode(final JsonNode node) {
        if (!node.path("id").isTextual())
            return null;

        try {
            return JsonRef.fromString(node.get("id").textValue());
        } catch (JsonReferenceException ignored) {
            return null;
        }
    }

    /**
     * Calculate the next URI context from a starting reference and node
     *
     * @param startingRef the starting reference
     * @param ptr the JSON Pointer
     * @param startingNode the starting node
     * @return the calculated reference
     */
    private static JsonRef nextRef(final JsonRef startingRef, final JsonPointer ptr, final JsonNode startingNode) {
        JsonRef ret = startingRef;
        JsonRef idRef;
        JsonNode node = startingNode;

        for (final TokenResolver<JsonNode> resolver : ptr) {
            node = resolver.get(node);
            if (node == null)
                break;
            idRef = idFromNode(node);
            if (idRef != null)
                ret = ret.resolve(idRef);
        }

        return ret;
    }

    private static JsonRef extractDollarSchema(final JsonNode schema) {
        final JsonNode node = schema.path("$schema");

        if (!node.isTextual())
            return JsonRef.emptyRef();

        try {
            final JsonRef ref = JsonRef.fromString(node.textValue());
            return ref.isAbsolute() ? ref : JsonRef.emptyRef();
        } catch (JsonReferenceException ignored) {
            return JsonRef.emptyRef();
        }
    }
}