/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 2007.
*
* Licensed under the Aduna BSD-style license.
*/
package net.enilink.komma.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Arjohn Kampman
*/
public class GraphUtil {
/**
* Compares two graphs, defined by two statement collections, and returns
* <tt>true</tt> if they are equal. Models are equal if they contain the
* same set of statements. Blank node IDs are not relevant for graph
* equality, they are mapped from one graph to the other by using the
* attached properties.
*/
public static boolean equals(Iterable<? extends IStatement> graph1,
Iterable<? extends IStatement> graph2) {
// Filter duplicates
Set<IStatement> set1 = new LinkedHashSet<IStatement>();
for (IStatement stmt : graph1) {
set1.add(stmt);
}
Set<IStatement> set2 = new LinkedHashSet<IStatement>();
for (IStatement stmt : graph2) {
set2.add(stmt);
}
return equals(set1, set2);
}
/**
* Compares two graphs, defined by two statement collections, and returns
* <tt>true</tt> if they are equal. Models are equal if they contain the
* same set of statements. Blank node IDs are not relevant for graph
* equality, they are mapped from one graph to the other by using the
* attached properties.
*/
public static boolean equals(Set<? extends IStatement> graph1,
Set<? extends IStatement> graph2) {
// Compare the number of statements in both sets
if (graph1.size() != graph2.size()) {
return false;
}
return isSubsetInternal(graph1, graph2);
}
/**
* Compares two graphs, defined by two statement collections, and returns
* <tt>true</tt> if the first graph is a subset of the second graph.
*/
public static boolean isSubset(Iterable<? extends IStatement> graph1,
Iterable<? extends IStatement> graph2) {
// Filter duplicates
Set<IStatement> set1 = new LinkedHashSet<IStatement>();
for (IStatement stmt : graph1) {
set1.add(stmt);
}
Set<IStatement> set2 = new LinkedHashSet<IStatement>();
for (IStatement stmt : graph2) {
set2.add(stmt);
}
return isSubset(set1, set2);
}
/**
* Compares two graphs, defined by two statement collections, and returns
* <tt>true</tt> if the first graph is a subset of the second graph.
*/
public static boolean isSubset(Set<? extends Statement> graph1,
Set<? extends Statement> graph2) {
// Compare the number of statements in both sets
if (graph1.size() > graph2.size()) {
return false;
}
return isSubsetInternal(graph1, graph2);
}
private static boolean isSubsetInternal(Set<? extends IStatement> graph1,
Set<? extends IStatement> graph2) {
// try to create a full blank node mapping
return matchModels(graph1, graph2);
}
private static boolean matchModels(Set<? extends IStatement> graph1,
Set<? extends IStatement> graph2) {
// Compare statements without blank nodes first, save the rest for later
List<IStatement> graph1BNodes = new ArrayList<IStatement>(graph1.size());
for (IStatement st : graph1) {
if (st.getSubject().getURI() == null
|| (st.getObject() instanceof IReference && ((IReference) st
.getObject()).getURI() == null)) {
graph1BNodes.add(st);
} else {
if (!graph2.contains(st)) {
return false;
}
}
}
return matchModels(graph1BNodes, graph2,
new HashMap<IReference, IReference>(), 0);
}
/**
* A recursive method for finding a complete mapping between blank nodes in
* graph1 and blank nodes in graph2. The algorithm does a depth-first search
* trying to establish a mapping for each blank node occurring in graph1.
*
* @param graph1
* @param graph2
* @param bNodeMapping
* @param idx
* @return true if a complete mapping has been found, false otherwise.
*/
private static boolean matchModels(List<? extends IStatement> graph1,
Iterable<? extends IStatement> graph2,
Map<IReference, IReference> bNodeMapping, int idx) {
boolean result = false;
if (idx < graph1.size()) {
IStatement st1 = graph1.get(idx);
List<IStatement> matchingStats = findMatchingStatements(st1,
graph2, bNodeMapping);
for (IStatement st2 : matchingStats) {
// Map bNodes in st1 to bNodes in st2
Map<IReference, IReference> newBNodeMapping = new HashMap<IReference, IReference>(
bNodeMapping);
if (st1.getSubject().getURI() == null
&& st2.getSubject().getURI() == null) {
newBNodeMapping.put(st1.getSubject(), st2.getSubject());
}
if (st1.getObject() instanceof IReference
&& ((IReference) st1.getObject()).getURI() == null
&& st2.getObject() instanceof IReference
&& ((IReference) st2.getObject()).getURI() == null) {
newBNodeMapping.put((IReference) st1.getObject(),
(IReference) st2.getObject());
}
// FIXME: this recursive implementation has a high risk of
// triggering a stack overflow
// Enter recursion
result = matchModels(graph1, graph2, newBNodeMapping, idx + 1);
if (result == true) {
// graphs match, look no further
break;
}
}
} else {
// All statements have been mapped successfully
result = true;
}
return result;
}
private static List<IStatement> findMatchingStatements(IStatement st,
Iterable<? extends IStatement> graph,
Map<IReference, IReference> bNodeMapping) {
List<IStatement> result = new ArrayList<IStatement>();
for (IStatement graphSt : graph) {
if (statementsMatch(st, graphSt, bNodeMapping)) {
// All components possibly match
result.add(graphSt);
}
}
return result;
}
private static boolean statementsMatch(IStatement st1, IStatement st2,
Map<IReference, IReference> bNodeMapping) {
IReference pred1 = st1.getPredicate();
IReference pred2 = st2.getPredicate();
if (!pred1.equals(pred2)) {
// predicates don't match
return false;
}
IReference subj1 = st1.getSubject();
IReference subj2 = st2.getSubject();
if (subj1.getURI() == null && subj2.getURI() == null) {
IReference mappedBNode = bNodeMapping.get(subj1);
if (mappedBNode != null) {
// bNode 'subj1' was already mapped to some other bNode
if (!subj2.equals(mappedBNode)) {
// 'subj1' and 'subj2' do not match
return false;
}
} else {
// 'subj1' was not yet mapped. we need to check if 'subj2' is a
// possible mapping candidate
if (bNodeMapping.containsValue(subj2)) {
// 'subj2' is already mapped to some other value.
return false;
}
}
} else {
// subjects are not (both) bNodes
if (!subj1.equals(subj2)) {
return false;
}
}
Object obj1 = st1.getObject();
Object obj2 = st2.getObject();
if (obj1 instanceof IReference && ((IReference) obj1).getURI() == null
&& obj2 instanceof IReference
&& ((IReference) obj2).getURI() == null) {
IReference mappedBNode = bNodeMapping.get(obj1);
if (mappedBNode != null) {
// bNode 'obj1' was already mapped to some other bNode
if (!obj2.equals(mappedBNode)) {
// 'obj1' and 'obj2' do not match
return false;
}
} else {
// 'obj1' was not yet mapped. we need to check if 'obj2' is a
// possible mapping candidate
if (bNodeMapping.containsValue(obj2)) {
// 'obj2' is already mapped to some other value.
return false;
}
}
} else {
// objects are not (both) bNodes
if (!obj1.equals(obj2)) {
return false;
}
}
return true;
}
}