From 119467e95e5ce65e82a0ff3c40687b1dd2ea0cf4 Mon Sep 17 00:00:00 2001 From: Jeen Broekstra Date: Fri, 15 Jan 2016 12:25:15 +1300 Subject: [PATCH] merge latest updates from master branch --- .../model/util/GetStatementOptional.java | 44 +++ .../rdf4j/model/util/ModelException.java | 4 +- .../org/eclipse/rdf4j/model/util/Models.java | 36 ++- .../rdf4j/model/util/RDFCollections.java | 304 ++++++++++++++++++ .../eclipse/rdf4j/model/util/Statements.java | 96 ++++++ .../rdf4j/model/vocabulary/DCTERMS.java | 23 +- .../rdf4j/model/util/RDFCollectionsTest.java | 107 ++++++ .../rdf4j/model/util/StatementsTest.java | 66 ++++ .../rdf4j/repository/util/Connections.java | 141 ++++++++ .../rdf4j/repository/util/Repositories.java | 28 +- 10 files changed, 816 insertions(+), 33 deletions(-) create mode 100644 core/model/src/main/java/org/eclipse/rdf4j/model/util/GetStatementOptional.java create mode 100644 core/model/src/main/java/org/eclipse/rdf4j/model/util/RDFCollections.java create mode 100644 core/model/src/main/java/org/eclipse/rdf4j/model/util/Statements.java create mode 100644 core/model/src/test/java/org/eclipse/rdf4j/model/util/RDFCollectionsTest.java create mode 100644 core/model/src/test/java/org/eclipse/rdf4j/model/util/StatementsTest.java create mode 100644 core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Connections.java diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/util/GetStatementOptional.java b/core/model/src/main/java/org/eclipse/rdf4j/model/util/GetStatementOptional.java new file mode 100644 index 00000000000..3c0193b6f1c --- /dev/null +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/util/GetStatementOptional.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ +package org.eclipse.rdf4j.model.util; + +import java.util.Optional; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; + +/** + * Either supplies a statement matching the given pattern, or + * {@link Optional#empty()} otherwise. + * + * @author Peter Ansell + */ +@FunctionalInterface +public interface GetStatementOptional { + + /** + * Either supplies a statement matching the given pattern, or + * {@link Optional#empty()} otherwise. + * + * @param subject + * A {@link Resource} to be used to match to statements. + * @param predicate + * An {@link IRI} to be used to match to statements. + * @param object + * A {@link Value} to be used to match to statements. + * @param contexts + * An array of context {@link Resource} objects, or left out (not + * null) to select from all contexts. + * @return An {@link Optional} either containing a single statement matching + * the pattern or {@link Optional#empty()} otherwise. + */ + Optional get(Resource subject, IRI predicate, Value object, Resource... contexts); + +} diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/util/ModelException.java b/core/model/src/main/java/org/eclipse/rdf4j/model/util/ModelException.java index 2c49c0c56d2..93a347e3004 100644 --- a/core/model/src/main/java/org/eclipse/rdf4j/model/util/ModelException.java +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/util/ModelException.java @@ -7,7 +7,7 @@ *******************************************************************************/ package org.eclipse.rdf4j.model.util; -import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.OpenRDFException; import org.eclipse.rdf4j.model.Value; /** @@ -16,7 +16,7 @@ * * @author Arjohn Kampman */ -public class ModelException extends RuntimeException { +public class ModelException extends OpenRDFException { private static final long serialVersionUID = 3886967415616842867L; diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/util/Models.java b/core/model/src/main/java/org/eclipse/rdf4j/model/util/Models.java index c4aecc0472f..8bd1673722f 100644 --- a/core/model/src/main/java/org/eclipse/rdf4j/model/util/Models.java +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/util/Models.java @@ -15,7 +15,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; +import java.util.function.Supplier; import org.eclipse.rdf4j.model.BNode; import org.eclipse.rdf4j.model.IRI; @@ -79,7 +79,8 @@ public static Value anyObject(Model m) { * @since 4.0 */ public static Optional objectLiteral(Model m) { - return m.stream().map(st -> st.getObject()).filter(o -> o instanceof Literal).map(l -> (Literal)l).findAny(); + return m.stream().map(st -> st.getObject()).filter(o -> o instanceof Literal).map( + l -> (Literal)l).findAny(); } /** @@ -103,7 +104,8 @@ public static Literal anyObjectLiteral(Model m) { * @since 4.0 */ public static Optional objectResource(Model m) { - return m.stream().map(st -> st.getObject()).filter(o -> o instanceof Resource).map(r -> (Resource)r).findAny(); + return m.stream().map(st -> st.getObject()).filter(o -> o instanceof Resource).map( + r -> (Resource)r).findAny(); } /** @@ -128,16 +130,17 @@ public static Resource anyObjectResource(Model m) { public static Optional objectIRI(Model m) { return m.stream().map(st -> st.getObject()).filter(o -> o instanceof IRI).map(r -> (IRI)r).findAny(); } - + /** * Retrieves an object value as a String from the statements in the given - * model. If more than one possible object value exists, any one value is picked - * and returned. + * model. If more than one possible object value exists, any one value is + * picked and returned. * * @param m * the model from which to retrieve an object String value. - * @return an {@link Optional} object String value from the given model, which - * will be {@link Optional#empty() empty} if no such value exists. + * @return an {@link Optional} object String value from the given model, + * which will be {@link Optional#empty() empty} if no such value + * exists. * @since 4.0 */ public static Optional objectString(Model m) { @@ -210,7 +213,8 @@ public static URI anySubjectURI(Model m) { * @since 4.0 */ public static Optional subjectBNode(Model m) { - return m.stream().map(st -> st.getSubject()).filter(s -> s instanceof BNode).map(s -> (BNode)s).findAny(); + return m.stream().map(st -> st.getSubject()).filter(s -> s instanceof BNode).map( + s -> (BNode)s).findAny(); } /** @@ -573,4 +577,18 @@ private static Set toSet(Iterable iterable) { } return set; } + + /** + * Creates a {@link Supplier} of {@link ModelException} objects that be + * passed to {@link Optional#orElseThrow(Supplier)} to generate exceptions as + * necessary. + * + * @param message + * The message to be used for the exception + * @return A {@link Supplier} that will create {@link ModelException} objects + * with the given message. + */ + public static Supplier modelException(String message) { + return () -> new ModelException(message); + } } \ No newline at end of file diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/util/RDFCollections.java b/core/model/src/main/java/org/eclipse/rdf4j/model/util/RDFCollections.java new file mode 100644 index 00000000000..bcb50cb2b3a --- /dev/null +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/util/RDFCollections.java @@ -0,0 +1,304 @@ +/******************************************************************************* + * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ +package org.eclipse.rdf4j.model.util; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.eclipse.rdf4j.OpenRDFException; +import org.eclipse.rdf4j.OpenRDFUtil; +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF; + +/** + * Utilities for working with RDF Collections and converting to/from Java + * {@link Collection} classes. + *

+ * RDF Collections are represented using a Lisp-like structure: the list starts + * with a head node, which is connected to the first collection member via the + * {@link RDF#FIRST} relation. The head node is then connected to the rest of + * the list via an {@link RDF#REST} relation. The last node in the list is + * marked using the {@link RDF#NIL} node. + *

+ * As an example, a list containing three literal values "A", "B", and "C" looks + * like this as an RDF Collection: + * + *

+ *   _:n1 -rdf:type--> rdf:List
+ *     |
+ *     +---rdf:first--> "A"
+ *     |
+ *     +---rdf:rest --> _:n2 -rdf:first--> "B"
+ *                        |
+ *                        +---rdf:rest--> _:n3 -rdf:first--> "C"
+ *                                          |
+ *                                          +---rdf:rest--> rdf:nil
+ * 
+ * + * Here, {@code _:n1} is the head node of the list. Note that in this example it + * is declared an instance of {@link RDF#LIST}, however this is not required for + * the collection to be considered well-formed. + * + * @author Jeen Broekstra + * @see RDF Schema + * 1.1 section on Collection vocabulary. + * @since 4.1.0 + */ +public class RDFCollections { + + /** + * Converts the supplied {@link Iterable} to an + * RDF + * Collection, using the supplied {@code head} resource as the starting + * resource of the RDF Collection. Each object in the Iterable that is + * not an instance of {@link Value} will be converted to a + * {@link Literal}. The statements making up the new RDF Collection will be + * reported to the supplied {@link Consumer} function. + * @param values + * an {@link Iterable} of objects (such as a Java {@link Collection} + * ), which will be converted to an RDF Collection. May not be + * {@code null}. Each object in the Iterable that is + * not an instance of {@link Value} will be + * converted to a {@link Literal}. + * @param head + * a {@link Resource} which will be used as the head of the list, + * that is, the starting point of the created RDF Collection. May be + * {@code null}, in which case a new resource is generated to + * represent the list head. + * @param consumer + * the {@link Consumer} function for the Statements of the RDF + * Collection. May not be {@code null}. + * @param contexts + * the context(s) in which to add the RDF Collection. This argument + * is an optional vararg and can be left out. + * + * @see RDF + * Schema 1.1 section on Collection vocabulary. + * @since 4.1.0 + */ + public static void consumeRDF(Iterable values, Resource head, Consumer consumer, + Resource... contexts) + { + Objects.requireNonNull(values, "input collection may not be null"); + Objects.requireNonNull(consumer, "model may not be null"); + final ValueFactory vf = SimpleValueFactory.getInstance(); + + Resource current = head != null ? head : vf.createBNode(); + + Statements.consume(vf, current, RDF.TYPE, RDF.LIST, consumer, contexts); + + Iterator iter = values.iterator(); + while (iter.hasNext()) { + Object o = iter.next(); + Value v = o instanceof Value ? (Value)o : Literals.createLiteral(vf, o); + Statements.consume(vf, current, RDF.FIRST, v, consumer, contexts); + if (iter.hasNext()) { + Resource next = vf.createBNode(); + Statements.consume(vf, current, RDF.REST, next, consumer, contexts); + current = next; + } + else { + Statements.consume(vf, current, RDF.REST, RDF.NIL, consumer, contexts); + } + } + } + + /** + * Converts the supplied {@link Iterable} to an + * RDF + * Collection, using the supplied {@code head} resource as the starting + * resource of the RDF Collection. The statements making up the new RDF + * Collection will be added to the supplied statement collection. + * + * @param values + * an {@link Iterable} (such as a Java {@link Collection}), which + * will be converted to an RDF Collection. May not be {@code null}. + * Each object in the Iterable that is not an + * instance of {@link Value} will be converted to a {@link Literal}. + * @param head + * a {@link Resource} which will be used as the head of the list, + * that is, the starting point of the created RDF Collection. May be + * {@code null}, in which case a new resource is generated to + * represent the list head. + * @param rdfCollection + * a {@link Collection} of {@link Statement} objects (for example a + * {@link Model}) to which the RDF Collection statements will be + * added. May not be {@code null}. + * @param contexts + * the context(s) in which to add the RDF Collection. This argument + * is an optional vararg and can be left out. + * @return the input {@link Collection} of {@link Statement}s, with the new + * Statements forming the RDF {@link Collection} added. + * @see RDF + * Schema 1.1 section on Collection vocabulary. + * @since 4.1.0 + */ + public static > C asRDF(Iterable values, Resource head, + C rdfCollection, Resource... contexts) + { + Objects.requireNonNull(rdfCollection); + consumeRDF(values, head, st -> rdfCollection.add(st), contexts); + return rdfCollection; + } + + /** + * Reads an RDF Collection starting with the supplied list head from the + * supplied {@link Model} and sends each collection member to the supplied + * {@link Consumer} function. This method expects the RDF Collection to be + * well-formed. If the collection is not well-formed the method may report + * only part of the collection, or may throw a {@link ModelException}. + * @param m + * the Model containing the collection to read. + * @param head + * the {@link Resource} that represents the list head, that is the + * start resource of the RDF Collection to be read. May not be + * {@code null}. + * @param consumer + * the Java {@link Consumer} function to which the collection items + * are reported. + * @param contexts + * the context(s) from which to read the RDF Collection. This + * argument is an optional vararg and can be left out. + * + * @throws ModelException + * if a problem occurs reading the RDF Collection, for example if + * the Collection is not well-formed. + * @see RDF + * Schema 1.1 section on Collection vocabulary. + * @since 4.1.0 + */ + public static void consumeValues(final Model m, Resource head, Consumer consumer, Resource... contexts) + throws ModelException + { + Objects.requireNonNull(consumer, "consumer may not be null"); + Objects.requireNonNull(m, "input model may not be null"); + + GetStatementOptional statementSupplier = (s, p, o, c) -> m.filter(s, p, o, c).stream().findAny(); + Function> exceptionSupplier = Models::modelException; + + consumeValues(statementSupplier, head, st -> { + if (RDF.FIRST.equals(st.getPredicate())) { + consumer.accept(st.getObject()); + } + } , exceptionSupplier, contexts); + } + + /** + * Converts an RDF Collection to a Java {@link Collection} of {@link Value} + * objects. The RDF Collection is given by the supplied {@link Model} and + * {@code head}. This method expects the RDF Collection to be well-formed. + * If the collection is not well-formed the method may return part of the + * collection, or may throw a {@link ModelException}. + * + * @param m + * the Model containing the collection to read. + * @param head + * the {@link Resource} that represents the list head, that is the + * start resource of the RDF Collection to be read. May not be + * {@code null}. + * @param collection + * the Java {@link Collection} to add the collection items to. + * @param contexts + * the context(s) from which to read the RDF Collection. This + * argument is an optional vararg and can be left out. + * @return the supplied Java {@link Collection}, filled with the items from + * the RDF Collection (if any). + * @throws ModelException + * if a problem occurs reading the RDF Collection, for example if + * the Collection is not well-formed. + * @see RDF + * Schema 1.1 section on Collection vocabulary. + * @since 4.1.0 + */ + public static > C asValues(final Model m, Resource head, C collection, + Resource... contexts) + throws ModelException + { + Objects.requireNonNull(collection, "collection may not be null"); + + consumeValues(m, head, v -> collection.add(v), contexts); + + return collection; + } + + /** + * Reads an RDF Collection starting with the supplied list head from the + * statement supplier and sends all statements that make up the collection + * to the supplied {@link Consumer} function. This method expects the RDF + * Collection to be well-formed. If the collection is not well-formed the + * method may report only part of the collection, or may throw an exception. + * + * @param statementSupplier + * the source of the statements from which the RDF collection is to + * be read, specified as a functional interface. + * @param head + * the {@link Resource} that represents the list head, that is the + * start resource of the RDF Collection to be read. May not be + * {@code null}. + * @param collectionConsumer + * the Java {@link Consumer} function to which the collection + * statements are reported. + * @param exceptionSupplier + * a functional interface that produces the exception type this + * method will throw when an error occurs. + * @param contexts + * the context(s) from which to read the RDF Collection. This + * argument is an optional vararg and can be left out. + * @throws E + * if a problem occurs reading the RDF Collection, for example if it + * is not well-formed. + * @since 4.1.0 + */ + public static void consumeValues(GetStatementOptional statementSupplier, + Resource head, Consumer collectionConsumer, + Function> exceptionSupplier, Resource... contexts) + throws E + { + OpenRDFUtil.verifyContextNotNull(contexts); + Objects.requireNonNull(head, "list head may not be null"); + Objects.requireNonNull(collectionConsumer, "collection consumer may not be null"); + + Resource current = head; + final Set visited = new HashSet<>(); + while (!RDF.NIL.equals(current)) { + if (visited.contains(current)) { + throw exceptionSupplier.apply("list not well-formed: cycle detected").get(); + } + + statementSupplier.get(current, RDF.TYPE, RDF.LIST, contexts).ifPresent(collectionConsumer); + + collectionConsumer.accept(statementSupplier.get(current, RDF.FIRST, null, contexts).orElseThrow( + exceptionSupplier.apply("list not wellformed: rdf:first statement missing."))); + + Statement next = statementSupplier.get(current, RDF.REST, null, contexts).orElseThrow( + exceptionSupplier.apply("list not well-formed: rdf:rest statement missing.")); + + collectionConsumer.accept(next); + + if (!(next.getObject() instanceof Resource)) { + throw exceptionSupplier.apply( + "list not well-formed: value of rdf:rest should be one of (IRI, BNode).").get(); + } + visited.add(current); + current = (Resource)next.getObject(); + } + } + +} diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/util/Statements.java b/core/model/src/main/java/org/eclipse/rdf4j/model/util/Statements.java new file mode 100644 index 00000000000..e3f0da2447f --- /dev/null +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/util/Statements.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ +package org.eclipse.rdf4j.model.util; + +import java.util.Collection; +import java.util.Objects; +import java.util.function.Consumer; + +import org.eclipse.rdf4j.OpenRDFUtil; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; + +/** + * Utility methods for {@link Statement} objects. + * + * @author Jeen Broekstra + */ +public class Statements { + + /** + * Creates one or more {@link Statement} objects with the given subject, + * predicate and object, one for each given context, and sends each created + * statement to the supplied {@link Consumer}. If no context is supplied, + * only a single statement (without any assigned context) is created. + * + * @param vf + * the {@link ValueFactory} to use for creating statements. + * @param subject + * the subject of each statement. May not be null. + * @param predicate + * the predicate of each statement. May not be null. + * @param object + * the object of each statement. May not be null. + * @param consumer + * the {@link Consumer} function for the produced statements. + * @param contexts + * the context(s) for which to produce statements. This argument is + * an optional vararg: leave it out completely to produce a single + * statement without context. + */ + public static void consume(ValueFactory vf, Resource subject, IRI predicate, Value object, + Consumer consumer, Resource... contexts) + { + OpenRDFUtil.verifyContextNotNull(contexts); + Objects.requireNonNull(consumer); + + if (contexts.length > 0) { + for (Resource context : contexts) { + consumer.accept(vf.createStatement(subject, predicate, object, context)); + } + } + else { + consumer.accept(vf.createStatement(subject, predicate, object)); + } + } + + /** + * Creates one or more {@link Statement} objects with the given subject, + * predicate and object, one for each given context. If no context is + * supplied, only a single statement (without any assigned context) is + * created. + * + * @param vf + * the {@link ValueFactory} to use for creating statements. + * @param subject + * the subject of each statement. May not be null. + * @param predicate + * the predicate of each statement. May not be null. + * @param object + * the object of each statement. May not be null. + * @param collection + * the collection of Statements to which the newly created Statements + * will be added. May not be null. + * @return the input collection of Statements, with the newly created + * Statements added. + * @param contexts + * the context(s) for which to produce statements. This argument is + * an optional vararg: leave it out completely to produce a single + * statement without context. + */ + public static > C create(ValueFactory vf, Resource subject, IRI predicate, + Value object, C collection, Resource... contexts) + { + Objects.requireNonNull(collection); + consume(vf, subject, predicate, object, st -> collection.add(st), contexts); + return collection; + } +} diff --git a/core/model/src/main/java/org/eclipse/rdf4j/model/vocabulary/DCTERMS.java b/core/model/src/main/java/org/eclipse/rdf4j/model/vocabulary/DCTERMS.java index 689042a1073..7de9cf13995 100644 --- a/core/model/src/main/java/org/eclipse/rdf4j/model/vocabulary/DCTERMS.java +++ b/core/model/src/main/java/org/eclipse/rdf4j/model/vocabulary/DCTERMS.java @@ -1,19 +1,10 @@ -/* - * Licensed to Aduna under one or more contributor license agreements. - * See the NOTICE.txt file distributed with this work for additional - * information regarding copyright ownership. - * - * Aduna licenses this file to you under the terms of the Aduna BSD - * License (the "License"); you may not use this file except in compliance - * with the License. See the LICENSE.txt file distributed with this work - * for the full License. - * - * 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. - */ +/******************************************************************************* + * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ package org.eclipse.rdf4j.model.vocabulary; import org.eclipse.rdf4j.model.IRI; diff --git a/core/model/src/test/java/org/eclipse/rdf4j/model/util/RDFCollectionsTest.java b/core/model/src/test/java/org/eclipse/rdf4j/model/util/RDFCollectionsTest.java new file mode 100644 index 00000000000..88cff2b6588 --- /dev/null +++ b/core/model/src/test/java/org/eclipse/rdf4j/model/util/RDFCollectionsTest.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ +package org.eclipse.rdf4j.model.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.impl.TreeModel; +import org.eclipse.rdf4j.model.util.Literals; +import org.eclipse.rdf4j.model.util.ModelException; +import org.eclipse.rdf4j.model.util.RDFCollections; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.junit.Before; +import org.junit.Test; + +public class RDFCollectionsTest { + + private final static ValueFactory vf = SimpleValueFactory.getInstance(); + + private final List values = new ArrayList<>(); + + private Literal a; + + private Literal b; + + private Literal c; + + @Before + public void setUp() + throws Exception + { + a = Literals.createLiteral(vf, "A"); + b = Literals.createLiteral(vf, "B"); + c = Literals.createLiteral(vf, "C"); + + values.add(a); + values.add(b); + values.add(c); + } + + @Test + public void testConversionRoundtrip() { + IRI head = vf.createIRI("urn:head"); + Model m = RDFCollections.asRDF(values, head, new TreeModel()); + assertNotNull(m); + assertTrue(m.contains(head, RDF.FIRST, a)); + assertFalse(m.contains(null, RDF.REST, head)); + + List newList = RDFCollections.asValues(m, head, new ArrayList()); + assertNotNull(newList); + assertTrue(newList.contains(a)); + assertTrue(newList.contains(b)); + assertTrue(newList.contains(c)); + + } + + public void testNonWellformedCollection() { + Resource head = vf.createBNode(); + Model m = RDFCollections.asRDF(values, head, new TreeModel()); + m.remove(null, RDF.REST, RDF.NIL); + try { + RDFCollections.asValues(m, head, new ArrayList()); + fail("collection missing terminator should result in error"); + } + catch (ModelException e) { + // fall through, expected + } + + m = RDFCollections.asRDF(values, head, new TreeModel()); + m.add(head, RDF.REST, head); + + try { + RDFCollections.asValues(m, head, new ArrayList()); + fail("collection with cycle should result in error"); + } + catch (ModelException e) { + // fall through, expected + } + + // supply incorrect head node + try { + RDFCollections.asValues(m, vf.createBNode(), new ArrayList()); + fail("resource that is not a collection should result in error"); + } + catch (ModelException e) { + // fall through, expected + } + + } +} diff --git a/core/model/src/test/java/org/eclipse/rdf4j/model/util/StatementsTest.java b/core/model/src/test/java/org/eclipse/rdf4j/model/util/StatementsTest.java new file mode 100644 index 00000000000..0f1be0e3671 --- /dev/null +++ b/core/model/src/test/java/org/eclipse/rdf4j/model/util/StatementsTest.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ +package org.eclipse.rdf4j.model.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.impl.TreeModel; +import org.eclipse.rdf4j.model.util.Statements; +import org.eclipse.rdf4j.model.vocabulary.FOAF; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.junit.Test; + +public class StatementsTest { + + private final ValueFactory vf = SimpleValueFactory.getInstance(); + + @Test + public void testMultipleContexts() { + Resource c1 = vf.createIRI("urn:c1"); + Resource c2 = vf.createIRI("urn:c1"); + Resource c3 = vf.createIRI("urn:c1"); + + Model m = Statements.create(vf, FOAF.AGE, RDF.TYPE, RDF.PROPERTY, new TreeModel(), c1, c2, null, c3); + assertFalse(m.isEmpty()); + assertTrue(m.contains(FOAF.AGE, RDF.TYPE, RDF.PROPERTY, (Resource)null)); + assertTrue(m.contains(FOAF.AGE, RDF.TYPE, RDF.PROPERTY, c1)); + assertTrue(m.contains(FOAF.AGE, RDF.TYPE, RDF.PROPERTY, c2)); + assertTrue(m.contains(FOAF.AGE, RDF.TYPE, RDF.PROPERTY, c3)); + } + + @Test + public void testNoContext() { + Model m = Statements.create(vf, FOAF.AGE, RDF.TYPE, RDF.PROPERTY, new TreeModel()); + assertFalse(m.isEmpty()); + assertTrue(m.contains(FOAF.AGE, RDF.TYPE, RDF.PROPERTY)); + } + + @Test + public void testInvalidInput() { + try { + Statements.consume(vf, FOAF.AGE, RDF.TYPE, RDF.PROPERTY, + st -> fail("should have resulted in Exception"), null); + } + catch (IllegalArgumentException e) { + // fall through. + } + + try { + Statements.consume(vf, null, RDF.TYPE, RDF.PROPERTY, st -> fail("should have resulted in Exception")); + } + catch (NullPointerException e) { + // fall through. + } + } +} diff --git a/core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Connections.java b/core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Connections.java new file mode 100644 index 00000000000..2a013c0c747 --- /dev/null +++ b/core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Connections.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ +package org.eclipse.rdf4j.repository.util; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.util.GetStatementOptional; +import org.eclipse.rdf4j.model.util.RDFCollections; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.RepositoryException; +import org.eclipse.rdf4j.repository.RepositoryResult; + +/** + * Convenience functions for use with {@link RepositoryConnection}s. + * + * @author Jeen Broekstra + * @since 4.1.0 + */ +public class Connections { + + /** + * Retrieve all {@link Statement}s that together form the RDF Collection + * starting with the supplied start resource and send them to the supplied + * {@link Consumer}. + * + * @param conn + * the {@link RepositoryConnection} to use for statement retrieval. + * @param head + * the start resource of the RDF Collection. May not be {@code null}. + * @param collectionConsumer + * a {@link Consumer} function to which all retrieved statements will + * be reported. May not be {@code null}. + * @param contexts + * the context(s) from which to read the RDF Collection. This + * argument is an optional vararg and can be left out. + * @throws RepositoryException + * if an error occurred while reading the collection statements, for + * example if a cycle is detected in the RDF collection, or some + * other anomaly which makes it non-wellformed. + * @see RDFCollections + * @see RDF + * Schema 1.1 section on Collection vocabulary. + * @since 4.1.0 + */ + public static void consumeRDFCollection(RepositoryConnection conn, Resource head, + Consumer collectionConsumer, Resource... contexts) + throws RepositoryException + { + GetStatementOptional statementSupplier = (s, p, o, c) -> getStatement(conn, s, p, o, c); + Function> exceptionSupplier = Repositories::repositoryException; + RDFCollections.consumeValues(statementSupplier, head, collectionConsumer, exceptionSupplier, + contexts); + } + + /** + * Retrieve all {@link Statement}s that together form the RDF Collection + * starting with the supplied starting resource. + * + * @param conn + * the {@link RepositoryConnection} to use for statement retrieval. + * @param head + * the start resource of the RDF Collection. May not be {@code null}. + * @param statementCollection + * a {@link Collection} of {@link Statement}s (for example, a + * {@link Model}) to which all retrieved statements will be reported. + * May not be {@code null}. + * @param contexts + * the context(s) from which to read the RDF Collection. This + * argument is an optional vararg and can be left out. + * @return the input statement collection, with the statements forming the + * retrieved RDF Collection added. + * @throws RepositoryException + * if an error occurred while reading the collection statements, for + * example if a cycle is detected in the RDF collection, or some + * other anomaly which makes it non-wellformed. + * @see RDFCollections + * @see RDF + * Schema 1.1 section on Collection vocabulary. + * @since 4.1.0 + */ + public static > C getRDFCollection(RepositoryConnection conn, + Resource head, C statementCollection, Resource... contexts) + throws RepositoryException + { + Objects.requireNonNull(statementCollection, "statementCollection may not be null"); + consumeRDFCollection(conn, head, st -> statementCollection.add(st), contexts); + return statementCollection; + } + + /** + * Retrieve a single {@link Statement} matching with the supplied subject, + * predicate, object and context(s) from the given + * {@link RepositoryConnection}. If more than one Statement matches, any one + * Statement is selected and returned. + * + * @param conn + * the {@link RepositoryConnection} from which to retrieve the + * statement. + * @param subject + * the subject to which the statement should match. May be + * {@code null}. + * @param predicate + * the predicate to which the statement should match. May be + * {@code null}. + * @param object + * the object to which the statement should match. May be + * {@code null} . + * @param contexts + * the context(s) from which to read the Statement. This argument is + * an optional vararg and can be left out. + * @return an {@link Optional} of {@link Statement}. If no matching + * Statement was found, {@link Optional#empty()} is returned. + * @throws RepositoryException + * @since 4.1.0 + */ + public static Optional getStatement(RepositoryConnection conn, Resource subject, IRI predicate, + Value object, Resource... contexts) + throws RepositoryException + { + try (RepositoryResult stmts = conn.getStatements(subject, predicate, object, contexts)) { + Statement st = stmts.hasNext() ? stmts.next() : null; + return Optional.ofNullable(st); + } + } + +} diff --git a/core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Repositories.java b/core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Repositories.java index 988118f312a..d745eb3a92f 100644 --- a/core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Repositories.java +++ b/core/repository/api/src/main/java/org/eclipse/rdf4j/repository/util/Repositories.java @@ -7,8 +7,10 @@ *******************************************************************************/ package org.eclipse.rdf4j.repository.util; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import org.eclipse.rdf4j.query.GraphQuery; import org.eclipse.rdf4j.query.GraphQueryResult; @@ -79,7 +81,7 @@ public static void consume(Repository repository, Consumer */ public static void consume(Repository repository, Consumer processFunction, Consumer exceptionHandler) - throws RepositoryException, UnknownTransactionStateException + throws RepositoryException, UnknownTransactionStateException { try { consume(repository, processFunction); @@ -179,7 +181,7 @@ public static T get(Repository repository, Function */ public static T get(Repository repository, Function processFunction, Consumer exceptionHandler) - throws RepositoryException, UnknownTransactionStateException + throws RepositoryException, UnknownTransactionStateException { try { return get(repository, processFunction); @@ -239,8 +241,8 @@ public static T getSilent(Repository repository, Function T tupleQuery(Repository repository, String query, Function processFunction) - throws RepositoryException, UnknownTransactionStateException, MalformedQueryException, - QueryEvaluationException + throws RepositoryException, UnknownTransactionStateException, MalformedQueryException, + QueryEvaluationException { return get(repository, conn -> { TupleQuery preparedQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query); @@ -307,8 +309,8 @@ public static void tupleQuery(Repository repository, String query, TupleQueryRes */ public static T graphQuery(Repository repository, String query, Function processFunction) - throws RepositoryException, UnknownTransactionStateException, MalformedQueryException, - QueryEvaluationException + throws RepositoryException, UnknownTransactionStateException, MalformedQueryException, + QueryEvaluationException { return get(repository, conn -> { GraphQuery preparedQuery = conn.prepareGraphQuery(QueryLanguage.SPARQL, query); @@ -348,6 +350,20 @@ public static void graphQuery(Repository repository, String query, RDFHandler ha }); } + /** + * Creates a {@link Supplier} of {@link RepositoryException} objects that be + * passed to {@link Optional#orElseThrow(Supplier)} to generate exceptions as + * necessary. + * + * @param message + * The message to be used for the exception + * @return A {@link Supplier} that will create {@link RepositoryException} + * objects with the given message. + */ + public static Supplier repositoryException(String message) { + return () -> new RepositoryException(message); + } + /** * Private constructor to prevent instantiation, this is a static helper * class.