COMMONSRDF-39 experimental RDFParser interface moved to org.apache.commons.rdf.experimental for now
Project: http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/commit/10d27cde Tree: http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/tree/10d27cde Diff: http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/diff/10d27cde Branch: refs/heads/rdf4j Commit: 10d27cde2f7310a68274eacf550f201a3690dab8 Parents: bfcead4 Author: Stian Soiland-Reyes <st...@apache.org> Authored: Wed Sep 28 14:16:02 2016 +0100 Committer: Stian Soiland-Reyes <st...@apache.org> Committed: Wed Sep 28 14:16:02 2016 +0100 ---------------------------------------------------------------------- .../commons/rdf/api/RDFParserBuilder.java | 479 ---------------- .../commons/rdf/experimental/RDFParser.java | 495 +++++++++++++++++ .../commons/rdf/experimental/package-info.java | 34 ++ .../test/resources/example-rdf/example.jsonld | 25 + api/src/test/resources/example-rdf/example.nq | 3 + api/src/test/resources/example-rdf/example.nt | 2 + api/src/test/resources/example-rdf/example.rdf | 23 + api/src/test/resources/example-rdf/example.trig | 3 + api/src/test/resources/example-rdf/example.ttl | 2 + .../rdf/simple/AbstractRDFParserBuilder.java | 541 ------------------ .../commons/rdf/simple/RDFParseException.java | 50 -- .../simple/experimental/AbstractRDFParser.java | 542 +++++++++++++++++++ .../simple/experimental/RDFParseException.java | 50 ++ .../rdf/simple/experimental/package-info.java | 34 ++ .../simple/AbstractRDFParserBuilderTest.java | 253 --------- .../rdf/simple/DummyRDFParserBuilder.java | 12 +- .../experimental/AbstractRDFParserTest.java | 256 +++++++++ 17 files changed, 1476 insertions(+), 1328 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/main/java/org/apache/commons/rdf/api/RDFParserBuilder.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/commons/rdf/api/RDFParserBuilder.java b/api/src/main/java/org/apache/commons/rdf/api/RDFParserBuilder.java deleted file mode 100644 index dde92ac..0000000 --- a/api/src/main/java/org/apache/commons/rdf/api/RDFParserBuilder.java +++ /dev/null @@ -1,479 +0,0 @@ -/** - * 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.api; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.Optional; -import java.util.concurrent.Future; -import java.util.function.Consumer; - -/** - * Builder for parsing an RDF source into a target (e.g. a Graph/Dataset). - * <p> - * This interface follows the - * <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern</a>, - * allowing to set parser settings like {@link #contentType(RDFSyntax)} and - * {@link #base(IRI)}. A caller MUST call one of the <code>source</code> methods - * (e.g. {@link #source(IRI)}, {@link #source(Path)}, - * {@link #source(InputStream)}), and MUST call one of the <code>target</code> - * methods (e.g. {@link #target(Consumer)}, {@link #target(Dataset)}, - * {@link #target(Graph)}) before calling {@link #parse()} on the returned - * RDFParserBuilder - however methods can be called in any order. - * <p> - * The call to {@link #parse()} returns a {@link Future}, allowing asynchronous - * parse operations. Callers are recommended to check {@link Future#get()} to - * ensure parsing completed successfully, or catch exceptions thrown during - * parsing. - * <p> - * Setting a method that has already been set will override any existing value - * in the returned builder - regardless of the parameter type (e.g. - * {@link #source(IRI)} will override a previous {@link #source(Path)}. Settings - * can be unset by passing <code>null</code> - note that this may - * require casting, e.g. <code>contentType( (RDFSyntax) null )</code> - * to undo a previous call to {@link #contentType(RDFSyntax)}. - * <p> - * It is undefined if a RDFParserBuilder is mutable or thread-safe, so callers - * should always use the returned modified RDFParserBuilder from the builder - * methods. The builder may return itself after modification, - * or a cloned builder with the modified settings applied. - * Implementations are however encouraged to be immutable, - * thread-safe and document this. As an example starting point, see - * {@link org.apache.commons.rdf.simple.AbstractRDFParserBuilder}. - * <p> - * Example usage: - * </p> - * - * <pre> - * Graph g1 = rDFTermFactory.createGraph(); - * new ExampleRDFParserBuilder() - * .source(Paths.get("/tmp/graph.ttl")) - * .contentType(RDFSyntax.TURTLE) - * .target(g1) - * .parse().get(30, TimeUnit.Seconds); - * </pre> - * - */ -public interface RDFParserBuilder { - - /** - * The result of {@link RDFParserBuilder#parse()} indicating - * parsing completed. - * <p> - * This is a marker interface that may be subclassed to include - * parser details, e.g. warning messages or triple counts. - */ - public interface ParseResult { - } - - /** - * Specify which {@link RDFTermFactory} to use for generating - * {@link RDFTerm}s. - * <p> - * This option may be used together with {@link #target(Graph)} to - * override the implementation's default factory and graph. - * <p> - * <strong>Warning:</strong> Using the same {@link RDFTermFactory} for - * multiple {@link #parse()} calls may accidentally merge - * {@link BlankNode}s having the same label, as the parser may - * use the {@link RDFTermFactory#createBlankNode(String)} method - * from the parsed blank node labels. - * - * @see #target(Graph) - * @param rdfTermFactory - * {@link RDFTermFactory} to use for generating RDFTerms. - * @return An {@link RDFParserBuilder} that will use the specified - * rdfTermFactory - */ - RDFParserBuilder rdfTermFactory(RDFTermFactory rdfTermFactory); - - /** - * Specify the content type of the RDF syntax to parse. - * <p> - * This option can be used to select the RDFSyntax of the source, overriding - * any <code>Content-Type</code> headers or equivalent. - * <p> - * The character set of the RDFSyntax is assumed to be - * {@link StandardCharsets#UTF_8} unless overridden within the document - * (e.g. <?xml version="1.0" encoding="iso-8859-1"?></code> in - * {@link RDFSyntax#RDFXML}). - * <p> - * This method will override any contentType set with - * {@link #contentType(String)}. - * - * @see #contentType(String) - * @param rdfSyntax - * An {@link RDFSyntax} to parse the source according to, e.g. - * {@link RDFSyntax#TURTLE}. - * @throws IllegalArgumentException - * If this RDFParserBuilder does not support the specified - * RDFSyntax. - * @return An {@link RDFParserBuilder} that will use the specified content - * type. - */ - RDFParserBuilder contentType(RDFSyntax rdfSyntax) throws IllegalArgumentException; - - /** - * Specify the content type of the RDF syntax to parse. - * <p> - * This option can be used to select the RDFSyntax of the source, overriding - * any <code>Content-Type</code> headers or equivalent. - * <p> - * The content type MAY include a <code>charset</code> parameter if the RDF - * media types permit it; the default charset is - * {@link StandardCharsets#UTF_8} unless overridden within the document. - * <p> - * This method will override any contentType set with - * {@link #contentType(RDFSyntax)}. - * - * @see #contentType(RDFSyntax) - * @param contentType - * A content-type string, e.g. <code>application/ld+json</code> - * or <code>text/turtle;charset="UTF-8"</code> as specified by - * <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.1"> - * RFC7231</a>. - * @return An {@link RDFParserBuilder} that will use the specified content - * type. - * @throws IllegalArgumentException - * If the contentType has an invalid syntax, or this - * RDFParserBuilder does not support the specified contentType. - */ - RDFParserBuilder contentType(String contentType) throws IllegalArgumentException; - - /** - * Specify a {@link Graph} to add parsed triples to. - * <p> - * If the source supports datasets (e.g. the {@link #contentType(RDFSyntax)} - * set has {@link RDFSyntax#supportsDataset} is true)), then only quads in - * the <em>default graph</em> will be added to the Graph as {@link Triple}s. - * <p> - * It is undefined if any triples are added to the specified {@link Graph} - * if {@link #parse()} throws any exceptions. (However implementations are - * free to prevent this using transaction mechanisms or similar). If - * {@link Future#get()} does not indicate an exception, the parser - * implementation SHOULD have inserted all parsed triples to the specified - * graph. - * <p> - * Calling this method will override any earlier targets set with - * {@link #target(Graph)}, {@link #target(Consumer)} or - * {@link #target(Dataset)}. - * <p> - * The default implementation of this method calls {@link #target(Consumer)} - * with a {@link Consumer} that does {@link Graph#add(Triple)} with - * {@link Quad#asTriple()} if the quad is in the default graph. - * - * @param graph - * The {@link Graph} to add triples to. - * @return An {@link RDFParserBuilder} that will insert triples into the - * specified graph. - */ - default RDFParserBuilder target(Graph graph) { - return target(q -> { - if (! q.getGraphName().isPresent()) { - graph.add(q.asTriple()); - } - }); - } - - /** - * Specify a {@link Dataset} to add parsed quads to. - * <p> - * It is undefined if any quads are added to the specified - * {@link Dataset} if {@link #parse()} throws any exceptions. - * (However implementations are free to prevent this using transaction - * mechanisms or similar). On the other hand, if {@link #parse()} - * does not indicate an exception, the - * implementation SHOULD have inserted all parsed quads - * to the specified dataset. - * <p> - * Calling this method will override any earlier targets set with - * {@link #target(Graph)}, {@link #target(Consumer)} or {@link #target(Dataset)}. - * <p> - * The default implementation of this method calls {@link #target(Consumer)} - * with a {@link Consumer} that does {@link Dataset#add(Quad)}. - * - * @param dataset - * The {@link Dataset} to add quads to. - * @return An {@link RDFParserBuilder} that will insert triples into the - * specified dataset. - */ - default RDFParserBuilder target(Dataset dataset) { - return target(dataset::add); - } - - /** - * Specify a consumer for parsed quads. - * <p> - * The quads will include triples in all named graphs of the parsed - * source, including any triples in the default graph. - * When parsing a source format which do not support datasets, all quads - * delivered to the consumer will be in the default graph - * (e.g. their {@link Quad#getGraphName()} will be - * as {@link Optional#empty()}), while for a source - * <p> - * It is undefined if any quads are consumed if {@link #parse()} throws any - * exceptions. On the other hand, if {@link #parse()} does not indicate an - * exception, the implementation SHOULD have produced all parsed quads to - * the specified consumer. - * <p> - * Calling this method will override any earlier targets set with - * {@link #target(Graph)}, {@link #target(Consumer)} or - * {@link #target(Dataset)}. - * <p> - * The consumer is not assumed to be thread safe - only one - * {@link Consumer#accept(Object)} is delivered at a time for a given - * {@link RDFParserBuilder#parse()} call. - * <p> - * This method is typically called with a functional consumer, for example: - * <pre> - * List<Quad> quads = new ArrayList<Quad>; - * parserBuilder.target(quads::add).parse(); - * </pre> - * - * @param consumer - * A {@link Consumer} of {@link Quad}s - * @return An {@link RDFParserBuilder} that will call the consumer for into - * the specified dataset. - */ - RDFParserBuilder target(Consumer<Quad> consumer); - - /** - * Specify a base IRI to use for parsing any relative IRI references. - * <p> - * Setting this option will override any protocol-specific base IRI (e.g. - * <code>Content-Location</code> header) or the {@link #source(IRI)} IRI, - * but does not override any base IRIs set within the source document (e.g. - * <code>@base</code> in Turtle documents). - * <p> - * If the source is in a syntax that does not support relative IRI - * references (e.g. {@link RDFSyntax#NTRIPLES}), setting the - * <code>base</code> has no effect. - * <p> - * This method will override any base IRI set with {@link #base(String)}. - * - * @see #base(String) - * @param base - * An absolute IRI to use as a base. - * @return An {@link RDFParserBuilder} that will use the specified base IRI. - */ - RDFParserBuilder base(IRI base); - - /** - * Specify a base IRI to use for parsing any relative IRI references. - * <p> - * Setting this option will override any protocol-specific base IRI (e.g. - * <code>Content-Location</code> header) or the {@link #source(IRI)} IRI, - * but does not override any base IRIs set within the source document (e.g. - * <code>@base</code> in Turtle documents). - * <p> - * If the source is in a syntax that does not support relative IRI - * references (e.g. {@link RDFSyntax#NTRIPLES}), setting the - * <code>base</code> has no effect. - * <p> - * This method will override any base IRI set with {@link #base(IRI)}. - * - * @see #base(IRI) - * @param base - * An absolute IRI to use as a base. - * @return An {@link RDFParserBuilder} that will use the specified base IRI. - * @throws IllegalArgumentException - * If the base is not a valid absolute IRI string - */ - RDFParserBuilder base(String base) throws IllegalArgumentException; - - /** - * Specify a source {@link InputStream} to parse. - * <p> - * The source set will not be read before the call to {@link #parse()}. - * <p> - * The InputStream will not be closed after parsing. The InputStream does - * not need to support {@link InputStream#markSupported()}. - * <p> - * The parser might not consume the complete stream (e.g. an RDF/XML parser - * may not read beyond the closing tag of - * <code></rdf:Description></code>). - * <p> - * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} - * SHOULD be set before calling {@link #parse()}. - * <p> - * The character set is assumed to be {@link StandardCharsets#UTF_8} unless - * the {@link #contentType(String)} specifies otherwise or the document - * declares its own charset (e.g. RDF/XML with a - * <code><?xml encoding="iso-8859-1"></code> header). - * <p> - * The {@link #base(IRI)} or {@link #base(String)} MUST be set before - * calling {@link #parse()}, unless the RDF syntax does not permit relative - * IRIs (e.g. {@link RDFSyntax#NTRIPLES}). - * <p> - * This method will override any source set with {@link #source(IRI)}, - * {@link #source(Path)} or {@link #source(String)}. - * - * @param inputStream - * An InputStream to consume - * @return An {@link RDFParserBuilder} that will use the specified source. - */ - RDFParserBuilder source(InputStream inputStream); - - /** - * Specify a source file {@link Path} to parse. - * <p> - * The source set will not be read before the call to {@link #parse()}. - * <p> - * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} - * SHOULD be set before calling {@link #parse()}. - * <p> - * The character set is assumed to be {@link StandardCharsets#UTF_8} unless - * the {@link #contentType(String)} specifies otherwise or the document - * declares its own charset (e.g. RDF/XML with a - * <code><?xml encoding="iso-8859-1"></code> header). - * <p> - * The {@link #base(IRI)} or {@link #base(String)} MAY be set before calling - * {@link #parse()}, otherwise {@link Path#toUri()} will be used as the base - * IRI. - * <p> - * This method will override any source set with {@link #source(IRI)}, - * {@link #source(InputStream)} or {@link #source(String)}. - * - * @param file - * A Path for a file to parse - * @return An {@link RDFParserBuilder} that will use the specified source. - */ - RDFParserBuilder source(Path file); - - /** - * Specify an absolute source {@link IRI} to retrieve and parse. - * <p> - * The source set will not be read before the call to {@link #parse()}. - * <p> - * If this builder does not support the given IRI protocol (e.g. - * <code>urn:uuid:ce667463-c5ab-4c23-9b64-701d055c4890</code>), this method - * should succeed, while the {@link #parse()} should throw an - * {@link IOException}. - * <p> - * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} MAY - * be set before calling {@link #parse()}, in which case that type MAY be - * used for content negotiation (e.g. <code>Accept</code> header in HTTP), - * and SHOULD be used for selecting the RDFSyntax. - * <p> - * The character set is assumed to be {@link StandardCharsets#UTF_8} unless - * the protocol's equivalent of <code>Content-Type</code> specifies - * otherwise or the document declares its own charset (e.g. RDF/XML with a - * <code><?xml encoding="iso-8859-1"></code> header). - * <p> - * The {@link #base(IRI)} or {@link #base(String)} MAY be set before calling - * {@link #parse()}, otherwise the source IRI will be used as the base IRI. - * <p> - * This method will override any source set with {@link #source(Path)}, - * {@link #source(InputStream)} or {@link #source(String)}. - * - * @param iri - * An IRI to retrieve and parse - * @return An {@link RDFParserBuilder} that will use the specified source. - */ - RDFParserBuilder source(IRI iri); - - /** - * Specify an absolute source IRI to retrieve and parse. - * <p> - * The source set will not be read before the call to {@link #parse()}. - * <p> - * If this builder does not support the given IRI (e.g. - * <code>urn:uuid:ce667463-c5ab-4c23-9b64-701d055c4890</code>), this method - * should succeed, while the {@link #parse()} should throw an - * {@link IOException}. - * <p> - * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} MAY - * be set before calling {@link #parse()}, in which case that type MAY be - * used for content negotiation (e.g. <code>Accept</code> header in HTTP), - * and SHOULD be used for selecting the RDFSyntax. - * <p> - * The character set is assumed to be {@link StandardCharsets#UTF_8} unless - * the protocol's equivalent of <code>Content-Type</code> specifies - * otherwise or the document declares its own charset (e.g. RDF/XML with a - * <code><?xml encoding="iso-8859-1"></code> header). - * <p> - * The {@link #base(IRI)} or {@link #base(String)} MAY be set before calling - * {@link #parse()}, otherwise the source IRI will be used as the base IRI. - * <p> - * This method will override any source set with {@link #source(Path)}, - * {@link #source(InputStream)} or {@link #source(IRI)}. - * - * @param iri - * An IRI to retrieve and parse - * @return An {@link RDFParserBuilder} that will use the specified source. - * @throws IllegalArgumentException - * If the base is not a valid absolute IRI string - * - */ - RDFParserBuilder source(String iri) throws IllegalArgumentException; - - /** - * Parse the specified source. - * <p> - * A source method (e.g. {@link #source(InputStream)}, {@link #source(IRI)}, - * {@link #source(Path)}, {@link #source(String)} or an equivalent subclass - * method) MUST have been called before calling this method, otherwise an - * {@link IllegalStateException} will be thrown. - * <p> - * A target method (e.g. {@link #target(Consumer)}, {@link #target(Dataset)}, - * {@link #target(Graph)} or an equivalent subclass method) MUST have been - * called before calling parse(), otherwise an - * {@link IllegalStateException} will be thrown. - * <p> - * It is undefined if this method is thread-safe, however the - * {@link RDFParserBuilder} may be reused (e.g. setting a different source) - * as soon as the {@link Future} has been returned from this method. - * <p> - * The RDFParserBuilder SHOULD perform the parsing as an asynchronous - * operation, and return the {@link Future} as soon as preliminary checks - * (such as validity of the {@link #source(IRI)} and - * {@link #contentType(RDFSyntax)} settings) have finished. The future - * SHOULD not mark {@link Future#isDone()} before parsing is complete. A - * synchronous implementation MAY be blocking on the <code>parse()</code> - * call and return a Future that is already {@link Future#isDone()}. - * <p> - * The returned {@link Future} contains a {@link ParseResult}. - * Implementations may subclass this interface to provide any - * parser details, e.g. list of warnings. <code>null</code> is a - * possible return value if no details are available, but - * parsing succeeded. - * <p> - * If an exception occurs during parsing, (e.g. {@link IOException} or - * {@link org.apache.commons.rdf.simple.RDFParseException}), - * it should be indicated as the - * {@link java.util.concurrent.ExecutionException#getCause()} in the - * {@link java.util.concurrent.ExecutionException} thrown on - * {@link Future#get()}. - * - * @return A Future that will return the populated {@link Graph} when the - * parsing has finished. - * @throws IOException - * If an error occurred while starting to read the source (e.g. - * file not found, unsupported IRI protocol). Note that IO - * errors during parsing would instead be the - * {@link java.util.concurrent.ExecutionException#getCause()} of - * the {@link java.util.concurrent.ExecutionException} thrown on - * {@link Future#get()}. - * @throws IllegalStateException - * If the builder is in an invalid state, e.g. a - * <code>source</code> has not been set. - */ - Future<? extends ParseResult> parse() throws IOException, IllegalStateException; -} http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/main/java/org/apache/commons/rdf/experimental/RDFParser.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/commons/rdf/experimental/RDFParser.java b/api/src/main/java/org/apache/commons/rdf/experimental/RDFParser.java new file mode 100644 index 0000000..dd6bcf0 --- /dev/null +++ b/api/src/main/java/org/apache/commons/rdf/experimental/RDFParser.java @@ -0,0 +1,495 @@ +/** + * 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.experimental; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.Future; +import java.util.function.Consumer; + +import org.apache.commons.rdf.api.BlankNode; +import org.apache.commons.rdf.api.Dataset; +import org.apache.commons.rdf.api.Graph; +import org.apache.commons.rdf.api.IRI; +import org.apache.commons.rdf.api.Quad; +import org.apache.commons.rdf.api.RDFSyntax; +import org.apache.commons.rdf.api.RDFTerm; +import org.apache.commons.rdf.api.RDFTermFactory; +import org.apache.commons.rdf.api.Triple; + +/** + * Parse an RDF source into a target (e.g. a Graph/Dataset). + * <h2>Experimental</h2> + * This interface (and its implementations) should be considered <strong>at + * risk</strong>; they might change or be removed in the next minor update of + * Commons RDF. It may move to the the {@link org.apache.commons.rdf.api} + * package when it has stabilized. + * <h2>Description</h2> + * <p> + * This interface follows the + * <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder pattern</a>, + * allowing to set parser settings like {@link #contentType(RDFSyntax)} and + * {@link #base(IRI)}. A caller MUST call one of the <code>source</code> methods + * (e.g. {@link #source(IRI)}, {@link #source(Path)}, + * {@link #source(InputStream)}), and MUST call one of the <code>target</code> + * methods (e.g. {@link #target(Consumer)}, {@link #target(Dataset)}, + * {@link #target(Graph)}) before calling {@link #parse()} on the returned + * RDFParser - however methods can be called in any order. + * <p> + * The call to {@link #parse()} returns a {@link Future}, allowing asynchronous + * parse operations. Callers are recommended to check {@link Future#get()} to + * ensure parsing completed successfully, or catch exceptions thrown during + * parsing. + * <p> + * Setting a method that has already been set will override any existing value + * in the returned builder - regardless of the parameter type (e.g. + * {@link #source(IRI)} will override a previous {@link #source(Path)}. Settings + * can be unset by passing <code>null</code> - note that this may + * require casting, e.g. <code>contentType( (RDFSyntax) null )</code> + * to undo a previous call to {@link #contentType(RDFSyntax)}. + * <p> + * It is undefined if a RDFParser is mutable or thread-safe, so callers + * should always use the returned modified RDFParser from the builder + * methods. The builder may return itself after modification, + * or a cloned builder with the modified settings applied. + * Implementations are however encouraged to be immutable, + * thread-safe and document this. As an example starting point, see + * {@link org.apache.commons.rdf.simple.AbstractRDFParserBuilder}. + * <p> + * Example usage: + * </p> + * + * <pre> + * Graph g1 = rDFTermFactory.createGraph(); + * new ExampleRDFParserBuilder() + * .source(Paths.get("/tmp/graph.ttl")) + * .contentType(RDFSyntax.TURTLE) + * .target(g1) + * .parse().get(30, TimeUnit.Seconds); + * </pre> + * + */ +public interface RDFParser { + + /** + * The result of {@link RDFParser#parse()} indicating + * parsing completed. + * <p> + * This is a marker interface that may be subclassed to include + * parser details, e.g. warning messages or triple counts. + */ + public interface ParseResult { + } + + /** + * Specify which {@link RDFTermFactory} to use for generating + * {@link RDFTerm}s. + * <p> + * This option may be used together with {@link #target(Graph)} to + * override the implementation's default factory and graph. + * <p> + * <strong>Warning:</strong> Using the same {@link RDFTermFactory} for + * multiple {@link #parse()} calls may accidentally merge + * {@link BlankNode}s having the same label, as the parser may + * use the {@link RDFTermFactory#createBlankNode(String)} method + * from the parsed blank node labels. + * + * @see #target(Graph) + * @param rdfTermFactory + * {@link RDFTermFactory} to use for generating RDFTerms. + * @return An {@link RDFParser} that will use the specified + * rdfTermFactory + */ + RDFParser rdfTermFactory(RDFTermFactory rdfTermFactory); + + /** + * Specify the content type of the RDF syntax to parse. + * <p> + * This option can be used to select the RDFSyntax of the source, overriding + * any <code>Content-Type</code> headers or equivalent. + * <p> + * The character set of the RDFSyntax is assumed to be + * {@link StandardCharsets#UTF_8} unless overridden within the document + * (e.g. <?xml version="1.0" encoding="iso-8859-1"?></code> in + * {@link RDFSyntax#RDFXML}). + * <p> + * This method will override any contentType set with + * {@link #contentType(String)}. + * + * @see #contentType(String) + * @param rdfSyntax + * An {@link RDFSyntax} to parse the source according to, e.g. + * {@link RDFSyntax#TURTLE}. + * @throws IllegalArgumentException + * If this RDFParser does not support the specified + * RDFSyntax. + * @return An {@link RDFParser} that will use the specified content + * type. + */ + RDFParser contentType(RDFSyntax rdfSyntax) throws IllegalArgumentException; + + /** + * Specify the content type of the RDF syntax to parse. + * <p> + * This option can be used to select the RDFSyntax of the source, overriding + * any <code>Content-Type</code> headers or equivalent. + * <p> + * The content type MAY include a <code>charset</code> parameter if the RDF + * media types permit it; the default charset is + * {@link StandardCharsets#UTF_8} unless overridden within the document. + * <p> + * This method will override any contentType set with + * {@link #contentType(RDFSyntax)}. + * + * @see #contentType(RDFSyntax) + * @param contentType + * A content-type string, e.g. <code>application/ld+json</code> + * or <code>text/turtle;charset="UTF-8"</code> as specified by + * <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.1"> + * RFC7231</a>. + * @return An {@link RDFParser} that will use the specified content + * type. + * @throws IllegalArgumentException + * If the contentType has an invalid syntax, or this + * RDFParser does not support the specified contentType. + */ + RDFParser contentType(String contentType) throws IllegalArgumentException; + + /** + * Specify a {@link Graph} to add parsed triples to. + * <p> + * If the source supports datasets (e.g. the {@link #contentType(RDFSyntax)} + * set has {@link RDFSyntax#supportsDataset} is true)), then only quads in + * the <em>default graph</em> will be added to the Graph as {@link Triple}s. + * <p> + * It is undefined if any triples are added to the specified {@link Graph} + * if {@link #parse()} throws any exceptions. (However implementations are + * free to prevent this using transaction mechanisms or similar). If + * {@link Future#get()} does not indicate an exception, the parser + * implementation SHOULD have inserted all parsed triples to the specified + * graph. + * <p> + * Calling this method will override any earlier targets set with + * {@link #target(Graph)}, {@link #target(Consumer)} or + * {@link #target(Dataset)}. + * <p> + * The default implementation of this method calls {@link #target(Consumer)} + * with a {@link Consumer} that does {@link Graph#add(Triple)} with + * {@link Quad#asTriple()} if the quad is in the default graph. + * + * @param graph + * The {@link Graph} to add triples to. + * @return An {@link RDFParser} that will insert triples into the + * specified graph. + */ + default RDFParser target(Graph graph) { + return target(q -> { + if (! q.getGraphName().isPresent()) { + graph.add(q.asTriple()); + } + }); + } + + /** + * Specify a {@link Dataset} to add parsed quads to. + * <p> + * It is undefined if any quads are added to the specified + * {@link Dataset} if {@link #parse()} throws any exceptions. + * (However implementations are free to prevent this using transaction + * mechanisms or similar). On the other hand, if {@link #parse()} + * does not indicate an exception, the + * implementation SHOULD have inserted all parsed quads + * to the specified dataset. + * <p> + * Calling this method will override any earlier targets set with + * {@link #target(Graph)}, {@link #target(Consumer)} or {@link #target(Dataset)}. + * <p> + * The default implementation of this method calls {@link #target(Consumer)} + * with a {@link Consumer} that does {@link Dataset#add(Quad)}. + * + * @param dataset + * The {@link Dataset} to add quads to. + * @return An {@link RDFParser} that will insert triples into the + * specified dataset. + */ + default RDFParser target(Dataset dataset) { + return target(dataset::add); + } + + /** + * Specify a consumer for parsed quads. + * <p> + * The quads will include triples in all named graphs of the parsed + * source, including any triples in the default graph. + * When parsing a source format which do not support datasets, all quads + * delivered to the consumer will be in the default graph + * (e.g. their {@link Quad#getGraphName()} will be + * as {@link Optional#empty()}), while for a source + * <p> + * It is undefined if any quads are consumed if {@link #parse()} throws any + * exceptions. On the other hand, if {@link #parse()} does not indicate an + * exception, the implementation SHOULD have produced all parsed quads to + * the specified consumer. + * <p> + * Calling this method will override any earlier targets set with + * {@link #target(Graph)}, {@link #target(Consumer)} or + * {@link #target(Dataset)}. + * <p> + * The consumer is not assumed to be thread safe - only one + * {@link Consumer#accept(Object)} is delivered at a time for a given + * {@link RDFParser#parse()} call. + * <p> + * This method is typically called with a functional consumer, for example: + * <pre> + * List<Quad> quads = new ArrayList<Quad>; + * parserBuilder.target(quads::add).parse(); + * </pre> + * + * @param consumer + * A {@link Consumer} of {@link Quad}s + * @return An {@link RDFParser} that will call the consumer for into + * the specified dataset. + */ + RDFParser target(Consumer<Quad> consumer); + + /** + * Specify a base IRI to use for parsing any relative IRI references. + * <p> + * Setting this option will override any protocol-specific base IRI (e.g. + * <code>Content-Location</code> header) or the {@link #source(IRI)} IRI, + * but does not override any base IRIs set within the source document (e.g. + * <code>@base</code> in Turtle documents). + * <p> + * If the source is in a syntax that does not support relative IRI + * references (e.g. {@link RDFSyntax#NTRIPLES}), setting the + * <code>base</code> has no effect. + * <p> + * This method will override any base IRI set with {@link #base(String)}. + * + * @see #base(String) + * @param base + * An absolute IRI to use as a base. + * @return An {@link RDFParser} that will use the specified base IRI. + */ + RDFParser base(IRI base); + + /** + * Specify a base IRI to use for parsing any relative IRI references. + * <p> + * Setting this option will override any protocol-specific base IRI (e.g. + * <code>Content-Location</code> header) or the {@link #source(IRI)} IRI, + * but does not override any base IRIs set within the source document (e.g. + * <code>@base</code> in Turtle documents). + * <p> + * If the source is in a syntax that does not support relative IRI + * references (e.g. {@link RDFSyntax#NTRIPLES}), setting the + * <code>base</code> has no effect. + * <p> + * This method will override any base IRI set with {@link #base(IRI)}. + * + * @see #base(IRI) + * @param base + * An absolute IRI to use as a base. + * @return An {@link RDFParser} that will use the specified base IRI. + * @throws IllegalArgumentException + * If the base is not a valid absolute IRI string + */ + RDFParser base(String base) throws IllegalArgumentException; + + /** + * Specify a source {@link InputStream} to parse. + * <p> + * The source set will not be read before the call to {@link #parse()}. + * <p> + * The InputStream will not be closed after parsing. The InputStream does + * not need to support {@link InputStream#markSupported()}. + * <p> + * The parser might not consume the complete stream (e.g. an RDF/XML parser + * may not read beyond the closing tag of + * <code></rdf:Description></code>). + * <p> + * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} + * SHOULD be set before calling {@link #parse()}. + * <p> + * The character set is assumed to be {@link StandardCharsets#UTF_8} unless + * the {@link #contentType(String)} specifies otherwise or the document + * declares its own charset (e.g. RDF/XML with a + * <code><?xml encoding="iso-8859-1"></code> header). + * <p> + * The {@link #base(IRI)} or {@link #base(String)} MUST be set before + * calling {@link #parse()}, unless the RDF syntax does not permit relative + * IRIs (e.g. {@link RDFSyntax#NTRIPLES}). + * <p> + * This method will override any source set with {@link #source(IRI)}, + * {@link #source(Path)} or {@link #source(String)}. + * + * @param inputStream + * An InputStream to consume + * @return An {@link RDFParser} that will use the specified source. + */ + RDFParser source(InputStream inputStream); + + /** + * Specify a source file {@link Path} to parse. + * <p> + * The source set will not be read before the call to {@link #parse()}. + * <p> + * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} + * SHOULD be set before calling {@link #parse()}. + * <p> + * The character set is assumed to be {@link StandardCharsets#UTF_8} unless + * the {@link #contentType(String)} specifies otherwise or the document + * declares its own charset (e.g. RDF/XML with a + * <code><?xml encoding="iso-8859-1"></code> header). + * <p> + * The {@link #base(IRI)} or {@link #base(String)} MAY be set before calling + * {@link #parse()}, otherwise {@link Path#toUri()} will be used as the base + * IRI. + * <p> + * This method will override any source set with {@link #source(IRI)}, + * {@link #source(InputStream)} or {@link #source(String)}. + * + * @param file + * A Path for a file to parse + * @return An {@link RDFParser} that will use the specified source. + */ + RDFParser source(Path file); + + /** + * Specify an absolute source {@link IRI} to retrieve and parse. + * <p> + * The source set will not be read before the call to {@link #parse()}. + * <p> + * If this builder does not support the given IRI protocol (e.g. + * <code>urn:uuid:ce667463-c5ab-4c23-9b64-701d055c4890</code>), this method + * should succeed, while the {@link #parse()} should throw an + * {@link IOException}. + * <p> + * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} MAY + * be set before calling {@link #parse()}, in which case that type MAY be + * used for content negotiation (e.g. <code>Accept</code> header in HTTP), + * and SHOULD be used for selecting the RDFSyntax. + * <p> + * The character set is assumed to be {@link StandardCharsets#UTF_8} unless + * the protocol's equivalent of <code>Content-Type</code> specifies + * otherwise or the document declares its own charset (e.g. RDF/XML with a + * <code><?xml encoding="iso-8859-1"></code> header). + * <p> + * The {@link #base(IRI)} or {@link #base(String)} MAY be set before calling + * {@link #parse()}, otherwise the source IRI will be used as the base IRI. + * <p> + * This method will override any source set with {@link #source(Path)}, + * {@link #source(InputStream)} or {@link #source(String)}. + * + * @param iri + * An IRI to retrieve and parse + * @return An {@link RDFParser} that will use the specified source. + */ + RDFParser source(IRI iri); + + /** + * Specify an absolute source IRI to retrieve and parse. + * <p> + * The source set will not be read before the call to {@link #parse()}. + * <p> + * If this builder does not support the given IRI (e.g. + * <code>urn:uuid:ce667463-c5ab-4c23-9b64-701d055c4890</code>), this method + * should succeed, while the {@link #parse()} should throw an + * {@link IOException}. + * <p> + * The {@link #contentType(RDFSyntax)} or {@link #contentType(String)} MAY + * be set before calling {@link #parse()}, in which case that type MAY be + * used for content negotiation (e.g. <code>Accept</code> header in HTTP), + * and SHOULD be used for selecting the RDFSyntax. + * <p> + * The character set is assumed to be {@link StandardCharsets#UTF_8} unless + * the protocol's equivalent of <code>Content-Type</code> specifies + * otherwise or the document declares its own charset (e.g. RDF/XML with a + * <code><?xml encoding="iso-8859-1"></code> header). + * <p> + * The {@link #base(IRI)} or {@link #base(String)} MAY be set before calling + * {@link #parse()}, otherwise the source IRI will be used as the base IRI. + * <p> + * This method will override any source set with {@link #source(Path)}, + * {@link #source(InputStream)} or {@link #source(IRI)}. + * + * @param iri + * An IRI to retrieve and parse + * @return An {@link RDFParser} that will use the specified source. + * @throws IllegalArgumentException + * If the base is not a valid absolute IRI string + * + */ + RDFParser source(String iri) throws IllegalArgumentException; + + /** + * Parse the specified source. + * <p> + * A source method (e.g. {@link #source(InputStream)}, {@link #source(IRI)}, + * {@link #source(Path)}, {@link #source(String)} or an equivalent subclass + * method) MUST have been called before calling this method, otherwise an + * {@link IllegalStateException} will be thrown. + * <p> + * A target method (e.g. {@link #target(Consumer)}, {@link #target(Dataset)}, + * {@link #target(Graph)} or an equivalent subclass method) MUST have been + * called before calling parse(), otherwise an + * {@link IllegalStateException} will be thrown. + * <p> + * It is undefined if this method is thread-safe, however the + * {@link RDFParser} may be reused (e.g. setting a different source) + * as soon as the {@link Future} has been returned from this method. + * <p> + * The RDFParser SHOULD perform the parsing as an asynchronous + * operation, and return the {@link Future} as soon as preliminary checks + * (such as validity of the {@link #source(IRI)} and + * {@link #contentType(RDFSyntax)} settings) have finished. The future + * SHOULD not mark {@link Future#isDone()} before parsing is complete. A + * synchronous implementation MAY be blocking on the <code>parse()</code> + * call and return a Future that is already {@link Future#isDone()}. + * <p> + * The returned {@link Future} contains a {@link ParseResult}. + * Implementations may subclass this interface to provide any + * parser details, e.g. list of warnings. <code>null</code> is a + * possible return value if no details are available, but + * parsing succeeded. + * <p> + * If an exception occurs during parsing, (e.g. {@link IOException} or + * {@link org.apache.commons.rdf.simple.RDFParseException}), + * it should be indicated as the + * {@link java.util.concurrent.ExecutionException#getCause()} in the + * {@link java.util.concurrent.ExecutionException} thrown on + * {@link Future#get()}. + * + * @return A Future that will return the populated {@link Graph} when the + * parsing has finished. + * @throws IOException + * If an error occurred while starting to read the source (e.g. + * file not found, unsupported IRI protocol). Note that IO + * errors during parsing would instead be the + * {@link java.util.concurrent.ExecutionException#getCause()} of + * the {@link java.util.concurrent.ExecutionException} thrown on + * {@link Future#get()}. + * @throws IllegalStateException + * If the builder is in an invalid state, e.g. a + * <code>source</code> has not been set. + */ + Future<? extends ParseResult> parse() throws IOException, IllegalStateException; +} http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/main/java/org/apache/commons/rdf/experimental/package-info.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/commons/rdf/experimental/package-info.java b/api/src/main/java/org/apache/commons/rdf/experimental/package-info.java new file mode 100644 index 0000000..5f24ddc --- /dev/null +++ b/api/src/main/java/org/apache/commons/rdf/experimental/package-info.java @@ -0,0 +1,34 @@ +/* + * 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. + */ +/** + * Experimental Commons RDF features. + * <p> + * Interfaces/classes in this package should be considered <strong>at + * risk</strong>; they might change or be removed in the next minor update of + * Commons RDF. + * <p> + * When class/interface has stabilized, it will move to the + * {@link org.apache.commons.rdf.api} package. + * <p> + * <ul> + * <li>{@link RDFParser} - a builder-like interface for parsing RDF to a + * {@link org.apache.commons.rdf.api.Graph} or + * {@link org.apache.commons.rdf.api.Dataset}.</li> + * </ul> + */ +package org.apache.commons.rdf.experimental; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/test/resources/example-rdf/example.jsonld ---------------------------------------------------------------------- diff --git a/api/src/test/resources/example-rdf/example.jsonld b/api/src/test/resources/example-rdf/example.jsonld new file mode 100644 index 0000000..d6fb670 --- /dev/null +++ b/api/src/test/resources/example-rdf/example.jsonld @@ -0,0 +1,25 @@ +{ + "@graph" : [ { + "@id" : "_:b0", + "license" : "http://www.apache.org/licenses/LICENSE-2.0", + "rights" : { + "@language" : "en", + "@value" : "Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements. See the NOTICE file\n distributed with this work for additional information\n regarding copyright ownership. The ASF licenses this file\n to you under the Apache License, Version 2.0 (the\n \"License\"); you may not use this file except in compliance\n with the License. You may obtain a copy of the License at\n \n http://www.apache.org/licenses/LICENSE-2.0\n \n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + } + }, { + "@graph" : [ { + "@id" : "_:b0", + "license" : "http://example.com/LICENSE" + } ], + "@id" : "http://example.com" + } ], + "@context" : { + "rights" : { + "@id" : "http://purl.org/dc/terms/rights" + }, + "license" : { + "@id" : "http://purl.org/dc/terms/license", + "@type" : "@id" + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/test/resources/example-rdf/example.nq ---------------------------------------------------------------------- diff --git a/api/src/test/resources/example-rdf/example.nq b/api/src/test/resources/example-rdf/example.nq new file mode 100644 index 0000000..9c5e749 --- /dev/null +++ b/api/src/test/resources/example-rdf/example.nq @@ -0,0 +1,3 @@ +_:b1 <http://purl.org/dc/terms/license> <http://www.apache.org/licenses/LICENSE-2.0> . +_:b1 <http://purl.org/dc/terms/rights> "Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements. See the NOTICE file\n distributed with this work for additional information\n regarding copyright ownership. The ASF licenses this file\n to you under the Apache License, Version 2.0 (the\n \"License\"); you may not use this file except in compliance\n with the License. You may obtain a copy of the License at\n \n http://www.apache.org/licenses/LICENSE-2.0\n \n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"@en . +_:b1 <http://purl.org/dc/terms/license> <http://example.com/LICENSE> <http://example.com> . http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/test/resources/example-rdf/example.nt ---------------------------------------------------------------------- diff --git a/api/src/test/resources/example-rdf/example.nt b/api/src/test/resources/example-rdf/example.nt new file mode 100644 index 0000000..53d3f94 --- /dev/null +++ b/api/src/test/resources/example-rdf/example.nt @@ -0,0 +1,2 @@ +_:b1 <http://purl.org/dc/terms/license> <http://www.apache.org/licenses/LICENSE-2.0> . +_:b1 <http://purl.org/dc/terms/rights> "Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements. See the NOTICE file\n distributed with this work for additional information\n regarding copyright ownership. The ASF licenses this file\n to you under the Apache License, Version 2.0 (the\n \"License\"); you may not use this file except in compliance\n with the License. You may obtain a copy of the License at\n \n http://www.apache.org/licenses/LICENSE-2.0\n \n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"@en . http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/test/resources/example-rdf/example.rdf ---------------------------------------------------------------------- diff --git a/api/src/test/resources/example-rdf/example.rdf b/api/src/test/resources/example-rdf/example.rdf new file mode 100644 index 0000000..44adfbc --- /dev/null +++ b/api/src/test/resources/example-rdf/example.rdf @@ -0,0 +1,23 @@ +<rdf:RDF + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:j.0="http://purl.org/dc/terms/"> + <rdf:Description> + <j.0:rights xml:lang="en">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. +</j.0:rights> + <j.0:license rdf:resource="http://www.apache.org/licenses/LICENSE-2.0"/> + </rdf:Description> +</rdf:RDF> http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/test/resources/example-rdf/example.trig ---------------------------------------------------------------------- diff --git a/api/src/test/resources/example-rdf/example.trig b/api/src/test/resources/example-rdf/example.trig new file mode 100644 index 0000000..9d433ce --- /dev/null +++ b/api/src/test/resources/example-rdf/example.trig @@ -0,0 +1,3 @@ +{ _:b0 <http://purl.org/dc/terms/license> <http://www.apache.org/licenses/LICENSE-2.0> ; + <http://purl.org/dc/terms/rights> "Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements. See the NOTICE file\n distributed with this work for additional information\n regarding copyright ownership. The ASF licenses this file\n to you under the Apache License, Version 2.0 (the\n \"License\"); you may not use this file except in compliance\n with the License. You may obtain a copy of the License at\n \n http://www.apache.org/licenses/LICENSE-2.0\n \n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"@en . +} http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/api/src/test/resources/example-rdf/example.ttl ---------------------------------------------------------------------- diff --git a/api/src/test/resources/example-rdf/example.ttl b/api/src/test/resources/example-rdf/example.ttl new file mode 100644 index 0000000..48b97af --- /dev/null +++ b/api/src/test/resources/example-rdf/example.ttl @@ -0,0 +1,2 @@ +_:b0 <http://purl.org/dc/terms/license> <http://www.apache.org/licenses/LICENSE-2.0> ; + <http://purl.org/dc/terms/rights> "Licensed to the Apache Software Foundation (ASF) under one\n or more contributor license agreements. See the NOTICE file\n distributed with this work for additional information\n regarding copyright ownership. The ASF licenses this file\n to you under the Apache License, Version 2.0 (the\n \"License\"); you may not use this file except in compliance\n with the License. You may obtain a copy of the License at\n \n http://www.apache.org/licenses/LICENSE-2.0\n \n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"@en . http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilder.java ---------------------------------------------------------------------- diff --git a/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilder.java b/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilder.java deleted file mode 100644 index 9e97487..0000000 --- a/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilder.java +++ /dev/null @@ -1,541 +0,0 @@ -/** - * 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 java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.function.Consumer; - -import org.apache.commons.rdf.api.Dataset; -import org.apache.commons.rdf.api.Graph; -import org.apache.commons.rdf.api.IRI; -import org.apache.commons.rdf.api.Quad; -import org.apache.commons.rdf.api.RDFParserBuilder; -import org.apache.commons.rdf.api.RDFSyntax; -import org.apache.commons.rdf.api.RDFTermFactory; - -/** - * Abstract RDFParserBuilder - * <p> - * This abstract class keeps the builder properties in protected fields like - * {@link #sourceFile} using {@link Optional}. Some basic checking like - * {@link #checkIsAbsolute(IRI)} is performed. - * <p> - * This class and its subclasses are {@link Cloneable}, immutable and - * (therefore) thread-safe - each call to option methods like - * {@link #contentType(String)} or {@link #source(IRI)} will return a cloned, - * mutated copy. - * <p> - * By default, parsing is done by the abstract method - * {@link #parseSynchronusly()} - which is executed in a cloned snapshot - hence - * multiple {@link #parse()} calls are thread-safe. The default {@link #parse()} - * uses a thread pool in {@link #threadGroup} - but implementations can override - * {@link #parse()} (e.g. because it has its own threading model or use - * asynchronous remote execution). - */ -public abstract class AbstractRDFParserBuilder<T extends AbstractRDFParserBuilder<T>> - implements RDFParserBuilder, Cloneable { - - public static final ThreadGroup threadGroup = new ThreadGroup("Commons RDF parsers"); - private static final ExecutorService threadpool = Executors.newCachedThreadPool(r -> new Thread(threadGroup, r)); - - // Basically only used for creating IRIs - private static RDFTermFactory internalRdfTermFactory = new SimpleRDFTermFactory(); - - /** - * Get the set {@link RDFTermFactory}, if any. - */ - public Optional<RDFTermFactory> getRdfTermFactory() { - return rdfTermFactory; - } - - /** - * Get the set content-type {@link RDFSyntax}, if any. - * <p> - * If this is {@link Optional#isPresent()}, then - * {@link #getContentType()} contains the - * value of {@link RDFSyntax#mediaType}. - */ - public Optional<RDFSyntax> getContentTypeSyntax() { - return contentTypeSyntax; - } - - /** - * Get the set content-type String, if any. - * <p> - * If this is {@link Optional#isPresent()} and - * is recognized by {@link RDFSyntax#byMediaType(String)}, then - * the corresponding {@link RDFSyntax} is set on - * {@link #getContentType()}, otherwise that is - * {@link Optional#empty()}. - */ - public final Optional<String> getContentType() { - return contentType; - } - - /** - * Get the target to consume parsed Quads. - * <p> - * From the call to {@link #parseSynchronusly()}, this - * method is always {@link Optional#isPresent()}. - * - */ - public Consumer<Quad> getTarget() { - return target; - } - - /** - * Get the target dataset as set by {@link #target(Dataset)}. - * <p> - * The return value is {@link Optional#isPresent()} if and only if - * {@link #target(Dataset)} has been set, meaning that the implementation - * may choose to append parsed quads to the {@link Dataset} directly instead - * of relying on the generated {@link #getTarget()} consumer. - * <p> - * If this value is present, then {@link #getTargetGraph()} MUST - * be {@link Optional#empty()}. - * - * @return The target Dataset, or {@link Optional#empty()} if another kind of target has been set. - */ - public Optional<Dataset> getTargetDataset() { - return targetDataset; - } - - /** - * Get the target graph as set by {@link #target(Graph)}. - * <p> - * The return value is {@link Optional#isPresent()} if and only if - * {@link #target(Graph)} has been set, meaning that the implementation - * may choose to append parsed triples to the {@link Graph} directly instead - * of relying on the generated {@link #getTarget()} consumer. - * <p> - * If this value is present, then {@link #getTargetDataset()} MUST - * be {@link Optional#empty()}. - * - * @return The target Graph, or {@link Optional#empty()} if another kind of target has been set. - */ - public Optional<Graph> getTargetGraph() { - return targetGraph; - } - - /** - * Get the set base {@link IRI}, if present. - * <p> - * - */ - public Optional<IRI> getBase() { - return base; - } - - /** - * Get the set source {@link InputStream}. - * <p> - * If this is {@link Optional#isPresent()}, then - * {@link #getSourceFile()} and {@link #getSourceIri()} - * are {@link Optional#empty()}. - */ - public Optional<InputStream> getSourceInputStream() { - return sourceInputStream; - } - - /** - * Get the set source {@link Path}. - * <p> - * If this is {@link Optional#isPresent()}, then - * {@link #getSourceInputStream()} and {@link #getSourceIri()} - * are {@link Optional#empty()}. - */ - public Optional<Path> getSourceFile() { - return sourceFile; - } - - /** - * Get the set source {@link Path}. - * <p> - * If this is {@link Optional#isPresent()}, then - * {@link #getSourceInputStream()} and {@link #getSourceInputStream()()} - * are {@link Optional#empty()}. - */ - public Optional<IRI> getSourceIri() { - return sourceIri; - } - - - private Optional<RDFTermFactory> rdfTermFactory = Optional.empty(); - private Optional<RDFSyntax> contentTypeSyntax = Optional.empty(); - private Optional<String> contentType = Optional.empty(); - private Optional<IRI> base = Optional.empty(); - private Optional<InputStream> sourceInputStream = Optional.empty(); - private Optional<Path> sourceFile = Optional.empty(); - private Optional<IRI> sourceIri = Optional.empty(); - private Consumer<Quad> target; - private Optional<Dataset> targetDataset; - private Optional<Graph> targetGraph; - - @SuppressWarnings("unchecked") - @Override - public T clone() { - try { - return (T) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - protected T asT() { - return (T) this; - } - - @Override - public T rdfTermFactory(RDFTermFactory rdfTermFactory) { - AbstractRDFParserBuilder<T> c = clone(); - c.rdfTermFactory = Optional.ofNullable(rdfTermFactory); - return c.asT(); - } - - @Override - public T contentType(RDFSyntax rdfSyntax) throws IllegalArgumentException { - AbstractRDFParserBuilder<T> c = clone(); - c.contentTypeSyntax = Optional.ofNullable(rdfSyntax); - c.contentType = c.contentTypeSyntax.map(syntax -> syntax.mediaType); - return c.asT(); - } - - @Override - public T contentType(String contentType) throws IllegalArgumentException { - AbstractRDFParserBuilder<T> c = clone(); - c.contentType = Optional.ofNullable(contentType); - c.contentTypeSyntax = c.contentType.flatMap(RDFSyntax::byMediaType); - return c.asT(); - } - - @Override - public T base(IRI base) { - AbstractRDFParserBuilder<T> c = clone(); - c.base = Optional.ofNullable(base); - c.base.ifPresent(i -> checkIsAbsolute(i)); - return c.asT(); - } - - @Override - public T base(String base) throws IllegalArgumentException { - return base(internalRdfTermFactory.createIRI(base)); - } - - @Override - public T source(InputStream inputStream) { - AbstractRDFParserBuilder<T> c = clone(); - c.resetSource(); - c.sourceInputStream = Optional.ofNullable(inputStream); - return c.asT(); - } - - @Override - public T source(Path file) { - AbstractRDFParserBuilder<T> c = clone(); - c.resetSource(); - c.sourceFile = Optional.ofNullable(file); - return c.asT(); - } - - @Override - public T source(IRI iri) { - AbstractRDFParserBuilder<T> c = clone(); - c.resetSource(); - c.sourceIri = Optional.ofNullable(iri); - c.sourceIri.ifPresent(i -> checkIsAbsolute(i)); - return c.asT(); - } - - @Override - public T source(String iri) throws IllegalArgumentException { - AbstractRDFParserBuilder<T> c = clone(); - c.resetSource(); - c.sourceIri = Optional.ofNullable(iri).map(internalRdfTermFactory::createIRI); - c.sourceIri.ifPresent(i -> checkIsAbsolute(i)); - return source(internalRdfTermFactory.createIRI(iri)); - } - - /** - * Check if an iri is absolute. - * <p> - * Used by {@link #source(String)} and {@link #base(String)} - * - * @param iri - */ - protected void checkIsAbsolute(IRI iri) { - if (!URI.create(iri.getIRIString()).isAbsolute()) { - throw new IllegalArgumentException("IRI is not absolute: " + iri); - } - } - - /** - * Check that one and only one source is present and valid. - * <p> - * Used by {@link #parse()}. - * <p> - * Subclasses might override this method, e.g. to support other - * source combinations, or to check if the sourceIri is - * resolvable. - * - * @throws IOException If a source file can't be read - */ - protected void checkSource() throws IOException { - if (!sourceFile.isPresent() && !sourceInputStream.isPresent() && !sourceIri.isPresent()) { - throw new IllegalStateException("No source has been set"); - } - if (sourceIri.isPresent() && sourceInputStream.isPresent()) { - throw new IllegalStateException("Both sourceIri and sourceInputStream have been set"); - } - if (sourceIri.isPresent() && sourceFile.isPresent()) { - throw new IllegalStateException("Both sourceIri and sourceFile have been set"); - } - if (sourceInputStream.isPresent() && sourceFile.isPresent()) { - throw new IllegalStateException("Both sourceInputStream and sourceFile have been set"); - } - if (sourceFile.isPresent() && !sourceFile.filter(Files::isReadable).isPresent()) { - throw new IOException("Can't read file: " + sourceFile); - } - } - - /** - * Check if base is required. - * - * @throws IllegalStateException if base is required, but not set. - */ - protected void checkBaseRequired() { - if (!base.isPresent() && sourceInputStream.isPresent() - && !contentTypeSyntax.filter(t -> t == RDFSyntax.NQUADS || t == RDFSyntax.NTRIPLES).isPresent()) { - throw new IllegalStateException("base iri required for inputstream source"); - } - } - - /** - * Reset all source* fields to Optional.empty() - * <p> - * Subclasses should override this and call <code>super.resetSource()</code> - * if they need to reset any additional source* fields. - * - */ - protected void resetSource() { - sourceInputStream = Optional.empty(); - sourceIri = Optional.empty(); - sourceFile = Optional.empty(); - } - - - /** - * Reset all optional target* fields to Optional.empty()</code> - * <p> - * Note that the consumer set for {@link #getTarget()} is - * NOT reset. - * <p> - * Subclasses should override this and call <code>super.resetTarget()</code> - * if they need to reset any additional target* fields. - * - */ - protected void resetTarget() { - targetDataset = Optional.empty(); - targetGraph = Optional.empty(); - } - - /** - * Parse {@link #sourceInputStream}, {@link #sourceFile} or - * {@link #sourceIri}. - * <p> - * One of the source fields MUST be present, as checked by {@link #checkSource()}. - * <p> - * {@link #checkBaseRequired()} is called to verify if {@link #getBase()} is required. - * - * @throws IOException If the source could not be read - * @throws RDFParseException If the source could not be parsed (e.g. a .ttl file was not valid Turtle) - */ - protected abstract void parseSynchronusly() throws IOException, RDFParseException; - - /** - * Prepare a clone of this RDFParserBuilder which have been checked and - * completed. - * <p> - * The returned clone will always have - * {@link #getTarget()} and {@link #getRdfTermFactory()} present. - * <p> - * If the {@link #getSourceFile()} is present, but the - * {@link #getBase()} is not present, the base will be set to the - * <code>file:///</code> IRI for the Path's real path (e.g. resolving any - * symbolic links). - * - * @return A completed and checked clone of this RDFParserBuilder - * @throws IOException If the source was not accessible (e.g. a file was not found) - * @throws IllegalStateException If the parser was not in a compatible setting (e.g. contentType was an invalid string) - */ - protected T prepareForParsing() throws IOException, IllegalStateException { - checkSource(); - checkBaseRequired(); - checkContentType(); - checkTarget(); - - // We'll make a clone of our current state which will be passed to - // parseSynchronously() - AbstractRDFParserBuilder<T> c = clone(); - - // Use a fresh SimpleRDFTermFactory for each parse - if (!c.rdfTermFactory.isPresent()) { - c.rdfTermFactory = Optional.of(createRDFTermFactory()); - } - // sourceFile, but no base? Let's follow any symlinks and use - // the file:/// URI - if (c.sourceFile.isPresent() && !c.base.isPresent()) { - URI baseUri = c.sourceFile.get().toRealPath().toUri(); - c.base = Optional.of(internalRdfTermFactory.createIRI(baseUri.toString())); - } - - return c.asT(); - } - - /** - * Subclasses can override this method to check the target is - * valid. - * <p> - * The default implementation throws an IllegalStateException if the - * target has not been set. - */ - protected void checkTarget() { - if (target == null) { - throw new IllegalStateException("target has not been set"); - } - if (targetGraph.isPresent() && targetDataset.isPresent()) { - // This should not happen as each target(..) method resets the optionals - throw new IllegalStateException("targetGraph and targetDataset can't both be set"); - } - } - - /** - * Subclasses can override this method to check compatibility with the - * contentType setting. - * - * @throws IllegalStateException - * if the {@link #getContentType()} or - * {@link #getContentTypeSyntax()} is not compatible or invalid - */ - protected void checkContentType() throws IllegalStateException { - } - - /** - * Guess RDFSyntax from a local file's extension. - * <p> - * This method can be used by subclasses if {@link #getContentType()} is not - * present and {@link #getSourceFile()} is set. - * - * @param path Path which extension should be checked - * @return The {@link RDFSyntax} which has a matching {@link RDFSyntax#fileExtension}, - * otherwise {@link Optional#empty()}. - */ - protected static Optional<RDFSyntax> guessRDFSyntax(Path path) { - return fileExtension(path).flatMap(RDFSyntax::byFileExtension); - } - - /** - * Return the file extension of a Path - if any. - * <p> - * The returned file extension includes the leading <code>.</code> - * <p> - * Note that this only returns the last extension, e.g. the - * file extension for <code>archive.tar.gz</code> would be <code>.gz</code> - * - * @param path Path which filename might contain an extension - * @return File extension (including the leading <code>.</code>, - * or {@link Optional#empty()} if the path has no extension - */ - private static Optional<String> fileExtension(Path path) { - Path fileName = path.getFileName(); - if (fileName == null) { - return Optional.empty(); - } - String filenameStr = fileName.toString(); - int last = filenameStr.lastIndexOf("."); - if (last > -1) { - return Optional.of(filenameStr.substring(last)); - } - return Optional.empty(); - } - - - /** - * Create a new {@link RDFTermFactory} for a parse session. - * <p> - * This is called by {@link #parse()} to set - * {@link #rdfTermFactory(RDFTermFactory)} if it is - * {@link Optional#empty()}. - * <p> - * As parsed blank nodes might be made with - * {@link RDFTermFactory#createBlankNode(String)}, - * each call to this method SHOULD return - * a new RDFTermFactory instance. - * - * @return A new {@link RDFTermFactory} - */ - protected RDFTermFactory createRDFTermFactory() { - return new SimpleRDFTermFactory(); - } - - @Override - public Future<ParseResult> parse() throws IOException, IllegalStateException { - final AbstractRDFParserBuilder<T> c = prepareForParsing(); - return threadpool.submit(() -> { - c.parseSynchronusly(); - return null; - }); - } - - @Override - public T target(Consumer<Quad> consumer) { - AbstractRDFParserBuilder<T> c = clone(); - c.resetTarget(); - c.target = consumer; - return c.asT(); - } - - @Override - public T target(Dataset dataset) { - @SuppressWarnings({ "rawtypes", "unchecked" }) - AbstractRDFParserBuilder<T> c = (AbstractRDFParserBuilder) RDFParserBuilder.super.target(dataset); - c.resetTarget(); - c.targetDataset = Optional.of(dataset); - return c.asT(); - } - - @Override - public T target(Graph graph) { - @SuppressWarnings({ "rawtypes", "unchecked" }) // super calls our .clone() - AbstractRDFParserBuilder<T> c = (AbstractRDFParserBuilder) RDFParserBuilder.super.target(graph); - c.resetTarget(); - c.targetGraph = Optional.of(graph); - return c.asT(); - } - - - -} http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/10d27cde/simple/src/main/java/org/apache/commons/rdf/simple/RDFParseException.java ---------------------------------------------------------------------- diff --git a/simple/src/main/java/org/apache/commons/rdf/simple/RDFParseException.java b/simple/src/main/java/org/apache/commons/rdf/simple/RDFParseException.java deleted file mode 100644 index ed16bb2..0000000 --- a/simple/src/main/java/org/apache/commons/rdf/simple/RDFParseException.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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.RDFParserBuilder; - -public class RDFParseException extends Exception { - private static final long serialVersionUID = 5427752643780702976L; - private RDFParserBuilder builder; - - public RDFParseException(RDFParserBuilder builder) { - super(); - this.builder = builder; - } - - public RDFParseException(RDFParserBuilder builder, String message, Throwable cause) { - super(message, cause); - this.builder = builder; - } - - public RDFParseException(RDFParserBuilder builder, String message) { - super(message); - this.builder = builder; - } - - public RDFParseException(RDFParserBuilder builder, Throwable cause) { - super(cause); - this.builder = builder; - } - - public RDFParserBuilder getRDFParserBuilder() { - return builder; - } -} \ No newline at end of file