001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.commons.rdf.jsonldjava;
019
020import java.nio.charset.StandardCharsets;
021import java.util.Objects;
022import java.util.UUID;
023
024import org.apache.commons.rdf.api.BlankNode;
025import org.apache.commons.rdf.api.BlankNodeOrIRI;
026import org.apache.commons.rdf.api.Dataset;
027import org.apache.commons.rdf.api.Graph;
028import org.apache.commons.rdf.api.IRI;
029import org.apache.commons.rdf.api.Literal;
030import org.apache.commons.rdf.api.RDFTerm;
031import org.apache.commons.rdf.api.RDF;
032import org.apache.commons.rdf.api.Triple;
033import org.apache.commons.rdf.simple.Types;
034
035import com.github.jsonldjava.core.RDFDataset;
036import com.github.jsonldjava.core.RDFDataset.Node;
037
038/**
039 * JSON-LD Java RDF implementation.
040 */
041public final class JsonLdRDF implements RDF {
042
043    final String bnodePrefix;
044
045    public JsonLdRDF() {
046        // An "outside Graph" bnodePrefix
047        this("urn:uuid:" + UUID.randomUUID() + "#b");
048    }
049
050    JsonLdRDF(final String bnodePrefix) {
051        this.bnodePrefix = Objects.requireNonNull(bnodePrefix);
052    }
053
054    /**
055     * Adapt a JsonLd {@link RDFDataset} as a Commons RDF {@link Dataset}.
056     * <p>
057     * Changes to the Commons RDF {@link Dataset} are reflected in the JsonLd
058     * {@link RDFDataset} and vice versa.
059     *
060     * @see #asGraph(RDFDataset)
061     * @param rdfDataSet
062     *            JsonLd {@link RDFDataset} to adapt
063     * @return Adapted {@link Dataset}
064     */
065    public JsonLdDataset asDataset(final RDFDataset rdfDataSet) {
066        return new JsonLdDatasetImpl(rdfDataSet);
067    }
068
069    /**
070     * Adapt a JsonLd {@link RDFDataset} as a Commons RDF {@link Graph}.
071     * <p>
072     * Only triples in the <em>default graph</em> are included. To retrieve any
073     * other graph, {@link #asDataset(RDFDataset)} together with
074     * {@link Dataset#getGraph(BlankNodeOrIRI)}.
075     * <p>
076     * Changes to the Commons RDF {@link Graph} are reflected in the JsonLd
077     * {@link RDFDataset} and vice versa.
078     *
079     * @see #asDataset(RDFDataset)
080     * @see #asUnionGraph(RDFDataset)
081     * @param rdfDataSet
082     *            JsonLd {@link RDFDataset} to adapt
083     * @return Adapted {@link Graph} covering the <em>default graph</em>
084     */
085    public JsonLdGraph asGraph(final RDFDataset rdfDataSet) {
086        return new JsonLdGraphImpl(rdfDataSet);
087    }
088
089    public Node asJsonLdNode(final RDFTerm term) {
090        if (term instanceof JsonLdBlankNode) {
091            final JsonLdBlankNode jsonLdBlankNode = (JsonLdBlankNode) term;
092            if (jsonLdBlankNode.uniqueReference().startsWith(bnodePrefix)) {
093                // Only return blank nodes 'as is' if they have the same prefix
094                return jsonLdBlankNode.asJsonLdNode();
095            }
096        } else if (term instanceof JsonLdTerm) {
097            // non-Bnodes can always be return as-is
098            return ((JsonLdTerm) term).asJsonLdNode();
099        }
100        if (term instanceof IRI) {
101            return new RDFDataset.IRI(((IRI) term).getIRIString());
102        }
103        if (term instanceof BlankNode) {
104            final String ref = ((BlankNode) term).uniqueReference();
105            if (ref.startsWith(bnodePrefix)) {
106                // one of our own (but no longer a JsonLdBlankNode),
107                // we can recover the label after our unique prefix
108                return new RDFDataset.BlankNode(ref.replace(bnodePrefix, ""));
109            }
110            // The "foreign" unique reference might not be a valid bnode string,
111            // we'll convert to a UUID
112            final UUID uuid = UUID.nameUUIDFromBytes(ref.getBytes(StandardCharsets.UTF_8));
113            return new RDFDataset.BlankNode("_:" + uuid);
114        }
115        if (term instanceof Literal) {
116            final Literal literal = (Literal) term;
117            return new RDFDataset.Literal(literal.getLexicalForm(), literal.getDatatype().getIRIString(),
118                    literal.getLanguageTag().orElse(null));
119        }
120        throw new IllegalArgumentException("RDFTerm not instanceof IRI, BlankNode or Literal: " + term);
121    }
122
123    /**
124     * Adapt a Commons RDF {@link org.apache.commons.rdf.api.Quad} as a JsonLd
125     * {@link com.github.jsonldjava.core.RDFDataset.Quad}.
126     *
127     * @param quad
128     *            Commons RDF {@link org.apache.commons.rdf.api.Quad} to adapt
129     * @return Adapted JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad}
130     */
131    public RDFDataset.Quad asJsonLdQuad(final org.apache.commons.rdf.api.Quad quad) {
132        final BlankNodeOrIRI g = quad.getGraphName().orElse(null);
133        return createJsonLdQuad(g, quad.getSubject(), quad.getPredicate(), quad.getObject());
134    }
135
136    /**
137     * Adapt a Commons RDF {@link Triple} as a JsonLd
138     * {@link com.github.jsonldjava.core.RDFDataset.Quad}.
139     *
140     * @param triple
141     *            Commons RDF {@link Triple} to adapt
142     * @return Adapted JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad}
143     */
144    public RDFDataset.Quad asJsonLdQuad(final Triple triple) {
145        return createJsonLdQuad(null, triple.getSubject(), triple.getPredicate(), triple.getObject());
146    }
147
148    /**
149     * Adapt a JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} as a
150     * Commons RDF {@link org.apache.commons.rdf.api.Quad}.
151     * <p>
152     * The underlying JsonLd quad can be retrieved with
153     * {@link JsonLdQuad#asJsonLdQuad()}.
154     *
155     * @param quad
156     *            A JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} to
157     *            adapt
158     * @return Adapted {@link JsonLdQuad}
159     */
160    public JsonLdQuad asQuad(final RDFDataset.Quad quad) {
161        return new JsonLdQuadImpl(quad, bnodePrefix);
162    }
163
164    /**
165     * Adapt a JsonLd {@link Node} as a Commons RDF {@link RDFTerm}.
166     * <p>
167     * The underlying node can be retrieved with
168     * {@link JsonLdTerm#asJsonLdNode()}.
169     *
170     * @param node
171     *            A JsonLd {@link Node} to adapt
172     * @return Adapted {@link JsonLdTerm}
173     */
174    public JsonLdTerm asRDFTerm(final Node node) {
175        return asRDFTerm(node, bnodePrefix);
176    }
177
178    /**
179     * Adapt a JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} as a
180     * Commons RDF {@link org.apache.commons.rdf.api.Triple}.
181     * <p>
182     * The underlying JsonLd quad can be retrieved with
183     * {@link JsonLdTriple#asJsonLdQuad()}.
184     *
185     * @param quad
186     *            A JsonLd {@link com.github.jsonldjava.core.RDFDataset.Quad} to
187     *            adapt
188     * @return Adapted {@link JsonLdTriple}
189     */
190    public JsonLdTriple asTriple(final RDFDataset.Quad quad) {
191        return new JsonLdTripleImpl(quad, bnodePrefix);
192    }
193
194    /**
195     * Adapt a JsonLd {@link RDFDataset} as a Commons RDF {@link Graph}.
196     * <p>
197     * The graph can be seen as a <em>union graph</em> as it will contains all
198     * the triples across all the graphs of the underlying {@link RDFDataset}.
199     * <p>
200     * Note that some triple operations on a union graph can be inefficient as
201     * they need to remove any duplicate triples across the graphs.
202     * <p>
203     * Changes to the Commons RDF {@link Graph} are reflected in the JsonLd
204     * {@link RDFDataset} and vice versa. Triples removed from the graph are
205     * removed from <strong>all</strong> graphs, while triples added are added
206     * to the <em>default graph</em>.
207     *
208     * @param rdfDataSet
209     *            JsonLd {@link RDFDataset} to adapt
210     * @return Adapted {@link Dataset}
211     */
212    public JsonLdUnionGraph asUnionGraph(final RDFDataset rdfDataSet) {
213        return new JsonLdUnionGraphImpl(rdfDataSet);
214    }
215
216    @Override
217    public JsonLdBlankNode createBlankNode() {
218        final String id = "_:" + UUID.randomUUID().toString();
219        return new JsonLdBlankNodeImpl(new RDFDataset.BlankNode(id), bnodePrefix);
220    }
221
222    @Override
223    public JsonLdBlankNode createBlankNode(final String name) {
224        final String id = "_:" + name;
225        // TODO: Check if name is valid JSON-LD BlankNode identifier
226        return new JsonLdBlankNodeImpl(new RDFDataset.BlankNode(id), bnodePrefix);
227    }
228
229    @Override
230    public JsonLdDataset createDataset() {
231        return new JsonLdDatasetImpl(bnodePrefix);
232    }
233
234    @Override
235    public JsonLdGraph createGraph() {
236        return new JsonLdGraphImpl(bnodePrefix);
237    }
238
239    @Override
240    public JsonLdIRI createIRI(final String iri) {
241        return new JsonLdIRIImpl(iri);
242    }
243
244    @Override
245    public JsonLdLiteral createLiteral(final String literal) {
246        return new JsonLdLiteralImpl(new RDFDataset.Literal(literal, null, null));
247    }
248
249    @Override
250    public JsonLdLiteral createLiteral(final String literal, final IRI dataType) {
251        return new JsonLdLiteralImpl(new RDFDataset.Literal(literal, dataType.getIRIString(), null));
252    }
253
254    @Override
255    public JsonLdLiteral createLiteral(final String literal, final String language) {
256        return new JsonLdLiteralImpl(new RDFDataset.Literal(literal, Types.RDF_LANGSTRING.getIRIString(), language));
257    }
258
259    @Override
260    public JsonLdQuad createQuad(final BlankNodeOrIRI graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object)
261            throws IllegalArgumentException, UnsupportedOperationException {
262        return new JsonLdQuadImpl(createJsonLdQuad(graphName, subject, predicate, object), bnodePrefix);
263    }
264
265    @Override
266    public JsonLdTriple createTriple(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
267        return new JsonLdTripleImpl(createJsonLdQuad(null, subject, predicate, object), bnodePrefix);
268    }
269
270    String asJsonLdString(final BlankNodeOrIRI blankNodeOrIRI) {
271        if (blankNodeOrIRI == null) {
272            return null;
273        }
274        if (blankNodeOrIRI instanceof IRI) {
275            return ((IRI) blankNodeOrIRI).getIRIString();
276        } else if (blankNodeOrIRI instanceof BlankNode) {
277            final BlankNode blankNode = (BlankNode) blankNodeOrIRI;
278            final String ref = blankNode.uniqueReference();
279            if (ref.startsWith(bnodePrefix)) {
280                // One of ours (but possibly not a JsonLdBlankNode) -
281                // we can use the suffix directly
282                return ref.replace(bnodePrefix, "");
283            }
284            // Map to unique bnode identifier, e.g.
285            // _:0dbd92ee-ab1a-45e7-bba2-7ade54f87ec5
286            final UUID uuid = UUID.nameUUIDFromBytes(ref.getBytes(StandardCharsets.UTF_8));
287            return "_:" + uuid;
288        } else {
289            throw new IllegalArgumentException("Expected a BlankNode or IRI, not: " + blankNodeOrIRI);
290        }
291    }
292
293    JsonLdTerm asRDFTerm(final Node node, final String blankNodePrefix) {
294        if (node == null) {
295            return null; // e.g. default graph
296        }
297        if (node.isIRI()) {
298            return new JsonLdIRIImpl(node);
299        } else if (node.isBlankNode()) {
300            return new JsonLdBlankNodeImpl(node, blankNodePrefix);
301        } else if (node.isLiteral()) {
302            // TODO: Our own JsonLdLiteral
303            if (node.getLanguage() != null) {
304                return createLiteral(node.getValue(), node.getLanguage());
305            }
306            return createLiteral(node.getValue(), createIRI(node.getDatatype()));
307        } else {
308            throw new IllegalArgumentException("Node is neither IRI, BlankNode nor Literal: " + node);
309        }
310    }
311
312    RDFDataset.Quad createJsonLdQuad(final BlankNodeOrIRI graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
313        return new RDFDataset.Quad(asJsonLdNode(subject), asJsonLdNode(predicate), asJsonLdNode(object),
314                asJsonLdString(graphName));
315    }
316
317}