/*******************************************************************************
* Copyright (c) 2004, 2007 IBM Corporation and Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* File: $Source: /cvsroot/slrp/glitter/com.ibm.adtech.glitter/src/com/ibm/adtech/glitter/query/SPARQLAlgebra.java,v $
* Created by: Lee Feigenbaum ( <a href="mailto:feigenbl@us.ibm.com">feigenbl@us.ibm.com </a>)
* Created on: February 13, 2007
* Revision: $Id: SPARQLAlgebra.java 164 2007-07-31 14:11:09Z mroy $
*
* Contributors:
* IBM Corporation - initial API and implementation
* Cambridge Semantics Incorporated - Fork to Anzo
*******************************************************************************/
package org.openanzo.glitter.query;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.collections15.MultiMap;
import org.openanzo.analysis.RequestAnalysis;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.glitter.exception.ExpressionEvaluationException;
import org.openanzo.glitter.exception.IncompatibleTypeException;
import org.openanzo.glitter.exception.MalformedLiteralException;
import org.openanzo.glitter.syntax.abstrakt.Expression;
import org.openanzo.glitter.util.TypeConversions;
import org.openanzo.rdf.Bindable;
import org.openanzo.rdf.Value;
import org.openanzo.rdf.Variable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A collection of static utility methods that implement the algebraic functions for composing sets of SPARQL solutions.
*
* @author lee <lee@cambridgesemantics.com>
*
*/
public class SPARQLAlgebra {
private static final Logger log = LoggerFactory.getLogger(SPARQLAlgebra.class);
/**
* Conjoins two solution sets.
*
* (A null list of solutions functions equivalently to a solution set with a single bindings-less solution. This is shorthand and is COMPLETELY different
* from a solution set with zero solutions (which conjoins with any other solution set to the empty solution set).)
*
*
* @param set1
* @param set2
* @return The conjunction of the two solution sets.
*/
static public SolutionSet join(SolutionSet set1, SolutionSet set2) {
boolean isEnabled = RequestAnalysis.getAnalysisLogger().isDebugEnabled();
log.trace(LogUtils.GLITTER_MARKER, "join");
if (set1 == null) {
if (isEnabled)
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_identityJoin-RHS] {}", Integer.toString(set2.size()));
return set2;
}
if (set2 == null) {
if (isEnabled)
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_identityJoin-LHS] {}", Integer.toString(set1.size()));
return set1;
}
Comparator<Value> comparator = getComparator(set1, set2);
if (set1.size() == 0 || set2.size() == 0) {
if (isEnabled)
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_nullJoin]");
return new SolutionList();
}
PatternSolution sol1[] = set1.toArray(new PatternSolution[0]);
PatternSolution sol2[] = set2.toArray(new PatternSolution[0]);
if (sol1.length == 1 && sol1[0].size() == 0) {
if (isEnabled)
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_identityJoin-RHS] {}", Integer.toString(set2.size()));
return set2;
} else if (sol2.length == 1 && sol2[0].size() == 0) {
if (isEnabled)
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_identityJoin-LHS] {}", Integer.toString(set1.size()));
return set1;
}
SolutionSet newSolutions = new CustomCompareSolutionSet.ComparableSolutionList(comparator);
long start = 0;
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_startJoin] {}:{}", Integer.toString(set1.size()), Integer.toString(set2.size()));
start = System.currentTimeMillis();
}
TreeSet<Bindable> count = new TreeSet<Bindable>();
for (PatternSolution element : sol1) {
for (Bindable bindable : element.getBoundDomain(true)) {
count.add(bindable);
}
}
TreeSet<Bindable> count2 = new TreeSet<Bindable>();
for (PatternSolution element : sol2) {
for (Bindable bindable : element.getBoundDomain(true)) {
count2.add(bindable);
}
}
TreeSet<Bindable> matchSet = new TreeSet<Bindable>();
if (count.size() < count2.size()) {
for (Bindable bindable : count) {
if (count2.contains(bindable))
matchSet.add(bindable);
}
} else {
for (Bindable bindable : count2) {
if (count.contains(bindable)) {
matchSet.add(bindable);
}
}
}
Bindable matchedBindables[] = matchSet.toArray(new Bindable[0]);
if (isEnabled) {
StringBuilder sb = new StringBuilder();
for (Bindable bindable : matchSet) {
sb.append(bindable.toString());
sb.append(",");
}
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_matchingBindings] {}", sb.toString());
}
long startSort = 0;
if (isEnabled) {
startSort = System.currentTimeMillis();
}
Arrays.sort(sol1, 0, sol1.length, new PatternSolutionImpl.SetSolutionComparator(matchSet, comparator));
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_leftSortTime] {}", Long.toString(System.currentTimeMillis() - startSort));
startSort = System.currentTimeMillis();
}
Arrays.sort(sol2, 0, sol2.length, new PatternSolutionImpl.SetSolutionComparator(matchSet, comparator));
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_rightSortTime] {}", Long.toString(System.currentTimeMillis() - startSort));
startSort = System.currentTimeMillis();
}
//System.err.println("Joining:" + sol1.length + ":" + sol2.length);
int j = 0;
//long start = System.currentTimeMillis();
if (matchSet.size() > 0) {
for (PatternSolution solution : sol1) {
Bindable sol1Bindables[] = solution.getBoundDomainArray();
boolean done = false;
for (int k = j; k < sol2.length && !done; k++) {
if (sol1Bindables.length == 0) {
newSolutions.add(sol2[k]);
} else {
boolean match = true;
boolean cloned = false;
PatternSolution solution2 = sol2[k];
boolean firstEmpty = true;
Bindable bindables2[] = solution2.getBoundDomainArray();
int m = 0;
if (bindables2.length > 0) {
boolean breaker = false;
for (Bindable element : matchedBindables) {
firstEmpty = false;
for (int mm = m; mm < bindables2.length && !breaker; mm++) {
int comp1 = element.compareTo(bindables2[mm]);
if (comp1 == 0) {
Value term = solution.getBinding(element);
Value term2 = solution2.getBinding(bindables2[mm]);
//If term is null, this means that lh solution does not have a binding for a shared binding, so have to do slow conjoin
if (term == null) {
PatternSolution psNew = conjoin(solution, solution2);
if (psNew != null) {
newSolutions.add(psNew);
}
match = false;
breaker = true;
} else {
int comp = comparator.compare(term, term2);
if (comp > 0) {
match = false;
breaker = true;
j = k;
break;
} else if (comp < 0) {
match = false;
done = true;
breaker = true;
break;
} else {
if (!cloned) {
bindables2 = bindables2.clone();
cloned = true;
}
// conjunction.put(bindables2[mm], term);
bindables2[mm] = null;
m = mm + 1;
break;
}
}
} else if (comp1 > 0) {
m = mm;
}
}
if (breaker)
break;
}
if (match) {
if (firstEmpty) {
newSolutions.add(solution2);
} else {
PatternSolutionImpl newSolution = new PatternSolutionImpl(solution);
for (Bindable element : bindables2) {
if (element != null) {
newSolution.setBinding(element, solution2.getBinding(element));
}
}
newSolutions.add(new PatternSolutionImpl(newSolution));
}
}
} else {
newSolutions.add(solution);
}
}
}
}
} else {
for (PatternSolution solution : sol1) {
for (PatternSolution solution2 : sol2) {
Bindable bindable[] = new Bindable[solution.getBoundDomainArray().length + solution2.getBoundDomainArray().length];
Value value[] = new Value[solution.getBoundDomainArray().length + solution2.getBoundDomainArray().length];
Bindable bs[] = solution.getBoundDomain(false);
Value vs[] = solution.getBoundVariablesArray(false);
System.arraycopy(bs, 0, bindable, 0, bs.length);
System.arraycopy(vs, 0, value, 0, vs.length);
int last = vs.length;
bs = solution2.getBoundDomain(false);
vs = solution2.getBoundVariablesArray(false);
System.arraycopy(bs, 0, bindable, last, bs.length);
System.arraycopy(vs, 0, value, last, vs.length);
newSolutions.add(new PatternSolutionImpl(bindable, value));
}
}
}
//System.err.println("Join:" + (System.currentTimeMillis() - start) + ":" + newSolutions.size());
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_endJoin] {}:{}", Integer.toString(newSolutions.size()), Long.toString(System.currentTimeMillis() - start));
}
return newSolutions;
/* SolutionSet newSolutions2 = new SolutionList();
for (PatternSolution ps1 : set1) {
for (PatternSolution ps2 : set2) {
PatternSolution psNew = ps1.conjoin(ps2);
if (psNew != null)
newSolutions2.add(psNew);
}
}
return newSolutions;*/
}
/**
* Returns whether or not the given solution contains values for all of the given variables.
*
* @param solution
* @param variables
* @return whether or not the given solution contains values for all of the given variables
*/
static private boolean solutionBindsAllVariables(PatternSolution solution, Collection<Variable> variables) {
return solution.bindsAllVariables(variables);
}
/**
*
* @param solution
* @param filters
* @return <tt>true</tt> if the given solution passes all of the supplied filters; <tt>false</tt> otherwise.
* @throws ExpressionEvaluationException
*/
static private boolean keepSolution(PatternSolution solution, Set<Expression> filters) throws ExpressionEvaluationException {
return keepSolution(solution, filters, false);
}
/**
*
* @param solution
* @param filters
* @param keepSolutionsWithUnboundVariables
* If <tt>true</tt>, a filter expression that acts on unbound variables is ignored. If <tt>false</tt>, such a filter expression evaluates to an
* error, which rejects the solution.
* @return <tt>true</tt> if the given solution passes all of the supplied filters; <tt>false</tt> otherwise.
* @throws ExpressionEvaluationException
*/
static private boolean keepSolution(PatternSolution solution, Set<Expression> filters, boolean keepSolutionsWithUnboundVariables) throws ExpressionEvaluationException {
for (Expression filter : filters) {
try {
// if we're OK with unbound variables and this filter has a variable that is unbound
// in this solution, then just move on to the next filter
if (keepSolutionsWithUnboundVariables && !solutionBindsAllVariables(solution, filter.getReferencedVariables()))
continue;
Value result = filter.evaluate(solution, null);
if (!TypeConversions.effectiveBooleanValue(result))
return false;
} catch (IncompatibleTypeException e) {
// type errors result in not keeping this solution
return false;
} catch (MalformedLiteralException e) {
// type errors result in not keeping this solution
return false;
} catch (ExpressionEvaluationException e) {
// anything else gets surfaced
throw e;
}
}
return true;
}
/**
* Applies the given set of filters to a full solution set.
*
* @param answers
* @param filters
* @return A solution set containing only those solutions from which apply filters returns <tt>true</tt>.
* @throws ExpressionEvaluationException
*/
static public SolutionSet filterSolutions(SolutionSet answers, Set<Expression> filters) throws ExpressionEvaluationException {
for (ListIterator<PatternSolution> it = answers.listIterator(); it.hasNext();) {
if (!keepSolution(it.next(), filters))
it.remove();
}
return answers;
}
/**
* Evaluates and binds the expressions in the given assignments to the corresponding variables.
*
* @param answers
* @param assignments
* @return SolutionSet with assignments assigned
* @throws ExpressionEvaluationException
*/
static public SolutionSet processAssignments(SolutionSet answers, MultiMap<Variable, Expression> assignments) throws ExpressionEvaluationException {
SolutionList newAnswers = new SolutionList();
ListIterator<PatternSolution> it = answers.listIterator();
while (it.hasNext()) {
// TODO there are evaluation optimizations here if the LET expressions don't depend on variables in
// the answers _and_ if the expressions are pure functional (no side effects/outside state involved)
PatternSolution solution = it.next();
PatternSolutionImpl newSolution = new PatternSolutionImpl(solution);
boolean keepSolution = true;
for (Variable v : assignments.keySet()) {
for (Expression e : assignments.get(v)) {
// allow aggregates to operate over the given solutions
// evaluate in the context of the solution going in, but compare with the new solution
// we're building up (extending the original solution)
Value assignedVal = e.evaluate(solution, answers);
if (assignedVal == null)
continue;
Value currentVal = newSolution.getBinding(v);
if (currentVal != null && !currentVal.equals(assignedVal)) {
// joining this assignment to this solution results fails, so the solution
// is removed from the results
keepSolution = false;
break;
} else if (currentVal == null) {
newSolution.setBinding(v, assignedVal);
}
}
if (!keepSolution)
break;
}
if (keepSolution)
newAnswers.add(newSolution);
}
return newAnswers;
}
/**
* Implements the SPARQL LeftJoin operator. (For the SPARQL <tt>OPTIONAL</tt> keyword.)
*
* @param set1
* The left-hand-side of the outer join. See the SPARQL spec. for precise semantics.
* @param set2
* The right-hand-side of the outer join.
* @param filters
* Filters that are scoped to this left join.
* @return The results of applying the LeftJoin operator.
*/
static public SolutionSet leftJoin(SolutionSet set1, SolutionSet set2, Set<Expression> filters) {
Comparator<Value> comparator = getComparator(set1, set2);
if (set1 == null)
return filterSolutions(set2, filters);
if (set2 == null)
return filterSolutions(set1, filters);
long start = 0;
boolean isEnabled = RequestAnalysis.getAnalysisLogger().isDebugEnabled();
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER,"[glitter_SPARQLAlgebra_startLeftJoin] {}:{}", Integer.toString(set1.size()), Integer.toString(set2.size()));
start = System.currentTimeMillis();
}
SolutionSet newSolutions = new CustomCompareSolutionSet.ComparableSolutionList(comparator);
PatternSolution sol1[] = set1.toArray(new PatternSolution[0]);
// count1 contains all the variables (& bnodes) in the first (required)
// solution set. count2 contains all the bindables that appear in the
// second.
TreeSet<Bindable> count1 = new TreeSet<Bindable>();
for (PatternSolution element : sol1) {
for (Bindable bindable : element.getBoundDomain(true)) {
count1.add(bindable);
}
}
PatternSolution sol2[] = set2.toArray(new PatternSolution[0]);
TreeSet<Bindable> count2 = new TreeSet<Bindable>();
for (PatternSolution element : sol2) {
for (Bindable bindable : element.getBoundDomain(true)) {
count2.add(bindable);
}
}
// populate matchSet with all the bindables that are in common
// between the two solution sets. Order the Binding names in the matchSet
TreeSet<Bindable> matchSet = new TreeSet<Bindable>();
if (count1.size() < count2.size()) {
for (Bindable bindable : count1) {
if (count2.contains(bindable))
matchSet.add(bindable);
}
} else {
for (Bindable bindable : count2) {
if (count1.contains(bindable)) {
matchSet.add(bindable);
}
}
}
Bindable matchedBindables[] = matchSet.toArray(new Bindable[0]);
//Matt:Sort the solutions in sol1+sol2 based on the ordered matchSet, ie
//the matchSet is sorted alphabetically by the binding names, so sort the solutions in sol1 + sol2
//by a progressive alphabetical sort based on the ordered binding names, ie
//if you have binding "A" and binding "B", the solutions would get ordered first by
//and alphabetical sort of the Binding "A" values, and when the values of Binding "A" match,
//alphabetical sort of Biding "B" values.
//
//Example: 2 Binding "A" and "B"
//
// row 1: A=adam B=zebra
// row 2: A=charlie b=apple
// row 3: A=adam B=david
// row 4: A=zed B=arrow
//Sort order would be
// row 3: A=adam B=david //since adam is alphabetically lower than charlie and zed, and then david is lower than zebra
// row 1: A=adam B=zebra
// row 2: A=charlie b=apple //charlie is after adam, and before zed, apple isn't used in sort since there are no other values with a match on A
// row 4: A=zed B=arrow //zed is after adam and charlie, arrow isn't used in sort since there are no other values with a match on A
long startSort = 0;
if (isEnabled) {
startSort = System.currentTimeMillis();
}
Arrays.sort(sol1, 0, sol1.length, new PatternSolutionImpl.SetSolutionComparator(matchSet, comparator));
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER,"[glitter_SPARQLAlgebra_leftSortTime] {}", Long.toString(System.currentTimeMillis() - startSort));
startSort = System.currentTimeMillis();
}
Arrays.sort(sol2, 0, sol2.length, new PatternSolutionImpl.SetSolutionComparator(matchSet, comparator));
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER,"[glitter_SPARQLAlgebra_rightSortTime] {}", Long.toString(System.currentTimeMillis() - startSort));
startSort = System.currentTimeMillis();
}
// j is our current starting position for iterating over the optional
// solutions. we can skip optional solutions p..r if we know that all
// of them are incompatible with required solutions
// //Matt:You know they are incompatiable because the sort order of the 2 solution sets is the same
int j = 0;
//long start = System.currentTimeMillis();
for (PatternSolution solution1 : sol1) {
Bindable sol1Bindables[] = solution1.getBoundDomainArray();
boolean oneMatch = false;
boolean done = false;
//Matt:Since the solutions in the 2 sets are in the same sort order, you know that if
//you compare solution(k) from the right solution set with solution(i) from the left solution set and solution(k) is
//lower in the sort order than solution(i), then all the remaining solutions in left solution set will also have a
//higher sort order than solutions(0-k) in right solution set.
for (int k = j; k < sol2.length && !done; k++) {
if (sol1Bindables.length == 0) {
// the empty solution is the unit / identity solution
oneMatch = true;
newSolutions.add(sol2[k]);
} else {
// match starts as false; if all the bindings in solution1
// match bindings in solution2 (i.e. solution1 is compatible
// with solution2), then match==true.
//
// Lee: There seems to be a bug here if the intersection of
// bindable1 and bindable2 is empty: in this case, solution1 does not get
// extended by solution2, as it should be. Would this be fixed
// by defaulting match to true?
boolean match = true;
boolean cloned = false;
PatternSolution solution2 = sol2[k];
// Map<Bindable, Value> conjunction = new HashMap<Bindable, Value>();
Bindable bindables2[] = solution2.getBoundDomainArray();
// we traverse the bindables in these solutions in parallel
// this counter is our current starting position in the optional bindables
//Matt:Since the bindables in the 2 arrays are in the same sort order, you know that if
//you compare bindable(m) from the right array with bindable(l) from the left array and bindable(m) is
//lower in the sort order than bindable(l), then all the remaining bindable in left array will also have a
//higher sort order than bindable(0-m) in right array.
int m = 0;
// breaker is true if solution1 and solution2 are not compatible;
// once one non-compatible binding has been found, the rest of
// the bindables in solution1 will be copied, but we won't bother
// checking them against solution2.
boolean breaker = false;
if (bindables2.length > 0) {
for (Bindable element : matchedBindables) {
// put in the required solution, unmodified, regardless of whether
// we've found any incompatibilities yet
//conjunction.put(element, solution1.getBinding(element));
for (int mm = m; mm < bindables2.length && !breaker; mm++) {
int comp1 = element.compareTo(bindables2[mm]);
if (comp1 == 0) {
// the same bindable is in both solutions, check
// the value its bound to; note that solutions in each
// solution set are ordered by comparing terms (bound
// values)
Value term1 = solution1.getBinding(element);
//If term is null, this means that lh solution does not have a binding for a shared binding, so have to do slow conjoin
if (term1 == null) {
PatternSolution newSolution = conjoin(solution1, solution2);
if (newSolution != null) {
if (keepSolution(newSolution, filters)) {
// oneMatch indicates that solution1 (from the left-hand side)
// was compatible with at least one solution from the right-hand
// solution set
oneMatch = true;
newSolutions.add(newSolution);
}
}
match = false;
breaker = true;
} else {
Value term2 = solution2.getBinding(bindables2[mm]);
int comp = comparator.compare(term1, term2);
if (comp > 0) {
// See note above - since the left solution has
// a higher value for this term, we can skip all
// right-hand solutions before this one when we
// start with the next left-hand solution.
match = false;
breaker = true;
j = k;
break;
} else if (comp < 0) {
match = false;
breaker = true;
done = true;
break;
} else {
if (!cloned) {
bindables2 = bindables2.clone();
cloned = true;
}
match = true;
// conjunction.put(bindables2[mm], term);
bindables2[mm] = null;
m = mm + 1;
break;
}
}
} else if (comp1 > 0) {
m = mm;
}
}
}
// match is true if all of the terms in common between solution1
// and solution2 are bound to the same value in both solutions
if (match) {
PatternSolutionImpl newSolution = new PatternSolutionImpl(solution1);
// before we accept this conjoined solution, we need to make sure
// it passes the filter
for (Bindable element : bindables2) {
// bindings that match those in solution1 were nulled out above
// so if a binding is not null, it extends solution 1 and we copy
// it into the conjunction
if (element != null) {
newSolution.setBinding(element, solution2.getBinding(element));
}
}
if (keepSolution(newSolution, filters)) {
// oneMatch indicates that solution1 (from the left-hand side)
// was compatible with at least one solution from the right-hand
// solution set
oneMatch = true;
newSolutions.add(newSolution);
}
}
}
}
}
// if solution1 wasn't compatible with any solutions in the right-hand side, then we just
// copy solution1 into our resultset untouched
if (!oneMatch) {
newSolutions.add(solution1);
}
}
//System.err.println("LeftJoin:" + (System.currentTimeMillis() - start) + ":" + newSolutions.size());
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SPARQLAlgebra_endLeftJoin] {}:{}", Integer.toString(newSolutions.size()), Long.toString(System.currentTimeMillis() - start));
}
return newSolutions;
/*if (set1 == null)
return set2;
if (set2 == null)
return set1;
SolutionSet newSolutions = new SolutionList();
for (PatternSolution ps1 : set1) {
boolean extendedPs1 = false;
for (PatternSolution ps2 : set2) {
PatternSolution psNew = ps1.conjoin(ps2);
if (psNew != null) {
extendedPs1 = true;
newSolutions.add(psNew);
}
}
// for this to be a left outer join, we have to include the left-hand
// pattern solution even if all of the right-hand solutions conflict
// with it
if (!extendedPs1)
newSolutions.add(ps1);
}
return newSolutions;
*/
}
private static Comparator<Value> getComparator(SolutionSet set1, SolutionSet set2) {
Comparator<Value> comparator = CustomCompareSolutionSet.LEXICAL_COMPARATOR;
if (set1 instanceof CustomCompareSolutionSet && set2 instanceof CustomCompareSolutionSet && ((CustomCompareSolutionSet) set1).getComparator().equals(((CustomCompareSolutionSet) set2).getComparator())) {
comparator = ((CustomCompareSolutionSet) set1).getComparator();
}
log.debug(LogUtils.GLITTER_MARKER, "using comparator: {}", comparator.getClass());
return comparator;
}
private static PatternSolution conjoin(PatternSolution sol1, PatternSolution sol2) {
Map<Bindable, Value> conjunction = new HashMap<Bindable, Value>();
for (Bindable var : sol1.getBoundDomain(false)) {
conjunction.put(var, sol1.getBinding(var));
}
for (Bindable var : sol2.getBoundDomain(false)) {
Value thisVal = sol1.getBinding(var);
Value otherVal = sol2.getBinding(var);
// if this variable is bound in the other one and not bound to the
// same term in this one, then the solutions are mutually exclusive
if (otherVal != null && thisVal != null && !otherVal.equals(thisVal))
return null;
if (otherVal != null)
conjunction.put(var, otherVal);
}
return new PatternSolutionImpl(conjunction);
}
}