You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2017/10/30 14:57:16 UTC

[16/22] commons-rdf git commit: Module names, directory names, and artifact names should match.

http://git-wip-us.apache.org/repos/asf/commons-rdf/blob/d59203ce/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractDatasetTest.java
----------------------------------------------------------------------
diff --git a/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractDatasetTest.java b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractDatasetTest.java
new file mode 100644
index 0000000..ab3dc32
--- /dev/null
+++ b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractDatasetTest.java
@@ -0,0 +1,778 @@
+/**
+ * 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.*;
+
+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.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test Dataset 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#createDataset()} and {@link RDF#createIRI(String)}, but
+ * ideally support all operations.
+ * <p>
+ * This test uses try-with-resources blocks for calls to {@link Dataset#stream()}
+ * and {@link Dataset#iterate()}.
+ * 
+ * @see Dataset
+ * @see RDF
+ */
+public abstract class AbstractDatasetTest {
+
+    protected RDF factory;
+    protected Dataset dataset;
+    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 Quad bobNameQuad;
+    private IRI isPrimaryTopicOf;
+    private IRI graph1;
+    private BlankNode graph2;
+
+    /**
+     * 
+     * This method must be overridden by the implementing test to provide a
+     * factory for the test to create {@link Dataset}, {@link IRI} etc.
+     * 
+     * @return {@link RDF} instance to be tested.
+     */
+    protected abstract RDF createFactory();
+
+    @Before
+    public void createDatasetAndAdd() {
+        factory = createFactory();
+        dataset = factory.createDataset();
+        assertEquals(0, dataset.size());
+
+        graph1 = factory.createIRI("http://example.com/graph1");
+        graph2 = factory.createBlankNode();
+        
+        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");
+        bnode1 = factory.createBlankNode("org1");
+        bnode2 = factory.createBlankNode("org2");
+
+        secretClubName = factory.createLiteral("The Secret Club");
+        companyName = factory.createLiteral("A company");
+        aliceName = factory.createLiteral("Alice");
+        bobName = factory.createLiteral("Bob", "en-US");
+
+        dataset.add(graph1, alice, name, aliceName);
+        dataset.add(graph1, alice, knows, bob);
+
+        dataset.add(graph1, alice, member, bnode1);
+
+        bobNameQuad = factory.createQuad(graph2, bob, name, bobName);
+        dataset.add(bobNameQuad);
+
+        dataset.add(factory.createQuad(graph2, bob, member, bnode1));
+        dataset.add(factory.createQuad(graph2, bob, member, bnode2));
+        // NOTE: bnode1 used in both graph1 and graph2
+        dataset.add(graph1, bnode1, name, secretClubName);
+        dataset.add(graph2, bnode2, name, companyName);
+        
+        // default graph describes graph1 and graph2        
+        isPrimaryTopicOf = factory.createIRI("http://xmlns.com/foaf/0.1/isPrimaryTopicOf");
+        dataset.add(null, alice, isPrimaryTopicOf, graph1);
+        dataset.add(null, bob, isPrimaryTopicOf, graph2);
+        
+        
+    }
+
+    @Test
+    public void size() throws Exception {
+        assertEquals(10, dataset.size());
+    }
+
+    @Test
+    public void iterate() throws Exception {
+        Assume.assumeTrue(dataset.size() > 0);
+        final List<Quad> quads = new ArrayList<>();
+        for (final Quad t : dataset.iterate()) {
+            quads.add(t);
+        }
+        assertEquals(dataset.size(), quads.size());
+        
+        //assertTrue(quads.contains(bobNameQuad));
+        // java.util.List won't do any BlankNode mapping, so 
+        // instead bobNameQuad of let's check for an IRI-centric quad 
+        final Quad q = factory.createQuad(graph1, alice, name, aliceName);
+        quads.contains(q);
+
+        // aborted iteration
+        final Iterable<Quad> iterate = dataset.iterate();
+        final Iterator<Quad> 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<Quad> iterable = dataset.iterate();
+        for (@SuppressWarnings("unused") final
+        Quad t : iterable) {
+            count++;
+        }
+        assertEquals(dataset.size(), count);
+        
+        // Pattern iteration which should cover multiple graphs.
+        
+        Set<Quad> aliceQuads = new HashSet<>();
+        for (Quad aliceQ : dataset.iterate(null, alice, null, null)) { 
+            aliceQuads.add(aliceQ);
+        }
+        assertTrue(aliceQuads.contains(factory.createQuad(graph1, alice, name, aliceName)));
+        assertTrue(aliceQuads.contains(factory.createQuad(graph1, alice, knows, bob)));
+        // We can't test this by Quad equality, as bnode1 might become mapped by the 
+        // dataset
+        //assertTrue(aliceQuads.contains(factory.createQuad(graph1, alice, member, bnode1)));
+        assertTrue(aliceQuads.contains(factory.createQuad(null, alice, isPrimaryTopicOf, graph1)));
+        assertEquals(4, aliceQuads.size());
+        
+        // Check the isPrimaryTopicOf statements in the default graph
+        int topics = 0;
+        for (Quad topic : dataset.iterate(null, null, isPrimaryTopicOf, null)) {
+            topics++;
+            // COMMONSRDF-55: should not be <urn:x-arq:defaultgraph> or similar
+            assertFalse(topic.getGraphName().isPresent());
+        }
+        assertEquals(2, topics);
+    }
+
+    @Test
+    public void streamDefaultGraphNameAlice() throws Exception {
+        // null below would match in ANY graph (including default graph)
+        Optional<? extends Quad> aliceTopic = dataset.stream(null, alice, isPrimaryTopicOf, null).findAny();
+        assertTrue(aliceTopic.isPresent());
+        // COMMONSRDF-55: should not be <urn:x-arq:defaultgraph> or similar
+        assertNull(aliceTopic.get().getGraphName().orElse(null));
+        assertFalse(aliceTopic.get().getGraphName().isPresent());
+    }
+
+
+    @Test
+    public void streamDefaultGraphNameByPattern() throws Exception {
+        // Explicitly select in only the default graph Optional.empty()
+        Optional<? extends Quad> aliceTopic = dataset.stream(Optional.empty(), null, null, null).findAny();
+        assertTrue(aliceTopic.isPresent());
+        // COMMONSRDF-55: should not be <urn:x-arq:defaultgraph> or similar
+        assertNull(aliceTopic.get().getGraphName().orElse(null));
+        assertFalse(aliceTopic.get().getGraphName().isPresent());
+    }
+    
+    
+    /**
+     * Special quad closing for RDF4J.
+     */
+    private void closeIterable(final Iterable<Quad> 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 Quad t : dataset.iterate(null, alice, knows, null)) {
+            friends.add(t.getObject());
+        }
+        assertEquals(1, friends.size());
+        assertEquals(bob, friends.get(0));
+
+        // .. can we iterate over zero hits?
+        final Iterable<Quad> iterate = dataset.iterate(Optional.of(graph2), bob, knows, alice);
+        for (final Quad unexpected : iterate) {
+            fail("Unexpected quad " + unexpected);
+        }
+        // closeIterable(iterate);
+    }
+
+    @Test
+    public void contains() throws Exception {
+        assertFalse(dataset.contains(null, bob, knows, alice)); // or so he claims..
+
+        assertTrue(dataset.contains(Optional.of(graph1), alice, knows, bob));
+
+        try (Stream<? extends Quad> stream = dataset.stream()) {
+            final Optional<? extends Quad> first = stream.skip(4).findFirst();
+            Assume.assumeTrue(first.isPresent());
+            final Quad existingQuad = first.get();
+            assertTrue(dataset.contains(existingQuad));
+        }
+
+        final Quad nonExistingQuad = factory.createQuad(graph2, bob, knows, alice);
+        assertFalse(dataset.contains(nonExistingQuad));
+
+        // An existing quad
+        final Quad quad = factory.createQuad(graph1, alice, knows, bob);
+        // FIXME: Should not this always be true?
+         assertTrue(dataset.contains(quad));
+    }
+
+    @Test
+    public void remove() throws Exception {
+        final long fullSize = dataset.size();
+        dataset.remove(Optional.of(graph1), alice, knows, bob);
+        final long shrunkSize = dataset.size();
+        assertEquals(1, fullSize - shrunkSize);
+
+        dataset.remove(Optional.of(graph1), alice, knows, bob);
+        assertEquals(shrunkSize, dataset.size()); // unchanged
+
+        dataset.add(graph1, alice, knows, bob);
+        dataset.add(graph2, alice, knows, bob);
+        dataset.add(graph2, alice, knows, bob);
+        // Undetermined size at this point -- but at least it
+        // should be bigger
+        assertTrue(dataset.size() > shrunkSize);
+
+        // and after a single remove they should all be gone
+        dataset.remove(null, alice, knows, bob);
+        assertEquals(shrunkSize, dataset.size());
+
+        Quad otherQuad;
+        try (Stream<? extends Quad> stream = dataset.stream()) {
+            final Optional<? extends Quad> anyQuad = stream.findAny();
+            Assume.assumeTrue(anyQuad.isPresent());
+            otherQuad = anyQuad.get();
+        }
+
+        dataset.remove(otherQuad);
+        assertEquals(shrunkSize - 1, dataset.size());
+        dataset.remove(otherQuad);
+        assertEquals(shrunkSize - 1, dataset.size()); // no change
+
+        // for some reason in rdf4j this causes duplicates!
+        dataset.add(otherQuad);
+        // dataset.stream().forEach(System.out::println);
+        // should have increased
+        assertTrue(dataset.size() >= shrunkSize);
+    }
+
+    @Test
+    public void clear() throws Exception {
+        dataset.clear();
+        assertFalse(dataset.contains(null, alice, knows, bob));
+        assertEquals(0, dataset.size());
+        dataset.clear(); // no-op
+        assertEquals(0, dataset.size());
+        assertFalse(dataset.contains(null, null, null, null)); // nothing here
+    }
+
+    @Test
+    public void getQuads() throws Exception {
+        long quadCount;
+        try (Stream<? extends Quad> stream = dataset.stream()) {
+            quadCount = stream.count();
+        }
+        assertTrue(quadCount > 0);
+
+        try (Stream<? extends Quad> stream = dataset.stream()) {
+            assertTrue(stream.allMatch(t -> dataset.contains(t)));
+        }
+
+        // Check exact count
+        Assume.assumeNotNull(bnode1, bnode2, aliceName, bobName, secretClubName, companyName, bobNameQuad);
+        assertEquals(10, quadCount);
+    }
+
+    @Test
+    public void getQuadsQuery() throws Exception {
+
+        try (Stream<? extends Quad> stream = dataset.stream(Optional.of(graph1), 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 Quad> stream = dataset.stream(null, null, name, null)) {
+            assertEquals(4, stream.count());
+        }
+        Assume.assumeNotNull(bnode1);
+        try (Stream<? extends Quad> stream = dataset.stream(null, null, member, null)) {
+            assertEquals(3, stream.count());
+        }
+    }
+
+    @Test
+    public void addBlankNodesFromMultipleDatasets() {
+            // Create two separate Dataset instances
+            final Dataset g1 = createDataset1();
+            final Dataset g2 = createDataset2();
+
+            // and add them to a new Dataset g3
+            final Dataset g3 = factory.createDataset();
+            addAllQuads(g1, g3);
+            addAllQuads(g2, g3);
+
+            // Let's make a map to find all those blank nodes after insertion
+            // (The Dataset 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 Quad> stream = g3.stream(null, 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");
+            // FIXME: Check graph2 BlankNode in these ..?
+            assertTrue(g3.contains(null, b1Alice, hasChild, b2Bob));
+            assertTrue(g3.contains(null, b2Dave, hasChild, b1Charlie));
+            // But not
+            assertFalse(g3.contains(null, b1Alice, hasChild, b1Alice));
+            assertFalse(g3.contains(null, b1Alice, hasChild, b1Charlie));
+            assertFalse(g3.contains(null, b1Alice, hasChild, b2Dave));
+            // nor
+            assertFalse(g3.contains(null, b2Dave, hasChild, b1Alice));
+            assertFalse(g3.contains(null, b2Dave, hasChild, b1Alice));
+
+            // and these don't have any children (as far as we know)
+            assertFalse(g3.contains(null, b2Bob, hasChild, null));
+            assertFalse(g3.contains(null, b1Charlie, hasChild, null));
+    }
+
+    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 dataset)
+        assertFalse(node1.ntriplesString().equals(node2.ntriplesString()));
+    }
+
+    /**
+     * Add all quads from the source to the target.
+     * <p>
+     * The quads may be copied in any order. No special conversion or
+     * adaptation of {@link BlankNode}s are performed.
+     *
+     * @param source
+     *            Source Dataset to copy quads from
+     * @param target
+     *            Target Dataset where quads will be added
+     */
+    private void addAllQuads(final Dataset source, final Dataset target) {
+
+        // unordered() as we don't need to preserve quad order
+        // sequential() as we don't (currently) require target Dataset to be
+        // thread-safe
+
+        try (Stream<? extends Quad> stream = source.stream()) {
+            stream.unordered().sequential().forEach(t -> target.add(t));
+        }
+    }
+
+    /**
+     * Make a new dataset with two BlankNodes - each with a different
+     * uniqueReference
+     */
+    private Dataset createDataset1() {
+        final RDF factory1 = createFactory();
+
+        final IRI name = factory1.createIRI("http://xmlns.com/foaf/0.1/name");
+        final Dataset g1 = factory1.createDataset();
+        final BlankNode b1 = createOwnBlankNode("b1", "0240eaaa-d33e-4fc0-a4f1-169d6ced3680");
+        g1.add(b1, b1, name, factory1.createLiteral("Alice"));
+
+        final BlankNode b2 = createOwnBlankNode("b2", "9de7db45-0ce7-4b0f-a1ce-c9680ffcfd9f");
+        g1.add(b2, b2, name, factory1.createLiteral("Bob"));
+
+        final IRI hasChild = factory1.createIRI("http://example.com/hasChild");
+        g1.add(null, b1, hasChild, b2);
+
+        return g1;
+    }
+
+    /**
+     * Create a different implementation of BlankNode to be tested with
+     * dataset.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 Dataset createDataset2() {
+        final RDF factory2 = createFactory();
+        final IRI name = factory2.createIRI("http://xmlns.com/foaf/0.1/name");
+
+        final Dataset g2 = factory2.createDataset();
+
+        final BlankNode b1 = createOwnBlankNode("b1", "bc8d3e45-a08f-421d-85b3-c25b373abf87");
+        g2.add(b1, b1, name, factory2.createLiteral("Charlie"));
+
+        final BlankNode b2 = createOwnBlankNode("b2", "2209097a-5078-4b03-801a-6a2d2f50d739");
+        g2.add(b2, b2, name, factory2.createLiteral("Dave"));
+
+        final IRI hasChild = factory2.createIRI("http://example.com/hasChild");
+        // NOTE: Opposite direction of loadDataset1
+        g2.add(b2, b2, hasChild, b1);
+        return g2;
+    }
+    
+    /**
+     * Ensure {@link Dataset#getGraphNames()} contains our two graphs.
+     * 
+     * @throws Exception
+     *             If test fails
+     */
+    @Test
+    public void getGraphNames() throws Exception {
+        final Set<BlankNodeOrIRI> names = dataset.getGraphNames().collect(Collectors.toSet());        
+        assertTrue("Can't find graph name " + graph1, names.contains(graph1));
+        assertTrue("Found no quads in graph1", dataset.contains(Optional.of(graph1), null, null, null));
+        
+        final Optional<BlankNodeOrIRI> graphName2 = dataset.getGraphNames().filter(BlankNode.class::isInstance).findAny();
+        assertTrue("Could not find graph2-like BlankNode", graphName2.isPresent()); 
+        assertTrue("Found no quads in graph2", dataset.contains(graphName2, null, null, null));
+
+        // Some implementations like Virtuoso might have additional internal graphs,
+        // so we can't assume this:
+        //assertEquals(2, names.size());
+    }
+    
+    @Test
+    public void getGraph() throws Exception {
+        final Graph defaultGraph = dataset.getGraph();
+        // TODO: Can we assume the default graph was empty before our new triples?
+        assertEquals(2, defaultGraph.size());
+        assertTrue(defaultGraph.contains(alice, isPrimaryTopicOf, graph1));
+        // NOTE: graph2 is a BlankNode
+        assertTrue(defaultGraph.contains(bob, isPrimaryTopicOf, null));
+    }
+
+
+    @Test
+    public void getGraphNull() throws Exception {
+        // Default graph should be present
+        final Graph defaultGraph = dataset.getGraph(null).get();
+        // TODO: Can we assume the default graph was empty before our new triples?
+        assertEquals(2, defaultGraph.size());
+        assertTrue(defaultGraph.contains(alice, isPrimaryTopicOf, graph1));
+        // NOTE: wildcard as graph2 is a (potentially mapped) BlankNode
+        assertTrue(defaultGraph.contains(bob, isPrimaryTopicOf, null));
+    }
+    
+
+    @Test
+    public void getGraph1() throws Exception {
+        // graph1 should be present
+        final Graph g1 = dataset.getGraph(graph1).get();
+        assertEquals(4, g1.size());
+        
+        assertTrue(g1.contains(alice, name, aliceName));
+        assertTrue(g1.contains(alice, knows, bob));
+        assertTrue(g1.contains(alice, member, null));
+        assertTrue(g1.contains(null, name, secretClubName));
+    }
+
+    @Test
+    public void getGraph2() throws Exception {
+        // graph2 should be present, even if is named by a BlankNode
+        // We'll look up the potentially mapped graph2 blanknode
+        final BlankNodeOrIRI graph2Name = (BlankNodeOrIRI) dataset.stream(Optional.empty(), bob, isPrimaryTopicOf, null)
+                .map(Quad::getObject).findAny().get();
+        
+        final Graph g2 = dataset.getGraph(graph2Name).get();
+        assertEquals(4, g2.size());
+        final Triple bobNameTriple = bobNameQuad.asTriple();
+        assertTrue(g2.contains(bobNameTriple));
+        assertTrue(g2.contains(bob, member, bnode1));
+        assertTrue(g2.contains(bob, member, bnode2));
+        assertFalse(g2.contains(bnode1, name, secretClubName));
+        assertTrue(g2.contains(bnode2, name, companyName));
+    }
+    
+
+    @Test
+    public void containsLanguageTagsCaseInsensitive() {
+        // COMMONSRDF-51: Ensure we can add/contains/remove with any casing
+        // of literal language tag
+        final Literal lower = factory.createLiteral("Hello there", "en-gb");
+        final Literal upper = factory.createLiteral("Hello there", "EN-GB");
+        final Literal mixed = factory.createLiteral("Hello there", "en-GB");
+
+        final IRI example1 = factory.createIRI("http://example.com/s1");
+        final IRI greeting = factory.createIRI("http://example.com/greeting");
+
+        
+        dataset.add(null, example1, greeting, upper);
+        
+        // any kind of Triple should match
+        assertTrue(dataset.contains(factory.createQuad(null, example1, greeting, upper)));
+        assertTrue(dataset.contains(factory.createQuad(null, example1, greeting, lower)));
+        assertTrue(dataset.contains(factory.createQuad(null, example1, greeting, mixed)));
+
+        // or as patterns
+        assertTrue(dataset.contains(null, null, null, upper));
+        assertTrue(dataset.contains(null, null, null, lower));
+        assertTrue(dataset.contains(null, null, null, mixed));
+    }
+
+    @Test
+    public void containsLanguageTagsCaseInsensitiveTurkish() {
+        // 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
+        Locale defaultLocale = Locale.getDefault();
+        try {
+            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");
+            dataset.add(null, exampleROOT, greeting, mixedROOT);
+
+            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");
+            dataset.add(null, exampleTR, greeting, upper);
+            assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, upper)));
+            assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, upperROOT)));
+            assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, lower)));
+            assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, lowerROOT)));
+            assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, mixed)));
+            assertTrue(dataset.contains(factory.createQuad(null, exampleTR, greeting, mixedROOT)));
+            assertTrue(dataset.contains(null, exampleTR, null, upper));
+            assertTrue(dataset.contains(null, exampleTR, null, upperROOT));
+            assertTrue(dataset.contains(null, exampleTR, null, lower));
+            assertTrue(dataset.contains(null, exampleTR, null, lowerROOT));
+            assertTrue(dataset.contains(null, exampleTR, null, mixed));
+            assertTrue(dataset.contains(null, exampleTR, null, mixedROOT));
+
+            // What about the triple we added while in ROOT locale?
+            assertTrue(dataset.contains(factory.createQuad(null, exampleROOT, greeting, upper)));
+            assertTrue(dataset.contains(factory.createQuad(null, exampleROOT, greeting, lower)));
+            assertTrue(dataset.contains(factory.createQuad(null, exampleROOT, greeting, mixed)));
+            assertTrue(dataset.contains(null, exampleROOT, null, upper));
+            assertTrue(dataset.contains(null, exampleROOT, null, lower));
+            assertTrue(dataset.contains(null, exampleROOT, null, mixed));
+        } finally {
+            Locale.setDefault(defaultLocale);
+        }
+    }
+    
+
+    @Test
+    public void removeLanguageTagsCaseInsensitive() {
+        // COMMONSRDF-51: Ensure we can remove with any casing
+        // of literal language tag
+        final Literal lower = factory.createLiteral("Howdy", "en-us");
+        final Literal upper = factory.createLiteral("Howdy", "EN-US");
+        final Literal mixed = factory.createLiteral("Howdy", "en-US");
+
+        final IRI example1 = factory.createIRI("http://example.com/s1");
+        final IRI greeting = factory.createIRI("http://example.com/greeting");
+
+        dataset.add(null, example1, greeting, upper);
+
+        // Remove should also honour any case
+        dataset.remove(null, example1, null, mixed);
+        assertFalse(dataset.contains(null, null, greeting, null));
+        
+        dataset.add(null, example1, greeting, lower);
+        dataset.remove(null, example1, null, upper);
+
+        // Check with Triple
+        dataset.add(factory.createQuad(null, example1, greeting, mixed));
+        dataset.remove(factory.createQuad(null, example1, greeting, upper));
+        assertFalse(dataset.contains(null, null, greeting, null));
+    }
+
+    private static Optional<? extends Quad> closableFindAny(Stream<? extends Quad> stream) {
+        try (Stream<? extends Quad> s = stream) {
+            return s.findAny();
+        }
+    }
+    
+    @Test
+    public void streamLanguageTagsCaseInsensitive() {
+        // COMMONSRDF-51: Ensure we can add/contains/remove with any casing
+        // of literal language tag
+        final Literal lower = factory.createLiteral("Good afternoon", "en-gb");
+        final Literal upper = factory.createLiteral("Good afternoon", "EN-GB");
+        final Literal mixed = factory.createLiteral("Good afternoon", "en-GB");
+
+        final IRI example1 = factory.createIRI("http://example.com/s1");
+        final IRI greeting = factory.createIRI("http://example.com/greeting");
+
+        dataset.add(null, example1, greeting, upper);
+
+        // or as patterns
+        assertTrue(closableFindAny(dataset.stream(null, null, null, upper)).isPresent());
+        assertTrue(closableFindAny(dataset.stream(null, null, null, lower)).isPresent());
+        assertTrue(closableFindAny(dataset.stream(null, null, null, mixed)).isPresent());
+        
+        // Check the quad returned equal a new quad
+        Quad q = closableFindAny(dataset.stream(null, null, null, lower)).get();
+        assertEquals(q, factory.createQuad(null, example1, greeting, mixed));
+    }
+
+    /**
+     * 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 Quad> stream = dataset.stream(null, null, knows, null)) {
+            assertEquals("\"The Secret Club\"",
+                    // Find One-way "knows"
+                    stream.filter(t -> !dataset.contains(null, (BlankNodeOrIRI) t.getObject(), knows, t.getSubject()))
+                            .map(knowsQuad -> {
+                                try (Stream<? extends Quad> memberOf = dataset
+                                        // and those they know, what are they
+                                        // member of?
+                                        .stream(null, (BlankNodeOrIRI) knowsQuad.getObject(), member, null)) {
+                                    return memberOf
+                                            // keep those which first-guy is a
+                                            // member of
+                                            .filter(memberQuad -> dataset.contains(null, knowsQuad.getSubject(), member,
+                                                    // First hit is good enough
+                                                    memberQuad.getObject()))
+                                            .findFirst().get().getObject();
+                                }
+                            })
+                            // then look up the name of that org
+                            .map(org -> {
+                                try (Stream<? extends Quad> orgName = dataset.stream(null, (BlankNodeOrIRI) org, name,
+                                        null)) {
+                                    return orgName.findFirst().get().getObject().ntriplesString();
+                                }
+                            }).findFirst().get());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-rdf/blob/d59203ce/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractGraphTest.java
----------------------------------------------------------------------
diff --git a/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractGraphTest.java b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractGraphTest.java
new file mode 100644
index 0000000..3020704
--- /dev/null
+++ b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractGraphTest.java
@@ -0,0 +1,683 @@
+/**
+ * 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() {
+
+        try {
+            // Create two separate Graph instances
+            final Graph g1 = createGraph1();
+            final Graph g2 = createGraph2();
+
+            // and add them to a new Graph g3
+            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() {
+        // 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");
+
+        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() {
+        // 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
+        Locale defaultLocale = Locale.getDefault();
+        try {
+            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 Graph g = factory.createGraph();
+            final IRI exampleROOT = factory.createIRI("http://example.com/s1");
+            final IRI greeting = factory.createIRI("http://example.com/greeting");
+            g.add(exampleROOT, greeting, mixedROOT);
+
+            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() {
+        // 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");
+
+        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(Stream<? extends Triple> stream) {
+        try (Stream<? extends Triple> s = stream) {
+            return s.findAny();
+        }
+    }
+    
+    @Test
+    public void streamLanguageTagsCaseInsensitive() {
+        // 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");
+
+        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
+        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());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-rdf/blob/d59203ce/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractRDFTest.java
----------------------------------------------------------------------
diff --git a/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractRDFTest.java b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractRDFTest.java
new file mode 100644
index 0000000..bb4dc5b
--- /dev/null
+++ b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/AbstractRDFTest.java
@@ -0,0 +1,454 @@
+/**
+ * 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.*;
+
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test RDF implementation (and thus its RDFTerm implementations)
+ * <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
+ * supports one of the operations, but ideally supports all operations.
+ *
+ * @see RDF
+ */
+public abstract class AbstractRDFTest {
+
+    private RDF factory;
+
+    /**
+     *
+     * This method must be overridden by the implementing test to provide a
+     * factory for the test to create {@link Literal}, {@link IRI} etc.
+     *
+     * @return {@link RDF} instance to be tested.
+     */
+    protected abstract RDF createFactory();
+
+    @Before
+    public void setUp() {
+        factory = createFactory();
+    }
+
+    @Test
+    public void testCreateBlankNode() throws Exception {
+        final BlankNode bnode = factory.createBlankNode();
+
+        final BlankNode bnode2 = factory.createBlankNode();
+        assertNotEquals("Second blank node has not got a unique internal identifier", bnode.uniqueReference(),
+                bnode2.uniqueReference());
+    }
+
+    @Test
+    public void testCreateBlankNodeIdentifierEmpty() throws Exception {
+        try {
+            factory.createBlankNode("");
+        } catch (final IllegalArgumentException e) {
+            // Expected exception
+        }
+    }
+
+    @Test
+    public void testCreateBlankNodeIdentifier() throws Exception {
+        factory.createBlankNode("example1");
+    }
+
+    @Test
+    public void testCreateBlankNodeIdentifierTwice() throws Exception {
+        BlankNode bnode1, bnode2, bnode3;
+        bnode1 = factory.createBlankNode("example1");
+        bnode2 = factory.createBlankNode("example1");
+        bnode3 = factory.createBlankNode("differ");
+        // We don't know what the identifier is, but it MUST be the same
+        assertEquals(bnode1.uniqueReference(), bnode2.uniqueReference());
+        // We don't know what the ntriplesString is, but it MUST be the same
+        assertEquals(bnode1.ntriplesString(), bnode2.ntriplesString());
+        // and here it MUST differ
+        assertNotEquals(bnode1.uniqueReference(), bnode3.uniqueReference());
+        assertNotEquals(bnode1.ntriplesString(), bnode3.ntriplesString());
+    }
+
+    @Test
+    public void testCreateBlankNodeIdentifierTwiceDifferentFactories() throws Exception {
+        BlankNode bnode1, differentFactory;
+        bnode1 = factory.createBlankNode();
+        // it MUST differ from a second factory
+        differentFactory = createFactory().createBlankNode();
+
+        // NOTE: We can't make similar assumption if we provide a
+        // name to createBlankNode(String) as its documentation
+        // only says:
+        //
+        // * BlankNodes created using this method with the same parameter, for
+        // * different instances of RDFFactory, SHOULD NOT be equivalent.
+        //
+        // https://github.com/apache/incubator-commonsrdf/pull/7#issuecomment-92312779
+        assertNotEquals(bnode1, differentFactory);
+        assertNotEquals(bnode1.uniqueReference(), differentFactory.uniqueReference());
+        // but we can't require:
+        // assertNotEquals(bnode1.ntriplesString(),
+        // differentFactory.ntriplesString());
+    }
+
+    @Test
+    public void testCreateGraph() {
+        final Graph graph = factory.createGraph();
+
+        assertEquals("Graph was not empty", 0, graph.size());
+        graph.add(factory.createBlankNode(), factory.createIRI("http://example.com/"), factory.createBlankNode());
+
+        final Graph graph2 = factory.createGraph();
+        assertNotSame(graph, graph2);
+        assertEquals("Graph was empty after adding", 1, graph.size());
+        assertEquals("New graph was not empty", 0, graph2.size());
+    }
+
+    @Test
+    public void testCreateIRI() throws Exception {
+        final IRI example = factory.createIRI("http://example.com/");
+
+        assertEquals("http://example.com/", example.getIRIString());
+        assertEquals("<http://example.com/>", example.ntriplesString());
+
+        final IRI term = factory.createIRI("http://example.com/vocab#term");
+        assertEquals("http://example.com/vocab#term", term.getIRIString());
+        assertEquals("<http://example.com/vocab#term>", term.ntriplesString());
+
+        // and now for the international fun!
+        // make sure this file is edited/compiled as UTF-8
+        final IRI latin1 = factory.createIRI("http://accént.example.com/première");
+        assertEquals("http://accént.example.com/première", latin1.getIRIString());
+        assertEquals("<http://accént.example.com/première>", latin1.ntriplesString());
+
+        final IRI cyrillic = factory.createIRI("http://example.испытание/Кириллица");
+        assertEquals("http://example.испытание/Кириллица", cyrillic.getIRIString());
+        assertEquals("<http://example.испытание/Кириллица>", cyrillic.ntriplesString());
+
+        final IRI deseret = factory.createIRI("http://𐐀.example.com/𐐀");
+        assertEquals("http://𐐀.example.com/𐐀", deseret.getIRIString());
+        assertEquals("<http://𐐀.example.com/𐐀>", deseret.ntriplesString());
+    }
+
+    @Test
+    public void testCreateLiteral() throws Exception {
+        final Literal example = factory.createLiteral("Example");
+        assertEquals("Example", example.getLexicalForm());
+        assertFalse(example.getLanguageTag().isPresent());
+        assertEquals("http://www.w3.org/2001/XMLSchema#string", example.getDatatype().getIRIString());
+        // http://lists.w3.org/Archives/Public/public-rdf-comments/2014Dec/0004.html
+        assertEquals("\"Example\"", example.ntriplesString());
+    }
+
+    @Test
+    public void testCreateLiteralDateTime() throws Exception {
+        final Literal dateTime = factory.createLiteral("2014-12-27T00:50:00T-0600",
+                factory.createIRI("http://www.w3.org/2001/XMLSchema#dateTime"));
+        assertEquals("2014-12-27T00:50:00T-0600", dateTime.getLexicalForm());
+        assertFalse(dateTime.getLanguageTag().isPresent());
+        assertEquals("http://www.w3.org/2001/XMLSchema#dateTime", dateTime.getDatatype().getIRIString());
+        assertEquals("\"2014-12-27T00:50:00T-0600\"^^<http://www.w3.org/2001/XMLSchema#dateTime>",
+                dateTime.ntriplesString());
+    }
+
+    @Test
+    public void testCreateLiteralLang() throws Exception {
+        final Literal example = factory.createLiteral("Example", "en");
+
+        assertEquals("Example", example.getLexicalForm());
+        assertEquals("en", example.getLanguageTag().get());
+        assertEquals("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString", example.getDatatype().getIRIString());
+        assertEquals("\"Example\"@en", example.ntriplesString());
+    }
+
+    @Test
+    public void testCreateLiteralLangISO693_3() throws Exception {
+        // see https://issues.apache.org/jira/browse/JENA-827
+        final Literal vls = factory.createLiteral("Herbert Van de Sompel", "vls"); // JENA-827
+
+        assertEquals("vls", vls.getLanguageTag().get());
+        assertEquals("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString", vls.getDatatype().getIRIString());
+        assertEquals("\"Herbert Van de Sompel\"@vls", vls.ntriplesString());
+    }
+
+
+    private void assertEqualsBothWays(Object a, Object b) {
+        assertEquals(a, b);
+        assertEquals(b, a);
+        // hashCode must match as well
+        assertEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testCreateLiteralLangCaseInsensitive() throws Exception {
+        /*
+         * COMMONSRDF-51: Literal langtag may not be in lowercase, but must be
+         * COMPARED (aka .equals and .hashCode()) in lowercase as the language
+         * space is lower case.
+         */
+        final Literal upper = factory.createLiteral("Hello", "EN-GB");
+        final Literal lower = factory.createLiteral("Hello", "en-gb");
+        final Literal mixed = factory.createLiteral("Hello", "en-GB");
+
+        /*
+         * Disabled as some RDF frameworks (namely RDF4J) can be c configured to
+         * do BCP47 normalization (e.g. "en-GB"), so we can't guarantee
+         * lowercase language tags are preserved.
+         */
+        // assertEquals("en-gb", lower.getLanguageTag().get());
+
+        /*
+         * NOTE: the RDF framework is free to lowercase the language tag or
+         * leave it as-is, so we can't assume:
+         */
+        // assertEquals("en-gb", upper.getLanguageTag().get());
+        // assertEquals("en-gb", mixed.getLanguageTag().get());
+
+        /* ..unless we do a case-insensitive comparison: */
+        assertEquals("en-gb",
+                lower.getLanguageTag().get().toLowerCase(Locale.ROOT));
+        assertEquals("en-gb",
+                upper.getLanguageTag().get().toLowerCase(Locale.ROOT));
+        assertEquals("en-gb",
+                mixed.getLanguageTag().get().toLowerCase(Locale.ROOT));
+
+        // However these should all be true using .equals
+        assertEquals(lower, lower);
+        assertEqualsBothWays(lower, upper);
+        assertEqualsBothWays(lower, mixed);
+        assertEquals(upper, upper);
+        assertEqualsBothWays(upper, mixed);
+        assertEquals(mixed, mixed);
+        // Note that assertEqualsBothWays also checks
+        // that .hashCode() matches
+    }
+
+    @Test
+    public void testCreateLiteralLangCaseInsensitiveOther() throws Exception {
+        // COMMONSRDF-51: Ensure the Literal is using case insensitive
+        // comparison against any literal implementation
+        // which may not have done .toLowerString() in their constructor
+        final Literal upper = factory.createLiteral("Hello", "EN-GB");
+        final Literal lower = factory.createLiteral("Hello", "en-gb");
+        final Literal mixed = factory.createLiteral("Hello", "en-GB");
+
+        Literal otherLiteral = new Literal() {
+            @Override
+            public String ntriplesString() {
+                return "Hello@eN-Gb";
+            }
+            @Override
+            public String getLexicalForm() {
+                return "Hello";
+            }
+            @Override
+            public Optional<String> getLanguageTag() {
+                return Optional.of("eN-Gb");
+            }
+            @Override
+            public IRI getDatatype() {
+                return factory.createIRI("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString");
+            }
+            @Override
+            public boolean equals(Object obj) {
+                throw new RuntimeException("Wrong way comparison of literal");
+            }
+        };
+
+        // NOTE: Our fake Literal can't do .equals() or .hashCode(),
+        // so don't check the wrong way around!
+        assertEquals(mixed, otherLiteral);
+        assertEquals(lower, otherLiteral);
+        assertEquals(upper, otherLiteral);
+    }
+
+    @Test
+    public void testCreateLiteralLangCaseInsensitiveInTurkish() 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/
+        Locale defaultLocale = Locale.getDefault();
+        try {
+            Locale.setDefault(Locale.ROOT);
+            final Literal mixedROOT = factory.createLiteral("moi", "fI");
+            final Literal lowerROOT = factory.createLiteral("moi", "fi");
+            final Literal upperROOT = factory.createLiteral("moi", "FI");
+
+            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"));
+
+            final Literal mixed = factory.createLiteral("moi", "fI");
+            final Literal lower = factory.createLiteral("moi", "fi");
+            final Literal upper = factory.createLiteral("moi", "FI");
+
+            assertEquals(lower, lower);
+            assertEqualsBothWays(lower, upper);
+            assertEqualsBothWays(lower, mixed);
+
+            assertEquals(upper, upper);
+            assertEqualsBothWays(upper, mixed);
+
+            assertEquals(mixed, mixed);
+
+            // And our instance created previously in ROOT locale
+            // should still be equal to the instance created in TR locale
+            // (e.g. test constructor is not doing a naive .toLowerCase())
+            assertEqualsBothWays(lower, lowerROOT);
+            assertEqualsBothWays(upper, lowerROOT);
+            assertEqualsBothWays(mixed, lowerROOT);
+
+            assertEqualsBothWays(lower, upperROOT);
+            assertEqualsBothWays(upper, upperROOT);
+            assertEqualsBothWays(mixed, upperROOT);
+
+            assertEqualsBothWays(lower, mixedROOT);
+            assertEqualsBothWays(upper, mixedROOT);
+            assertEqualsBothWays(mixed, mixedROOT);
+        } finally {
+            Locale.setDefault(defaultLocale);
+        }
+    }
+
+    @Test
+    public void testCreateLiteralString() throws Exception {
+        final Literal example = factory.createLiteral("Example",
+                factory.createIRI("http://www.w3.org/2001/XMLSchema#string"));
+        assertEquals("Example", example.getLexicalForm());
+        assertFalse(example.getLanguageTag().isPresent());
+        assertEquals("http://www.w3.org/2001/XMLSchema#string", example.getDatatype().getIRIString());
+        // http://lists.w3.org/Archives/Public/public-rdf-comments/2014Dec/0004.html
+        assertEquals("\"Example\"", example.ntriplesString());
+    }
+
+    @Test
+    public void testCreateTripleBnodeBnode() {
+        final BlankNode subject = factory.createBlankNode("b1");
+        final IRI predicate = factory.createIRI("http://example.com/pred");
+        final BlankNode object = factory.createBlankNode("b2");
+        final Triple triple = factory.createTriple(subject, predicate, object);
+
+        // bnode equivalence should be OK as we used the same
+        // factory and have not yet inserted Triple into a Graph
+        assertEquals(subject, triple.getSubject());
+        assertEquals(predicate, triple.getPredicate());
+        assertEquals(object, triple.getObject());
+    }
+
+    @Test
+    public void testCreateTripleBnodeIRI() {
+        final BlankNode subject = factory.createBlankNode("b1");
+        final IRI predicate = factory.createIRI("http://example.com/pred");
+        final IRI object = factory.createIRI("http://example.com/obj");
+        final Triple triple = factory.createTriple(subject, predicate, object);
+
+        // bnode equivalence should be OK as we used the same
+        // factory and have not yet inserted Triple into a Graph
+        assertEquals(subject, triple.getSubject());
+        assertEquals(predicate, triple.getPredicate());
+        assertEquals(object, triple.getObject());
+    }
+
+    @Test
+    public void testCreateTripleBnodeTriple() {
+        final BlankNode subject = factory.createBlankNode();
+        final IRI predicate = factory.createIRI("http://example.com/pred");
+        final Literal object = factory.createLiteral("Example", "en");
+        final Triple triple = factory.createTriple(subject, predicate, object);
+
+        // bnode equivalence should be OK as we used the same
+        // factory and have not yet inserted Triple into a Graph
+        assertEquals(subject, triple.getSubject());
+        assertEquals(predicate, triple.getPredicate());
+        assertEquals(object, triple.getObject());
+    }
+
+    @Test
+    public void testPossiblyInvalidBlankNode() throws Exception {
+        BlankNode withColon;
+        try {
+            withColon = factory.createBlankNode("with:colon");
+        } catch (final IllegalArgumentException ex) {
+            // Good!
+            return;
+        }
+        // Factory allows :colon, which is OK as long as it's not causing an
+        // invalid ntriplesString
+        assertFalse(withColon.ntriplesString().contains("with:colon"));
+
+        // and creating it twice gets the same ntriplesString
+        assertEquals(withColon.ntriplesString(), factory.createBlankNode("with:colon").ntriplesString());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidIRI() throws Exception {
+        factory.createIRI("<no_brackets>");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLiteralLang() throws Exception {
+        factory.createLiteral("Example", "with space");
+    }
+
+    @Test(expected = Exception.class)
+    public void testInvalidTriplePredicate() {
+        final BlankNode subject = factory.createBlankNode("b1");
+        final BlankNode predicate = factory.createBlankNode("b2");
+        final BlankNode object = factory.createBlankNode("b3");
+        factory.createTriple(subject, (IRI) predicate, object);
+    }
+
+    @Test
+    public void hashCodeBlankNode() throws Exception {
+        final BlankNode bnode1 = factory.createBlankNode();
+        assertEquals(bnode1.uniqueReference().hashCode(), bnode1.hashCode());
+    }
+
+    @Test
+    public void hashCodeIRI() throws Exception {
+        final IRI iri = factory.createIRI("http://example.com/");
+        assertEquals(iri.getIRIString().hashCode(), iri.hashCode());
+    }
+
+    @Test
+    public void hashCodeLiteral() throws Exception {
+        final Literal literal = factory.createLiteral("Hello");
+        assertEquals(Objects.hash(literal.getLexicalForm(), literal.getDatatype(), literal.getLanguageTag()),
+                literal.hashCode());
+    }
+
+    @Test
+    public void hashCodeTriple() throws Exception {
+        final IRI iri = factory.createIRI("http://example.com/");
+        final Triple triple = factory.createTriple(iri, iri, iri);
+        assertEquals(Objects.hash(iri, iri, iri), triple.hashCode());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-rdf/blob/d59203ce/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultDatasetTest.java
----------------------------------------------------------------------
diff --git a/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultDatasetTest.java b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultDatasetTest.java
new file mode 100644
index 0000000..024c2cf
--- /dev/null
+++ b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultDatasetTest.java
@@ -0,0 +1,58 @@
+/**
+ * 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.assertTrue;
+
+import org.junit.Test;
+
+public class DefaultDatasetTest {
+    
+    DummyDataset dataset = new DummyDataset();
+    
+    @Test
+    public void close() throws Exception {
+        dataset.close(); // no-op
+    }
+    
+    @Test
+    public void defaultIterate() throws Exception {
+        assertFalse(dataset.streamCalled);
+        assertFalse(dataset.filteredStreamCalled);
+        for (final Quad t : dataset.iterate()) {
+            assertEquals(t, new DummyQuad());
+        }
+        assertTrue(dataset.streamCalled);
+        assertFalse(dataset.filteredStreamCalled);
+    }
+    
+    @Test
+    public void defaultFilteredIterate() throws Exception {
+        assertFalse(dataset.streamCalled);
+        assertFalse(dataset.filteredStreamCalled);
+        for (final Quad t : dataset.iterate(null, null, new DummyIRI(2), null)) {
+            assertEquals(t, new DummyQuad());
+        }
+        assertTrue(dataset.filteredStreamCalled);
+        assertFalse(dataset.streamCalled);
+    }
+    
+}
+

http://git-wip-us.apache.org/repos/asf/commons-rdf/blob/d59203ce/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultGraphTest.java
----------------------------------------------------------------------
diff --git a/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultGraphTest.java b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultGraphTest.java
new file mode 100644
index 0000000..8d6f337
--- /dev/null
+++ b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultGraphTest.java
@@ -0,0 +1,78 @@
+/**
+ * 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.*;
+
+import org.junit.Test;
+
+public class DefaultGraphTest {
+    
+    DummyGraph graph = new DummyGraph();
+    
+    @Test
+    public void close() throws Exception {
+        graph.close(); // no-op
+    }
+    
+    @SuppressWarnings("deprecation")
+    @Test
+    public void defaultGetTriples() throws Exception {
+        assertFalse(graph.streamCalled);
+        assertFalse(graph.filteredStreamCalled);
+        assertEquals(1L, graph.getTriples().count());        
+        assertTrue(graph.streamCalled);
+        assertFalse(graph.filteredStreamCalled);        
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void defaultGetTriplesFiltered() throws Exception {
+        assertFalse(graph.streamCalled);
+        assertFalse(graph.filteredStreamCalled);
+        assertEquals(1L, graph.getTriples(null,null,null).count());
+        assertFalse(graph.streamCalled);
+        assertTrue(graph.filteredStreamCalled);
+        // Ensure arguments are passed on to graph.stream(s,p,o);
+        assertEquals(0L, graph.getTriples(new DummyIRI(0),null,null).count());
+    }
+    
+    @Test
+    public void defaultIterate() throws Exception {
+        assertFalse(graph.streamCalled);
+        assertFalse(graph.filteredStreamCalled);
+        for (final Triple t : graph.iterate()) {
+            assertEquals(t, new DummyTriple());
+        }
+        assertTrue(graph.streamCalled);
+        assertFalse(graph.filteredStreamCalled);
+    }
+    
+    @Test
+    public void defaultFilteredIterate() throws Exception {
+        assertFalse(graph.streamCalled);
+        assertFalse(graph.filteredStreamCalled);
+        for (final Triple t : graph.iterate(null, new DummyIRI(2), null)) {
+            assertEquals(t, new DummyTriple());
+        }
+        assertTrue(graph.filteredStreamCalled);
+        assertFalse(graph.streamCalled);
+    }
+    
+}
+

http://git-wip-us.apache.org/repos/asf/commons-rdf/blob/d59203ce/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultQuadTest.java
----------------------------------------------------------------------
diff --git a/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultQuadTest.java b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultQuadTest.java
new file mode 100644
index 0000000..00d66b5
--- /dev/null
+++ b/commons-rdf-api/src/test/java/org/apache/commons/rdf/api/DefaultQuadTest.java
@@ -0,0 +1,47 @@
+/**
+ * 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.*;
+
+import java.util.Objects;
+
+import org.junit.Test;
+
+public class DefaultQuadTest {
+    @Test
+    public void asQuad() throws Exception {
+        final Quad q = new DummyQuad();
+        final Triple t = q.asTriple();
+        assertEquals(t, t);
+        assertNotEquals(t,  q);
+        assertEquals(t, new DummyTriple());
+        assertEquals(t, new DummyQuad().asTriple());
+        
+        // FIXME: This would not catch if asTriple() accidentally mixed up s/p/o
+        // as they are here all the same
+        assertEquals(new DummyIRI(1), t.getSubject());
+        assertEquals(new DummyIRI(2), t.getPredicate());
+        assertEquals(new DummyIRI(3), t.getObject());
+        
+        
+        
+        assertEquals(Objects.hash(q.getSubject(), q.getPredicate(), q.getObject()), t.hashCode());
+    }
+    
+}