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.api;
19  
20  import static org.junit.Assert.*;
21  
22  import java.util.ArrayList;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.Set;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.stream.Collectors;
32  import java.util.stream.Stream;
33  
34  import org.junit.Assume;
35  import org.junit.Before;
36  import org.junit.Test;
37  
38  /**
39   * Test Dataset implementation
40   * <p>
41   * To add to your implementation's tests, create a subclass with a name ending
42   * in <code>Test</code> and provide {@link #createFactory()} which minimally
43   * must support {@link RDF#createDataset()} and {@link RDF#createIRI(String)}, but
44   * ideally support all operations.
45   * <p>
46   * This test uses try-with-resources blocks for calls to {@link Dataset#stream()}
47   * and {@link Dataset#iterate()}.
48   *
49   * @see Dataset
50   * @see RDF
51   */
52  public abstract class AbstractDatasetTest {
53  
54      protected RDF factory;
55      protected Dataset dataset;
56      protected IRI alice;
57      protected IRI bob;
58      protected IRI name;
59      protected IRI knows;
60      protected IRI member;
61      protected BlankNode bnode1;
62      protected BlankNode bnode2;
63      protected Literal aliceName;
64      protected Literal bobName;
65      protected Literal secretClubName;
66      protected Literal companyName;
67      protected Quad bobNameQuad;
68      private IRI isPrimaryTopicOf;
69      private IRI graph1;
70      private BlankNode graph2;
71  
72      /**
73       *
74       * This method must be overridden by the implementing test to provide a
75       * factory for the test to create {@link Dataset}, {@link IRI} etc.
76       *
77       * @return {@link RDF} instance to be tested.
78       */
79      protected abstract RDF createFactory();
80  
81      @Before
82      public void createDatasetAndAdd() {
83          factory = createFactory();
84          dataset = factory.createDataset();
85          assertEquals(0, dataset.size());
86  
87          graph1 = factory.createIRI("http://example.com/graph1");
88          graph2 = factory.createBlankNode();
89  
90          alice = factory.createIRI("http://example.com/alice");
91          bob = factory.createIRI("http://example.com/bob");
92          name = factory.createIRI("http://xmlns.com/foaf/0.1/name");
93          knows = factory.createIRI("http://xmlns.com/foaf/0.1/knows");
94          member = factory.createIRI("http://xmlns.com/foaf/0.1/member");
95          bnode1 = factory.createBlankNode("org1");
96          bnode2 = factory.createBlankNode("org2");
97  
98          secretClubName = factory.createLiteral("The Secret Club");
99          companyName = factory.createLiteral("A company");
100         aliceName = factory.createLiteral("Alice");
101         bobName = factory.createLiteral("Bob", "en-US");
102 
103         dataset.add(graph1, alice, name, aliceName);
104         dataset.add(graph1, alice, knows, bob);
105 
106         dataset.add(graph1, alice, member, bnode1);
107 
108         bobNameQuad = factory.createQuad(graph2, bob, name, bobName);
109         dataset.add(bobNameQuad);
110 
111         dataset.add(factory.createQuad(graph2, bob, member, bnode1));
112         dataset.add(factory.createQuad(graph2, bob, member, bnode2));
113         // NOTE: bnode1 used in both graph1 and graph2
114         dataset.add(graph1, bnode1, name, secretClubName);
115         dataset.add(graph2, bnode2, name, companyName);
116 
117         // default graph describes graph1 and graph2
118         isPrimaryTopicOf = factory.createIRI("http://xmlns.com/foaf/0.1/isPrimaryTopicOf");
119         dataset.add(null, alice, isPrimaryTopicOf, graph1);
120         dataset.add(null, bob, isPrimaryTopicOf, graph2);
121 
122 
123     }
124 
125     @Test
126     public void size() throws Exception {
127         assertEquals(10, dataset.size());
128     }
129 
130     @Test
131     public void iterate() throws Exception {
132         Assume.assumeTrue(dataset.size() > 0);
133         final List<Quad> quads = new ArrayList<>();
134         for (final Quad t : dataset.iterate()) {
135             quads.add(t);
136         }
137         assertEquals(dataset.size(), quads.size());
138 
139         //assertTrue(quads.contains(bobNameQuad));
140         // java.util.List won't do any BlankNode mapping, so
141         // instead bobNameQuad of let's check for an IRI-centric quad
142         final Quad q = factory.createQuad(graph1, alice, name, aliceName);
143         quads.contains(q);
144 
145         // aborted iteration
146         final Iterable<Quad> iterate = dataset.iterate();
147         final Iterator<Quad> it = iterate.iterator();
148 
149         assertTrue(it.hasNext());
150         it.next();
151         closeIterable(iterate);
152 
153         // second iteration - should start from fresh and
154         // get the same count
155         long count = 0;
156         final Iterable<Quad> iterable = dataset.iterate();
157         for (@SuppressWarnings("unused") final
158         Quad t : iterable) {
159             count++;
160         }
161         assertEquals(dataset.size(), count);
162 
163         // Pattern iteration which should cover multiple graphs.
164 
165         final Set<Quad> aliceQuads = new HashSet<>();
166         for (final Quad aliceQ : dataset.iterate(null, alice, null, null)) {
167             aliceQuads.add(aliceQ);
168         }
169         assertTrue(aliceQuads.contains(factory.createQuad(graph1, alice, name, aliceName)));
170         assertTrue(aliceQuads.contains(factory.createQuad(graph1, alice, knows, bob)));
171         // We can't test this by Quad equality, as bnode1 might become mapped by the
172         // dataset
173         //assertTrue(aliceQuads.contains(factory.createQuad(graph1, alice, member, bnode1)));
174         assertTrue(aliceQuads.contains(factory.createQuad(null, alice, isPrimaryTopicOf, graph1)));
175         assertEquals(4, aliceQuads.size());
176 
177         // Check the isPrimaryTopicOf statements in the default graph
178         int topics = 0;
179         for (final Quad topic : dataset.iterate(null, null, isPrimaryTopicOf, null)) {
180             topics++;
181             // COMMONSRDF-55: should not be <urn:x-arq:defaultgraph> or similar
182             assertFalse(topic.getGraphName().isPresent());
183         }
184         assertEquals(2, topics);
185     }
186 
187     @Test
188     public void streamDefaultGraphNameAlice() throws Exception {
189         // null below would match in ANY graph (including default graph)
190         final Optional<? extends Quad> aliceTopic = dataset.stream(null, alice, isPrimaryTopicOf, null).findAny();
191         assertTrue(aliceTopic.isPresent());
192         // COMMONSRDF-55: should not be <urn:x-arq:defaultgraph> or similar
193         assertNull(aliceTopic.get().getGraphName().orElse(null));
194         assertFalse(aliceTopic.get().getGraphName().isPresent());
195     }
196 
197 
198     @Test
199     public void streamDefaultGraphNameByPattern() throws Exception {
200         // Explicitly select in only the default graph Optional.empty()
201         final Optional<? extends Quad> aliceTopic = dataset.stream(Optional.empty(), null, null, null).findAny();
202         assertTrue(aliceTopic.isPresent());
203         // COMMONSRDF-55: should not be <urn:x-arq:defaultgraph> or similar
204         assertNull(aliceTopic.get().getGraphName().orElse(null));
205         assertFalse(aliceTopic.get().getGraphName().isPresent());
206     }
207 
208 
209     /**
210      * Special quad closing for RDF4J.
211      */
212     private void closeIterable(final Iterable<Quad> iterate) throws Exception {
213         if (iterate instanceof AutoCloseable) {
214             ((AutoCloseable) iterate).close();
215         }
216     }
217 
218     @Test
219     public void iterateFilter() throws Exception {
220         final List<RDFTerm> friends = new ArrayList<>();
221         final IRI alice = factory.createIRI("http://example.com/alice");
222         final IRI knows = factory.createIRI("http://xmlns.com/foaf/0.1/knows");
223         for (final Quad t : dataset.iterate(null, alice, knows, null)) {
224             friends.add(t.getObject());
225         }
226         assertEquals(1, friends.size());
227         assertEquals(bob, friends.get(0));
228 
229         // .. can we iterate over zero hits?
230         final Iterable<Quad> iterate = dataset.iterate(Optional.of(graph2), bob, knows, alice);
231         for (final Quad unexpected : iterate) {
232             fail("Unexpected quad " + unexpected);
233         }
234         // closeIterable(iterate);
235     }
236 
237     @Test
238     public void contains() throws Exception {
239         assertFalse(dataset.contains(null, bob, knows, alice)); // or so he claims..
240 
241         assertTrue(dataset.contains(Optional.of(graph1), alice, knows, bob));
242 
243         try (Stream<? extends Quad> stream = dataset.stream()) {
244             final Optional<? extends Quad> first = stream.skip(4).findFirst();
245             Assume.assumeTrue(first.isPresent());
246             final Quad existingQuad = first.get();
247             assertTrue(dataset.contains(existingQuad));
248         }
249 
250         final Quad nonExistingQuad = factory.createQuad(graph2, bob, knows, alice);
251         assertFalse(dataset.contains(nonExistingQuad));
252 
253         // An existing quad
254         final Quad quad = factory.createQuad(graph1, alice, knows, bob);
255         // FIXME: Should not this always be true?
256          assertTrue(dataset.contains(quad));
257     }
258 
259     @Test
260     public void remove() throws Exception {
261         final long fullSize = dataset.size();
262         dataset.remove(Optional.of(graph1), alice, knows, bob);
263         final long shrunkSize = dataset.size();
264         assertEquals(1, fullSize - shrunkSize);
265 
266         dataset.remove(Optional.of(graph1), alice, knows, bob);
267         assertEquals(shrunkSize, dataset.size()); // unchanged
268 
269         dataset.add(graph1, alice, knows, bob);
270         dataset.add(graph2, alice, knows, bob);
271         dataset.add(graph2, alice, knows, bob);
272         // Undetermined size at this point -- but at least it
273         // should be bigger
274         assertTrue(dataset.size() > shrunkSize);
275 
276         // and after a single remove they should all be gone
277         dataset.remove(null, alice, knows, bob);
278         assertEquals(shrunkSize, dataset.size());
279 
280         Quad otherQuad;
281         try (Stream<? extends Quad> stream = dataset.stream()) {
282             final Optional<? extends Quad> anyQuad = stream.findAny();
283             Assume.assumeTrue(anyQuad.isPresent());
284             otherQuad = anyQuad.get();
285         }
286 
287         dataset.remove(otherQuad);
288         assertEquals(shrunkSize - 1, dataset.size());
289         dataset.remove(otherQuad);
290         assertEquals(shrunkSize - 1, dataset.size()); // no change
291 
292         // for some reason in rdf4j this causes duplicates!
293         dataset.add(otherQuad);
294         // dataset.stream().forEach(System.out::println);
295         // should have increased
296         assertTrue(dataset.size() >= shrunkSize);
297     }
298 
299     @Test
300     public void clear() throws Exception {
301         dataset.clear();
302         assertFalse(dataset.contains(null, alice, knows, bob));
303         assertEquals(0, dataset.size());
304         dataset.clear(); // no-op
305         assertEquals(0, dataset.size());
306         assertFalse(dataset.contains(null, null, null, null)); // nothing here
307     }
308 
309     @Test
310     public void getQuads() throws Exception {
311         long quadCount;
312         try (Stream<? extends Quad> stream = dataset.stream()) {
313             quadCount = stream.count();
314         }
315         assertTrue(quadCount > 0);
316 
317         try (Stream<? extends Quad> stream = dataset.stream()) {
318             assertTrue(stream.allMatch(t -> dataset.contains(t)));
319         }
320 
321         // Check exact count
322         Assume.assumeNotNull(bnode1, bnode2, aliceName, bobName, secretClubName, companyName, bobNameQuad);
323         assertEquals(10, quadCount);
324     }
325 
326     @Test
327     public void getQuadsQuery() throws Exception {
328 
329         try (Stream<? extends Quad> stream = dataset.stream(Optional.of(graph1), alice, null, null)) {
330             final long aliceCount = stream.count();
331             assertTrue(aliceCount > 0);
332             Assume.assumeNotNull(aliceName);
333             assertEquals(3, aliceCount);
334         }
335 
336         Assume.assumeNotNull(bnode1, bnode2, bobName, companyName, secretClubName);
337         try (Stream<? extends Quad> stream = dataset.stream(null, null, name, null)) {
338             assertEquals(4, stream.count());
339         }
340         Assume.assumeNotNull(bnode1);
341         try (Stream<? extends Quad> stream = dataset.stream(null, null, member, null)) {
342             assertEquals(3, stream.count());
343         }
344     }
345 
346     @Test
347     public void addBlankNodesFromMultipleDatasets() throws Exception {
348         // Create two separate Dataset instances
349         try (final Dataset g1 = createDataset1();
350                 final Dataset g2 = createDataset2();
351                 final Dataset g3 = factory.createDataset()) {
352 
353             addAllQuads(g1, g3);
354             addAllQuads(g2, g3);
355 
356             // Let's make a map to find all those blank nodes after insertion
357             // (The Dataset implementation is not currently required to
358             // keep supporting those BlankNodes with contains() - see
359             // COMMONSRDF-15)
360 
361             final Map<String, BlankNodeOrIRI> whoIsWho = new ConcurrentHashMap<>();
362             // ConcurrentHashMap as we will try parallel forEach below,
363             // which should not give inconsistent results (it does with a
364             // HashMap!)
365 
366             // look up BlankNodes by name
367             final IRI name = factory.createIRI("http://xmlns.com/foaf/0.1/name");
368             try (Stream<? extends Quad> stream = g3.stream(null, null, name, null)) {
369                 stream.parallel().forEach(t -> whoIsWho.put(t.getObject().ntriplesString(), t.getSubject()));
370             }
371 
372             assertEquals(4, whoIsWho.size());
373             // and contains 4 unique values
374             assertEquals(4, new HashSet<>(whoIsWho.values()).size());
375 
376             final BlankNodeOrIRI b1Alice = whoIsWho.get("\"Alice\"");
377             assertNotNull(b1Alice);
378             final BlankNodeOrIRI b2Bob = whoIsWho.get("\"Bob\"");
379             assertNotNull(b2Bob);
380             final BlankNodeOrIRI b1Charlie = whoIsWho.get("\"Charlie\"");
381             assertNotNull(b1Charlie);
382             final BlankNodeOrIRI b2Dave = whoIsWho.get("\"Dave\"");
383             assertNotNull(b2Dave);
384 
385             // All blank nodes should differ
386             notEquals(b1Alice, b2Bob);
387             notEquals(b1Alice, b1Charlie);
388             notEquals(b1Alice, b2Dave);
389             notEquals(b2Bob, b1Charlie);
390             notEquals(b2Bob, b2Dave);
391             notEquals(b1Charlie, b2Dave);
392 
393             // And we should be able to query with them again
394             // as we got them back from g3
395             final IRI hasChild = factory.createIRI("http://example.com/hasChild");
396             // FIXME: Check graph2 BlankNode in these ..?
397             assertTrue(g3.contains(null, b1Alice, hasChild, b2Bob));
398             assertTrue(g3.contains(null, b2Dave, hasChild, b1Charlie));
399             // But not
400             assertFalse(g3.contains(null, b1Alice, hasChild, b1Alice));
401             assertFalse(g3.contains(null, b1Alice, hasChild, b1Charlie));
402             assertFalse(g3.contains(null, b1Alice, hasChild, b2Dave));
403             // nor
404             assertFalse(g3.contains(null, b2Dave, hasChild, b1Alice));
405             assertFalse(g3.contains(null, b2Dave, hasChild, b1Alice));
406 
407             // and these don't have any children (as far as we know)
408             assertFalse(g3.contains(null, b2Bob, hasChild, null));
409             assertFalse(g3.contains(null, b1Charlie, hasChild, null));
410         }
411     }
412 
413     private void notEquals(final BlankNodeOrIRI node1, final BlankNodeOrIRI node2) {
414         assertFalse(node1.equals(node2));
415         // in which case we should be able to assume
416         // (as they are in the same dataset)
417         assertFalse(node1.ntriplesString().equals(node2.ntriplesString()));
418     }
419 
420     /**
421      * Add all quads from the source to the target.
422      * <p>
423      * The quads may be copied in any order. No special conversion or
424      * adaptation of {@link BlankNode}s are performed.
425      *
426      * @param source
427      *            Source Dataset to copy quads from
428      * @param target
429      *            Target Dataset where quads will be added
430      */
431     private void addAllQuads(final Dataset source, final Dataset target) {
432 
433         // unordered() as we don't need to preserve quad order
434         // sequential() as we don't (currently) require target Dataset to be
435         // thread-safe
436 
437         try (Stream<? extends Quad> stream = source.stream()) {
438             stream.unordered().sequential().forEach(t -> target.add(t));
439         }
440     }
441 
442     /**
443      * Make a new dataset with two BlankNodes - each with a different
444      * uniqueReference
445      */
446     private Dataset createDataset1() {
447         final RDF factory1 = createFactory();
448 
449         final IRI name = factory1.createIRI("http://xmlns.com/foaf/0.1/name");
450         final Dataset g1 = factory1.createDataset();
451         final BlankNode b1 = createOwnBlankNode("b1", "0240eaaa-d33e-4fc0-a4f1-169d6ced3680");
452         g1.add(b1, b1, name, factory1.createLiteral("Alice"));
453 
454         final BlankNode b2 = createOwnBlankNode("b2", "9de7db45-0ce7-4b0f-a1ce-c9680ffcfd9f");
455         g1.add(b2, b2, name, factory1.createLiteral("Bob"));
456 
457         final IRI hasChild = factory1.createIRI("http://example.com/hasChild");
458         g1.add(null, b1, hasChild, b2);
459 
460         return g1;
461     }
462 
463     /**
464      * Create a different implementation of BlankNode to be tested with
465      * dataset.add(a,b,c); (the implementation may or may not then choose to
466      * translate such to its own instances)
467      *
468      * @param name
469      * @return
470      */
471     private BlankNode createOwnBlankNode(final String name, final String uuid) {
472         return new BlankNode() {
473             @Override
474             public String ntriplesString() {
475                 return "_: " + name;
476             }
477 
478             @Override
479             public String uniqueReference() {
480                 return uuid;
481             }
482 
483             @Override
484             public int hashCode() {
485                 return uuid.hashCode();
486             }
487 
488             @Override
489             public boolean equals(final Object obj) {
490                 if (!(obj instanceof BlankNode)) {
491                     return false;
492                 }
493                 final BlankNode other = (BlankNode) obj;
494                 return uuid.equals(other.uniqueReference());
495             }
496         };
497     }
498 
499     private Dataset createDataset2() {
500         final RDF factory2 = createFactory();
501         final IRI name = factory2.createIRI("http://xmlns.com/foaf/0.1/name");
502 
503         final Dataset g2 = factory2.createDataset();
504 
505         final BlankNode b1 = createOwnBlankNode("b1", "bc8d3e45-a08f-421d-85b3-c25b373abf87");
506         g2.add(b1, b1, name, factory2.createLiteral("Charlie"));
507 
508         final BlankNode b2 = createOwnBlankNode("b2", "2209097a-5078-4b03-801a-6a2d2f50d739");
509         g2.add(b2, b2, name, factory2.createLiteral("Dave"));
510 
511         final IRI hasChild = factory2.createIRI("http://example.com/hasChild");
512         // NOTE: Opposite direction of loadDataset1
513         g2.add(b2, b2, hasChild, b1);
514         return g2;
515     }
516 
517     /**
518      * Ensure {@link Dataset#getGraphNames()} contains our two graphs.
519      *
520      * @throws Exception
521      *             If test fails
522      */
523     @Test
524     public void getGraphNames() throws Exception {
525         final Set<BlankNodeOrIRI> names = dataset.getGraphNames().collect(Collectors.toSet());
526         assertTrue("Can't find graph name " + graph1, names.contains(graph1));
527         assertTrue("Found no quads in graph1", dataset.contains(Optional.of(graph1), null, null, null));
528 
529         final Optional<BlankNodeOrIRI> graphName2 = dataset.getGraphNames().filter(BlankNode.class::isInstance).findAny();
530         assertTrue("Could not find graph2-like BlankNode", graphName2.isPresent());
531         assertTrue("Found no quads in graph2", dataset.contains(graphName2, null, null, null));
532 
533         // Some implementations like Virtuoso might have additional internal graphs,
534         // so we can't assume this:
535         //assertEquals(2, names.size());
536     }
537 
538     @Test
539     public void getGraph() throws Exception {
540         try (final Graph defaultGraph = dataset.getGraph()) {
541             // TODO: Can we assume the default graph was empty before our new triples?
542             assertEquals(2, defaultGraph.size());
543             assertTrue(defaultGraph.contains(alice, isPrimaryTopicOf, graph1));
544             // NOTE: graph2 is a BlankNode
545             assertTrue(defaultGraph.contains(bob, isPrimaryTopicOf, null));
546         }
547     }
548 
549 
550     @Test
551     public void getGraphNull() throws Exception {
552         // Default graph should be present
553         try (final Graph defaultGraph = dataset.getGraph(null).get()) {
554             // TODO: Can we assume the default graph was empty before our new triples?
555             assertEquals(2, defaultGraph.size());
556             assertTrue(defaultGraph.contains(alice, isPrimaryTopicOf, graph1));
557             // NOTE: wildcard as graph2 is a (potentially mapped) BlankNode
558             assertTrue(defaultGraph.contains(bob, isPrimaryTopicOf, null));
559         }
560     }
561 
562 
563     @Test
564     public void getGraph1() throws Exception {
565         // graph1 should be present
566         try (final Graph g1 = dataset.getGraph(graph1).get()) {
567             assertEquals(4, g1.size());
568 
569             assertTrue(g1.contains(alice, name, aliceName));
570             assertTrue(g1.contains(alice, knows, bob));
571             assertTrue(g1.contains(alice, member, null));
572             assertTrue(g1.contains(null, name, secretClubName));
573         }
574     }
575 
576     @Test
577     public void getGraph2() throws Exception {
578         // graph2 should be present, even if is named by a BlankNode
579         // We'll look up the potentially mapped graph2 blanknode
580         final BlankNodeOrIRI graph2Name = (BlankNodeOrIRI) dataset.stream(Optional.empty(), bob, isPrimaryTopicOf, null)
581                 .map(Quad::getObject).findAny().get();
582 
583         try (final Graph g2 = dataset.getGraph(graph2Name).get()) {
584             assertEquals(4, g2.size());
585             final Triple bobNameTriple = bobNameQuad.asTriple();
586             assertTrue(g2.contains(bobNameTriple));
587             assertTrue(g2.contains(bob, member, bnode1));
588             assertTrue(g2.contains(bob, member, bnode2));
589             assertFalse(g2.contains(bnode1, name, secretClubName));
590             assertTrue(g2.contains(bnode2, name, companyName));
591         }
592     }
593 
594     @Test
595     public void containsLanguageTagsCaseInsensitive() {
596         // COMMONSRDF-51: Ensure we can add/contains/remove with any casing
597         // of literal language tag
598         final Literal lower = factory.createLiteral("Hello there", "en-gb");
599         final Literal upper = factory.createLiteral("Hello there", "EN-GB");
600         final Literal mixed = factory.createLiteral("Hello there", "en-GB");
601 
602         final IRI example1 = factory.createIRI("http://example.com/s1");
603         final IRI greeting = factory.createIRI("http://example.com/greeting");
604 
605 
606         dataset.add(null, example1, greeting, upper);
607 
608         // any kind of Triple should match
609         assertTrue(dataset.contains(factory.createQuad(null, example1, greeting, upper)));
610         assertTrue(dataset.contains(factory.createQuad(null, example1, greeting, lower)));
611         assertTrue(dataset.contains(factory.createQuad(null, example1, greeting, mixed)));
612 
613         // or as patterns
614         assertTrue(dataset.contains(null, null, null, upper));
615         assertTrue(dataset.contains(null, null, null, lower));
616         assertTrue(dataset.contains(null, null, null, mixed));
617     }
618 
619     @Test
620     public void containsLanguageTagsCaseInsensitiveTurkish() {
621         // COMMONSRDF-51: Special test for Turkish issue where
622         // "i".toLowerCase() != "i"
623         // See also:
624         // https://garygregory.wordpress.com/2015/11/03/java-lowercase-conversion-turkey/
625 
626         // This is similar to the test in AbstractRDFTest, but on a graph
627         final Locale defaultLocale = Locale.getDefault();
628         try {
629             Locale.setDefault(Locale.ROOT);
630             final Literal lowerROOT = factory.createLiteral("moi", "fi");
631             final Literal upperROOT = factory.createLiteral("moi", "FI");
632             final Literal mixedROOT = factory.createLiteral("moi", "fI");
633             final IRI exampleROOT = factory.createIRI("http://example.com/s1");
634             final IRI greeting = factory.createIRI("http://example.com/greeting");
635             dataset.add(null, exampleROOT, greeting, mixedROOT);
636 
637             final Locale turkish = Locale.forLanguageTag("TR");
638             Locale.setDefault(turkish);
639             // If the below assertion fails, then the Turkish
640             // locale no longer have this peculiarity that
641             // we want to test.
642             Assume.assumeFalse("FI".toLowerCase().equals("fi"));
643 
644             // Below is pretty much the same as in
645             // containsLanguageTagsCaseInsensitive()
646             final Literal lower = factory.createLiteral("moi", "fi");
647             final Literal upper = factory.createLiteral("moi", "FI");
648             final Literal mixed = factory.createLiteral("moi", "fI");
649 
650             final IRI exampleTR = factory.createIRI("http://example.com/s2");
651             dataset.add(null, exampleTR, greeting, upper);
652             assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, upper)));
653             assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, upperROOT)));
654             assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, lower)));
655             assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, lowerROOT)));
656             assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, mixed)));
657             assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, mixedROOT)));
658             assertTrue(dataset.contains(null, exampleTR, null, upper));
659             assertTrue(dataset.contains(null, exampleTR, null, upperROOT));
660             assertTrue(dataset.contains(null, exampleTR, null, lower));
661             assertTrue(dataset.contains(null, exampleTR, null, lowerROOT));
662             assertTrue(dataset.contains(null, exampleTR, null, mixed));
663             assertTrue(dataset.contains(null, exampleTR, null, mixedROOT));
664 
665             // What about the triple we added while in ROOT locale?
666             assertTrue(dataset.contains(factory.createQuad(null, exampleROOT, greeting, upper)));
667             assertTrue(dataset.contains(factory.createQuad(null, exampleROOT, greeting, lower)));
668             assertTrue(dataset.contains(factory.createQuad(null, exampleROOT, greeting, mixed)));
669             assertTrue(dataset.contains(null, exampleROOT, null, upper));
670             assertTrue(dataset.contains(null, exampleROOT, null, lower));
671             assertTrue(dataset.contains(null, exampleROOT, null, mixed));
672         } finally {
673             Locale.setDefault(defaultLocale);
674         }
675     }
676 
677 
678     @Test
679     public void removeLanguageTagsCaseInsensitive() {
680         // COMMONSRDF-51: Ensure we can remove with any casing
681         // of literal language tag
682         final Literal lower = factory.createLiteral("Howdy", "en-us");
683         final Literal upper = factory.createLiteral("Howdy", "EN-US");
684         final Literal mixed = factory.createLiteral("Howdy", "en-US");
685 
686         final IRI example1 = factory.createIRI("http://example.com/s1");
687         final IRI greeting = factory.createIRI("http://example.com/greeting");
688 
689         dataset.add(null, example1, greeting, upper);
690 
691         // Remove should also honour any case
692         dataset.remove(null, example1, null, mixed);
693         assertFalse(dataset.contains(null, null, greeting, null));
694 
695         dataset.add(null, example1, greeting, lower);
696         dataset.remove(null, example1, null, upper);
697 
698         // Check with Triple
699         dataset.add(factory.createQuad(null, example1, greeting, mixed));
700         dataset.remove(factory.createQuad(null, example1, greeting, upper));
701         assertFalse(dataset.contains(null, null, greeting, null));
702     }
703 
704     private static Optional<? extends Quad> closableFindAny(final Stream<? extends Quad> stream) {
705         try (Stream<? extends Quad> s = stream) {
706             return s.findAny();
707         }
708     }
709 
710     @Test
711     public void streamLanguageTagsCaseInsensitive() {
712         // COMMONSRDF-51: Ensure we can add/contains/remove with any casing
713         // of literal language tag
714         final Literal lower = factory.createLiteral("Good afternoon", "en-gb");
715         final Literal upper = factory.createLiteral("Good afternoon", "EN-GB");
716         final Literal mixed = factory.createLiteral("Good afternoon", "en-GB");
717 
718         final IRI example1 = factory.createIRI("http://example.com/s1");
719         final IRI greeting = factory.createIRI("http://example.com/greeting");
720 
721         dataset.add(null, example1, greeting, upper);
722 
723         // or as patterns
724         assertTrue(closableFindAny(dataset.stream(null, null, null, upper)).isPresent());
725         assertTrue(closableFindAny(dataset.stream(null, null, null, lower)).isPresent());
726         assertTrue(closableFindAny(dataset.stream(null, null, null, mixed)).isPresent());
727 
728         // Check the quad returned equal a new quad
729         final Quad q = closableFindAny(dataset.stream(null, null, null, lower)).get();
730         assertEquals(q, factory.createQuad(null, example1, greeting, mixed));
731     }
732 
733     /**
734      * An attempt to use the Java 8 streams to look up a more complicated query.
735      * <p>
736      * FYI, the equivalent SPARQL version (untested):
737      *
738      * <pre>
739      *     SELECT ?orgName WHERE {
740      *             ?org foaf:name ?orgName .
741      *             ?alice foaf:member ?org .
742      *             ?bob foaf:member ?org .
743      *             ?alice foaf:knows ?bob .
744      *           FILTER NOT EXIST { ?bob foaf:knows ?alice }
745      *    }
746      * </pre>
747      *
748      * @throws Exception If test fails
749      */
750     @Test
751     public void whyJavaStreamsMightNotTakeOverFromSparql() throws Exception {
752         Assume.assumeNotNull(bnode1, bnode2, secretClubName);
753         // Find a secret organizations
754         try (Stream<? extends Quad> stream = dataset.stream(null, null, knows, null)) {
755             assertEquals("\"The Secret Club\"",
756                     // Find One-way "knows"
757                     stream.filter(t -> !dataset.contains(null, (BlankNodeOrIRI) t.getObject(), knows, t.getSubject()))
758                             .map(knowsQuad -> {
759                                 try (Stream<? extends Quad> memberOf = dataset
760                                         // and those they know, what are they
761                                         // member of?
762                                         .stream(null, (BlankNodeOrIRI) knowsQuad.getObject(), member, null)) {
763                                     return memberOf
764                                             // keep those which first-guy is a
765                                             // member of
766                                             .filter(memberQuad -> dataset.contains(null, knowsQuad.getSubject(), member,
767                                                     // First hit is good enough
768                                                     memberQuad.getObject()))
769                                             .findFirst().get().getObject();
770                                 }
771                             })
772                             // then look up the name of that org
773                             .map(org -> {
774                                 try (Stream<? extends Quad> orgName = dataset.stream(null, (BlankNodeOrIRI) org, name,
775                                         null)) {
776                                     return orgName.findFirst().get().getObject().ntriplesString();
777                                 }
778                             }).findFirst().get());
779         }
780     }
781 }