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 org.apache.commons.rdf.api.*;
21  import org.apache.commons.rdf.simple.SimpleRDF.SimpleRDFTerm;
22  
23  import java.util.HashSet;
24  import java.util.Set;
25  import java.util.function.Predicate;
26  import java.util.stream.Collectors;
27  import java.util.stream.Stream;
28  
29  /**
30   * A simple, memory-based implementation of Graph.
31   * <p>
32   * {@link Triple}s in the graph are kept in a {@link Set}.
33   * <p>
34   * All Stream operations are performed using parallel and unordered directives.
35   */
36  final class GraphImpl implements Graph {
37  
38      private static final int TO_STRING_MAX = 10;
39      private final Set<Triple> triples = new HashSet<>();
40      private final SimpleRDF factory;
41  
42      GraphImpl(final SimpleRDF simpleRDF) {
43          this.factory = simpleRDF;
44      }
45  
46      @Override
47      public void add(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
48          final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(subject);
49          final IRI newPredicate = (IRI) internallyMap(predicate);
50          final RDFTerm newObject = internallyMap(object);
51          final Triple result = factory.createTriple(newSubject, newPredicate, newObject);
52          triples.add(result);
53      }
54  
55      @Override
56      public void add(final Triple triple) {
57          triples.add(internallyMap(triple));
58      }
59  
60      private <T extends RDFTerm> RDFTerm internallyMap(final T object) {
61          if (object == null || object instanceof SimpleRDFTerm) {
62              // No need to re-map our own objects.
63              // We support null as internallyMap() is also used by the filters,
64              // and the
65              // factory constructors later do null checks
66              return object;
67          }
68          if (object instanceof BlankNode) {
69              final BlankNode blankNode = (BlankNode) object;
70              // This guarantees that adding the same BlankNode multiple times to
71              // this graph will generate a local object that is mapped to an
72              // equivalent object, based on the code in the package private
73              // BlankNodeImpl class
74              return factory.createBlankNode(blankNode.uniqueReference());
75          } else if (object instanceof IRI) {
76              final IRI iri = (IRI) object;
77              return factory.createIRI(iri.getIRIString());
78          } else if (object instanceof Literal) {
79              final Literal literal = (Literal) object;
80              if (literal.getLanguageTag().isPresent()) {
81                  return factory.createLiteral(literal.getLexicalForm(), literal.getLanguageTag().get());
82              } else {
83                  return factory.createLiteral(literal.getLexicalForm(), (IRI) internallyMap(literal.getDatatype()));
84              }
85          } else {
86              throw new IllegalArgumentException("RDFTerm was neither a BlankNode, IRI nor Literal: " + object);
87          }
88      }
89  
90      private Triple internallyMap(final Triple triple) {
91          final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(triple.getSubject());
92          final IRI newPredicate = (IRI) internallyMap(triple.getPredicate());
93          final RDFTerm newObject = internallyMap(triple.getObject());
94          // Check if any of the object references changed during the mapping, to
95          // avoid creating a new Triple object if possible
96          if (newSubject == triple.getSubject() && newPredicate == triple.getPredicate()
97                  && newObject == triple.getObject()) {
98              return triple;
99          } else {
100             return factory.createTriple(newSubject, newPredicate, newObject);
101         }
102     }
103 
104     @Override
105     public void clear() {
106         triples.clear();
107     }
108 
109     @Override
110     public boolean contains(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
111         return stream(subject, predicate, object).findFirst().isPresent();
112     }
113 
114     @Override
115     public boolean contains(final Triple triple) {
116         return triples.contains(internallyMap(triple));
117     }
118 
119     @Override
120     public Stream<Triple> stream() {
121         return triples.parallelStream().unordered();
122     }
123 
124     @Override
125     public Stream<Triple> stream(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
126         final BlankNodeOrIRI newSubject = (BlankNodeOrIRI) internallyMap(subject);
127         final IRI newPredicate = (IRI) internallyMap(predicate);
128         final RDFTerm newObject = internallyMap(object);
129 
130         return getTriples(t -> {
131             // Lacking the requirement for .equals() we have to be silly
132             // and test ntriples string equivalance
133             if (subject != null && !t.getSubject().equals(newSubject)) {
134                 return false;
135             }
136             if (predicate != null && !t.getPredicate().equals(newPredicate)) {
137                 return false;
138             }
139             if (object != null && !t.getObject().equals(newObject)) {
140                 return false;
141             }
142             return true;
143         });
144     }
145 
146     private Stream<Triple> getTriples(final Predicate<Triple> filter) {
147         return stream().filter(filter);
148     }
149 
150     @Override
151     public void remove(final BlankNodeOrIRI subject, final IRI predicate, final RDFTerm object) {
152         final Stream<Triple> toRemove = stream(subject, predicate, object);
153         for (final Triple t : toRemove.collect(Collectors.toList())) {
154             // Avoid ConcurrentModificationException in ArrayList
155             remove(t);
156         }
157     }
158 
159     @Override
160     public void remove(final Triple triple) {
161         triples.remove(internallyMap(triple));
162     }
163 
164     @Override
165     public long size() {
166         return triples.size();
167     }
168 
169     @Override
170     public String toString() {
171         final String s = stream().limit(TO_STRING_MAX).map(Object::toString).collect(Collectors.joining("\n"));
172         if (size() > TO_STRING_MAX) {
173             return s + "\n# ... +" + (size() - TO_STRING_MAX) + " more";
174         } else {
175             return s;
176         }
177     }
178 
179 }