blob: e557df4b0e7d0dec88b27fb667463b8c02e4ce65 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.jena.irix;
import java.util.Objects;
import java.util.Optional;
import org.apache.jena.atlas.lib.Cache;
import org.apache.jena.atlas.lib.CacheFactory;
/**
* A resolver is a base IRI and a policy for resolution. The policy choices are
* whether to resolve against the base, or only consider the IRI being processed, and
* whether to allow relative IRIs or to enforce that "base resolve IRI" is an
* acceptable IRI for use in RDF (the test is {@link IRIx#isReference()}).
* <p>
* Normal use is to resolve and not allow relative IRIs.
* <p>
* The base may be null to support passing around a resolver that accepts/reject
* IRIs. For application to check IRIs, use {@link IRIs#check}/{@link IRIs#checkEx}
* directly.
*/
public class IRIxResolver {
private final IRIx base;
private final boolean resolve;
private final boolean allowRelative;
private IRIxResolver(IRIx base, boolean resolve, boolean allowRelative) {
this.base = base;
this.resolve = resolve;
this.allowRelative = allowRelative;
}
/** Return the base of this resolver */
public IRIx getBase() { return base; }
/** Return the base of this resolver as a string */
public String getBaseURI() { return base == null ? null : base.str(); }
/*
* Some providers are expensive compared to the cost of parsing.
* During parsing a lot of IRIs are being created, many the same. For example,
* e.g. properties or blocks of triples with the same subject.
*/
private static int DftCacheSize = 500;
private Cache<String, IRIx> cache = CacheFactory.createCache(DftCacheSize);
/** Resolve the argument URI string according to resolver policy */
public IRIx resolve(String other) {
Objects.requireNonNull(other);
if ( cache == null )
return resolve0(other);
// To allow for exceptions, we try the cache, and if no hit,
// make the result then try again to fill the cache.
// Because for a given key there is one right answer, it does not matter
// if any thread gets in and changes the cache (cache operations are
// thread safe).
IRIx iri = cache.getIfPresent(other);
if ( iri != null )
return iri;
// May throw an exception
IRIx iriValue = resolve0(other);
return cache.getOrFill(other, ()->iriValue);
}
private IRIx resolve0(String str) {
IRIx x = (base != null && resolve)
? base.resolve(str)
: IRIx.create(str);
if ( ! allowRelative && x.isRelative() )
throw new RelativeIRIException("Relative IRI: <"+str+">");
return x;
}
/** Create a new resolver with the same policies as the old one. */
public IRIxResolver resetBase(IRIx newBase) {
return new IRIxResolver(newBase, resolve, allowRelative);
}
@Override
public String toString() {
return "IRIxResolver[base=" + base + ", resolve=" + resolve + ", relative=" + allowRelative+"]";
}
public static Builder create() { return new Builder(); }
/**
* Create a {@link IRIxResolver} with the base URI which is resolved against the
* current system default base.
*/
public static Builder create(IRIxResolver original) {
Builder builder = new Builder();
builder.base = Optional.ofNullable(original.base);
builder.resolve = original.resolve;
builder.allowRelative = original.allowRelative;
return builder;
}
/**
* Create a builder for a {@link IRIxResolver} with the base URI which is resolved against the
* current system default base.
*/
public static Builder create(IRIx baseIRI) {
return new Builder().base(baseIRI);
}
/**
* Create a builder for a {@link IRIxResolver} with the base URI
* which is resolved against the current system default base.
*/
public static Builder create(String baseStr) {
IRIx base = (baseStr == null) ? null : IRIs.resolveIRI(baseStr);
return new Builder().base(base);
}
public static class Builder {
// null is "unset".
private Optional<IRIx> base = null;
private boolean resolve = true;
private boolean allowRelative = true;
private Builder() {}
public Builder base(IRIx baseURI) {
this.base = Optional.ofNullable(baseURI);
return this;
}
public Builder base(String baseStr) {
IRIx baseIRI = (baseStr == null) ? null : IRIs.resolveIRI(baseStr);
this.base = Optional.ofNullable(baseIRI);
return this;
}
public Builder noBase() {
this.base = Optional.empty();
return this;
}
public Builder resolve(boolean resolveURIs) {
this.resolve = resolveURIs;
return this;
}
public Builder allowRelative(boolean allowRelative) {
this.allowRelative = allowRelative;
return this;
}
public IRIxResolver build() {
if ( base == null )
throw new IRIException("Base has not been set");
IRIx baseIRI = base.orElse(null);
return new IRIxResolver(baseIRI, resolve, allowRelative);
}
}
}