Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.rdf.api; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import org.junit.Assume; import org.junit.Before; import org.junit.Test; /** * Test Graph implementation * <p> * To add to your implementation's tests, create a subclass with a name ending * in <code>Test</code> and provide {@link #createFactory()} which minimally * must support {@link RDF#createGraph()} and {@link RDF#createIRI(String)}, but * ideally support all operations. * <p> * This test uses try-with-resources blocks for calls to {@link Graph#stream()} * and {@link Graph#iterate()}. * * @see Graph * @see RDF */ public abstract class AbstractGraphTest { protected RDF factory; protected Graph graph; protected IRI alice; protected IRI bob; protected IRI name; protected IRI knows; protected IRI member; protected BlankNode bnode1; protected BlankNode bnode2; protected Literal aliceName; protected Literal bobName; protected Literal secretClubName; protected Literal companyName; protected Triple bobNameTriple; /** * * This method must be overridden by the implementing test to provide a * factory for the test to create {@link Graph}, {@link IRI} etc. * * @return {@link RDF} instance to be tested. */ protected abstract RDF createFactory(); @Before public void createGraphAndAdd() { factory = createFactory(); graph = factory.createGraph(); assertEquals(0, graph.size()); alice = factory.createIRI("http://example.com/alice"); bob = factory.createIRI("http://example.com/bob"); name = factory.createIRI("http://xmlns.com/foaf/0.1/name"); knows = factory.createIRI("http://xmlns.com/foaf/0.1/knows"); member = factory.createIRI("http://xmlns.com/foaf/0.1/member"); try { bnode1 = factory.createBlankNode("org1"); bnode2 = factory.createBlankNode("org2"); } catch (final UnsupportedOperationException ex) { // leave as null } try { secretClubName = factory.createLiteral("The Secret Club"); companyName = factory.createLiteral("A company"); aliceName = factory.createLiteral("Alice"); bobName = factory.createLiteral("Bob", "en-US"); } catch (final UnsupportedOperationException ex) { // leave as null } if (aliceName != null) { graph.add(alice, name, aliceName); } graph.add(alice, knows, bob); if (bnode1 != null) { graph.add(alice, member, bnode1); } if (bobName != null) { try { bobNameTriple = factory.createTriple(bob, name, bobName); } catch (final UnsupportedOperationException ex) { // leave as null } if (bobNameTriple != null) { graph.add(bobNameTriple); } } if (bnode1 != null) { graph.add(factory.createTriple(bob, member, bnode1)); graph.add(factory.createTriple(bob, member, bnode2)); if (secretClubName != null) { graph.add(bnode1, name, secretClubName); graph.add(bnode2, name, companyName); } } } @Test public void size() throws Exception { assertTrue(graph.size() > 0); Assume.assumeNotNull(bnode1, bnode2, aliceName, bobName, secretClubName, companyName, bobNameTriple); // Can only reliably predict size if we could create all triples assertEquals(8, graph.size()); } @Test public void iterate() throws Exception { Assume.assumeTrue(graph.size() > 0); final List<Triple> triples = new ArrayList<>(); for (final Triple t : graph.iterate()) { triples.add(t); } assertEquals(graph.size(), triples.size()); if (bobNameTriple != null) { assertTrue(triples.contains(bobNameTriple)); } // aborted iteration final Iterable<Triple> iterate = graph.iterate(); final Iterator<Triple> it = iterate.iterator(); assertTrue(it.hasNext()); it.next(); closeIterable(iterate); // second iteration - should start from fresh and // get the same count long count = 0; final Iterable<Triple> iterable = graph.iterate(); for (@SuppressWarnings("unused") final Triple t : iterable) { count++; } assertEquals(graph.size(), count); } /** * Special triple closing for RDF4J. */ private void closeIterable(final Iterable<Triple> iterate) throws Exception { if (iterate instanceof AutoCloseable) { ((AutoCloseable) iterate).close(); } } @Test public void iterateFilter() throws Exception { final List<RDFTerm> friends = new ArrayList<>(); final IRI alice = factory.createIRI("http://example.com/alice"); final IRI knows = factory.createIRI("http://xmlns.com/foaf/0.1/knows"); for (final Triple t : graph.iterate(alice, knows, null)) { friends.add(t.getObject()); } assertEquals(1, friends.size()); assertEquals(bob, friends.get(0)); // .. can we iterate over zero hits? final Iterable<Triple> iterate = graph.iterate(bob, knows, alice); for (final Triple unexpected : iterate) { fail("Unexpected triple " + unexpected); } // closeIterable(iterate); } @Test public void contains() throws Exception { assertFalse(graph.contains(bob, knows, alice)); // or so he claims.. assertTrue(graph.contains(alice, knows, bob)); try (Stream<? extends Triple> stream = graph.stream()) { final Optional<? extends Triple> first = stream.skip(4).findFirst(); Assume.assumeTrue(first.isPresent()); final Triple existingTriple = first.get(); assertTrue(graph.contains(existingTriple)); } final Triple nonExistingTriple = factory.createTriple(bob, knows, alice); assertFalse(graph.contains(nonExistingTriple)); Triple triple = null; try { triple = factory.createTriple(alice, knows, bob); } catch (final UnsupportedOperationException ex) { } if (triple != null) { // FIXME: Should not this always be true? // assertTrue(graph.contains(triple)); } } @Test public void remove() throws Exception { final long fullSize = graph.size(); graph.remove(alice, knows, bob); final long shrunkSize = graph.size(); assertEquals(1, fullSize - shrunkSize); graph.remove(alice, knows, bob); assertEquals(shrunkSize, graph.size()); // unchanged graph.add(alice, knows, bob); graph.add(alice, knows, bob); graph.add(alice, knows, bob); // Undetermined size at this point -- but at least it // should be bigger assertTrue(graph.size() > shrunkSize); // and after a single remove they should all be gone graph.remove(alice, knows, bob); assertEquals(shrunkSize, graph.size()); Triple otherTriple; try (Stream<? extends Triple> stream = graph.stream()) { final Optional<? extends Triple> anyTriple = stream.findAny(); Assume.assumeTrue(anyTriple.isPresent()); otherTriple = anyTriple.get(); } graph.remove(otherTriple); assertEquals(shrunkSize - 1, graph.size()); graph.remove(otherTriple); assertEquals(shrunkSize - 1, graph.size()); // no change // for some reason in rdf4j this causes duplicates! graph.add(otherTriple); // graph.stream().forEach(System.out::println); // should have increased assertTrue(graph.size() >= shrunkSize); } @Test public void clear() throws Exception { graph.clear(); assertFalse(graph.contains(alice, knows, bob)); assertEquals(0, graph.size()); graph.clear(); // no-op assertEquals(0, graph.size()); } @Test public void getTriples() throws Exception { long tripleCount; try (Stream<? extends Triple> stream = graph.stream()) { tripleCount = stream.count(); } assertTrue(tripleCount > 0); try (Stream<? extends Triple> stream = graph.stream()) { assertTrue(stream.allMatch(t -> graph.contains(t))); } // Check exact count Assume.assumeNotNull(bnode1, bnode2, aliceName, bobName, secretClubName, companyName, bobNameTriple); assertEquals(8, tripleCount); } @Test public void getTriplesQuery() throws Exception { try (Stream<? extends Triple> stream = graph.stream(alice, null, null)) { final long aliceCount = stream.count(); assertTrue(aliceCount > 0); Assume.assumeNotNull(aliceName); assertEquals(3, aliceCount); } Assume.assumeNotNull(bnode1, bnode2, bobName, companyName, secretClubName); try (Stream<? extends Triple> stream = graph.stream(null, name, null)) { assertEquals(4, stream.count()); } Assume.assumeNotNull(bnode1); try (Stream<? extends Triple> stream = graph.stream(null, member, null)) { assertEquals(3, stream.count()); } } @Test public void addBlankNodesFromMultipleGraphs() throws Exception { // Create two separate Graph instances // and add them to a new Graph g3 try (final Graph g1 = createGraph1(); final Graph g2 = createGraph2(); final Graph g3 = factory.createGraph()) { addAllTriples(g1, g3); addAllTriples(g2, g3); // Let's make a map to find all those blank nodes after insertion // (The Graph implementation is not currently required to // keep supporting those BlankNodes with contains() - see // COMMONSRDF-15) final Map<String, BlankNodeOrIRI> whoIsWho = new ConcurrentHashMap<>(); // ConcurrentHashMap as we will try parallel forEach below, // which should not give inconsistent results (it does with a // HashMap!) // look up BlankNodes by name final IRI name = factory.createIRI("http://xmlns.com/foaf/0.1/name"); try (Stream<? extends Triple> stream = g3.stream(null, name, null)) { stream.parallel().forEach(t -> whoIsWho.put(t.getObject().ntriplesString(), t.getSubject())); } assertEquals(4, whoIsWho.size()); // and contains 4 unique values assertEquals(4, new HashSet<>(whoIsWho.values()).size()); final BlankNodeOrIRI b1Alice = whoIsWho.get("\"Alice\""); assertNotNull(b1Alice); final BlankNodeOrIRI b2Bob = whoIsWho.get("\"Bob\""); assertNotNull(b2Bob); final BlankNodeOrIRI b1Charlie = whoIsWho.get("\"Charlie\""); assertNotNull(b1Charlie); final BlankNodeOrIRI b2Dave = whoIsWho.get("\"Dave\""); assertNotNull(b2Dave); // All blank nodes should differ notEquals(b1Alice, b2Bob); notEquals(b1Alice, b1Charlie); notEquals(b1Alice, b2Dave); notEquals(b2Bob, b1Charlie); notEquals(b2Bob, b2Dave); notEquals(b1Charlie, b2Dave); // And we should be able to query with them again // as we got them back from g3 final IRI hasChild = factory.createIRI("http://example.com/hasChild"); assertTrue(g3.contains(b1Alice, hasChild, b2Bob)); assertTrue(g3.contains(b2Dave, hasChild, b1Charlie)); // But not assertFalse(g3.contains(b1Alice, hasChild, b1Alice)); assertFalse(g3.contains(b1Alice, hasChild, b1Charlie)); assertFalse(g3.contains(b1Alice, hasChild, b2Dave)); // nor assertFalse(g3.contains(b2Dave, hasChild, b1Alice)); assertFalse(g3.contains(b2Dave, hasChild, b1Alice)); // and these don't have any children (as far as we know) assertFalse(g3.contains(b2Bob, hasChild, null)); assertFalse(g3.contains(b1Charlie, hasChild, null)); } catch (final UnsupportedOperationException ex) { Assume.assumeNoException(ex); } } @Test public void containsLanguageTagsCaseInsensitive() throws Exception { // COMMONSRDF-51: Ensure we can add/contains/remove with any casing // of literal language tag final Literal lower = factory.createLiteral("Hello", "en-gb"); final Literal upper = factory.createLiteral("Hello", "EN-GB"); final Literal mixed = factory.createLiteral("Hello", "en-GB"); final IRI example1 = factory.createIRI("http://example.com/s1"); final IRI greeting = factory.createIRI("http://example.com/greeting"); try (final Graph graph = factory.createGraph()) { graph.add(example1, greeting, upper); // any kind of Triple should match assertTrue(graph.contains(factory.createTriple(example1, greeting, upper))); assertTrue(graph.contains(factory.createTriple(example1, greeting, lower))); assertTrue(graph.contains(factory.createTriple(example1, greeting, mixed))); // or as patterns assertTrue(graph.contains(null, null, upper)); assertTrue(graph.contains(null, null, lower)); assertTrue(graph.contains(null, null, mixed)); } } @Test public void containsLanguageTagsCaseInsensitiveTurkish() throws Exception { // COMMONSRDF-51: Special test for Turkish issue where // "i".toLowerCase() != "i" // See also: // https://garygregory.wordpress.com/2015/11/03/java-lowercase-conversion-turkey/ // This is similar to the test in AbstractRDFTest, but on a graph final Locale defaultLocale = Locale.getDefault(); try (final Graph g = factory.createGraph()) { Locale.setDefault(Locale.ROOT); final Literal lowerROOT = factory.createLiteral("moi", "fi"); final Literal upperROOT = factory.createLiteral("moi", "FI"); final Literal mixedROOT = factory.createLiteral("moi", "fI"); final IRI exampleROOT = factory.createIRI("http://example.com/s1"); final IRI greeting = factory.createIRI("http://example.com/greeting"); g.add(exampleROOT, greeting, mixedROOT); final Locale turkish = Locale.forLanguageTag("TR"); Locale.setDefault(turkish); // If the below assertion fails, then the Turkish // locale no longer have this peculiarity that // we want to test. Assume.assumeFalse("FI".toLowerCase().equals("fi")); // Below is pretty much the same as in // containsLanguageTagsCaseInsensitive() final Literal lower = factory.createLiteral("moi", "fi"); final Literal upper = factory.createLiteral("moi", "FI"); final Literal mixed = factory.createLiteral("moi", "fI"); final IRI exampleTR = factory.createIRI("http://example.com/s2"); g.add(exampleTR, greeting, upper); assertTrue(g.contains(factory.createTriple(exampleTR, greeting, upper))); assertTrue(g.contains(factory.createTriple(exampleTR, greeting, upperROOT))); assertTrue(g.contains(factory.createTriple(exampleTR, greeting, lower))); assertTrue(g.contains(factory.createTriple(exampleTR, greeting, lowerROOT))); assertTrue(g.contains(factory.createTriple(exampleTR, greeting, mixed))); assertTrue(g.contains(factory.createTriple(exampleTR, greeting, mixedROOT))); assertTrue(g.contains(exampleTR, null, upper)); assertTrue(g.contains(exampleTR, null, upperROOT)); assertTrue(g.contains(exampleTR, null, lower)); assertTrue(g.contains(exampleTR, null, lowerROOT)); assertTrue(g.contains(exampleTR, null, mixed)); assertTrue(g.contains(exampleTR, null, mixedROOT)); // What about the triple we added while in ROOT locale? assertTrue(g.contains(factory.createTriple(exampleROOT, greeting, upper))); assertTrue(g.contains(factory.createTriple(exampleROOT, greeting, lower))); assertTrue(g.contains(factory.createTriple(exampleROOT, greeting, mixed))); assertTrue(g.contains(exampleROOT, null, upper)); assertTrue(g.contains(exampleROOT, null, lower)); assertTrue(g.contains(exampleROOT, null, mixed)); } finally { Locale.setDefault(defaultLocale); } } @Test public void removeLanguageTagsCaseInsensitive() throws Exception { // COMMONSRDF-51: Ensure we can remove with any casing // of literal language tag final Literal lower = factory.createLiteral("Hello", "en-gb"); final Literal upper = factory.createLiteral("Hello", "EN-GB"); final Literal mixed = factory.createLiteral("Hello", "en-GB"); final IRI example1 = factory.createIRI("http://example.com/s1"); final IRI greeting = factory.createIRI("http://example.com/greeting"); try (final Graph graph = factory.createGraph()) { graph.add(example1, greeting, upper); // Remove should also honour any case graph.remove(example1, null, mixed); assertFalse(graph.contains(null, greeting, null)); graph.add(example1, greeting, lower); graph.remove(example1, null, upper); // Check with Triple graph.add(factory.createTriple(example1, greeting, mixed)); graph.remove(factory.createTriple(example1, greeting, upper)); assertFalse(graph.contains(null, greeting, null)); } } private static Optional<? extends Triple> closableFindAny(final Stream<? extends Triple> stream) { try (Stream<? extends Triple> s = stream) { return s.findAny(); } } @Test public void streamLanguageTagsCaseInsensitive() throws Exception { // COMMONSRDF-51: Ensure we can add/contains/remove with any casing // of literal language tag final Literal lower = factory.createLiteral("Hello", "en-gb"); final Literal upper = factory.createLiteral("Hello", "EN-GB"); final Literal mixed = factory.createLiteral("Hello", "en-GB"); final IRI example1 = factory.createIRI("http://example.com/s1"); final IRI greeting = factory.createIRI("http://example.com/greeting"); try (final Graph graph = factory.createGraph()) { graph.add(example1, greeting, upper); // or as patterns assertTrue(closableFindAny(graph.stream(null, null, upper)).isPresent()); assertTrue(closableFindAny(graph.stream(null, null, lower)).isPresent()); assertTrue(closableFindAny(graph.stream(null, null, mixed)).isPresent()); // Check the triples returned equal a new triple final Triple t = closableFindAny(graph.stream(null, null, lower)).get(); assertEquals(t, factory.createTriple(example1, greeting, mixed)); } } private void notEquals(final BlankNodeOrIRI node1, final BlankNodeOrIRI node2) { assertFalse(node1.equals(node2)); // in which case we should be able to assume // (as they are in the same graph) assertFalse(node1.ntriplesString().equals(node2.ntriplesString())); } /** * Add all triples from the source to the target. * <p> * The triples may be copied in any order. No special conversion or * adaptation of {@link BlankNode}s are performed. * * @param source * Source Graph to copy triples from * @param target * Target Graph where triples will be added */ private void addAllTriples(final Graph source, final Graph target) { // unordered() as we don't need to preserve triple order // sequential() as we don't (currently) require target Graph to be // thread-safe try (Stream<? extends Triple> stream = source.stream()) { stream.unordered().sequential().forEach(t -> target.add(t)); } } /** * Make a new graph with two BlankNodes - each with a different * uniqueReference */ private Graph createGraph1() { final RDF factory1 = createFactory(); final IRI name = factory1.createIRI("http://xmlns.com/foaf/0.1/name"); final Graph g1 = factory1.createGraph(); final BlankNode b1 = createOwnBlankNode("b1", "0240eaaa-d33e-4fc0-a4f1-169d6ced3680"); g1.add(b1, name, factory1.createLiteral("Alice")); final BlankNode b2 = createOwnBlankNode("b2", "9de7db45-0ce7-4b0f-a1ce-c9680ffcfd9f"); g1.add(b2, name, factory1.createLiteral("Bob")); final IRI hasChild = factory1.createIRI("http://example.com/hasChild"); g1.add(b1, hasChild, b2); return g1; } /** * Create a different implementation of BlankNode to be tested with * graph.add(a,b,c); (the implementation may or may not then choose to * translate such to its own instances) * * @param name * @return */ private BlankNode createOwnBlankNode(final String name, final String uuid) { return new BlankNode() { @Override public String ntriplesString() { return "_: " + name; } @Override public String uniqueReference() { return uuid; } @Override public int hashCode() { return uuid.hashCode(); } @Override public boolean equals(final Object obj) { if (!(obj instanceof BlankNode)) { return false; } final BlankNode other = (BlankNode) obj; return uuid.equals(other.uniqueReference()); } }; } private Graph createGraph2() { final RDF factory2 = createFactory(); final IRI name = factory2.createIRI("http://xmlns.com/foaf/0.1/name"); final Graph g2 = factory2.createGraph(); final BlankNode b1 = createOwnBlankNode("b1", "bc8d3e45-a08f-421d-85b3-c25b373abf87"); g2.add(b1, name, factory2.createLiteral("Charlie")); final BlankNode b2 = createOwnBlankNode("b2", "2209097a-5078-4b03-801a-6a2d2f50d739"); g2.add(b2, name, factory2.createLiteral("Dave")); final IRI hasChild = factory2.createIRI("http://example.com/hasChild"); // NOTE: Opposite direction of loadGraph1 g2.add(b2, hasChild, b1); return g2; } /** * An attempt to use the Java 8 streams to look up a more complicated query. * <p> * FYI, the equivalent SPARQL version (untested): * * <pre> * SELECT ?orgName WHERE { * ?org foaf:name ?orgName . * ?alice foaf:member ?org . * ?bob foaf:member ?org . * ?alice foaf:knows ?bob . * FILTER NOT EXIST { ?bob foaf:knows ?alice } * } * </pre> * * @throws Exception If test fails */ @Test public void whyJavaStreamsMightNotTakeOverFromSparql() throws Exception { Assume.assumeNotNull(bnode1, bnode2, secretClubName); // Find a secret organizations try (Stream<? extends Triple> stream = graph.stream(null, knows, null)) { assertEquals("\"The Secret Club\"", // Find One-way "knows" stream.filter(t -> !graph.contains((BlankNodeOrIRI) t.getObject(), knows, t.getSubject())) .map(knowsTriple -> { try (Stream<? extends Triple> memberOf = graph // and those they know, what are they // member of? .stream((BlankNodeOrIRI) knowsTriple.getObject(), member, null)) { return memberOf // keep those which first-guy is a // member of .filter(memberTriple -> graph.contains(knowsTriple.getSubject(), member, // First hit is good enough memberTriple.getObject())) .findFirst().get().getObject(); } }) // then look up the name of that org .map(org -> { try (Stream<? extends Triple> orgName = graph.stream((BlankNodeOrIRI) org, name, null)) { return orgName.findFirst().get().getObject().ntriplesString(); } }).findFirst().get()); } } }