Merge pull request #645 from projekt-opal/master

DCAT vocabulary version 2
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
index 60d3390..57516eb 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/ResultSetCompare.java
@@ -27,9 +27,9 @@
 import org.apache.jena.sparql.core.Var ;
 import org.apache.jena.sparql.engine.binding.Binding ;
 import org.apache.jena.sparql.engine.binding.BindingUtils ;
+import org.apache.jena.sparql.util.EqualityTest;
 import org.apache.jena.sparql.util.NodeIsomorphismMap ;
 import org.apache.jena.sparql.util.NodeUtils ;
-import org.apache.jena.sparql.util.NodeUtils.EqualityTest ;
 
 /** Comparison of ResultSets.
  *  Note that reading ResultSets is destructive so consider using {@link ResultSetRewindable}
@@ -134,7 +134,7 @@
         ResultSetRewindable rs2a = ResultSetFactory.makeRewindable(rs2) ;
         
         // Aligned rows
-        if ( equivalent(convert(rs1a), convert(rs2a), new BNodeIso(NodeUtils.sameTerm)) )
+        if ( equivalent(convert(rs1a), convert(rs2a), new BNodeIso(NodeUtils.sameNode)) )
             return true ;
         rs1a.reset() ;    
         rs2a.reset() ;
@@ -161,7 +161,7 @@
     }
 
     /** compare two result sets for equivalence.  Equivalence means:
-     * Each row in rs1 matchs the same index row in rs2.
+     * Each row in rs1 matches the same index row in rs2.
      * Rows match if they have the same variables with the same values, 
      * bNodes must map to a consistent other bNodes.  
      * RDF term comparisons of nodes.   
@@ -172,8 +172,9 @@
      * @return true if they are equivalent
      */
     public static boolean equalsByTermAndOrder(ResultSet rs1, ResultSet rs2) {
-        if ( ! compareHeader(rs1, rs2) ) return false ;
-        return equivalentByOrder(convert(rs1) , convert(rs2), new BNodeIso(NodeUtils.sameTerm)) ;
+        if ( ! compareHeader(rs1, rs2) )
+            return false ;
+        return equivalentByOrder(convert(rs1) , convert(rs2), new BNodeIso(NodeUtils.sameNode)) ;
     }
 
     /** compare two result sets for exact equality equivalence.
@@ -187,11 +188,10 @@
      * @param rs2
      * @return true if they are equivalent
      */
-    public static boolean equalsExact(ResultSet rs1, ResultSet rs2)
-    {
-        if ( ! compareHeader(rs1, rs2) ) return false ;
-
-        return equivalentByOrder(convert(rs1) , convert(rs2), new EqualityTest(){}) ;
+    public static boolean equalsExact(ResultSet rs1, ResultSet rs2) {
+        if ( !compareHeader(rs1, rs2) )
+            return false;
+        return equivalentByOrder(convert(rs1) , convert(rs2), NodeUtils.sameNode);
     }
 
     /** Compare two result sets for bNode isomorphism equivalence.
@@ -204,7 +204,7 @@
     }
     
     /** Compare two bindings, use the node equality test provided */
-    static public boolean equal(Binding bind1, Binding bind2, NodeUtils.EqualityTest test) {
+    static public boolean equal(Binding bind1, Binding bind2, EqualityTest test) {
         if ( bind1 == bind2 ) 
             return true ;
         if ( bind1.size() != bind2.size() )
@@ -325,6 +325,9 @@
             if ( n1.isBlank() && n2.isBlank() )
                 return mapping.makeIsomorphic(n1, n2) ;
             
+            if ( n1.isVariable() && n2.isVariable() )
+                return mapping.makeIsomorphic(n1, n2) ;
+            
             return false ;
         }
     }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java
index 2d5aede..54c01e3 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/syntax/ElementData.java
@@ -74,7 +74,7 @@
         ElementData f2 = (ElementData)el2 ;
         if ( ! vars.equals(f2.vars) )
             return false ;
-        if ( ! ResultSetCompare.equalsByTest(rows, f2.rows, new ResultSetCompare.BNodeIso(NodeUtils.sameTerm)) )
+        if ( ! ResultSetCompare.equalsByTest(rows, f2.rows, new ResultSetCompare.BNodeIso(NodeUtils.sameNode)) )
             return false ;
         return true ;
     }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/EqualityTest.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/EqualityTest.java
new file mode 100644
index 0000000..3ebe7e0
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/EqualityTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.jena.sparql.util;
+
+import org.apache.jena.graph.Node;
+
+/**
+ * Interface for tests when two nodes are considered "equal". This may be "same
+ * term", "same value" or "same object" or customised such as with a blank node
+ * mapping.
+ */
+@FunctionalInterface
+public interface EqualityTest {
+    boolean equal(Node n1, Node n2);
+}
\ No newline at end of file
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java
index 31f3eea..46ffd75 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/Iso.java
@@ -18,18 +18,26 @@
 
 package org.apache.jena.sparql.util;
 
-import java.util.List ;
+import java.util.List;
 
 import org.apache.jena.graph.Node ;
-import org.apache.jena.graph.Triple ;
-import org.apache.jena.sparql.core.Quad ;
-import org.apache.jena.sparql.core.TriplePath ;
-import org.apache.jena.sparql.core.Var ;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.core.TriplePath;
+import org.apache.jena.sparql.core.Var;
 
