View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.commons.rdf.simple;
19  
20  import java.util.HashSet;
21  import java.util.Objects;
22  import java.util.Optional;
23  import java.util.Set;
24  import java.util.function.Predicate;
25  import java.util.stream.Collectors;
26  import java.util.stream.Stream;
27  
28  import org.apache.commons.rdf.api.BlankNode;
29  import org.apache.commons.rdf.api.BlankNodeOrIRI;
30  import org.apache.commons.rdf.api.Dataset;
31  import org.apache.commons.rdf.api.Graph;
32  import org.apache.commons.rdf.api.IRI;
33  import org.apache.commons.rdf.api.Literal;
34  import org.apache.commons.rdf.api.Quad;
35  import org.apache.commons.rdf.api.RDFTerm;
36  import org.apache.commons.rdf.simple.SimpleRDF.SimpleRDFTerm;
37  
38  /**
39   * A simple, memory-based implementation of Dataset.
40   * <p>
41   * {@link Quad}s in the graph are kept in a {@link Set}.
42   * <p>
43   * All Stream operations are performed using parallel and unordered directives.
44   */
45  final class DatasetImpl implements Dataset {
46  
47      private static final int TO_STRING_MAX = 10;
48      private final Set<Quad> quads = new HashSet<>();
49      private final SimpleRDF factory;
50  
51      DatasetImpl(final SimpleRDF simpleRDF) {
52          this.factory = simpleRDF;
53      }
54  
55      @Override
56      public void add(final BlankNodeOrIRI graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
57          final BlankNodeOrIRI newGraphName = (BlankNodeOrIRI) internallyMap(graphName);
58          final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(subject);
59          final IRI newPredicate = (IRI) internallyMap(predicate);
60          final RDFTerm newObject = internallyMap(object);
61          final Quad result = factory.createQuad(newGraphName, newSubject, newPredicate, newObject);
62          quads.add(result);
63      }
64  
65      @Override
66      public void add(final Quad quad) {
67          final BlankNodeOrIRI newGraph = (BlankNodeOrIRI) internallyMap(quad.getGraphName().orElse(null));
68          final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(quad.getSubject());
69          final IRI newPredicate = (IRI) internallyMap(quad.getPredicate());
70          final RDFTerm newObject = internallyMap(quad.getObject());
71          // Check if any of the object references changed during the mapping, to
72          // avoid creating a new Quad object if possible
73          if (newGraph == quad.getGraphName().orElse(null) && newSubject == quad.getSubject()
74                  && newPredicate == quad.getPredicate() && newObject == quad.getObject()) {
75              quads.add(quad);
76          } else {
77              // Make a new Quad with our mapped instances
78              final Quad result = factory.createQuad(newGraph, newSubject, newPredicate, newObject);
79              quads.add(result);
80          }
81      }
82  
83      private <T extends RDFTerm> RDFTerm internallyMap(final T object) {
84          if (object == null || object instanceof SimpleRDFTerm) {
85              return object;
86          }
87          if (object instanceof BlankNode && !(object instanceof BlankNodeImpl)) {
88              final BlankNode blankNode = (BlankNode) object;
89              // This guarantees that adding the same BlankNode multiple times to
90              // this graph will generate a local object that is mapped to an
91              // equivalent object, based on the code in the package private
92              // BlankNodeImpl class
93              return factory.createBlankNode(blankNode.uniqueReference());
94          } else if (object instanceof IRI && !(object instanceof IRIImpl)) {
95              final IRI iri = (IRI) object;
96              return factory.createIRI(iri.getIRIString());
97          } else if (object instanceof Literal && !(object instanceof LiteralImpl)) {
98              final Literal literal = (Literal) object;
99              if (literal.getLanguageTag().isPresent()) {
100                 return factory.createLiteral(literal.getLexicalForm(), literal.getLanguageTag().get());
101             } else {
102                 return factory.createLiteral(literal.getLexicalForm(), (IRI) internallyMap(literal.getDatatype()));
103             }
104         } else {
105             throw new IllegalArgumentException("Not a BlankNode, IRI or Literal: " + object);
106         }
107     }
108 
109     @Override
110     public void clear() {
111         quads.clear();
112     }
113 
114     @Override
115     public boolean contains(final Optional<BlankNodeOrIRI> graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
116         return stream(graphName, subject, predicate, object).findAny().isPresent();
117     }
118 
119     @Override
120     public boolean contains(final Quad quad) {
121         return quads.contains(Objects.requireNonNull(quad));
122     }
123 
124     @Override
125     public Stream<Quad> stream() {
126         return quads.parallelStream().unordered();
127     }
128 
129     @Override
130     public Stream<Quad> stream(final Optional<BlankNodeOrIRI> graphName, final BlankNodeOrIRI subject, final IRI predicate,
131             final RDFTerm object) {
132         final Optional<BlankNodeOrIRI> newGraphName;
133         if (graphName == null) { 
134             // Avoid Optional<Optional<BlankNodeOrIRI>> ...
135             newGraphName = null;
136         } else {
137             newGraphName = graphName.map(g -> (BlankNodeOrIRI) internallyMap(g));
138         }
139         final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(subject);
140         final IRI newPredicate = (IRI) internallyMap(predicate);
141         final RDFTerm newObject = internallyMap(object);
142 
143         return getQuads(t -> {
144             if (newGraphName != null && !t.getGraphName().equals(newGraphName)) {
145                 // This would check Optional.empty() == Optional.empty()
146                 return false;
147             }
148             if (subject != null && !t.getSubject().equals(newSubject)) {
149                 return false;
150             }
151             if (predicate != null && !t.getPredicate().equals(newPredicate)) {
152                 return false;
153             }
154             if (object != null && !t.getObject().equals(newObject)) {
155                 return false;
156             }
157             return true;
158         });
159     }
160 
161     private Stream<Quad> getQuads(final Predicate<Quad> filter) {
162         return stream().filter(filter);
163     }
164 
165     @Override
166     public void remove(final Optional<BlankNodeOrIRI> graphName, final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
167         final Stream<Quad> toRemove = stream(graphName, subject, predicate, object);
168         for (final Quad t : toRemove.collect(Collectors.toList())) {
169             // Avoid ConcurrentModificationException in ArrayList
170             remove(t);
171         }
172     }
173 
174     @Override
175     public void remove(final Quad quad) {
176         quads.remove(Objects.requireNonNull(quad));
177     }
178 
179     @Override
180     public long size() {
181         return quads.size();
182     }
183 
184     @Override
185     public String toString() {
186         final String s = stream().limit(TO_STRING_MAX).map(Object::toString).collect(Collectors.joining("\n"));
187         if (size() > TO_STRING_MAX) {
188             return s + "\n# ... +" + (size() - TO_STRING_MAX) + " more";
189         } else {
190             return s;
191         }
192     }
193 
194     @Override
195     public void close() {
196     }
197 
198     @Override
199     public Graph getGraph() {
200         return getGraph(null).get();
201     }
202 
203     @Override
204     public Optional<Graph> getGraph(final BlankNodeOrIRI graphName) {
205         return Optional.of(new DatasetGraphView(this, graphName));
206     }
207 
208     @Override
209     public Stream<BlankNodeOrIRI> getGraphNames() {
210         // Not very efficient..
211         return stream().map(Quad::getGraphName).filter(Optional::isPresent).map(Optional::get).distinct();
212     }
213 
214 }