com.networknt.schema.SchemaLocation Maven / Gradle / Ivy
Show all versions of json-schema-validator Show documentation
/*
* Copyright (c) 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.networknt.schema;
import java.util.Objects;
/**
* The schema location is the canonical IRI of the schema object plus a JSON
* Pointer fragment indicating the subschema that produced a result. In contrast
* with the evaluation path, the schema location MUST NOT include by-reference
* applicators such as $ref or $dynamicRef.
*/
public class SchemaLocation {
private static final JsonNodePath JSON_POINTER = new JsonNodePath(PathType.JSON_POINTER);
private static final JsonNodePath ANCHOR = new JsonNodePath(PathType.URI_REFERENCE);
/**
* Represents a relative schema location to the current document.
*/
public static final SchemaLocation DOCUMENT = new SchemaLocation(null, JSON_POINTER);
private final AbsoluteIri absoluteIri;
private final JsonNodePath fragment;
private volatile String value = null; // computed lazily
/**
* Constructs a new {@link SchemaLocation}.
*
* @param absoluteIri canonical absolute IRI of the schema object
* @param fragment the fragment
*/
public SchemaLocation(AbsoluteIri absoluteIri, JsonNodePath fragment) {
this.absoluteIri = absoluteIri;
this.fragment = fragment;
}
/**
* Constructs a new {@link SchemaLocation}.
*
* @param absoluteIri canonical absolute IRI of the schema object
*/
public SchemaLocation(AbsoluteIri absoluteIri) {
this(absoluteIri, JSON_POINTER);
}
/**
* Gets the canonical absolute IRI of the schema object.
*
* This is a unique identifier indicated by the $id property or id property in
* Draft 4 and earlier. This does not have to be network accessible.
*
* @return the canonical absolute IRI of the schema object.
*/
public AbsoluteIri getAbsoluteIri() {
return this.absoluteIri;
}
/**
* Gets the fragment.
*
* @return the fragment
*/
public JsonNodePath getFragment() {
return this.fragment;
}
/**
* Appends the token to the fragment.
*
* @param token the segment
* @return a new schema location with the segment
*/
public SchemaLocation append(String token) {
return new SchemaLocation(this.absoluteIri, this.fragment.append(token));
}
/**
* Appends the index to the fragment.
*
* @param index the segment
* @return a new schema location with the segment
*/
public SchemaLocation append(int index) {
return new SchemaLocation(this.absoluteIri, this.fragment.append(index));
}
/**
* Parses a string representing an IRI of the schema location.
*
* @param iri the IRI
* @return the schema location
*/
public static SchemaLocation of(String iri) {
if (iri == null) {
return null;
}
if ("#".equals(iri)) {
return DOCUMENT;
}
String[] iriParts = iri.split("#");
AbsoluteIri absoluteIri = null;
JsonNodePath fragment = JSON_POINTER;
if (iriParts.length > 0) {
absoluteIri = AbsoluteIri.of(iriParts[0]);
}
if (iriParts.length > 1) {
fragment = Fragment.of(iriParts[1]);
}
return new SchemaLocation(absoluteIri, fragment);
}
/**
* Resolves against a absolute IRI reference or fragment.
*
* @param absoluteIriReferenceOrFragment to resolve
* @return the resolved schema location
*/
public SchemaLocation resolve(String absoluteIriReferenceOrFragment) {
if (absoluteIriReferenceOrFragment == null) {
return this;
}
if ("#".equals(absoluteIriReferenceOrFragment)) {
return new SchemaLocation(this.getAbsoluteIri(), JSON_POINTER);
}
JsonNodePath fragment = JSON_POINTER;
String[] parts = absoluteIriReferenceOrFragment.split("#");
AbsoluteIri absoluteIri = this.getAbsoluteIri();
if (absoluteIri != null) {
if (!parts[0].isEmpty()) {
absoluteIri = absoluteIri.resolve(parts[0]);
}
} else {
absoluteIri = AbsoluteIri.of(parts[0]);
}
if (parts.length > 1 && !parts[1].isEmpty()) {
fragment = Fragment.of(parts[1]);
}
return new SchemaLocation(absoluteIri, fragment);
}
/**
* Resolves against a absolute IRI reference or fragment.
*
* @param schemaLocation the parent
* @param absoluteIriReferenceOrFragment to resolve
* @return the resolved schema location
*/
public static String resolve(SchemaLocation schemaLocation, String absoluteIriReferenceOrFragment) {
if ("#".equals(absoluteIriReferenceOrFragment)) {
return schemaLocation.getAbsoluteIri().toString() + "#";
}
String[] parts = absoluteIriReferenceOrFragment.split("#");
AbsoluteIri absoluteIri = schemaLocation.getAbsoluteIri();
String resolved = parts[0];
if (absoluteIri != null) {
if (!parts[0].isEmpty()) {
resolved = absoluteIri.resolve(parts[0]).toString();
} else {
resolved = absoluteIri.toString();
}
}
if (parts.length > 1 && !parts[1].isEmpty()) {
resolved = resolved + "#" + parts[1];
} else {
resolved = resolved + "#";
}
return resolved;
}
/**
* The fragment can be a JSON pointer to the document or an anchor.
*/
public static class Fragment {
/**
* Parses a string representing a fragment.
*
* @param fragmentString the fragment
* @return the path
*/
public static JsonNodePath of(String fragmentString) {
if (fragmentString.startsWith("#")) {
fragmentString = fragmentString.substring(1);
}
JsonNodePath fragment = JSON_POINTER;
String[] fragmentParts = fragmentString.split("/");
boolean jsonPointer = false;
if (fragmentString.startsWith("/")) {
// json pointer
jsonPointer = true;
} else {
// anchor
fragment = ANCHOR;
}
int index = -1;
for (int fragmentPartIndex = 0; fragmentPartIndex < fragmentParts.length; fragmentPartIndex++) {
if (fragmentPartIndex == 0 && jsonPointer) {
continue;
}
String fragmentPart = fragmentParts[fragmentPartIndex];
for (int x = 0; x < fragmentPart.length(); x++) {
char ch = fragmentPart.charAt(x);
if (ch >= '0' && ch <= '9') {
if (x == 0) {
index = 0;
} else {
index = index * 10;
}
index += (ch - '0');
} else {
index = -1; // Not an index
break;
}
}
if (index != -1) {
fragment = fragment.append(index);
} else {
fragment = fragment.append(fragmentPart.toString());
}
}
if (index == -1 && fragmentString.endsWith("/")) {
// Trailing / in fragment
fragment = fragment.append("");
}
return fragment;
}
/**
* Determine if the string is a fragment.
*
* @param fragmentString to evaluate
* @return true if it is a fragment
*/
public static boolean isFragment(String fragmentString) {
return fragmentString.startsWith("#");
}
/**
* Determine if the string is a JSON Pointer fragment.
*
* @param fragmentString to evaluate
* @return true if it is a JSON Pointer fragment
*/
public static boolean isJsonPointerFragment(String fragmentString) {
return fragmentString.startsWith("#/");
}
/**
* Determine if the string is an anchor fragment.
*
* @param fragmentString to evaluate
* @return true if it is an anchor fragment
*/
public static boolean isAnchorFragment(String fragmentString) {
return isFragment(fragmentString) && !isDocumentFragment(fragmentString)
&& !isJsonPointerFragment(fragmentString);
}
/**
* Determine if the string is a fragment referencing the document.
*
* @param fragmentString to evaluate
* @return true if it is a fragment
*/
public static boolean isDocumentFragment(String fragmentString) {
return "#".equals(fragmentString);
}
}
/**
* Returns a builder for building {@link SchemaLocation}.
*
* @return the builder
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder for building {@link SchemaLocation}.
*/
public static class Builder {
private AbsoluteIri absoluteIri;
private JsonNodePath fragment = JSON_POINTER;
/**
* Sets the canonical absolute IRI of the schema object.
*
* This is a unique identifier indicated by the $id property or id property in
* Draft 4 and earlier. This does not have to be network accessible.
*
* @param absoluteIri the canonical IRI of the schema object
* @return the builder
*/
protected Builder absoluteIri(AbsoluteIri absoluteIri) {
this.absoluteIri = absoluteIri;
return this;
}
/**
* Sets the canonical absolute IRI of the schema object.
*
* This is a unique identifier indicated by the $id property or id property in
* Draft 4 and earlier. This does not have to be network accessible.
*
* @param absoluteIri the canonical IRI of the schema object
* @return the builder
*/
protected Builder absoluteIri(String absoluteIri) {
return absoluteIri(AbsoluteIri.of(absoluteIri));
}
/**
* Sets the fragment.
*
* @param fragment the fragment
* @return the builder
*/
protected Builder fragment(JsonNodePath fragment) {
this.fragment = fragment;
return this;
}
/**
* Sets the fragment.
*
* @param fragment the fragment
* @return the builder
*/
protected Builder fragment(String fragment) {
return fragment(Fragment.of(fragment));
}
/**
* Builds a {@link SchemaLocation}.
*
* @return the schema location
*/
public SchemaLocation build() {
return new SchemaLocation(absoluteIri, fragment);
}
}
@Override
public String toString() {
if (this.value == null) {
if (this.absoluteIri != null && this.fragment == null) {
this.value = this.absoluteIri.toString();
} else {
StringBuilder result = new StringBuilder();
if (this.absoluteIri != null) {
result.append(this.absoluteIri.toString());
}
result.append("#");
if (this.fragment != null) {
result.append(this.fragment.toString());
}
this.value = result.toString();
}
}
return this.value;
}
@Override
public int hashCode() {
return Objects.hash(fragment, absoluteIri);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SchemaLocation other = (SchemaLocation) obj;
return Objects.equals(fragment, other.fragment) && Objects.equals(absoluteIri, other.absoluteIri);
}
}