-/** Isomorphism utilities */
+/**
+ * Isomorphism utilities, based in order lists. See {@link IsoMatcher} for
+ * isomorphism for un-ordered collections. In this class, "isomorphism" is based on the
+ * policy in {@link NodeIsomorphismMap}, which is blank node isomorphism unless
+ * {@link NodeIsomorphismMap#makeIsomorphic} is overridden.
+ */
 public class Iso {
-    // See also IsoMatcher
-    
+
+    /* 
+     * Are lists of triples isomorphic? 
+     * The mapping policy {@link NodeIsomorphismMap} is mutated. 
+     */
     public static boolean isomorphicTriples(List<Triple> triples1, List<Triple> triples2, NodeIsomorphismMap isoMap) {
         if ( triples1.size() != triples2.size() )
             return false ;
@@ -42,6 +50,10 @@
         return true ;
     }
     
+    /* 
+     * Are lists of quads isomorphic? 
+     * The mapping policy {@link NodeIsomorphismMap} is mutated. 
+     */
     public static boolean isomorphicQuads(List<Quad> quads1, List<Quad> quads2, NodeIsomorphismMap isoMap) {
         if ( quads1.size() != quads2.size() )
             return false ;
@@ -54,6 +66,10 @@
         return true ;
     }
 
+    /* 
+     * Are lists of nodes isomorphic? 
+     * The mapping policy {@link NodeIsomorphismMap} is mutated. 
+     */
     public static boolean isomorphicNodes(List<Node> nodes1, List<Node> nodes2, NodeIsomorphismMap isoMap) {
         if ( nodes1.size() != nodes2.size() )
             return false ;
@@ -66,6 +82,10 @@
         return true ;
     }
 
+    /* 
+     * Are two triple paths isomorphic? 
+     * The mapping policy {@link NodeIsomorphismMap} is mutated. 
+     */
     public static boolean triplePathIso(TriplePath tp1, TriplePath tp2, NodeIsomorphismMap isoMap)
     {
         if ( tp1.isTriple() ^ tp2.isTriple() ) 
@@ -79,6 +99,10 @@
                    tp1.getPath().equalTo(tp2.getPath(), isoMap) ;
     }
 
+    /* 
+     * Are two triples isomorphic? 
+     * The mapping policy {@link NodeIsomorphismMap} is mutated. 
+     */
     public static boolean tripleIso(Triple t1, Triple t2, NodeIsomorphismMap labelMap)
     {
         Node s1 = t1.getSubject() ;
@@ -99,6 +123,10 @@
         return true ;
     }
 
+    /* 
+     * Are two quads isomorphic? 
+     * The mapping policy {@link NodeIsomorphismMap} is mutated. 
+     */
     public static boolean quadIso(Quad t1, Quad t2, NodeIsomorphismMap labelMap)
     {
         Node g1 = t1.getGraph() ;
@@ -123,6 +151,10 @@
         return true ;
     }
 
+    /* 
+     * Are two nodes isomorphic? 
+     * The mapping policy {@link NodeIsomorphismMap} is mutated. 
+     */
     public static boolean nodeIso(Node n1, Node n2, NodeIsomorphismMap isoMap)
     {
         if ( isoMap != null ) { 
@@ -133,5 +165,20 @@
     }
         return n1.equals(n2) ;
     }
+
+    /** Interface for choosing the pairs of node that can be map[ped for isomorphism. */ 
+    public interface Mappable {
+        boolean mappable(Node n1, Node n2);
+    }
+
+    /** Blank nodes are mappable in {@link IsoAlg} */
+    public static Iso.Mappable mappableBlankNodes = (n1, n2) -> (n1.isBlank() && n2.isBlank());
+    
+    /** Blank nodes and variables are mappable in {@link IsoAlg} */
+    public static Iso.Mappable mappableBlankNodesVariables = (n1, n2) -> {
+        if ( n1.isBlank() && n2.isBlank() ) return true;
+        if ( n1.isVariable() && n2.isVariable() ) return true;
+        return false;
+    };
 }
 
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoAlg.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoAlg.java
new file mode 100644
index 0000000..1f6460d
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoAlg.java
@@ -0,0 +1,260 @@
+/**
+ * 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.jena.sparql.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.jena.atlas.io.IndentedWriter;
+import org.apache.jena.atlas.lib.tuple.Tuple;
+import org.apache.jena.graph.Node;
+import org.apache.jena.sparql.util.EqualityTest;
+
+/**
+ * Simple isomorphism testing for collections of tuples of nodes. This can be used
+ * for graphs, datasets and results sets The Graph isomorphism code in Jena is much
+ * better (better tested, better performance) for graph isomorphism. This code is
+ * simple and slow but covers more cases.
+ */
+public class IsoAlg {
+    // Possible speed ups
+    // A/ Preprocess to look for constants and use a subset of tuples first.
+
+    /** Record the mapping of a mapping of a node. */
+    static class Mapping {
+        final Node     node1;
+        final Node     node2;
+        final Mapping  parent;
+
+        static Mapping rootMapping = new Mapping(null, null, null);
+
+        public Mapping(Mapping parent, Node node1, Node node2) {
+            super();
+            this.parent = parent;
+            this.node1 = node1;
+            this.node2 = node2;
+        }
+
+        public boolean mapped(Node node) {
+            return map(node) != null;
+        }
+
+        public Node map(Node node) {
+            Mapping mapping = this;
+            while (mapping != rootMapping) {
+                if ( mapping.node1.equals(node) )
+                    return mapping.node2;
+                mapping = mapping.parent;
+            }
+            return null;
+        }
+
+        public boolean reverseMapped(Node node) {
+            return reverseMap(node) != null;
+        }
+        
+        public Node reverseMap(Node node) {
+            Mapping mapping = this;
+            while (mapping != rootMapping) {
+                if ( mapping.node2.equals(node) )
+                    return mapping.node1;
+                mapping = mapping.parent;
+            }
+            return null;
+        }
+
+        
+        @Override
+        public String toString() {
+            StringBuilder sbuff = new StringBuilder();
+            Mapping mapping = this;
+            while (mapping != rootMapping) {
+                sbuff.append("{" + mapping.node1 + " => " + mapping.node2 + "}");
+                mapping = mapping.parent;
+            }
+            sbuff.append("{}");
+            return sbuff.toString();
+        }
+    }
+
+    static class Possibility {
+        final Tuple<Node> tuple;
+        final Mapping     mapping;
+
+        public Possibility(Tuple<Node> tuple, Mapping mapping) {
+            super();
+            this.tuple = tuple;
+            this.mapping = mapping;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Poss|%s %s|", tuple, mapping);
+        }
+    }
+
+    /**
+     * Blank node isomorphism test.
+     * Are the two collections of tuples of nodes isomorphic?
+     * In addition, when as two nodes considered "equal" (whether by 
+     * {@link NodeUtils#sameValue} (SPARQL valuet testing), {@link NodeUtils#sameNode} (Node.equals),
+     * {@link NodeUtils#sameRdfTerm} (Node.equals, with lang tag insensitive testing).
+     */
+    public static boolean isIsomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, EqualityTest nodeTest) {
+        return isIsomorphic(x1, x2, Iso.mappableBlankNodes, nodeTest);
+    }
+
+    /**
+     * Isomorphism test based on a class of mappable elements (e.g. blank nodes {@linkplain Iso#mappableBlankNodes},
+     * or blank nodes and variables {@linkplain Iso#mappableBlankNodesVariables}).
+     * In addition, when are two nodes considered "equal" (whether by 
+     * {@link NodeUtils#sameValue} (SPARQL value testing), {@link NodeUtils#sameNode} (Node.equals),
+     * {@link NodeUtils#sameRdfTerm} (Node.equals, with lang tag insensitive testing).
+     */
+    public static boolean isIsomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, Iso.Mappable mappable, EqualityTest nodeTest) {
+        return matcher(x1, x2, Mapping.rootMapping, mappable, nodeTest);
+    }
+
+    // Debug.
+    // XXX Restore private static final
+    public static boolean        DEBUG = false;
+    private static final IndentedWriter out   = new IndentedWriter(System.out);
+    static {
+        out.setFlushOnNewline(true);
+    }
+
+    private static boolean matcher(Collection<Tuple<Node>> tuples1, Collection<Tuple<Node>> tuples2, Mapping mapping, Iso.Mappable mappable,
+                                   EqualityTest nodeTest) {
+        if ( DEBUG ) {
+            out.println("match: ");
+            out.println("  1: " + tuples1);
+            out.println("  2: " + tuples2);
+            out.println("  M: " + mapping);
+        }
+        if ( tuples1.size() != tuples2.size() )
+            return false;
+
+        // List-copy, these will be mutated.
+        tuples2 = new ArrayList<>(tuples2);
+        tuples1 = new ArrayList<>(tuples1);
+
+        for ( Tuple<Node> t1 : tuples1 ) {
+            if ( DEBUG )
+                out.println("  Process t1 = " + t1);
+            tuples1.remove(t1);
+
+            List<Possibility> causes = matcher(t1, tuples2, mapping, mappable, nodeTest);
+
+            if ( DEBUG )
+                out.println("    Possibilities: Tuple" + t1 + " :: " + causes);
+
+            out.incIndent();
+            try {
+                // Try each possible tuple-tuple matching until one succeeds all the
+                // way.
+                for ( Possibility c : causes ) {
+                    if ( DEBUG )
+                        out.println("  Try: " + c);
+                    // Try t1 -> t2
+                    Tuple<Node> t2 = c.tuple;
+                    tuples2.remove(t2);
+                    // Try without t1 and t2, using the mapping of this cause.
+                    if ( tuples1.isEmpty() && tuples2.isEmpty() ) // They are the
+                                                                  // same size.
+                        return true;
+                    // Recurse
+                    if ( matcher(tuples1, tuples2, c.mapping, mappable, nodeTest) ) {
+                        if ( DEBUG )
+                            out.println("Yes");
+                        return true;
+                    }
+                    if ( DEBUG )
+                        out.println("No");
+                    tuples2.add(t2);
+                }
+                return false;
+            }
+            finally {
+                out.decIndent();
+            }
+        }
+        // The empty-empty case
+        return true;
+    }
+
+    /** Return all possible tuple-tuple matches from tuple t1 to tuples in x2 */
+    private static List<Possibility> matcher(Tuple<Node> t1, Collection<Tuple<Node>> g2, Mapping mapping, Iso.Mappable mappable,
+                                       EqualityTest nodeTest) {
+        List<Possibility> matches = new ArrayList<>();
+        for ( Tuple<Node> t2 : g2 ) {
+            // No - multiple bNodes.
+            Mapping step = gen(t1, t2, mapping, mappable, nodeTest);
+            if ( step != null ) {
+                Possibility c = new Possibility(t2, step);
+                matches.add(c);
+            }
+        }
+        return matches;
+    }
+
+    /**
+     * Find a mapping between the tuples, given a start mapping.
+     * Return a mapping or null for "no match".
+     */
+    private static Mapping gen(Tuple<Node> t1, Tuple<Node> t2, Mapping _mapping, Iso.Mappable mappable, EqualityTest nodeTest) {
+        if ( t1.len() != t2.len() )
+            return null;
+
+        Mapping mapping = _mapping;
+        for ( int i = 0 ; i < t1.len() ; i++ ) {
+            Node n1 = t1.get(i);
+            Node n2 = t2.get(i);
+            Node n1m = mapping.map(n1);
+
+            if ( n1m != null ) {
+                // Already mapped
+                if ( n1m.equals(n2) )
+                    // Exact equals after mapping t1 slot.
+                    continue;
+                // No match.
+                return null;
+            }
+
+            // Not supported: mappable literals (or mapping any term where "same" is
+            // not "Node.equals").
+            if ( mappable.mappable(n1, n2) ) {
+                if ( mapping.reverseMapped(n2) ) {
+                    // Already a target.
+                    // but not the same (else n1m != null)
+                    return null;
+                }
+                // **** If n2 not already mapped.
+                
+                mapping = new Mapping(mapping, n1, n2);
+                continue;
+            }
+
+            if ( !nodeTest.equal(n1, n2) )
+                // No isomorphism.
+                return null;
+        }
+        return mapping;
+    }
+}
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java
index 446faae..ad5d4dd 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/IsoMatcher.java
@@ -20,245 +20,85 @@
 
 import static org.apache.jena.atlas.lib.tuple.TupleFactory.tuple ;
 
