org.eel.kitchen.jsonschema.ref.JsonRef.java Source code

Java tutorial

Introduction

Here is the source code for org.eel.kitchen.jsonschema.ref.JsonRef.java

Source

/*
 * Copyright (c) 2012, Francis Galiegue <fgaliegue@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Lesser GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.eel.kitchen.jsonschema.ref;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Preconditions;
import org.eel.kitchen.jsonschema.main.JsonSchemaException;
import org.eel.kitchen.jsonschema.report.Domain;
import org.eel.kitchen.jsonschema.report.Message;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * Representation of a JSON Reference
 *
 * <p><a href="http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03">JSON
 * Reference</a>, currently a draft, is a way to define a path within a JSON
 * document.</p>
 *
 * <p>To quote the draft, "A JSON Reference is a JSON object, which contains
 * a member named "$ref", which has a JSON string value." This string value
 * must be a URI. Example:</p>
 *
 * <pre>
 *     {
 *         "$ref": "http://example.com/example.json#/foo/bar"
 *     }
 * </pre>
 *
 * <p>This class is used in a more general way than the draft. It is also used
 * as a backing class for schema identifiers.</p>
 *
 * <p>The implementation is a wrapper over Java's {@link URI}, with the
 * following differences:</p>
 *
 * <ul>
 *     <li>all URIs are normalized from the get go;</li>
 *     <li>an empty fragment is equivalent to no fragment at all, and stands for
 *     a root JSON Pointer;</li>
 *     <li>a reference is taken to be absolute if the underlying URI is
 *     absolute <i>and</i> it has no fragment, or an empty fragment.</li>
 * </ul>
 *
 * <p>It also special cases the following:</p>
 *
 * <ul>
 *     <li>an empty reference (for instance, used in anonymouns schemas);</li>
 *     <li>URIs with the {@code jar} scheme (the resolving algorithm differs --
 *     please note that this breaks URI resolution rules).</li>
 * </ul>
 *
 * <p>This class is thread safe and immutable.</p>
 */

public abstract class JsonRef {
    private static final URI EMPTY_URI = URI.create("");

    protected static final URI HASHONLY_URI = URI.create("#");

    /**
     * The URI, as provided by the input, with an appended empty fragment if
     * no fragment was provided
     */
    protected final URI uri;

    /**
     * The locator of this reference. This is the URI with an empty fragment
     * part.
     */
    protected final URI locator;

    /**
     * The fragment of this reference.
     *
     * @see JsonFragment
     */
    protected final JsonFragment fragment;

    /**
     * String representation
     */
    private final String asString;

    /**
     * Hashcode
     */
    private final int hashCode;

    /**
     * Main constructor, {@code protected} by design
     *
     * @param uri the URI to build that reference
     */
    protected JsonRef(final URI uri) {
        final String scheme = uri.getScheme();
        final String ssp = uri.getSchemeSpecificPart();
        final String uriFragment = uri.getFragment();

        final String realFragment = uriFragment == null ? "" : uriFragment;

        try {
            this.uri = new URI(scheme, ssp, realFragment);
            locator = new URI(scheme, ssp, "");
            fragment = JsonFragment.fromFragment(realFragment);
            asString = this.uri.toString();
            hashCode = asString.hashCode();
        } catch (URISyntaxException e) {
            throw new RuntimeException("WTF??", e);
        }
    }

    /**
     * Build a JSON Reference from a URI
     *
     * @param uri the provided URI
     * @return the JSON Reference
     * @throws NullPointerException the provided URI is null
     */
    public static JsonRef fromURI(final URI uri) {
        Preconditions.checkNotNull(uri, "URI must not be null");

        final URI normalized = uri.normalize();

        if (HASHONLY_URI.equals(normalized) || EMPTY_URI.equals(normalized))
            return EmptyJsonRef.getInstance();

        return "jar".equals(normalized.getScheme()) ? new JarJsonRef(normalized)
                : new HierarchicalJsonRef(normalized);
    }

    /**
     * Build a JSON Reference from a string input
     *
     * @param s the string
     * @return the reference
     * @throws JsonSchemaException string is not a valid URI
     * @throws NullPointerException provided string is null
     */
    public static JsonRef fromString(final String s) throws JsonSchemaException {
        Preconditions.checkNotNull(s, "string must not be null");

        try {
            return fromURI(new URI(s));
        } catch (URISyntaxException e) {
            final Message.Builder msg = Domain.REF_RESOLVING.newMessage().setKeyword("N/A").addInfo("uri", s)
                    .setMessage("invalid URI");
            throw new JsonSchemaException(msg.build(), e);
        }
    }

    /**
     * Build a JSON Reference from a {@link JsonNode}
     *
     * <p>If the node is not textual, this returns an empty reference.
     * Otherwise, it calls {@link #fromString(String)} with this node's text
     * value.</p>
     *
     * @param node the node
     * @return the reference
     * @throws JsonSchemaException see {@link #fromString(String)}
     * @throws NullPointerException provided node is null
     */
    public static JsonRef fromNode(final JsonNode node) throws JsonSchemaException {
        Preconditions.checkNotNull(node, "node must not be null");

        return node.isTextual() ? fromString(node.textValue()) : EmptyJsonRef.getInstance();
    }

    /**
     * Return an empty reference
     *
     * <p>An empty reference is a reference which only has an empty fragment.
     * </p>
     *
     * @return see above
     */
    public static JsonRef emptyRef() {
        return EmptyJsonRef.getInstance();
    }

    /**
     * Return the underlying URI for this JSON Reference
     *
     * @return the URI
     */
    public final URI toURI() {
        return uri;
    }

    /**
     * Tell whether this reference is an absolute reference
     *
     * <p>A JSON Reference is considered absolute iif the underlying URI is
     * itself absolute <b>and</b> it has an empty, or no, fragment part.</p>
     *
     * @return see above
     */
    public abstract boolean isAbsolute();

    /**
     * Resolve this reference against another reference
     *
     * @param other the reference to resolve
     * @return the resolved reference
     */
    public abstract JsonRef resolve(final JsonRef other);

    /**
     * Return this JSON Reference's locator
     *
     * <p>The locator of a fragment is the URI with an empty fragment.</p>
     *
     * @return an URI
     */
    public final URI getLocator() {
        return locator;
    }

    /**
     * Return this JSON Reference's fragment
     *
     * @return the fragment
     */
    public final JsonFragment getFragment() {
        return fragment;
    }

    /**
     * Tell whether the current JSON Reference "contains" another
     *
     * <p>This is considered true iif both references have the same locator,
     * in other words, if they differ only by their fragment part.</p>
     *
     * @param other the other reference
     * @return see above
     */
    public final boolean contains(final JsonRef other) {
        return locator.equals(other.locator);
    }

    @Override
    public final int hashCode() {
        return hashCode;
    }

    @Override
    public final boolean equals(final Object o) {
        if (o == null)
            return false;
        if (this == o)
            return true;

        if (!(o instanceof JsonRef))
            return false;

        final JsonRef that = (JsonRef) o;
        return asString.equals(that.asString);
    }

    @Override
    public final String toString() {
        return asString;
    }
}