blob: 92307ce1052afa5ddaad6a4f0fab104dafdfc8a8 [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.commons.rdf.simple;
import org.apache.commons.rdf.api.*;
import org.apache.commons.rdf.simple.SimpleRDF.SimpleRDFTerm;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* A simple, memory-based implementation of Graph.
* <p>
* {@link Triple}s in the graph are kept in a {@link Set}.
* <p>
* All Stream operations are performed using parallel and unordered directives.
*/
final class GraphImpl implements Graph {
private static final int TO_STRING_MAX = 10;
private final Set<Triple> triples = new HashSet<>();
private final SimpleRDF factory;
GraphImpl(final SimpleRDF simpleRDF) {
this.factory = simpleRDF;
}
@Override
public void add(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(subject);
final IRI newPredicate = (IRI) internallyMap(predicate);
final RDFTerm newObject = internallyMap(object);
final Triple result = factory.createTriple(newSubject, newPredicate, newObject);
triples.add(result);
}
@Override
public void add(final Triple triple) {
triples.add(internallyMap(triple));
}
private <T extends RDFTerm> RDFTerm internallyMap(final T object) {
if (object == null || object instanceof SimpleRDFTerm) {
// No need to re-map our own objects.
// We support null as internallyMap() is also used by the filters,
// and the
// factory constructors later do null checks
return object;
}
if (object instanceof BlankNode) {
final BlankNode blankNode = (BlankNode) object;
// This guarantees that adding the same BlankNode multiple times to
// this graph will generate a local object that is mapped to an
// equivalent object, based on the code in the package private
// BlankNodeImpl class
return factory.createBlankNode(blankNode.uniqueReference());
} else if (object instanceof IRI) {
final IRI iri = (IRI) object;
return factory.createIRI(iri.getIRIString());
} else if (object instanceof Literal) {
final Literal literal = (Literal) object;
if (literal.getLanguageTag().isPresent()) {
return factory.createLiteral(literal.getLexicalForm(), literal.getLanguageTag().get());
}
return factory.createLiteral(literal.getLexicalForm(), (IRI) internallyMap(literal.getDatatype()));
} else {
throw new IllegalArgumentException("RDFTerm was neither a BlankNode, IRI nor Literal: " + object);
}
}
private Triple internallyMap(final Triple triple) {
final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(triple.getSubject());
final IRI newPredicate = (IRI) internallyMap(triple.getPredicate());
final RDFTerm newObject = internallyMap(triple.getObject());
// Check if any of the object references changed during the mapping, to
// avoid creating a new Triple object if possible
if (newSubject == triple.getSubject() && newPredicate == triple.getPredicate()
&& newObject == triple.getObject()) {
return triple;
}
return factory.createTriple(newSubject, newPredicate, newObject);
}
@Override
public void clear() {
triples.clear();
}
@Override
public boolean contains(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
return stream(subject, predicate, object).findFirst().isPresent();
}
@Override
public boolean contains(final Triple triple) {
return triples.contains(internallyMap(triple));
}
@Override
public Stream<Triple> stream() {
return triples.parallelStream().unordered();
}
@Override
public Stream<Triple> stream(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(subject);
final IRI newPredicate = (IRI) internallyMap(predicate);
final RDFTerm newObject = internallyMap(object);
return getTriples(t -> {
// Lacking the requirement for .equals() we have to be silly
// and test ntriples string equivalance
if (subject != null && !t.getSubject().equals(newSubject)) {
return false;
}
if (predicate != null && !t.getPredicate().equals(newPredicate)) {
return false;
}
if (object != null && !t.getObject().equals(newObject)) {
return false;
}
return true;
});
}
private Stream<Triple> getTriples(final Predicate<Triple> filter) {
return stream().filter(filter);
}
@Override
public void remove(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
final Stream<Triple> toRemove = stream(subject, predicate, object);
for (final Triple t : toRemove.collect(Collectors.toList())) {
// Avoid ConcurrentModificationException in ArrayList
remove(t);
}
}
@Override
public void remove(final Triple triple) {
triples.remove(internallyMap(triple));
}
@Override
public long size() {
return triples.size();
}
@Override
public String toString() {
final String s = stream().limit(TO_STRING_MAX).map(Object::toString).collect(Collectors.joining("\n"));
if (size() > TO_STRING_MAX) {
return s + "\n# ... +" + (size() - TO_STRING_MAX) + " more";
}
return s;
}
}