-import java.util.ArrayList ;
 import java.util.Collection ;
 import java.util.Iterator ;
 import java.util.List ;
 
+import org.apache.jena.atlas.iterator.Iter;
 import org.apache.jena.atlas.lib.tuple.Tuple ;
 import org.apache.jena.graph.Graph ;
 import org.apache.jena.graph.Node ;
 import org.apache.jena.graph.Triple ;
 import org.apache.jena.sparql.core.DatasetGraph ;
 import org.apache.jena.sparql.core.Quad ;
-import org.apache.jena.sparql.util.NodeUtils.EqualityTest ;
 
-// Needs to be back tracking?
-
-/** Simple isomorphism testing
+/**
+ * Simple isomorphism testing for on unordered collections. 
  * This code is simple and slow.
  * For graphs, the Graph isomorphism code in Jena is much better (better tested, better performance)
- * This code can work on any tuples of nodes. 
+ * This code can work on any tuples of nodes.
+ * 
+ * See {@link Iso} for isomorphism for ordered lists.
+ * 
+ * See {@link IsoAlg} for the isomorphism algorithm.
  */
 public class IsoMatcher
 {
-    // Possible speed ups 
-    //  A/ Phase 1 - do all non-bNode tuples. / Phase 2 : all tuples with a bNode 
-    //  B/ turn tuples2 into a map, keyed by (1) constants or (2) first term. 
-    
-    static boolean DEBUG = false ;
-    private final List<Tuple<Node>>        tuples1 ;
-    private final List<Tuple<Node>>        tuples2 ;
-    
-//    private final Map<Node, Node>           mapping = new HashMap<>();
-//    private final Queue<Pair<Node, Node>>   causes  = new LinkedList<>() ;  
-    private final EqualityTest nodeTest ;
-
-    static class Mapping {
-        final Node     node1 ;
-        final Node     node2 ;
-        final Mapping  parent ;
-
-        static Mapping rootMapping = new Mapping(null, null, null) ;
-
-        public Mapping(Mapping parent, Node node1, Node node2) {
-            super() ;
-            this.parent = parent ;
-            this.node1 = node1 ;
-            this.node2 = node2 ;
-        }
-
-        public boolean mapped(Node node) {
-            return map(node) != null ;
-        }
-
-        public boolean revmapped(Node node) {
-            return revmap(node) != null ;
-        }
-
-        public Node map(Node node) {
-            Mapping mapping = this ;
-            while (mapping != rootMapping) {
-                if ( mapping.node1.equals(node) )
-                    return mapping.node2 ;
-                mapping = mapping.parent ;
-            }
-            return null ;
-        }
-
-        // Reverse mapping.
-        public Node revmap(Node node) {
-            Mapping mapping = this ;
-            while (mapping != rootMapping) {
-                if ( mapping.node2.equals(node) )
-                    return mapping.node1 ;
-                mapping = mapping.parent ;
-            }
-            return null ;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sbuff = new StringBuilder() ;
-            Mapping mapping = this ;
-            while (mapping != rootMapping) {
-                sbuff.append("{" + mapping.node1 + " => " + mapping.node2 + "}") ;
-                mapping = mapping.parent ;
-            }
-            sbuff.append("{}") ;
-            return sbuff.toString() ;
-        }
-    }
-    
-    static class Cause {
-        final Tuple<Node> tuple ;
-        final Mapping     mapping ;
-
-        public Cause(Tuple<Node> tuple, Mapping mapping) {
-            super() ;
-            this.tuple = tuple ;
-            this.mapping = mapping ;
-        }
-    }
-
+    /** Graph isomorphism */
     public static boolean isomorphic(Graph g1, Graph g2) {
-        List<Tuple<Node>> x1 = tuplesTriples(g1.find(null, null, null)) ;
-        List<Tuple<Node>> x2 = tuplesTriples(g2.find(null, null, null)) ;
-        
-        IsoMatcher matcher = new IsoMatcher(x1, x2, NodeUtils.sameTerm) ;
-        return matcher.match() ;
+        List<Tuple<Node>> x1 = tuplesTriples(g1.find());
+        List<Tuple<Node>> x2 = tuplesTriples(g2.find());
+        return isomorphic(x1, x2, NodeUtils.sameRdfTerm);
     }
 
+    /** Dataset isomorphism */
     public static boolean isomorphic(DatasetGraph dsg1, DatasetGraph dsg2) {
-        List<Tuple<Node>> x1 = tuplesQuads(dsg1.find()) ;
-        List<Tuple<Node>> x2 = tuplesQuads(dsg2.find()) ;
-        
-        IsoMatcher matcher = new IsoMatcher(x1, x2, NodeUtils.sameTerm) ;
-        return matcher.match() ;
+        List<Tuple<Node>> x1 = tuplesQuads(dsg1.find());
+        List<Tuple<Node>> x2 = tuplesQuads(dsg2.find());
+        return isomorphic(x1, x2, NodeUtils.sameRdfTerm);
     }
 
-    public static boolean isomorphic(List<Tuple<Node>> x1, List<Tuple<Node>> x2) {
-        x1 = new ArrayList<>(x1) ;
-        x2 = new ArrayList<>(x2) ;
-        IsoMatcher matcher = new IsoMatcher(x1, x2, NodeUtils.sameTerm) ;
-        return matcher.match() ;
+    /** Collection of tuples isomorphism */
+    public static boolean isomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2) {
+        return isomorphic(x1, x2, NodeUtils.sameRdfTerm);
     }
 
-    private static List<Tuple<Node>> tuplesTriples(Iterator<Triple> iter) {
-        List<Tuple<Node>> tuples = new ArrayList<>() ;
-        for ( ; iter.hasNext() ; ) {
-            Triple t = iter.next() ;
-            Tuple<Node> tuple = tuple(t.getSubject(), t.getPredicate(), t.getObject()) ;
-            tuples.add(tuple) ;
-        }
-        return tuples ;
+    /** Helper - convert to {@code List<Tuple<Node>>} */
+    public static List<Tuple<Node>> tuplesTriples(Iterator<Triple> iter) {
+        return Iter.iter(iter).map(t->tuple(t.getSubject(), t.getPredicate(), t.getObject())).toList();
     }
 
-    private static List<Tuple<Node>> tuplesQuads(Iterator<Quad> iter) {
-        List<Tuple<Node>> tuples = new ArrayList<>() ;
-        for ( ; iter.hasNext() ; ) {
-            Quad q = iter.next() ;
-            Tuple<Node> tuple = tuple(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject()) ;
-            tuples.add(tuple) ;
-        }
-        return tuples ;
+    /** Helper - convert to {@code List<Tuple<Node>>} */
+    public static List<Tuple<Node>> tuplesQuads(Iterator<Quad> iter) {
+        return Iter.iter(iter).map(q->tuple(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject())).toList();
     }
 
+    /** Collection of tuples isomorphism, with choice of when two nodes are "equal".
+     * See also {@link IsoAlg#isIsomorphic(Collection, Collection, org.apache.jena.sparql.util.Iso.Mappable, EqualityTest)}
+     * for isomorphisms testing for more than just blank nodes.
+     */
+    public static boolean isomorphic(Collection<Tuple<Node>> x1, Collection<Tuple<Node>> x2, EqualityTest nodeTest) {
+        return IsoAlg.isIsomorphic(x1, x2, nodeTest);
+    }
+
+    // --- Compatibility. Deprecated, and due for removal.
+    private List<Tuple<Node>> tuples1;
+    private List<Tuple<Node>> tuples2;
+    private EqualityTest nodeTest;
+
+    /** @deprecated Use {@link IsoMatcher#match} */
+    @Deprecated
     public IsoMatcher(List<Tuple<Node>> g1, List<Tuple<Node>> g2, EqualityTest nodeTest) {
-        this.tuples1 = g1 ;
-        this.tuples2 = g2 ;
-        this.nodeTest = nodeTest ;
+        this.tuples1 = g1;
+        this.tuples2 = g2;
+        this.nodeTest = nodeTest;
     }
 
-    // May MUTATE tuples1 or tuples2
+    /** @deprecated Use {@link IsoMatcher#match} */
+    @Deprecated
     public boolean match() {
-        return match(tuples1, tuples2, Mapping.rootMapping) ;
+        return isomorphic(tuples2, tuples1, nodeTest);
     }
-
-    private boolean match(List<Tuple<Node>> tuples1, List<Tuple<Node>> tuples2, Mapping mapping) {
-        if ( DEBUG ) {
-            System.out.println("match: ") ;
-            System.out.println("  "+tuples1) ;
-            System.out.println("  "+tuples2) ;
-            System.out.println("  "+mapping) ; 
-        }
-        if ( tuples1.size() != tuples2.size() )
-            return false;
-        
-        List<Tuple<Node>> tuples = new ArrayList<>(tuples1) ;  // Copy, mutate
-        for ( Tuple<Node> t1 : tuples1 ) {
-            if ( DEBUG )
-                System.out.println("  t1 = "+t1) ;
-            tuples.remove(t1) ;
-            List<Cause> causes = match(t1, tuples2, mapping) ;
-            for ( Cause c : causes ) {
-                if ( DEBUG ) 
-                    System.out.println("  Try: "+c.mapping) ;
-                // Try t1 -> t2
-                Tuple<Node> t2 = c.tuple ;
-                tuples2.remove(t2) ;
-                if ( tuples2.isEmpty() )
-                    return true ;
-                if ( match(tuples, tuples2, c.mapping) ) {
-                    if ( DEBUG ) 
-                        System.out.println("Yes") ;
-                    return true ;
-                }
-                if ( DEBUG ) 
-                    System.out.println("No") ;
-                tuples2.add(t2) ;
-            }
-            return false ;
-        }
-        // The empty-empty case
-        return true ;
-    }
-
-    private List<Cause> match(Tuple<Node> t1, Collection<Tuple<Node>> g2, Mapping mapping) {
-        List<Cause> matches = new ArrayList<>() ;
-        for ( Tuple<Node> t2 : g2 ) {
-            // No - multiple bNodes.
-            Mapping step = gen(t1, t2, mapping) ;
-            if (step != null) { 
-                Cause c = new Cause(t2, step) ;
-                matches.add(c) ;
-            }
-        }
-        return matches ;
-    }
-
-    // Maybe several mappings!
-    private Mapping gen(Tuple<Node> t1, Tuple<Node> t2, Mapping _mapping) {
-        if ( t1.len() != t2.len() )
-            return null ;
-        
-        Mapping mapping = _mapping ;
-        for ( int i = 0 ; i < t1.len() ; i++ ) {
-            Node n1 = t1.get(i) ;
-            Node n2 = t2.get(i) ;
-            if ( ! nodeTest.equal(n1, n2) ) {
-                mapping = gen(n1, n2, mapping) ;
-                if ( mapping == null )
-                    return null ;
-            }
-        }            
-        return mapping ;
-    }
-
-    private Mapping gen(Node x1, Node x2, Mapping mapping) {
-        if ( x1.isBlank() && x2.isBlank() ) {
-            // Is x1 already mapped?
-            Node z = mapping.map(x1) ;
-            if ( z != null )
-                // Already mapped
-                return (nodeTest.equal(x2, z)) ? mapping : null ;
-            // Check reverse
-            if ( mapping.revmapped(x2) )
-                return null ;
-            return new Mapping(mapping, x1, x2) ;
-            
-        }
-        return null ;
-    }
+    // ---
     
 }
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeUtils.java b/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeUtils.java
index cb277b1..ea60740 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeUtils.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/NodeUtils.java
@@ -18,12 +18,7 @@
 
 package org.apache.jena.sparql.util;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
 
 import org.apache.jena.atlas.lib.ListUtils;
 import org.apache.jena.atlas.lib.SetUtils;
@@ -47,12 +42,6 @@
 /** Node utilities */ 
 public class NodeUtils
 {
-    public interface EqualityTest {
-        default boolean equal(Node n1, Node n2) {
-			return Objects.equals(n1, n2) ;
-		}
-    }
-
     /** IRI to Node */ 
     public static Node asNode(IRI iri) {
         return NodeFactory.createURI(iri.toString()) ;
@@ -284,29 +273,36 @@
      * (RDF 1.0 and RDF 1.1)
      */
     public static boolean isLangString(Node n) { return Util.isLangString(n) ; }
+    
+    
+    // --- Equality tests.
+    
+    /** Both null or same node : {@code Node.equals} */
+    public static EqualityTest sameNode  = (n1,n2) -> Objects.equals(n1, n2);
 
-    // This is term comparison.
-    public static EqualityTest sameTerm = new EqualityTest() {
-        @Override
-        public boolean equal(Node n1, Node n2)
+    /**
+     * Term comparison. Node.equals or lang tags are case insensitive 
+     */
+    public static EqualityTest sameRdfTerm  = (n1,n2) -> NodeFunctions.sameTerm(n1,n2);
+    
+    /** @deprecated Use {@link NodeUtils#sameRdfTerm} */
+    @Deprecated 
+    public static EqualityTest sameTerm  = sameRdfTerm;
+
+    /** sameValue by SPARQL rules */ 
+    public static EqualityTest sameValue = (n1,n2) -> {
+        if ( Objects.equals(n1, n2) )
+            return true;
+        if ( ! n1.isLiteral() || ! n2.isLiteral() )
+            return false;
+        // 2 literals.
+        NodeValue nv1 = NodeValue.makeNode(n1);
+        NodeValue nv2 = NodeValue.makeNode(n2);
+        try { return NodeValue.sameAs(nv1, nv2); } 
+        catch(ExprEvalException ex)
         {
-            return NodeFunctions.sameTerm(n1, n2) ;
+            // Incomparable as values - must be different for our purposes.
+            return false; 
         }
-    } ;
-    // This is value comparison
-    public static EqualityTest sameValue = new EqualityTest() {
-        @Override
-        public boolean equal(Node n1, Node n2)
-        {
-            NodeValue nv1 = NodeValue.makeNode(n1) ;
-            NodeValue nv2 = NodeValue.makeNode(n2) ;
-            try {
-                return NodeValue.sameAs(nv1, nv2) ;
-            } catch(ExprEvalException ex)
-            {
-                // Incomparible as values - must be different for our purposes.
-                return false ; 
-            }
-        }
-    } ;
+    };
 }
diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java
index ac06f50..4b337b8 100644
--- a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java
+++ b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataBag.java
@@ -91,7 +91,7 @@
         }
         
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameTerm));
+        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
     }
     
     @Test public void testTemporaryFilesAreCleanedUpAfterCompletion()
