diff --git a/README.md b/README.md index a22157b9..63dfd0bd 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,10 @@ JsonLd.compact("https://example/expanded.jsonld", "https://example/context.jsonl // Flattening JsonLd.flatten("https://example/document.jsonld").get(); -// JSON-LD to RDF +// JSON-LD to RDF JsonLd.toRdf("https://example/document.jsonld").get(); +// or, since 1.6.0 +JsonLd.toRdf("https://example/document.jsonld").process(RdfConsumer); // RDF to JSON-LD JsonLd.fromRdf("https://example/document.nq").options(options).get(); diff --git a/pom.xml b/pom.xml index f1468022..3c2a87da 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.apicatalog titanium - 1.5.0 + 1.6.0-SNAPSHOT pom_parent.xml titanium-json-ld diff --git a/pom_jre8.xml b/pom_jre8.xml index 8e63ab4f..57d07a6e 100644 --- a/pom_jre8.xml +++ b/pom_jre8.xml @@ -6,7 +6,7 @@ com.apicatalog titanium - 1.5.0 + 1.6.0-SNAPSHOT pom_parent.xml titanium-json-ld-jre8 diff --git a/pom_parent.xml b/pom_parent.xml index bf0cd88b..ae62de78 100644 --- a/pom_parent.xml +++ b/pom_parent.xml @@ -6,7 +6,7 @@ 4.0.0 com.apicatalog titanium - 1.5.0 + 1.6.0-SNAPSHOT pom Titanium JSON-LD 1.1 diff --git a/src/main/java/com/apicatalog/jsonld/api/ToRdfApi.java b/src/main/java/com/apicatalog/jsonld/api/ToRdfApi.java index c30a70c8..c2472b52 100644 --- a/src/main/java/com/apicatalog/jsonld/api/ToRdfApi.java +++ b/src/main/java/com/apicatalog/jsonld/api/ToRdfApi.java @@ -26,11 +26,13 @@ import com.apicatalog.jsonld.loader.DocumentLoader; import com.apicatalog.jsonld.processor.ToRdfProcessor; import com.apicatalog.jsonld.uri.UriUtils; +import com.apicatalog.rdf.RdfConsumer; import com.apicatalog.rdf.RdfDataset; +import com.apicatalog.rdf.RdfDatasetConsumer; import jakarta.json.JsonStructure; -public final class ToRdfApi implements CommonApi, LoaderApi, ContextApi{ +public final class ToRdfApi implements CommonApi, LoaderApi, ContextApi { // required private final Document document; @@ -98,7 +100,9 @@ public ToRdfApi context(Document context) { } /** - * If set to true, the JSON-LD processor may emit blank nodes for triple predicates, otherwise they will be omitted. + * If set to true, the JSON-LD processor may emit blank nodes for triple + * predicates, otherwise they will be omitted. + * * @param enable * @return builder instance */ @@ -117,7 +121,8 @@ public ToRdfApi produceGeneralizedRdf() { } /** - * Determines how value objects containing a base direction are transformed to and from RDF. + * Determines how value objects containing a base direction are transformed to + * and from RDF. * * @param direction * @return builder instance @@ -154,23 +159,36 @@ public ToRdfApi ordered(boolean enable) { /** * Transform provided JSON-LD document into {@link RdfDataset}. * - * @return {@link RdfDataset} representing provided JSON-LD document + * @return {@link RdfDataset} representing provided JSON-LD + * document * @throws JsonLdError */ public RdfDataset get() throws JsonLdError { + final RdfDatasetConsumer consumer = new RdfDatasetConsumer(); + process(consumer); + return consumer.dataset(); + } + + /** + * Emit transformed JSON-LD as RDF statements. + * + * @param consumer that accepts emitted RDF statements + * @throws JsonLdError + */ + public void process(RdfConsumer consumer) throws JsonLdError { if (documentUri != null) { - return ToRdfProcessor.toRdf(documentUri, options); - } + ToRdfProcessor.toRdf(consumer, documentUri, options); - if (document != null) { - return ToRdfProcessor.toRdf(document, options); - } + } else if (document != null) { + ToRdfProcessor.toRdf(consumer, document, options); - throw new IllegalArgumentException(); + } else { + throw new IllegalArgumentException(); + } } /** - * Experimental: Accept numeric @id. Disabled by default. + * Experimental: Accept numeric @id. Disabled by default. * * @return builder instance */ diff --git a/src/main/java/com/apicatalog/jsonld/deseralization/JsonLdToRdf.java b/src/main/java/com/apicatalog/jsonld/deseralization/JsonLdToRdf.java index 5ba19cde..087632dc 100644 --- a/src/main/java/com/apicatalog/jsonld/deseralization/JsonLdToRdf.java +++ b/src/main/java/com/apicatalog/jsonld/deseralization/JsonLdToRdf.java @@ -15,8 +15,6 @@ */ package com.apicatalog.jsonld.deseralization; -import java.util.ArrayList; -import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,10 +29,9 @@ import com.apicatalog.jsonld.uri.UriUtils; import com.apicatalog.jsonld.uri.UriValidationPolicy; import com.apicatalog.rdf.Rdf; +import com.apicatalog.rdf.RdfConsumer; import com.apicatalog.rdf.RdfDataset; -import com.apicatalog.rdf.RdfResource; -import com.apicatalog.rdf.RdfTriple; -import com.apicatalog.rdf.RdfValue; +import com.apicatalog.rdf.RdfDatasetConsumer; import com.apicatalog.rdf.lang.RdfConstants; import jakarta.json.JsonString; @@ -46,13 +43,15 @@ public final class JsonLdToRdf { // required private final NodeMap nodeMap; - private final RdfDataset dataset; // optional private boolean produceGeneralizedRdf; private RdfDirection rdfDirection; private UriValidationPolicy uriValidation; + // deprecated + private RdfDataset dataset; + private JsonLdToRdf(NodeMap nodeMap, RdfDataset dataset) { this.nodeMap = nodeMap; this.dataset = dataset; @@ -62,10 +61,15 @@ private JsonLdToRdf(NodeMap nodeMap, RdfDataset dataset) { this.uriValidation = JsonLdOptions.DEFAULT_URI_VALIDATION; } + @Deprecated public static final JsonLdToRdf with(NodeMap nodeMap, RdfDataset dataset) { return new JsonLdToRdf(nodeMap, dataset); } + public static final JsonLdToRdf with(NodeMap nodeMap) { + return new JsonLdToRdf(nodeMap, null); + } + public JsonLdToRdf produceGeneralizedRdf(boolean enable) { this.produceGeneralizedRdf = enable; return this; @@ -76,135 +80,113 @@ public JsonLdToRdf rdfDirection(RdfDirection rdfDirection) { return this; } - public RdfDataset build() throws JsonLdError { + public void process(RdfConsumer consumer) throws JsonLdError { - // 1. for (final String graphName : Utils.index(nodeMap.graphs(), true)) { - // 1.2. - final RdfResource rdfGraphName; - if (Keywords.DEFAULT.equals(graphName)) { - rdfGraphName = null; - - } else { - - // 1.1. - if (BlankNode.isWellFormed(graphName)) { + consumer.defaultGraph(); - rdfGraphName = Rdf.createBlankNode(graphName); + } else if (BlankNode.isWellFormed(graphName)) { + consumer.namedGraph(graphName, true); - } else if (UriUtils.isAbsoluteUri(graphName, uriValidation)) { + } else if (UriUtils.isAbsoluteUri(graphName, uriValidation)) { + consumer.namedGraph(graphName, false); - rdfGraphName = Rdf.createIRI(graphName); - - } else { - continue; - } + } else { + continue; } - // 1.3. for (final String subject : Utils.index(nodeMap.subjects(graphName), true)) { - final RdfResource rdfSubject; + boolean blankSubject = false; - // 1.3.1. if (BlankNode.isWellFormed(subject)) { - rdfSubject = Rdf.createBlankNode(subject); - - } else if (UriUtils.isAbsoluteUri(subject, uriValidation)) { - rdfSubject = Rdf.createIRI(subject); + blankSubject = true; - } else { + } else if (UriUtils.isNotAbsoluteUri(subject, uriValidation)) { LOGGER.log(Level.WARNING, "Non well-formed subject [{0}] has been skipped.", subject); continue; } - // 1.3.2. for (final String property : Utils.index(nodeMap.properties(graphName, subject), true)) { - // 1.3.2.1. if (Keywords.TYPE.equals(property)) { - for (JsonValue type : nodeMap.get(graphName, subject, property).asJsonArray()) { + for (final JsonValue type : nodeMap.get(graphName, subject, property).asJsonArray()) { if (JsonUtils.isNotString(type)) { continue; } - final String typeString = ((JsonString)type).getString(); + final String typeString = ((JsonString) type).getString(); - final RdfValue rdfObject; + boolean blankType = false; if (BlankNode.isWellFormed(typeString)) { - rdfObject = Rdf.createBlankNode(typeString); + blankType = true; - } else if (UriUtils.isAbsoluteUri(typeString, uriValidation)) { - rdfObject = Rdf.createIRI(typeString); - - } else { + } else if (UriUtils.isNotAbsoluteUri(typeString, uriValidation)) { continue; } - dataset.add(Rdf.createNQuad( - rdfSubject, - Rdf.createIRI(RdfConstants.TYPE), - rdfObject, - rdfGraphName - )); + consumer.accept( + subject, + blankSubject, + RdfConstants.TYPE, + false, + typeString, + blankType); } - // 1.3.2.2. } else if (!Keywords.contains(property)) { - final RdfResource rdfProperty; - - if (BlankNode.isWellFormed(property)) { - rdfProperty = !produceGeneralizedRdf ? Rdf.createBlankNode(property) : null; + boolean blankProperty = false; - } else if (UriUtils.isAbsoluteUri(property, uriValidation)) { - rdfProperty = Rdf.createIRI(property); + if (BlankNode.isWellFormed(property) && !produceGeneralizedRdf) { + blankProperty = true; - } else { - rdfProperty = null; + } else if (UriUtils.isNotAbsoluteUri(property, uriValidation)) { + continue; } - if (rdfProperty != null) { - - // 1.3.2.5. - for (JsonValue item : nodeMap.get(graphName, subject, property).asJsonArray()) { - - // 1.3.2.5.1. - final List listTriples = new ArrayList<>(); - - // 1.3.2.5.2. - ObjectToRdf - .with(item.asJsonObject(), listTriples, nodeMap) - .rdfDirection(rdfDirection) - .uriValidation(uriValidation) - .build() - .ifPresent(rdfObject -> - dataset.add(Rdf.createNQuad( - rdfSubject, - rdfProperty, - rdfObject, - rdfGraphName - ))); - // 1.3.2.5.3. - listTriples.stream() - .map(triple -> Rdf.createNQuad(triple, rdfGraphName)) - .forEach(dataset::add); - } + for (final JsonValue item : nodeMap.get(graphName, subject, property).asJsonArray()) { + + ObjectToRdf + .with(item.asJsonObject(), consumer, nodeMap) + .rdfDirection(rdfDirection) + .uriValidation(uriValidation) + .produce( + subject, + blankSubject, + property, + blankProperty); } } } } } + } + + /** + * @deprecated since 1.6.0, use {@link #process(RdfConsumer)}. + * @return + * @throws JsonLdError + */ + @Deprecated + public RdfDataset build() throws JsonLdError { + + if (dataset == null) { + dataset = Rdf.createDataset(); + } + + process(new RdfDatasetConsumer(dataset)); + return dataset; } /** - * @deprecated since 1.5.0, use JsonLdToRdf#uriValidation(com.apicatalog.jsonld.uri.UriValidationPolicy) + * @deprecated since 1.5.0, use {@link #uriValidation(UriValidationPolicy)}. */ @Deprecated public JsonLdToRdf uriValidation(boolean enabled) { diff --git a/src/main/java/com/apicatalog/jsonld/deseralization/ListToRdf.java b/src/main/java/com/apicatalog/jsonld/deseralization/ListToRdf.java index aafe654f..d8e90061 100644 --- a/src/main/java/com/apicatalog/jsonld/deseralization/ListToRdf.java +++ b/src/main/java/com/apicatalog/jsonld/deseralization/ListToRdf.java @@ -15,8 +15,6 @@ */ package com.apicatalog.jsonld.deseralization; -import java.util.ArrayList; -import java.util.List; import java.util.stream.IntStream; import com.apicatalog.jsonld.JsonLdError; @@ -25,9 +23,7 @@ import com.apicatalog.jsonld.flattening.NodeMap; import com.apicatalog.jsonld.json.JsonUtils; import com.apicatalog.jsonld.uri.UriValidationPolicy; -import com.apicatalog.rdf.Rdf; -import com.apicatalog.rdf.RdfTriple; -import com.apicatalog.rdf.RdfValue; +import com.apicatalog.rdf.RdfConsumer; import com.apicatalog.rdf.lang.RdfConstants; import jakarta.json.JsonArray; @@ -42,16 +38,16 @@ final class ListToRdf { // required private JsonArray list; - private List triples; + private RdfConsumer consumer; private NodeMap nodeMap; // optional private RdfDirection rdfDirection; private UriValidationPolicy uriValidation; - private ListToRdf(final JsonArray list, final List triples, NodeMap nodeMap) { + private ListToRdf(final JsonArray list, final RdfConsumer consumer, NodeMap nodeMap) { this.list = list; - this.triples = triples; + this.consumer = consumer; this.nodeMap = nodeMap; // default values @@ -59,8 +55,8 @@ private ListToRdf(final JsonArray list, final List triples, NodeMap n this.uriValidation = JsonLdOptions.DEFAULT_URI_VALIDATION; } - public static final ListToRdf with(final JsonArray list, final List triples, NodeMap nodeMap) { - return new ListToRdf(list, triples, nodeMap); + public static final ListToRdf with(final JsonArray list, final RdfConsumer consumer, NodeMap nodeMap) { + return new ListToRdf(list, consumer, nodeMap); } public ListToRdf rdfDirection(RdfDirection rdfDirection) { @@ -68,11 +64,15 @@ public ListToRdf rdfDirection(RdfDirection rdfDirection) { return this; } - public RdfValue build() throws JsonLdError { + public void process( + String subject, boolean blankSubject, + String predicate, boolean blankPredicate + ) throws JsonLdError { // 1. if (JsonUtils.isEmptyArray(list)) { - return Rdf.createIRI(RdfConstants.NIL); + consumer.accept(subject, blankSubject, predicate, blankPredicate, RdfConstants.NIL, false); + return; } // 2. @@ -80,53 +80,29 @@ public RdfValue build() throws JsonLdError { IntStream.range(0, bnodes.length).forEach(i -> bnodes[i] = nodeMap.createIdentifier()); + consumer.accept(subject, blankSubject, predicate, blankPredicate, bnodes[0], true); + // 3. int index = 0; for (final JsonValue item : list) { - final String subject = bnodes[index]; + final String blankNodeSubject = bnodes[index]; index++; - // 3.1. - final List embeddedTriples = new ArrayList<>(); - - // 3.2. ObjectToRdf - .with(item.asJsonObject(), embeddedTriples, nodeMap) + .with(item.asJsonObject(), consumer, nodeMap) .rdfDirection(rdfDirection) .uriValidation(uriValidation) - .build() - .ifPresent(object -> - triples.add(Rdf.createTriple( - Rdf.createBlankNode(subject), - Rdf.createIRI(RdfConstants.FIRST), - object))); - + .produce(blankNodeSubject, true, RdfConstants.FIRST, false); + // 3.4. - final RdfValue rest = (index < bnodes.length) ? Rdf.createBlankNode(bnodes[index]) - : Rdf.createIRI(RdfConstants.NIL) - ; - - triples.add(Rdf.createTriple( - Rdf.createBlankNode(subject), - Rdf.createIRI(RdfConstants.REST), - rest - )); - - // 3.5. - triples.addAll(embeddedTriples); + if (index < bnodes.length) { + consumer.accept(blankNodeSubject, true, RdfConstants.REST, false, bnodes[index], true); + + } else { + consumer.accept(blankNodeSubject, true, RdfConstants.REST, false, RdfConstants.NIL, false); + } } - - // 4. - return Rdf.createBlankNode(bnodes[0]); - } - - /** - * @deprecated since 1.5.0, use ListToRdf#uriValidation(com.apicatalog.jsonld.uri.UriValidationPolicy) - */ - @Deprecated - public ListToRdf uriValidation(boolean enabled) { - return uriValidation(enabled ? UriValidationPolicy.Full : UriValidationPolicy.SchemeOnly); } public ListToRdf uriValidation(UriValidationPolicy uriValidation) { diff --git a/src/main/java/com/apicatalog/jsonld/deseralization/ObjectToRdf.java b/src/main/java/com/apicatalog/jsonld/deseralization/ObjectToRdf.java index 22d9c1ed..eb33ceee 100644 --- a/src/main/java/com/apicatalog/jsonld/deseralization/ObjectToRdf.java +++ b/src/main/java/com/apicatalog/jsonld/deseralization/ObjectToRdf.java @@ -18,9 +18,7 @@ import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; -import java.util.List; import java.util.Locale; -import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,11 +36,7 @@ import com.apicatalog.jsonld.lang.ValueObject; import com.apicatalog.jsonld.uri.UriUtils; import com.apicatalog.jsonld.uri.UriValidationPolicy; -import com.apicatalog.rdf.Rdf; -import com.apicatalog.rdf.RdfLiteral; -import com.apicatalog.rdf.RdfResource; -import com.apicatalog.rdf.RdfTriple; -import com.apicatalog.rdf.RdfValue; +import com.apicatalog.rdf.RdfConsumer; import com.apicatalog.rdf.lang.RdfConstants; import com.apicatalog.rdf.lang.XsdConstants; @@ -70,16 +64,16 @@ final class ObjectToRdf { // required private JsonObject item; - private List triples; + private RdfConsumer consumer; private NodeMap nodeMap; // optional private RdfDirection rdfDirection; private UriValidationPolicy uriValidation; - private ObjectToRdf(JsonObject item, List triples, NodeMap nodeMap) { + private ObjectToRdf(JsonObject item, RdfConsumer consumer, NodeMap nodeMap) { this.item = item; - this.triples = triples; + this.consumer = consumer; this.nodeMap = nodeMap; // default values @@ -87,8 +81,8 @@ private ObjectToRdf(JsonObject item, List triples, NodeMap nodeMap) { this.uriValidation = JsonLdOptions.DEFAULT_URI_VALIDATION; } - public static final ObjectToRdf with(JsonObject item, List triples, NodeMap nodeMap) { - return new ObjectToRdf(item, triples, nodeMap); + public static final ObjectToRdf with(JsonObject item, RdfConsumer consumer, NodeMap nodeMap) { + return new ObjectToRdf(item, consumer, nodeMap); } public ObjectToRdf rdfDirection(RdfDirection rdfDirection) { @@ -96,7 +90,11 @@ public ObjectToRdf rdfDirection(RdfDirection rdfDirection) { return this; } - public Optional build() throws JsonLdError { + public void produce( + final String subject, + final boolean blankSubject, + final String predicate, + final boolean blankPredicate) throws JsonLdError { // 1. - 2. if (NodeObject.isNodeObject(item)) { @@ -104,33 +102,33 @@ public Optional build() throws JsonLdError { JsonValue id = item.get(Keywords.ID); if (JsonUtils.isNotString(id) || JsonUtils.isNull(id)) { - return Optional.empty(); + return; } String idString = ((JsonString) id).getString(); if (BlankNode.isWellFormed(idString)) { - return Optional.of(Rdf.createBlankNode(idString)); + consumer.accept(subject, blankSubject, predicate, blankPredicate, idString, true); } else if (UriUtils.isAbsoluteUri(idString, uriValidation)) { - return Optional.of(Rdf.createIRI(idString)); + consumer.accept(subject, blankSubject, predicate, blankPredicate, idString, false); } - return Optional.empty(); + return; } // 3. if (ListObject.isListObject(item)) { - return Optional.of(ListToRdf - .with(item.get(Keywords.LIST).asJsonArray(), triples, nodeMap) + ListToRdf + .with(item.get(Keywords.LIST).asJsonArray(), consumer, nodeMap) .rdfDirection(rdfDirection) .uriValidation(uriValidation) - .build()); + .process(subject, blankSubject, predicate, blankPredicate); } // 4. if (!ValueObject.isValueObject(item)) { - return Optional.empty(); + return; } final JsonValue value = item.get(Keywords.VALUE); @@ -143,7 +141,7 @@ public Optional build() throws JsonLdError { // 6. if (datatype != null && !Keywords.JSON.equals(datatype) && !UriUtils.isAbsoluteUri(datatype, uriValidation)) { LOGGER.log(Level.WARNING, "Datatype [{0}] is not an absolute IRI nor @json and value is skipped.", datatype); - return Optional.empty(); + return; } // 7. @@ -151,7 +149,7 @@ public Optional build() throws JsonLdError { && (JsonUtils.isNotString(item.get(Keywords.LANGUAGE)) || !LanguageTag.isWellFormed(item.getString(Keywords.LANGUAGE)))) { LOGGER.log(Level.WARNING, "Language tag [{0}] is not well formed string and value is skipped.", item.get(Keywords.LANGUAGE)); - return Optional.empty(); + return; } String valueString = null; @@ -217,14 +215,12 @@ public Optional build() throws JsonLdError { if (valueString == null) { if (JsonUtils.isNotString(value)) { - return Optional.empty(); + return; } valueString = ((JsonString) value).getString(); } - RdfLiteral rdfLiteral = null; - // 13. if (item.containsKey(Keywords.DIRECTION) && rdfDirection != null) { @@ -239,67 +235,73 @@ public Optional build() throws JsonLdError { .concat("_") .concat(item.getString(Keywords.DIRECTION)); - rdfLiteral = Rdf.createTypedString(valueString, datatype); + consumer.accept( + subject, blankSubject, + predicate, blankPredicate, + valueString, datatype, null); // 13.3. } else if (RdfDirection.COMPOUND_LITERAL == rdfDirection) { final String blankNodeId = nodeMap.createIdentifier(); - // 13.3.1. - final RdfResource subject = Rdf.createBlankNode(blankNodeId); - // 13.3.2. - triples.add(Rdf.createTriple( - subject, - Rdf.createIRI(RdfConstants.VALUE), - Rdf.createString(valueString))); + consumer.accept( + blankNodeId, + true, + RdfConstants.VALUE, + false, + valueString, + XsdConstants.STRING, + null); // 13.3.3. if (item.containsKey(Keywords.LANGUAGE) && JsonUtils.isString(item.get(Keywords.LANGUAGE))) { - triples.add(Rdf.createTriple( - subject, - Rdf.createIRI(RdfConstants.LANGUAGE), - Rdf.createString(item.getString(Keywords.LANGUAGE).toLowerCase()))); + consumer.accept( + blankNodeId, + true, + RdfConstants.LANGUAGE, + false, + item.getString(Keywords.LANGUAGE).toLowerCase(), + XsdConstants.STRING, + null); } // 13.3.4. - triples.add(Rdf.createTriple( - subject, - Rdf.createIRI(RdfConstants.DIRECTION), - Rdf.createString(item.getString(Keywords.DIRECTION)))); - - return Optional.of(Rdf.createBlankNode(blankNodeId)); + consumer.accept( + blankNodeId, + true, + RdfConstants.DIRECTION, + false, + item.getString(Keywords.DIRECTION), + XsdConstants.STRING, + null); + + consumer.accept(subject, blankSubject, predicate, blankPredicate, blankNodeId, true); + return; } // 14. } else { if (item.containsKey(Keywords.LANGUAGE) && JsonUtils.isString(item.get(Keywords.LANGUAGE))) { - - rdfLiteral = Rdf.createLangString(valueString, item.getString(Keywords.LANGUAGE)); + consumer.accept( + subject, blankSubject, + predicate, blankPredicate, + valueString, RdfConstants.LANG_STRING, item.getString(Keywords.LANGUAGE)); } else { - rdfLiteral = Rdf.createTypedString(valueString, datatype); + consumer.accept( + subject, blankSubject, + predicate, blankPredicate, + valueString, datatype, null); } } - - // 15. - return Optional.ofNullable(rdfLiteral); } private static final String toXsdDouble(BigDecimal bigDecimal) { return xsdNumberFormat.format(bigDecimal); } - /** - * @deprecated since 1.5.0, use - * Object#uriValidation(com.apicatalog.jsonld.uri.UriValidationPolicy) - */ - @Deprecated - public ObjectToRdf uriValidation(boolean enabled) { - return uriValidation(enabled ? UriValidationPolicy.Full : UriValidationPolicy.SchemeOnly); - } - public ObjectToRdf uriValidation(UriValidationPolicy uriValidation) { this.uriValidation = uriValidation; return this; diff --git a/src/main/java/com/apicatalog/jsonld/processor/ToRdfProcessor.java b/src/main/java/com/apicatalog/jsonld/processor/ToRdfProcessor.java index 863b9d8a..855dccca 100644 --- a/src/main/java/com/apicatalog/jsonld/processor/ToRdfProcessor.java +++ b/src/main/java/com/apicatalog/jsonld/processor/ToRdfProcessor.java @@ -25,14 +25,16 @@ import com.apicatalog.jsonld.flattening.NodeMap; import com.apicatalog.jsonld.flattening.NodeMapBuilder; import com.apicatalog.jsonld.loader.DocumentLoaderOptions; -import com.apicatalog.rdf.Rdf; +import com.apicatalog.rdf.RdfConsumer; import com.apicatalog.rdf.RdfDataset; +import com.apicatalog.rdf.RdfDatasetConsumer; import jakarta.json.JsonArray; /** * - * @see JsonLdProcessor.toRdf() + * @see JsonLdProcessor.toRdf() * */ public final class ToRdfProcessor { @@ -40,8 +42,16 @@ public final class ToRdfProcessor { private ToRdfProcessor() { } + @Deprecated public static final RdfDataset toRdf(final URI input, final JsonLdOptions options) throws JsonLdError { + final RdfDatasetConsumer consumer = new RdfDatasetConsumer(); + toRdf(consumer, input, options); + return consumer.dataset(); + + } + + public static final void toRdf(final RdfConsumer consumer, final URI input, final JsonLdOptions options) throws JsonLdError { if (options.getDocumentLoader() == null) { throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, "Document loader is null. Cannot fetch [" + input + "]."); } @@ -55,11 +65,17 @@ public static final RdfDataset toRdf(final URI input, final JsonLdOptions option throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED); } - return toRdf(remoteDocument, options); + toRdf(consumer, remoteDocument, options); } + @Deprecated public static final RdfDataset toRdf(Document input, final JsonLdOptions options) throws JsonLdError { + final RdfDatasetConsumer consumer = new RdfDatasetConsumer(); + toRdf(consumer, input, options); + return consumer.dataset(); + } + public static final void toRdf(RdfConsumer consumer, Document input, final JsonLdOptions options) throws JsonLdError { final JsonLdOptions expansionOptions = new JsonLdOptions(options); expansionOptions.setProcessingMode(options.getProcessingMode()); @@ -68,14 +84,11 @@ public static final RdfDataset toRdf(Document input, final JsonLdOptions options final JsonArray expandedInput = ExpansionProcessor.expand(input, expansionOptions, false); - return JsonLdToRdf - .with( - NodeMapBuilder.with(expandedInput, new NodeMap()).build(), - Rdf.createDataset() - ) - .produceGeneralizedRdf(options.isProduceGeneralizedRdf()) - .rdfDirection(options.getRdfDirection()) - .uriValidation(options.getUriValidation()) - .build(); + JsonLdToRdf + .with(NodeMapBuilder.with(expandedInput, new NodeMap()).build()) + .produceGeneralizedRdf(options.isProduceGeneralizedRdf()) + .rdfDirection(options.getRdfDirection()) + .uriValidation(options.getUriValidation()) + .process(consumer); } } diff --git a/src/main/java/com/apicatalog/rdf/RdfConsumer.java b/src/main/java/com/apicatalog/rdf/RdfConsumer.java new file mode 100644 index 00000000..264ef806 --- /dev/null +++ b/src/main/java/com/apicatalog/rdf/RdfConsumer.java @@ -0,0 +1,70 @@ +package com.apicatalog.rdf; + +/** + * RDF dataset consumer interface designed for + * speed, streamlined processing, and efficiency. + * + * This interface minimizes unnecessary object instantiation + * and facilitates seamless integration with third-party libraries, + * enabling efficient RDF data processing. + */ +public interface RdfConsumer { + + /** + * Sets the default graph as the active scope. + * Invoked when triples belong to the unnamed, default graph. + */ + void defaultGraph(); + + /** + * Sets a named graph as the active scope. + * Ensures that subsequent triples are associated with the specified graph. + * + * @param graph The name of the graph (IRI or blank node identifier). + * @param blankGraph {@code true} if the graph name is a blank node identifier. + */ + void namedGraph(String graph, boolean blankGraph); + + /** + * Accepts a new RDF triple where the object is an IRI or a blank node. + * The triple is processed within the currently active graph scope. + * + * @param subject The subject of the triple (IRI or blank node identifier). + * @param blankSubject {@code true} if the subject is a blank node identifier. + * @param predicate The predicate of the triple (IRI or blank node identifier). + * @param blankPredicate {@code true} if the predicate is a blank node identifier. + * @param object The object of the triple (IRI or blank node identifier). + * @param blankObject {@code true} if the object is a blank node identifier. + */ + void accept( + String subject, + boolean blankSubject, + String predicate, + boolean blankPredicate, + String object, + boolean blankObject + ); + + /** + * Accepts a new RDF triple where the object is a literal value. + * The triple is processed within the currently active graph scope. + * Optimized for efficient handling of typed and language-tagged literals. + * + * @param subject The subject of the triple (IRI or blank node identifier). + * @param blankSubject {@code true} if the subject is a blank node identifier. + * @param predicate The predicate of the triple (IRI or blank node identifier). + * @param blankPredicate {@code true} if the predicate is a blank node identifier. + * @param literal The literal value of the object. + * @param datatype The datatype IRI of the literal, never {@code null}. + * @param language The language tag of the literal (optional, may be {@code null}). + */ + void accept( + String subject, + boolean blankSubject, + String predicate, + boolean blankPredicate, + String literal, + String datatype, + String language + ); +} diff --git a/src/main/java/com/apicatalog/rdf/RdfDatasetConsumer.java b/src/main/java/com/apicatalog/rdf/RdfDatasetConsumer.java new file mode 100644 index 00000000..81d72004 --- /dev/null +++ b/src/main/java/com/apicatalog/rdf/RdfDatasetConsumer.java @@ -0,0 +1,57 @@ +package com.apicatalog.rdf; + +public class RdfDatasetConsumer implements RdfConsumer { + + protected final RdfDataset dataset; + + protected RdfResource graphName; + + public RdfDatasetConsumer() { + this(Rdf.createDataset()); + } + + public RdfDatasetConsumer(RdfDataset dataset) { + this.dataset = dataset; + this.graphName = null; + } + + public RdfDataset dataset() { + return dataset; + } + + protected static final RdfResource createResource(String name, boolean blank) { + return blank ? Rdf.createBlankNode(name) : Rdf.createIRI(name); + } + + @Override + public void namedGraph(String graph, boolean blankGraph) { + this.graphName = createResource(graph, blankGraph); + } + + @Override + public void defaultGraph() { + this.graphName = null; + } + + @Override + public void accept(String subject, boolean blankSubject, String predicate, boolean blankPredicate, String literal, String datatype, String language) { + dataset.add( + Rdf.createNQuad( + createResource(subject, blankSubject), + createResource(predicate, blankPredicate), + language != null + ? Rdf.createLangString(literal, language) + : Rdf.createTypedString(literal, datatype), + graphName)); + } + + @Override + public void accept(String subject, boolean blankSubject, String predicate, boolean blankPredicate, String object, boolean blankObject) { + dataset.add( + Rdf.createNQuad( + createResource(subject, blankSubject), + createResource(predicate, blankPredicate), + createResource(object, blankObject), + graphName)); + } +}