
com.metreeca.json.shapes.Field Maven / Gradle / Ivy
/*
* Copyright © 2013-2021 Metreeca srl
*
* 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.metreeca.json.shapes;
import com.metreeca.json.Shape;
import com.metreeca.json.Values;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static com.metreeca.json.shapes.All.all;
import static com.metreeca.json.shapes.And.and;
import static com.metreeca.json.shapes.Or.or;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyMap;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
/**
* Field structural constraint.
*
* States that the derived focus set generated by traversing a single step path is consistent with a given {@link
* Shape shape}.
*/
public final class Field extends Shape {
private static final java.util.regex.Pattern LabelPattern=Pattern.compile(
"\\w+"
);
private static final Pattern NamedIRIPattern=Pattern.compile(
"([/#:])(?"+LabelPattern+")(/|#|#_|#id|#this)?$"
);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static Shape field(final IRI iri, final Shape... shapes) {
if ( iri == null ) {
throw new NullPointerException("null iri");
}
if ( shapes == null || stream(shapes).anyMatch(Objects::isNull) ) {
throw new NullPointerException("null shapes");
}
return field("", iri, and(shapes));
}
public static Shape field(final IRI iri, final Object... values) {
if ( iri == null ) {
throw new NullPointerException("null iri");
}
if ( values == null || stream(values).anyMatch(Objects::isNull) ) {
throw new NullPointerException("null values");
}
return field("", iri, all(values));
}
public static Shape field(final IRI iri, final Shape shape) {
if ( iri == null ) {
throw new NullPointerException("null iri");
}
if ( shape == null ) {
throw new NullPointerException("null shape");
}
return field("", iri, shape);
}
public static Shape field(final String label, final IRI iri, final Shape... shapes) {
if ( label == null ) {
throw new NullPointerException("null label");
}
if ( !(label.isEmpty() || LabelPattern.matcher(label).matches()) ) {
throw new IllegalArgumentException(format("malformed label <%s>", label));
}
if ( iri == null ) {
throw new NullPointerException("null iri");
}
if ( shapes == null || stream(shapes).anyMatch(Objects::isNull) ) {
throw new NullPointerException("null shapes");
}
return field(label, iri, and(shapes));
}
public static Shape field(final String label, final IRI iri, final Object... values) {
if ( label == null ) {
throw new NullPointerException("null label");
}
if ( !(label.isEmpty() || LabelPattern.matcher(label).matches()) ) {
throw new IllegalArgumentException(format("malformed label <%s>", label));
}
if ( iri == null ) {
throw new NullPointerException("null iri");
}
if ( values == null || stream(values).anyMatch(Objects::isNull) ) {
throw new NullPointerException("null values");
}
return field(label, iri, all(values));
}
public static Shape field(final String label, final IRI iri, final Shape shape) {
if ( label == null ) {
throw new NullPointerException("null label");
}
if ( iri == null ) {
throw new NullPointerException("null iri");
}
if ( shape == null ) {
throw new NullPointerException("null shape");
}
return shape.equals(or()) ? and() : new Field(label, iri, shape);
}
public static Stream fields(final Shape shape) {
if ( shape == null ) {
throw new NullPointerException("null shape");
}
return shape.map(new FieldsProbe());
}
public static Optional field(final Shape shape, final IRI iri) {
if ( shape == null ) {
throw new NullPointerException("null shape");
}
if ( iri == null ) {
throw new NullPointerException("null iri");
}
return fields(shape)
.filter(field -> field.iri().equals(iri))
.findFirst();
}
public static Optional field(final Shape shape, final Collection path) {
if ( shape == null ) {
throw new NullPointerException("null shape");
}
if ( path == null || path.stream().anyMatch(Objects::isNull) ) {
throw new NullPointerException("null path");
}
Optional field=null;
for (final IRI step : path) {
field=(field != null ? field.map(Field::shape) : Optional.of(shape)).flatMap(s -> field(s, step));
}
return field != null ? field : Optional.empty();
}
public static Map labels(final Shape shape) {
if ( shape == null ) {
throw new NullPointerException("null shape");
}
return labels(shape, emptyMap());
}
public static Map labels(final Shape shape, final Map keywords) {
if ( shape == null ) {
throw new NullPointerException("null shape");
}
if ( keywords == null ) {
throw new NullPointerException("null keywords");
}
return fields(shape).collect(toMap(
field -> {
final IRI iri=field.iri();
final boolean direct=Values.direct(iri);
if ( direct && iri.equals(RDF.TYPE) ) {
return keywords.getOrDefault("@type", "@type");
} else {
final String label=Optional.of(field.label()).filter(s -> !s.isEmpty()).orElseGet(() -> Optional
.of(NamedIRIPattern.matcher(iri.stringValue()))
.filter(Matcher::find)
.map(matcher -> matcher.group("name"))
.map(name -> direct ? name : name+"Of")
.orElseThrow(() ->
new IllegalArgumentException(format("undefined label for %s", iri))
)
);
if ( keywords.containsValue(label) ) {
throw new IllegalArgumentException(format("reserved label <%s> for %s", label, iri));
}
return label;
}
},
identity(),
(x, y) -> {
throw new IllegalArgumentException(format(
"clashing labels for fields <%s>=%s / <%s>=%s", x.label(), x.iri(), y.label(), y.iri()
));
},
LinkedHashMap::new
));
}
static Optional label(final String x, final String y) {
return y.isEmpty() || x.equals(y) ? Optional.of(x) : x.isEmpty() ? Optional.of(y) : Optional.empty();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private final String label;
private final IRI iri;
private final Shape shape;
Field(final String label, final IRI iri, final Shape shape) {
this.label=label;
this.iri=iri;
this.shape=shape;
}
public String label() {
return label;
}
public IRI iri() {
return iri;
}
public Shape shape() {
return shape;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override public V map(final Probe probe) {
if ( probe == null ) {
throw new NullPointerException("null probe");
}
return probe.probe(this);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override public boolean equals(final Object object) {
return this == object || object instanceof Field
&& label.equals(((Field)object).label)
&& iri.equals(((Field)object).iri)
&& shape.equals(((Field)object).shape);
}
@Override public int hashCode() {
return label.hashCode()
^iri.hashCode()
^shape.hashCode();
}
@Override public String toString() {
final StringBuilder builder=new StringBuilder(25);
builder.append("field(");
if ( !label.isEmpty() ) {
builder.append('\'').append(label).append("' = ");
}
builder.append('<').append(iri).append('>');
if ( !shape.equals(and()) ) {
builder.append(", ").append(shape);
}
builder.append(")");
return builder.toString();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private static final class FieldsProbe extends Probe> {
@Override public Stream probe(final Link link) {
return link.shape().map(this);
}
@Override public Stream probe(final Field field) {
return Stream.of(field);
}
@Override public Stream probe(final When when) {
return Stream.of(when.pass(), when.fail()).flatMap(this);
}
@Override public Stream probe(final And and) {
return and.shapes().stream().flatMap(shape -> shape.map(this));
}
@Override public Stream probe(final Or or) {
return or.shapes().stream().flatMap(shape -> shape.map(this));
}
@Override public Stream probe(final Shape shape) {
return Stream.empty();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy