http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/experimental/RDF4JParser.java ---------------------------------------------------------------------- diff --git a/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/experimental/RDF4JParser.java b/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/experimental/RDF4JParser.java new file mode 100644 index 0000000..c185419 --- /dev/null +++ b/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/experimental/RDF4JParser.java @@ -0,0 +1,197 @@ +/** + * 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.rdf4j.experimental; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Consumer; + +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.experimental.RDFParser; +import org.apache.commons.rdf.rdf4j.RDF4JDataset; +import org.apache.commons.rdf.rdf4j.RDF4JGraph; +import org.apache.commons.rdf.rdf4j.RDF4JTermFactory; +import org.apache.commons.rdf.simple.experimental.AbstractRDFParser; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.repository.util.RDFInserter; +import org.eclipse.rdf4j.repository.util.RDFLoader; +import org.eclipse.rdf4j.rio.ParserConfig; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.rio.RDFHandler; +import org.eclipse.rdf4j.rio.RDFHandlerException; +import org.eclipse.rdf4j.rio.Rio; +import org.eclipse.rdf4j.rio.helpers.AbstractRDFHandler; + +/** + * RDF4J-based parser. + * <p> + * This can handle the RDF syntaxes {@link RDFSyntax#JSONLD}, + * {@link RDFSyntax#NQUADS}, {@link RDFSyntax#NTRIPLES}, + * {@link RDFSyntax#RDFXML}, {@link RDFSyntax#TRIG} and {@link RDFSyntax#TURTLE} + * - additional syntaxes can be supported by including the corresponding + * <em>rdf4j-rio-*</em> module on the classpath. + * + */ +public class RDF4JParser extends AbstractRDFParser<RDF4JParser> implements RDFParser { + + private final class AddToQuadConsumer extends AbstractRDFHandler { + private final Consumer<Quad> quadTarget; + + private AddToQuadConsumer(Consumer<Quad> quadTarget) { + this.quadTarget = quadTarget; + } + + public void handleStatement(org.eclipse.rdf4j.model.Statement st) + throws org.eclipse.rdf4j.rio.RDFHandlerException { + // TODO: if getRdfTermFactory() is a non-rdf4j factory, should + // we use factory.createQuad() instead? + // Unsure what is the promise of setting getRdfTermFactory() -- + // does it go all the way down to creating BlankNode, IRI and + // Literal? + quadTarget.accept(rdf4jTermFactory.asQuad(st)); + // Performance note: + // Graph/Quad.add should pick up again our + // RDF4JGraphLike.asStatement() + // and avoid double conversion. + // Additionally the RDF4JQuad and RDF4JTriple implementations + // are lazily converting subj/obj/pred/graph.s + } + } + + private final static class AddToModel extends AbstractRDFHandler { + private final Model model; + + public AddToModel(Model model) { + this.model = model; + } + + public void handleStatement(org.eclipse.rdf4j.model.Statement st) + throws org.eclipse.rdf4j.rio.RDFHandlerException { + model.add(st); + } + + @Override + public void handleNamespace(String prefix, String uri) throws RDFHandlerException { + model.setNamespace(prefix, uri); + } + } + + private RDF4JTermFactory rdf4jTermFactory; + + @Override + protected RDF4JTermFactory createRDFTermFactory() { + return new RDF4JTermFactory(); + } + + @Override + protected RDF4JParser prepareForParsing() throws IOException, IllegalStateException { + RDF4JParser c = prepareForParsing(); + // Ensure we have an RDF4JTermFactory for conversion. + // We'll make a new one if user has provided a non-RDF4J factory + c.rdf4jTermFactory = (RDF4JTermFactory) getRdfTermFactory().filter(RDF4JTermFactory.class::isInstance) + .orElseGet(c::createRDFTermFactory); + return c; + } + + @Override + protected void parseSynchronusly() throws IOException { + Optional<RDFFormat> formatByMimeType = getContentType().flatMap(Rio::getParserFormatForMIMEType); + String base = getBase().map(IRI::getIRIString).orElse(null); + + ParserConfig parserConfig = new ParserConfig(); + // TODO: Should we need to set anything? + RDFLoader loader = new RDFLoader(parserConfig, rdf4jTermFactory.getValueFactory()); + RDFHandler rdfHandler = makeRDFHandler(); + if (getSourceFile().isPresent()) { + // NOTE: While we could have used + // loader.load(sourcePath.toFile() + // if the path fs provider == FileSystems.getDefault(), + // that RDFLoader method does not use absolute path + // as the base URI, so to be consistent + // we'll always do it with our own input stream + // + // That means we may have to guess format by extensions: + Optional<RDFFormat> formatByFilename = getSourceFile().map(Path::getFileName).map(Path::toString) + .flatMap(Rio::getParserFormatForFileName); + // TODO: for the excited.. what about the extension after following symlinks? + + RDFFormat format = formatByMimeType.orElse(formatByFilename.orElse(null)); + try (InputStream in = Files.newInputStream(getSourceFile().get())) { + loader.load(in, base, format, rdfHandler); + } + } else if (getSourceIri().isPresent()) { + try { + // TODO: Handle international IRIs properly + // (Unicode support for for hostname, path and query) + URL url = new URL(getSourceIri().get().getIRIString()); + // TODO: This probably does not support https:// -> http:// redirections + loader.load(url, base, formatByMimeType.orElse(null), makeRDFHandler()); + } catch (MalformedURLException ex) { + throw new IOException("Can't handle source URL: " + getSourceIri().get(), ex); + } + } + // must be getSourceInputStream then, this is guaranteed by super.checkSource(); + loader.load(getSourceInputStream().get(), base, formatByMimeType.orElse(null), rdfHandler); + } + + protected RDFHandler makeRDFHandler() { + + // TODO: Can we join the below DF4JDataset and RDF4JGraph cases + // using RDF4JGraphLike<TripleLike<BlankNodeOrIRI,IRI,RDFTerm>> + // or will that need tricky generics types? + + if (getTargetDataset().filter(RDF4JDataset.class::isInstance).isPresent()) { + // One of us, we can add them as Statements directly + RDF4JDataset dataset = (RDF4JDataset) getTargetDataset().get(); + if (dataset.asRepository().isPresent()) { + return new RDFInserter(dataset.asRepository().get().getConnection()); + } + if (dataset.asModel().isPresent()) { + Model model = dataset.asModel().get(); + return new AddToModel(model); + } + // Not backed by Repository or Model? + // Third-party RDF4JDataset subclass, so we'll fall through to the + // getTarget() handling further down + } else if (getTargetGraph().filter(RDF4JGraph.class::isInstance).isPresent()) { + RDF4JGraph graph = (RDF4JGraph) getTargetGraph().get(); + + if (graph.asRepository().isPresent()) { + RDFInserter inserter = new RDFInserter(graph.asRepository().get().getConnection()); + graph.getContextFilter().ifPresent(inserter::enforceContext); + return inserter; + } + if (graph.asModel().isPresent() && graph.getContextFilter().isPresent()) { + Model model = graph.asModel().get(); + return new AddToModel(model); + } + // else - fall through + } + + // Fall thorough: let target() consume our converted quads. + return new AddToQuadConsumer(getTarget()); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/experimental/package-info.java ---------------------------------------------------------------------- diff --git a/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/experimental/package-info.java b/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/experimental/package-info.java new file mode 100644 index 0000000..192a148 --- /dev/null +++ b/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/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 RDF4J implementations. + * <p> + * 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 a class has stabilized, it will move to the + * {@link org.apache.commons.rdf.rdf4j} package. + * <p> + * <ul> + * <li>{@link RDF4JParser} - an RDF4J-backed + * implementations of + * {@link org.apache.commons.rdf.api.experimental.RDFParser}.</li> + * </ul> + */ +package org.apache.commons.rdf.rdf4j.experimental; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/package-info.java ---------------------------------------------------------------------- diff --git a/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/package-info.java b/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/package-info.java index 036b54b..e384f3d 100644 --- a/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/package-info.java +++ b/rdf4j/src/main/java/org/apache/commons/rdf/rdf4j/package-info.java @@ -41,11 +41,11 @@ * {@link org.apache.commons.rdf.rdf4j.RDF4JDataset} provide access to the * underlying RDF4J implementations. * <p> - * The {@link org.apache.commons.rdf.rdf4j.RDF4JParser} can be used to + * The {@link org.apache.commons.rdf.rdf4j.experimental.RDF4JParser} can be used to * parse RDF files using RDF4j. It should be most efficient if used with - * {@link org.apache.commons.rdf.rdf4j.RDF4JParser#target(org.apache.commons.rdf.api.Dataset)} + * {@link org.apache.commons.rdf.rdf4j.experimental.RDF4JParser#target(org.apache.commons.rdf.api.Dataset)} * and an adapted {@link org.apache.commons.rdf.rdf4j.RDF4JDataset}, or - * {@link org.apache.commons.rdf.rdf4j.RDF4JParser#target(org.apache.commons.rdf.api.Graph)} + * {@link org.apache.commons.rdf.rdf4j.experimental.RDF4JParser#target(org.apache.commons.rdf.api.Graph)} * and a an adapted {@link org.apache.commons.rdf.rdf4j.RDF4JGraph} * * http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParser.java ---------------------------------------------------------------------- diff --git a/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParser.java b/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParser.java deleted file mode 100644 index e58fced..0000000 --- a/simple/src/main/java/org/apache/commons/rdf/simple/AbstractRDFParser.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.RDFParser; -import org.apache.commons.rdf.api.RDFSyntax; -import org.apache.commons.rdf.api.RDFTermFactory; - -/** - * Abstract RDFParser - * <p> - * This abstract class keeps the 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 AbstractRDFParser<T extends AbstractRDFParser<T>> - implements RDFParser, 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) { - AbstractRDFParser<T> c = clone(); - c.rdfTermFactory = Optional.ofNullable(rdfTermFactory); - return c.asT(); - } - - @Override - public T contentType(RDFSyntax rdfSyntax) throws IllegalArgumentException { - AbstractRDFParser<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 { - AbstractRDFParser<T> c = clone(); - c.contentType = Optional.ofNullable(contentType); - c.contentTypeSyntax = c.contentType.flatMap(RDFSyntax::byMediaType); - return c.asT(); - } - - @Override - public T base(IRI base) { - AbstractRDFParser<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) { - AbstractRDFParser<T> c = clone(); - c.resetSource(); - c.sourceInputStream = Optional.ofNullable(inputStream); - return c.asT(); - } - - @Override - public T source(Path file) { - AbstractRDFParser<T> c = clone(); - c.resetSource(); - c.sourceFile = Optional.ofNullable(file); - return c.asT(); - } - - @Override - public T source(IRI iri) { - AbstractRDFParser<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 { - AbstractRDFParser<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 RDFParser 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 RDFParser - * @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() - AbstractRDFParser<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 AbstractRDFParser<T> c = prepareForParsing(); - return threadpool.submit(() -> { - c.parseSynchronusly(); - return null; - }); - } - - @Override - public T target(Consumer<Quad> consumer) { - AbstractRDFParser<T> c = clone(); - c.resetTarget(); - c.target = consumer; - return c.asT(); - } - - @Override - public T target(Dataset dataset) { - @SuppressWarnings({ "rawtypes", "unchecked" }) - AbstractRDFParser<T> c = (AbstractRDFParser) RDFParser.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() - AbstractRDFParser<T> c = (AbstractRDFParser) RDFParser.super.target(graph); - c.resetTarget(); - c.targetGraph = Optional.of(graph); - return c.asT(); - } - - - -} http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/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 index d1cdd57..adb5b0e 100644 --- a/simple/src/main/java/org/apache/commons/rdf/simple/RDFParseException.java +++ b/simple/src/main/java/org/apache/commons/rdf/simple/RDFParseException.java @@ -18,7 +18,7 @@ package org.apache.commons.rdf.simple; -import org.apache.commons.rdf.api.RDFParser; +import org.apache.commons.rdf.experimental.RDFParser; public class RDFParseException extends Exception { private static final long serialVersionUID = 5427752643780702976L; http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/simple/src/main/java/org/apache/commons/rdf/simple/experimental/AbstractRDFParser.java ---------------------------------------------------------------------- diff --git a/simple/src/main/java/org/apache/commons/rdf/simple/experimental/AbstractRDFParser.java b/simple/src/main/java/org/apache/commons/rdf/simple/experimental/AbstractRDFParser.java new file mode 100644 index 0000000..f675459 --- /dev/null +++ b/simple/src/main/java/org/apache/commons/rdf/simple/experimental/AbstractRDFParser.java @@ -0,0 +1,543 @@ +/** + * 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.experimental; + +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.RDFSyntax; +import org.apache.commons.rdf.api.RDFTermFactory; +import org.apache.commons.rdf.experimental.RDFParser; +import org.apache.commons.rdf.simple.RDFParseException; +import org.apache.commons.rdf.simple.SimpleRDFTermFactory; + +/** + * Abstract RDFParser + * <p> + * This abstract class keeps the 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 AbstractRDFParser<T extends AbstractRDFParser<T>> + implements RDFParser, 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) { + AbstractRDFParser<T> c = clone(); + c.rdfTermFactory = Optional.ofNullable(rdfTermFactory); + return c.asT(); + } + + @Override + public T contentType(RDFSyntax rdfSyntax) throws IllegalArgumentException { + AbstractRDFParser<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 { + AbstractRDFParser<T> c = clone(); + c.contentType = Optional.ofNullable(contentType); + c.contentTypeSyntax = c.contentType.flatMap(RDFSyntax::byMediaType); + return c.asT(); + } + + @Override + public T base(IRI base) { + AbstractRDFParser<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) { + AbstractRDFParser<T> c = clone(); + c.resetSource(); + c.sourceInputStream = Optional.ofNullable(inputStream); + return c.asT(); + } + + @Override + public T source(Path file) { + AbstractRDFParser<T> c = clone(); + c.resetSource(); + c.sourceFile = Optional.ofNullable(file); + return c.asT(); + } + + @Override + public T source(IRI iri) { + AbstractRDFParser<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 { + AbstractRDFParser<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 RDFParser 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 RDFParser + * @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() + AbstractRDFParser<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 AbstractRDFParser<T> c = prepareForParsing(); + return threadpool.submit(() -> { + c.parseSynchronusly(); + return null; + }); + } + + @Override + public T target(Consumer<Quad> consumer) { + AbstractRDFParser<T> c = clone(); + c.resetTarget(); + c.target = consumer; + return c.asT(); + } + + @Override + public T target(Dataset dataset) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + AbstractRDFParser<T> c = (AbstractRDFParser) RDFParser.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() + AbstractRDFParser<T> c = (AbstractRDFParser) RDFParser.super.target(graph); + c.resetTarget(); + c.targetGraph = Optional.of(graph); + return c.asT(); + } + + + +} http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/simple/src/main/java/org/apache/commons/rdf/simple/experimental/package-info.java ---------------------------------------------------------------------- diff --git a/simple/src/main/java/org/apache/commons/rdf/simple/experimental/package-info.java b/simple/src/main/java/org/apache/commons/rdf/simple/experimental/package-info.java new file mode 100644 index 0000000..5196f42 --- /dev/null +++ b/simple/src/main/java/org/apache/commons/rdf/simple/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 Simple implementations. + * <p> + * 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 a class has stabilized, it will move to the + * {@link org.apache.commons.rdf.simple} package. + * <p> + * <ul> + * <li>{@link AbstractRDFParser} - an abstract helper class + * for implementations of + * {@link org.apache.commons.rdf.api.experimental.RDFParser}.</li> + * </ul> + */ +package org.apache.commons.rdf.simple.experimental; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/simple/src/test/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilderTest.java ---------------------------------------------------------------------- diff --git a/simple/src/test/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilderTest.java b/simple/src/test/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilderTest.java index acb75b7..f115e94 100644 --- a/simple/src/test/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilderTest.java +++ b/simple/src/test/java/org/apache/commons/rdf/simple/AbstractRDFParserBuilderTest.java @@ -32,11 +32,12 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.rdf.api.Graph; import org.apache.commons.rdf.api.IRI; import org.apache.commons.rdf.api.Literal; -import org.apache.commons.rdf.api.RDFParser; 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; +import org.apache.commons.rdf.experimental.RDFParser; +import org.apache.commons.rdf.simple.experimental.AbstractRDFParser; import org.junit.After; import org.junit.Before; import org.junit.Rule; http://git-wip-us.apache.org/repos/asf/incubator-commonsrdf/blob/a189f91e/simple/src/test/java/org/apache/commons/rdf/simple/DummyRDFParserBuilder.java ---------------------------------------------------------------------- diff --git a/simple/src/test/java/org/apache/commons/rdf/simple/DummyRDFParserBuilder.java b/simple/src/test/java/org/apache/commons/rdf/simple/DummyRDFParserBuilder.java index fe0b36e..99ed576 100644 --- a/simple/src/test/java/org/apache/commons/rdf/simple/DummyRDFParserBuilder.java +++ b/simple/src/test/java/org/apache/commons/rdf/simple/DummyRDFParserBuilder.java @@ -23,8 +23,9 @@ import java.util.function.Consumer; import org.apache.commons.rdf.api.IRI; import org.apache.commons.rdf.api.Quad; -import org.apache.commons.rdf.api.RDFParser; import org.apache.commons.rdf.api.RDFTermFactory; +import org.apache.commons.rdf.experimental.RDFParser; +import org.apache.commons.rdf.simple.experimental.AbstractRDFParser; /** * For test purposes - a {@link RDFParser} that inserts information