Merge pull request #642 from afs/iso
JENA-1789: Isomorphism fix and refactor
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) {