@@ -220,6 +220,6 @@
         }
 
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameTerm));
+        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
     }
 }
diff --git a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java
index 93e876e..b5c365e 100644
--- a/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java
+++ b/jena-arq/src/test/java/org/apache/jena/atlas/data/TestDistinctDataNet.java
@@ -92,7 +92,7 @@
         }
         
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameTerm));
+        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
     }
     
     @Test
@@ -139,7 +139,7 @@
         }
         
         assertEquals(control.size(), distinct.size());
-        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameTerm));
+        assertTrue(ResultSetCompare.equalsByTest(control, distinct, NodeUtils.sameNode));
     }
     
     @Test
diff --git a/jena-arq/src/test/java/org/apache/jena/common/TestIsoMatcher.java b/jena-arq/src/test/java/org/apache/jena/common/TestIsoMatcher.java
index 1bc26f0..f22016e 100644
--- a/jena-arq/src/test/java/org/apache/jena/common/TestIsoMatcher.java
+++ b/jena-arq/src/test/java/org/apache/jena/common/TestIsoMatcher.java
@@ -19,6 +19,7 @@
 package org.apache.jena.common;
 
 import java.util.ArrayList ;
+import java.util.Collection;
 import java.util.List ;
 
 import org.apache.jena.atlas.junit.BaseTest ;
@@ -30,7 +31,10 @@
 import org.apache.jena.sparql.sse.Item ;
 import org.apache.jena.sparql.sse.SSE ;
 import org.apache.jena.sparql.sse.builders.BuilderNode ;
+import org.apache.jena.sparql.util.Iso;
+import org.apache.jena.sparql.util.IsoAlg;
 import org.apache.jena.sparql.util.IsoMatcher ;
+import org.apache.jena.sparql.util.NodeUtils;
 import org.junit.Test ;
 
 public class TestIsoMatcher extends BaseTest
@@ -39,83 +43,115 @@
                                            "",
                                            true) ; }
 
+    @Test public void iso_graph_01() { testGraph("(<x> <p> 1)",
+                                                 "(<x> <p> 1)",
+                                                 true) ; }
 
-    @Test public void iso_01() { testGraph("(<x> <p> 1)",
-                                           "(<x> <p> 1)",
-                                           true) ; }
+    @Test public void iso_graph_02() { testGraph("(<x> <p> 1)",
+                                                 "(<x> <p> 2)",
+                                                 false) ; }
 
-    @Test public void iso_02() { testGraph("(<x> <p> 1)",
-                                           "(<x> <p> 2)",
-                                           false) ; }
+    @Test public void iso_graph_03() { testGraph("(<x> <p> 1) (<x> <p> 2)",
+                                                 "(<x> <p> 2)",
+                                                 false) ; }
 
-    @Test public void iso_03() { testGraph("(<x> <p> 1) (<x> <p> 2)",
-                                           "(<x> <p> 2)",
-                                           false) ; }
+    @Test public void iso_graph_04() { testGraph("(<x> <p> _:a)",
+                                                 "(<x> <p> 2)",
+                                                 false) ; }
 
-    @Test public void iso_04() { testGraph("(<x> <p> _:a)",
-                                           "(<x> <p> 2)",
-                                           false) ; }
+    @Test public void iso_graph_05() { testGraph("(<x> <p> _:a)",
+                                                 "(<x> <p> _:b)",
+                                                 true) ; }
 
-    @Test public void iso_05() { testGraph("(<x> <p> _:a)",
-                                           "(<x> <p> _:b)",
-                                           true) ; }
-
-    @Test public void iso_06() { testGraph("(_:a <p> _:a)",
-                                           "(_:b <p> _:b)",
+    @Test public void iso_graph_06() { testGraph("(_:a <p> _:a)",
+                                                   "(_:b <p> _:b)",
                                            true)  ; }
 
-    @Test public void iso_07() { testGraph("(_:a1 <p> _:a2)",
-                                           "(_:bb <p> _:bb)",
-                                           false)  ; }
+    @Test public void iso_graph_07() { testGraph("(_:a1 <p> _:a2)",
+                                                 "(_:bb <p> _:bb)",
+                                                 false)  ; }
 
-    @Test public void iso_10() { testGraph("(_:a _:a _:a)",
-                                           "(_:b _:b _:b)",
-                                           true)  ; }
+    @Test public void iso_graph_10() { testGraph("(_:a _:a _:a)",
+                                                 "(_:b _:b _:b)",
+                                                 true)  ; }
 
-    @Test public void iso_11() { testGraph("(_:a _:a _:a)",
-                                           "(_:z _:b _:b)",
-                                           false)  ; }
+    @Test public void iso_graph_11() { testGraph("(_:a _:a _:a)",
+                                                 "(_:z _:b _:b)",
+                                                 false) ; }
 
-    @Test public void iso_12() { testGraph("(_:a _:a _:a)",
-                                           "(_:b _:z _:b)",
-                                           false)  ; }
+    @Test public void iso_graph_12() { testGraph("(_:a _:a _:a)",
+                                                 "(_:b _:z _:b)",
+                                                 false)  ; }
 
-    @Test public void iso_13() { testGraph("(_:a _:a _:a)",
-                                           "(_:b _:b _:z)",
-                                           false)  ; }
+    @Test public void iso_graph_13() { testGraph("(_:a _:a _:a)",
+                                                 "(_:b _:b _:z)",
+                                                 false)  ; }
 
-    @Test public void iso_14() { testGraph("(_:a _:a _:b)",
-                                           "(_:b _:b _:z)",
-                                           true)  ; }
+    @Test public void iso_graph_14() { testGraph("(_:a _:a _:b)",
+                                                 "(_:b _:b _:z)",
+                                                 true)  ; }
 
-    @Test public void iso_15() { testGraph("(_:a _:x _:a)",
-                                           "(_:b _:z _:b)",
-                                           true)  ; }
+    @Test public void iso_graph_15() { testGraph("(_:a _:x _:a)",
+                                                 "(_:b _:z _:b)",
+                                                 true)  ; }
 
-    @Test public void iso_16() { testGraph("(_:x _:a _:a)",
-                                           "(_:z _:b _:b)",
-                                           true)  ; }
+    @Test public void iso_graph_16() { testGraph("(_:x _:a _:a)",
+                                                 "(_:z _:b _:b)",
+                                                 true)  ; }
 
-    @Test public void iso_20() { testGraph("(<x> <p> _:a) (<z> <p> _:a)",
-                                           "(<x> <p> _:b) (<z> <p> _:b)",
-                                           true)  ; }
+    @Test public void iso_graph_20() { testGraph("(<x> <p> _:a) (<z> <p> _:a)",
+                                                 "(<x> <p> _:b) (<z> <p> _:b)",
+                                                 true)  ; }
 
-    @Test public void iso_21() { testGraph("(<x> <p> _:a1) (<z> <p> _:a2)",
-                                           "(<x> <p> _:b) (<z> <p> _:b)",
-                                           false)  ; }
+    @Test public void iso_graph_21() { testGraph("(<x> <p> _:a1) (<z> <p> _:a2)",
+                                                 "(<x> <p> _:b) (<z> <p> _:b)",
+                                                 false)  ; }
 
-    @Test public void iso_22() { testGraph("(_:a <p> _:a) (<s> <q> _:a)",
-                                           "(_:b <p> _:b) (<s> <q> _:b)",
-                                           true)  ; }
+    @Test public void iso_graph_22() { testGraph("(_:a <p> _:a) (<s> <q> _:a)",
+                                                 "(_:b <p> _:b) (<s> <q> _:b)",
+                                                 true)  ; }
 
-    @Test public void iso_23() { testGraph("(_:a <p> _:a) (<s> <q> _:a)",
-                                           "(_:b <p> _:b) (<s> <q> _:c)",
-                                           false)  ; }
+    @Test public void iso_graph_23() { testGraph("(_:a <p> _:a) (<s> <q> _:a)",
+                                                 "(_:b <p> _:b) (<s> <q> _:c)",
+                                                 false)  ; }
 
-    @Test public void iso_24() { testGraph("(_:a <p> _:a) (<s> <q> _:a) (_:b <q> _:b)",
-                                           "(_:b <p> _:b) (<s> <q> _:b) (_:b <q> _:b)",
-                                           false)  ; }
+    @Test public void iso_graph_24() { testGraph("(_:a <p> _:a) (<s> <q> _:a) (_:b <q> _:b)",
+                                                 "(_:b <p> _:b) (<s> <q> _:b) (_:b <q> _:b)",
+                                                 false)  ; }
 
+    @Test public void iso_graph_30() { testGraphVar("(?A :p1 ?B)  (?B :p2 ?A)",
+                                                    "(?A :p1 ?B1) (?A :p2 ?B1)",
+                                                    false); }
+    
+    //JENA-1789
+    @Test public void iso_graph_31() { testGraphVar("(?A :p1 ?B) (?B :p2 ?A)",
+                                                    "(?A :p1 ?B) (?A :p2 ?B)",
+                                                    false); }
+
+    //JENA-1789
+    @Test public void iso_graph_32() { testGraphVar("(?X :p1 ?Y) (?Y :p2 ?X)",
+                                                    "(?A :p1 ?B) (?A :p2 ?B)",
+                                                    false); }
+    
+    @Test public void iso_graph_33() { testGraphVar("(?X :p1 ?Y) (?Y :p2 ?X)",
+                                                    "(?A :p1 ?B) (?B :p2 ?A)",
+                                                    true); }
+
+    @Test public void iso_graph_34() { testGraphVar("(?X :p1 ?Y) (?X :p2 ?Y)",
+                                                    "(?A :p1 ?B) (?A :p2 ?B)",
+                                                    true); }
+
+    //JENA-1789
+    @Test public void iso_graph_35() { testGraph("(<_:a> :p1 <_:b>) (<_:b> :p2 <_:a>)",
+                                                 "(<_:a> :p1 <_:b>) (<_:a> :p2 <_:b>)",
+                                                 false); }
+
+    //JENA-1789
+    @Test public void iso_graph_36() { testGraph("(<_:a> :p1 <_:b>) (<_:b> :p2 <_:a>)",
+                                                 "(<_:a> :p1 <_:b>) (<_:b> :p2 <_:a>)",
+                                                 true); }
+
+    
     @Test public void iso_50() { testDSG("(graph (_:a <p> _:a)) (graph <g> (<s> <q> _:a))" ,
                                          "(graph (_:a <p> _:a)) (graph <g> (<s> <q> _:a))" ,
                                          true)  ; }
@@ -125,6 +161,7 @@
                                          "(graph (_:a <p> _:a)) (graph <g> (<s> <q> _:b))" ,
                                          false)  ; }
     
+    
     // List based tests
     @Test public void iso_61() { 
         String[] x1 = {} ;
@@ -183,7 +220,6 @@
         assertEquals(iso, b) ;
     }
 
-
     private static Node[] T = new Node[0] ;
     private List<Tuple<Node>> tuples(String[] strings)
     {
@@ -198,31 +234,58 @@
         return tuples ;
     }
 
-    private void testGraph(String s1, String s2, boolean iso) {
-        testGraph$(s1, s2, iso) ;
-        testGraph$(s2, s1, iso) ;
+    private void testGraph(String s1, String s2, boolean result) {
+        testGraph$(s1, s2, result) ;
+        testGraph$(s2, s1, result) ;
     }
     
-    private void testGraph$(String s1, String s2, boolean iso) {
+    private void testGraph$(String s1, String s2, boolean expected) {
         s1 = "(graph "+s1+")" ;
         s2 = "(graph "+s2+")" ;
-        
+
         Graph g1 = SSE.parseGraph(s1) ;
         Graph g2 = SSE.parseGraph(s2) ;
+
         boolean b = IsoMatcher.isomorphic(g1, g2) ;
-            
-        if ( b != iso ) {
+
+        if ( b != expected ) {
             System.out.println("====") ;
             SSE.write(g1) ;
             System.out.println("----") ;
             SSE.write(g2) ;
-            System.out.println("Expected: "+iso+" ; got: "+b) ;
+            System.out.println("Expected: "+expected+" ; got: "+b) ;
         }
-        assertEquals(iso, b) ;
+        assertEquals(expected, b) ;
         // Check with the other code.
         assertEquals(b, g1.isIsomorphicWith(g2)) ;
     }
-    
+
+    private void testGraphVar(String s1, String s2, boolean result) {
+        testGraphVar$(s1, s2, result);
+        testGraphVar$(s2, s1, result);
+    }
+
+    private void testGraphVar$(String s1, String s2, boolean expected) {
+        s1 = "(graph "+s1+")" ;
+        s2 = "(graph "+s2+")" ;
+
+        Graph g1 = SSE.parseGraph(s1) ;
+        Graph g2 = SSE.parseGraph(s2) ;
+        
+        Collection<Tuple<Node>> x1 = IsoMatcher.tuplesTriples(g1.find());
+        Collection<Tuple<Node>> x2 = IsoMatcher.tuplesTriples(g2.find());
+        
+        boolean b = IsoAlg.isIsomorphic(x1, x2, Iso.mappableBlankNodesVariables, NodeUtils.sameRdfTerm);
+        if ( b != expected ) {
+            System.out.println("====") ;
+            SSE.write(g1) ;
+            System.out.println("----") ;
+            SSE.write(g2) ;
+            System.out.println("Expected: "+expected+" ; got: "+b) ;
+        }
+        assertEquals(expected, b) ;
+    }
+
     private void testDSG(String s1, String s2, boolean iso) {
         testDSG$(s1, s2, iso) ;
         testDSG$(s2, s1, iso) ;
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java b/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java
index 557cdb9..0f64819 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/engine/binding/TestBindingStreams.java
@@ -173,7 +173,7 @@
     private static boolean equalBindings(Binding binding1, Binding binding2)
     {
         // Need to have the exact same terms coming back (therefore we can't use BNodeIso to compare values)
-        return ResultSetCompare.equal(binding1, binding2, NodeUtils.sameTerm) ;
+        return ResultSetCompare.equal(binding1, binding2, NodeUtils.sameNode) ;
     }
 
 
diff --git a/jena-core/src/main/java/org/apache/jena/graph/Node_Ext.java b/jena-core/src/main/java/org/apache/jena/graph/Node_Ext.java
index b012681..dcefe6f 100644
--- a/jena-core/src/main/java/org/apache/jena/graph/Node_Ext.java
+++ b/jena-core/src/main/java/org/apache/jena/graph/Node_Ext.java
@@ -24,13 +24,13 @@
  *  This class and any subclasses exist for experimentation and custom extensions.
  *  There is no support for them within Apache Jena.
  *  <p>
- *  Extension nodes exist so that the machinary of datastructures (graphs, triples)
+ *  Extension nodes exist so that the machinery of datastructures (graphs, triples)
  *  can be used.  There is no guarantee that processing Nodes (e.g. writing) will handle
- *  extensions. For the usual RDF syntaxes, {@code NodeExt} are not handled. 
+ *  extensions. For the usual RDF syntaxes, {@code Node_Ext} are not handled. 
  */
 public abstract class Node_Ext<X> extends Node {
 
-    Node_Ext(X label) {
+    protected Node_Ext(X label) {
         super(label);
     }
 
@@ -49,19 +49,24 @@
         return (X)label;
     }
 
-    // Super is OK : it is based on label. 
-//    @Override
-//    public int hashCode() {
-//    }
+    // Only based on label.
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result + Objects.hash(label);
+        return result;
+    }
 
     @Override
-    public boolean equals(Object o) {
-        if ( o == this )
+    public boolean equals(Object obj) {
+        if ( this == obj )
             return true;
-        if ( !(o instanceof Node_Ext<? >) )
+        if ( obj == null )
             return false;
-        @SuppressWarnings("unchecked")
-        Node_Ext<X> other = (Node_Ext<X>)o;
-        return Objects.equals(this.get(), other.get());
+        if ( getClass() != obj.getClass() )
+            return false;
+        Node_Ext<?> other = (Node_Ext<?>)obj;
+        return Objects.equals(label, other.label);
     }
 }
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/ThreadBufferingCache.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/ThreadBufferingCache.java
index e6ff9c9..e946e53 100644
--- a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/ThreadBufferingCache.java
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/ThreadBufferingCache.java
@@ -53,7 +53,7 @@
     private final AtomicReference<Thread> bufferingThread = new AtomicReference<>();
     private Object lock = new Object();
     private String label;
-    // This turns the feature off. Development only. Do not release with this set "true".
+    // This turns the feature off. Development only. Do not release with this set "false".
     private static final boolean BUFFERING = true;
     
     public ThreadBufferingCache(String label, Cache<Key,Value> mainCache, int size) {