/*
* Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007.
*
* Licensed under the Aduna BSD-style license.
*/
package org.openrdf.result.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openrdf.model.BNode;
import org.openrdf.model.Statement;
import org.openrdf.model.Value;
import org.openrdf.model.util.ModelUtil;
import org.openrdf.query.Binding;
import org.openrdf.query.BindingSet;
import org.openrdf.query.TupleQueryResultHandler;
import org.openrdf.query.TupleQueryResultHandlerException;
import org.openrdf.result.GraphResult;
import org.openrdf.result.TupleResult;
import org.openrdf.rio.RDFHandler;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.store.StoreException;
/**
* Class offering utility methods related to query results.
*
* @author Arjohn Kampman
*/
public class QueryResultUtil {
/**
* Reports a tuple query result to a {@link TupleQueryResultHandler}.
*
* @param tqr
* The query result to report.
* @param handler
* The handler to report the query result to.
* @throws TupleQueryResultHandlerException
* If such an exception is thrown by the used query result writer.
*/
public static void report(TupleResult tqr, TupleQueryResultHandler handler)
throws TupleQueryResultHandlerException, StoreException
{
handler.startQueryResult(tqr.getBindingNames());
try {
while (tqr.hasNext()) {
BindingSet bindingSet = tqr.next();
handler.handleSolution(bindingSet);
}
}
finally {
tqr.close();
}
handler.endQueryResult();
}
/**
* Reports a graph query result to an {@link RDFHandler}.
*
* @param gqr
* The query result to report.
* @param rdfHandler
* The handler to report the query result to.
* @throws RDFHandlerException
* If such an exception is thrown by the used RDF writer.
* @throws StoreException
*/
public static void report(GraphResult gqr, RDFHandler rdfHandler)
throws RDFHandlerException, StoreException
{
try {
rdfHandler.startRDF();
for (Map.Entry<String, String> entry : gqr.getNamespaces().entrySet()) {
String prefix = entry.getKey();
String namespace = entry.getValue();
rdfHandler.handleNamespace(prefix, namespace);
}
while (gqr.hasNext()) {
Statement st = gqr.next();
rdfHandler.handleStatement(st);
}
rdfHandler.endRDF();
}
finally {
gqr.close();
}
}
/**
* Compares the two query results by converting them to graphs and returns
* true if they are equal. QueryResults are equal if they contain the same
* set of BindingSet and have the headers. Blank nodes identifiers are not
* relevant for equality, they are mapped from one model to the other by
* using the attached properties. Note that the method consumes both query
* results fully.
*
* @throws StoreException
*/
public static boolean equals(TupleResult tqr1, TupleResult tqr2)
throws StoreException
{
List<BindingSet> list1 = tqr1.asList();
List<BindingSet> list2 = tqr2.asList();
// Compare the number of statements in both sets
if (list1.size() != list2.size()) {
return false;
}
return matchBindingSets(list1, list2);
}
public static boolean isSubset(TupleResult tqr1, TupleResult tqr2)
throws StoreException
{
List<BindingSet> list1 = tqr1.asList();
List<BindingSet> list2 = tqr2.asList();
// Compare the number of statements in both sets
if (list1.size() > list2.size()) {
return false;
}
return matchBindingSets(list1, list2);
}
public static boolean equals(GraphResult result1, GraphResult result2)
throws StoreException
{
Set<? extends Statement> graph1 = result1.asSet();
Set<? extends Statement> graph2 = result1.asSet();
return ModelUtil.equals(graph1, graph2);
}
private static boolean matchBindingSets(List<? extends BindingSet> queryResult1,
Iterable<? extends BindingSet> queryResult2)
{
return matchBindingSets(queryResult1, queryResult2, new HashMap<BNode, BNode>(), 0);
}
/**
* A recursive method for finding a complete mapping between blank nodes in
* queryResult1 and blank nodes in queryResult2. The algorithm does a
* depth-first search trying to establish a mapping for each blank node
* occurring in queryResult1.
*
* @return true if a complete mapping has been found, false otherwise.
*/
private static boolean matchBindingSets(List<? extends BindingSet> queryResult1,
Iterable<? extends BindingSet> queryResult2, Map<BNode, BNode> bNodeMapping, int idx)
{
boolean result = false;
if (idx < queryResult1.size()) {
BindingSet bs1 = queryResult1.get(idx);
List<BindingSet> matchingBindingSets = findMatchingBindingSets(bs1, queryResult2, bNodeMapping);
for (BindingSet bs2 : matchingBindingSets) {
// Map bNodes in bs1 to bNodes in bs2
Map<BNode, BNode> newBNodeMapping = new HashMap<BNode, BNode>(bNodeMapping);
for (Binding binding : bs1) {
if (binding.getValue() instanceof BNode) {
newBNodeMapping.put((BNode)binding.getValue(), (BNode)bs2.getValue(binding.getName()));
}
}
// FIXME: this recursive implementation has a high risk of
// triggering a stack overflow
// Enter recursion
result = matchBindingSets(queryResult1, queryResult2, newBNodeMapping, idx + 1);
if (result == true) {
// models match, look no further
break;
}
}
}
else {
// All statements have been mapped successfully
result = true;
}
return result;
}
private static List<BindingSet> findMatchingBindingSets(BindingSet st,
Iterable<? extends BindingSet> model, Map<BNode, BNode> bNodeMapping)
{
List<BindingSet> result = new ArrayList<BindingSet>();
for (BindingSet modelSt : model) {
if (bindingSetsMatch(st, modelSt, bNodeMapping)) {
// All components possibly match
result.add(modelSt);
}
}
return result;
}
private static boolean bindingSetsMatch(BindingSet bs1, BindingSet bs2, Map<BNode, BNode> bNodeMapping) {
for (Binding binding1 : bs1) {
Value value1 = binding1.getValue();
Value value2 = bs2.getValue(binding1.getName());
if (value1 instanceof BNode && value2 instanceof BNode) {
BNode mappedBNode = bNodeMapping.get(value1);
if (mappedBNode != null) {
// bNode 'value1' was already mapped to some other bNode
if (!value2.equals(mappedBNode)) {
// 'value1' and 'value2' do not match
return false;
}
}
else {
// 'value1' was not yet mapped, we need to check if 'value2' is a
// possible mapping candidate
if (bNodeMapping.containsValue(value2)) {
// 'value2' is already mapped to some other value.
return false;
}
}
}
else {
// values are not (both) bNodes
if (!value1.equals(value2)) {
return false;
}
}
}
return true;
}
/**
* Check whether two {@link BindingSet}s are compatible. Two binding sets are
* compatible if they have equal values for each binding name that occurs in
* both binding sets.
*/
public static boolean bindingSetsCompatible(BindingSet bs1, BindingSet bs2) {
Set<String> sharedBindings = new HashSet<String>(bs1.getBindingNames());
sharedBindings.retainAll(bs2.getBindingNames());
for (String bindingName : sharedBindings) {
Value value1 = bs1.getValue(bindingName);
Value value2 = bs2.getValue(bindingName);
if (!value1.equals(value2)) {
return false;
}
}
return true;
}
}