com.metreeca.json.Frame Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metreeca-json Show documentation
Show all versions of metreeca-json Show documentation
A shape-based JSON modelling framework.
The newest version!
/*
* Copyright © 2013-2022 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;
import com.metreeca.json.shifts.Path;
import org.eclipse.rdf4j.model.*;
import org.eclipse.rdf4j.model.vocabulary.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static com.metreeca.json.Values.*;
import static com.metreeca.json.shifts.Alt.alt;
import static com.metreeca.json.shifts.Seq.seq;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
/**
* Linked data frame.
*
* Describes a linked data graph centered on a focus value.
*/
public final class Frame {
private static final Path Labels=alt(
RDFS.LABEL, DC.TITLE, iri("http://schema.org/", "name")
);
private static final Path Notes=alt(
RDFS.COMMENT, DC.DESCRIPTION, iri("http://schema.org/", "description")
);
public static Frame frame(final Value focus) {
if ( focus == null ) {
throw new NullPointerException("null focus");
}
return new Frame(focus, emptyMap());
}
public static Frame frame(final Value focus, final Map> traits) {
if ( focus == null ) {
throw new NullPointerException("null focus");
}
return new Frame(focus, traits.entrySet().stream().collect(toMap(
Entry::getKey, e -> unmodifiableSet(new LinkedHashSet<>(e.getValue()))
)));
}
public static Frame frame(final Value focus, final Collection model) {
if ( focus == null ) {
throw new NullPointerException("null focus");
}
if ( model == null || model.stream().anyMatch(Objects::isNull) ) {
throw new NullPointerException("null model or model statement");
}
return frame(focus, model, value -> false);
}
private static Frame frame(final Value focus, final Collection model, final Predicate visited) {
return new Frame(focus, visited.test(focus) || !focus.isResource() ? emptyMap() :
Stream.>concat(
model.stream()
.filter(pattern(focus, null, null))
.map(s -> new SimpleImmutableEntry<>(s.getPredicate(), s.getObject())),
model.stream()
.filter(pattern(null, null, focus))
.filter(s -> !visited.test(s.getSubject()))
.map(s -> new SimpleImmutableEntry<>(inverse(s.getPredicate()), s.getSubject()))
).collect(groupingBy(Entry::getKey, collectingAndThen(
mapping(entry -> entry.getKey().equals(RDF.TYPE)
? frame(entry.getValue()) // don't follow inverse type links
: frame(entry.getValue(), model, visited.or(focus::equals)),
toSet()
),
Collections::unmodifiableSet
))));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private final Value focus;
private final Map> traits;
private Frame(final Value focus, final Map> traits) {
this.focus=focus;
this.traits=unmodifiableMap(traits);
}
public boolean empty() {
return traits.isEmpty();
}
public int size() {
return traits.size()+traits.values().stream()
.flatMap(Collection::stream)
.mapToInt(Frame::size)
.sum();
}
public Optional label() {
return string(Labels);
}
public Optional notes() {
return string(Notes);
}
/**
* Retrieves the frame focus.
*
* @return the frame focus value.
*/
public Value focus() {
return focus;
}
public Map> traits() {
return traits;
}
public Stream model() {
return traits.entrySet().stream().flatMap(trait -> {
final IRI predicate=trait.getKey();
final Collection frames=trait.getValue();
return frames.stream().flatMap(frame -> {
final Statement statement=traverse(predicate,
direct -> statement((Resource)focus, direct, frame.focus),
inverse -> statement((Resource)frame.focus, inverse, focus)
);
return Stream.concat(Stream.of(statement), frame.model());
});
});
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Frame refocus(final IRI focus) {
if ( focus == null ) {
throw new NullPointerException("null focus");
}
return frame(focus, model()
.map(statement -> rewrite(this.focus, focus, statement))
.collect(toList())
);
}
private Statement rewrite(final Value source, final IRI target, final Statement statement) {
return statement(
rewrite(source, target, statement.getSubject()),
rewrite(source, target, statement.getPredicate()),
rewrite(source, target, statement.getObject())
);
}
private V rewrite(final Value source, final V target, final V value) {
return value.equals(source) ? target : value;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Optional bool(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return bool(seq(predicate));
}
public Optional bool(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return value(shift).flatMap(Values::bool);
}
public Frame bool(final IRI predicate, final Boolean bool) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return bool == null ? this : value(predicate, Values.literal(bool));
}
public Frame bool(final IRI predicate, final Optional bool) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( bool == null ) {
throw new NullPointerException("null bool");
}
return bool.map(object -> value(predicate, Values.literal(object))).orElse(this);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Optional integer(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return integer(seq(predicate));
}
public Optional integer(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return value(shift).flatMap(Values::integer);
}
public Stream integers(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return integers(seq(predicate));
}
public Stream integers(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return values(shift).map(Values::integer).filter(Optional::isPresent).map(Optional::get);
}
public Frame integer(final IRI predicate, final Number integer) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return integer == null ? this : integers(predicate, Stream.of(integer));
}
public Frame integer(final IRI predicate, final Optional integer) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( integer == null ) {
throw new NullPointerException("null integer");
}
return integer.map(object -> integers(predicate, Stream.of(object))).orElse(this);
}
public Frame integers(final IRI predicate, final Number... integers) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( integers == null ) {
throw new NullPointerException("null integers");
}
return integers.length == 0 ? this : integers(predicate, Arrays.stream(integers));
}
public Frame integers(final IRI predicate, final Collection integers) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( integers == null ) {
throw new NullPointerException("null integers");
}
return integers.isEmpty() ? this : integers(predicate, integers.stream());
}
public Frame integers(final IRI predicate, final Stream integers) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( integers == null ) {
throw new NullPointerException("null integers");
}
return values(predicate, integers.map(value
-> value instanceof BigInteger ? (BigInteger)value
: value instanceof BigDecimal ? ((BigDecimal)value).toBigInteger()
: BigInteger.valueOf(value.longValue())
).map(Values::literal));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Optional decimal(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return decimal(seq(predicate));
}
public Optional decimal(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return value(shift).flatMap(Values::decimal);
}
public Stream decimals(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return decimals(seq(predicate));
}
public Stream decimals(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return values(shift).map(Values::decimal).filter(Optional::isPresent).map(Optional::get);
}
public Frame decimal(final IRI predicate, final Number decimal) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return decimal == null ? this : decimals(predicate, Stream.of(decimal));
}
public Frame decimal(final IRI predicate, final Optional decimal) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( decimal == null ) {
throw new NullPointerException("null decimal");
}
return decimal.map(object -> decimals(predicate, Stream.of(object))).orElse(this);
}
public Frame decimals(final IRI predicate, final Number... decimals) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( decimals == null ) {
throw new NullPointerException("null decimals");
}
return decimals.length == 0 ? this : decimals(predicate, Arrays.stream(decimals));
}
public Frame decimals(final IRI predicate, final Collection decimals) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( decimals == null ) {
throw new NullPointerException("null decimals");
}
return decimals.isEmpty() ? this : decimals(predicate, decimals.stream());
}
public Frame decimals(final IRI predicate, final Stream decimals) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( decimals == null ) {
throw new NullPointerException("null decimals");
}
return values(predicate, decimals.map(value
-> value instanceof BigDecimal ? (BigDecimal)value
: value instanceof BigInteger ? new BigDecimal((BigInteger)value)
: BigDecimal.valueOf(value.doubleValue())
).map(Values::literal));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Optional string(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return string(seq(predicate));
}
public Optional string(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return value(shift).map(Value::stringValue);
}
public Stream strings(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return strings(seq(predicate));
}
public Stream strings(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return values(shift).map(Value::stringValue);
}
public Frame string(final IRI predicate, final String string) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return string == null ? this : strings(predicate, Stream.of(string));
}
public Frame string(final IRI predicate, final Optional string) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( string == null ) {
throw new NullPointerException("null string");
}
return string.map(object -> strings(predicate, Stream.of(object))).orElse(this);
}
public Frame strings(final IRI predicate, final String... strings) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( strings == null ) {
throw new NullPointerException("null strings");
}
return strings.length == 0 ? this : strings(predicate, Arrays.stream(strings));
}
public Frame strings(final IRI predicate, final Collection strings) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( strings == null ) {
throw new NullPointerException("null strings");
}
return strings.isEmpty() ? this : strings(predicate, strings.stream());
}
public Frame strings(final IRI predicate, final Stream strings) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( strings == null ) {
throw new NullPointerException("null strings");
}
return values(predicate, strings.map(Values::literal));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Optional value(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return values(predicate).findFirst();
}
public Optional value(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return values(shift).findFirst();
}
public Stream values(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return frames(predicate).map(frame -> frame.focus);
}
public Stream values(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return frames(shift).map(frame -> frame.focus);
}
public Frame value(final IRI predicate, final Value value) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return value == null ? this : values(predicate, Stream.of(value));
}
public Frame value(final IRI predicate, final Optional extends Value> value) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( value == null ) {
throw new NullPointerException("null value");
}
return value.map(object -> values(predicate, Stream.of(object))).orElse(this);
}
public Frame values(final IRI predicate, final Value... values) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( values == null ) {
throw new NullPointerException("null values");
}
return values.length == 0 ? this : values(predicate, Arrays.stream(values));
}
public Frame values(final IRI predicate, final Collection extends Value> values) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( values == null ) {
throw new NullPointerException("null values");
}
return values.isEmpty() ? this : values(predicate, values.stream());
}
public Frame values(final IRI predicate, final Stream extends Value> values) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( values == null ) {
throw new NullPointerException("null values");
}
return frames(predicate, values.map(value -> new Frame(value, emptyMap())));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Optional frame(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return frame(seq(predicate));
}
public Optional frame(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return frames(shift).findFirst();
}
public Stream frames(final IRI predicate) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return traits.getOrDefault(predicate, emptySet()).stream();
}
public Stream frames(final Shift shift) {
if ( shift == null ) {
throw new NullPointerException("null shift");
}
return shift.map(new ShiftEvaluator(this));
}
public Frame frame(final IRI predicate, final Frame frame) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
return frame == null ? this : frames(predicate, Stream.of(frame));
}
public Frame frame(final IRI predicate, final Optional frame) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( frame == null ) {
throw new NullPointerException("null frame");
}
return frame.map(object -> frames(predicate, Stream.of(object))).orElse(this);
}
public Frame frames(final IRI predicate, final Frame... frames) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( frames == null ) {
throw new NullPointerException("null frames");
}
return frames.length == 0 ? this : frames(predicate, Arrays.stream(frames));
}
public Frame frames(final IRI predicate, final Collection frames) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( frames == null ) {
throw new NullPointerException("null frames");
}
return frames.isEmpty() ? this : frames(predicate, frames.stream());
}
public Frame frames(final IRI predicate, final Stream frames) {
if ( predicate == null ) {
throw new NullPointerException("null predicate");
}
if ( frames == null ) {
throw new NullPointerException("null frames");
}
if ( !focus.isResource() && direct(predicate) ) {
throw new IllegalArgumentException(String.format(
"direct predicate %s with focus %s", Values.format(predicate), Values.format(focus)
));
}
final Collection merged=unmodifiableSet(new LinkedHashSet<>(index(Stream.concat(
traits.getOrDefault(predicate, emptySet()).stream(),
frames.peek(frame -> {
if ( !frame.focus.isResource() && !direct(predicate) ) {
throw new IllegalArgumentException(String.format(
"inverse predicate %s with value %s", Values.format(predicate), Values.format(focus)
));
}
})
)).values()));
if ( merged.isEmpty() ) {return this;} else {
final Map> extended=new LinkedHashMap<>(traits);
extended.put(predicate, merged);
return new Frame(focus, extended);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private Map index(final Stream frames) {
return frames.collect(groupingBy(Frame::focus, LinkedHashMap::new, reducing(null, (x, y) ->
x == null ? y : y == null ? x : new Frame(x.focus, merge(x.traits, y.traits))
)));
}
private Map> merge(
final Map> x, final Map> y
) {
final Map> merged=new LinkedHashMap<>(x);
y.forEach((predicate, frames) -> merged.compute(predicate, (key, value) ->
value == null ? frames : merge(frames, value)
));
return merged;
}
private Collection merge(final Collection x, final Collection y) {
final Map merged=index(x.stream());
y.forEach(frame -> merged.compute(focus, (key, value) ->
value == null ? frame : new Frame(frame.focus, merge(value.traits, frame.traits))
));
return unmodifiableSet(new LinkedHashSet<>(merged.values()));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public String format() { // !!! test/review
final StringBuilder builder=new StringBuilder(Values.format(focus));
label().ifPresent(label -> builder.append(" : ").append(label));
notes().ifPresent(notes -> builder.append(" / ").append(notes));
if ( !traits.isEmpty() ) {
builder.append(' ').append(traits.entrySet().stream()
.map(this::format)
.map(Values::indent)
.collect(joining(",\n\t", "{\n\t", "\n}"))
);
}
return builder.toString();
}
private String format(final Entry> trait) {
return Values.format(trait.getKey())+" : "+format(trait.getValue());
}
private String format(final Collection values) {
return values.isEmpty() ? "[]" // unexpected
: values.size() == 1 ? Values.format(values.iterator().next())
: values.stream()
.map(Frame::format)
.map(Values::indent)
.collect(joining(",\n\t", "[\n\t", "\n]"));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override public boolean equals(final Object object) {
return this == object || object instanceof Frame
&& focus.equals(((Frame)object).focus)
&& traits.equals(((Frame)object).traits);
}
@Override public int hashCode() {
return focus.hashCode()
^traits.hashCode();
}
@Override public String toString() {
return Values.format(focus)
+label().map(l -> String.format(" : %s", clip(l))).orElse("")
+notes().map(n -> String.format(" / %s", clip(n))).orElse("")
+(traits.isEmpty() ? "" : String.format(" { [%d] }", size()));
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy