package org.aksw.sparqlify.restriction.experiment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.aksw.commons.util.Pair;
import org.aksw.jena_sparql_api.exprs_ext.E_StrConcatPermissive;
import org.aksw.jena_sparql_api.normal_form.Clause;
import org.aksw.jena_sparql_api.normal_form.NestedNormalForm;
import org.aksw.jena_sparql_api.restriction.RestrictionImpl;
import org.aksw.jena_sparql_api.restriction.RestrictionSetImpl;
import org.aksw.jena_sparql_api.utils.CnfUtils;
import org.aksw.jena_sparql_api.views.RdfTermType;
import org.aksw.sparqlify.database.IndirectEquiMap;
import org.apache.commons.lang.NotImplementedException;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.engine.binding.BindingHashMap;
import org.apache.jena.sparql.engine.binding.BindingMap;
import org.apache.jena.sparql.expr.E_Equals;
import org.apache.jena.sparql.expr.E_LogicalNot;
import org.apache.jena.sparql.expr.E_StrConcat;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprFunction;
import org.apache.jena.sparql.expr.NodeValue;
import org.apache.jena.sparql.util.ExprUtils;
/**
* A monotone container for assigning constraints to expressions.
* A constraint that has been added cannot be removed anymore.
*
* WARNING: While working on an instance having the parent set,
* do not modify any of the parents in the chain. Otherwise
* results can be unexpected.
*
*
*
* Note adding constraints to variables is logically equivalent
* to extending the encapsulated filter expression with
* [...] AND (constraint). e.g. [?a = foo] AND (?a prefix bar)
*
* Therfore, if a constraint is inconsistent (FALSE), then the
* whole expression is inconsistent.
*
* Furthermore, it is possible to state expressions, such as
* ?a = concat('foo', bar).
* In this case, constraints for the variable will be derived
* from the expression.
*
* TODO Now that i realize: actually we first derive a description
* (startsWith), and depending on the context we derive a constraint.
*
* However, a description is always a constraint (for what it describes and vice versa)
* so there is no point in separating the concepts in the class hierarchy,
* but on the instance level (so when using these constraints).
*
* TODO Actually we can delay checking of filter expressions
*
*
* @author Claus Stadler <cstadler@informatik.uni-leipzig.de>
*
*/
public class RestrictionManager2 {
private RestrictionManager2 parent;
private IndirectEquiMap<Var, RestrictionSetImpl> restrictions = new IndirectEquiMap<Var, RestrictionSetImpl>();
private NestedNormalForm cnf;
/*
public void getLocalVariables(Collection<Var> result) {
result.addAll(restrictions.keySet());
}*/
public Set<Var> getVariables() {
Set<Var> result = new HashSet<Var>();
RestrictionManager2 current = this;
while(current != null) {
result.addAll(current.restrictions.keySet());
current = current.parent;
}
return result;
}
/*
public IndirectEquiMap<Var, RestrictionSet> getRestrictions() {
if(parent == null) {
return restrictions;
}
IndirectEquiMap<Var, RestrictionSet> result = new IndirectEquiMap<Var, RestrictionSet>();
//result.
RestrictionManager2 current = this;
while(this != null) {
result.addAll(current.restrictions.keySet());
current = current.parent;
}
return result;
Set<Var> vars = getVariables();
for(Var var : vars) {
RestrictionSet r = getRestriction(var);
//restrictions.pu
}
}
*/
//private ExprIndex expr;
// Mapping of constraints derived from the expressions in expr
//private Map<Expr, Restriction> exprToRestriction = new HashMap<Expr, Restriction>();
// Mapping of variables to constants - derived from the restrictions
private Map<Var, Node> binding = new HashMap<Var, Node>();
private BindingMap bindingMap = new BindingHashMap();
// Without any constraints, we assume a tautology
private Boolean satisfiability = Boolean.TRUE;
public RestrictionManager2() {
this.cnf = new NestedNormalForm(null, false);
Set<Expr> emptyExprSet = Collections.emptySet();
this.cnf.add(new Clause(emptyExprSet));
this.satisfiability = Boolean.TRUE;
}
public RestrictionManager2(RestrictionManager2 parent) {
this.parent = parent;
this.cnf = new NestedNormalForm(parent.getCnf(), true);
}
public RestrictionManager2(NestedNormalForm cnf) {
this.cnf = cnf;
deriveRestrictions(cnf);
}
public NestedNormalForm getCnf() {
return cnf;
}
public Boolean getSatisfiability()
{
return satisfiability;
}
public static RestrictionImpl deriveRestriction(Expr expr) {
if(expr instanceof E_StrConcat || expr instanceof E_StrConcatPermissive) {
return deriveRestriction(expr);
} else if(expr.isConstant()) {
RestrictionImpl result = new RestrictionImpl();
result.stateNode(expr.getConstant().asNode());
return result;
}
return null;
}
public static RestrictionImpl deriveRestriction(E_StrConcat expr) {
return deriveRestrictionConcat(expr);
}
public static RestrictionImpl deriveRestriction(E_StrConcatPermissive expr) {
return deriveRestrictionConcat(expr);
}
public static RestrictionImpl deriveRestrictionConcat(ExprFunction concat) {
// TODO If all arguments are constant, we could infer a constant constraint
String prefix = "";
for(Expr arg : concat.getArgs()) {
if(arg.isConstant()) {
prefix += arg.getConstant().asUnquotedString();
} else {
break;
}
}
RestrictionImpl result = new RestrictionImpl();
result.stateUriPrefixes(new org.aksw.jena_sparql_api.views.PrefixSet(prefix));
return result;
};
// Actually we could have a global cache here - exprs have an identity, so we
// will always derive the same constraint - so thats a nice property I should exploit!
public void deriveRestrictions(Set<Clause> cnf) {
for(Clause clause : cnf) {
if(clause.getExprs().size() == 1) {
for(Entry<Var, RestrictionImpl> entry : clause.getRestrictions().entrySet()) {
stateRestriction(entry.getKey(), entry.getValue());
}
//deriveRestriction(clause.getExprs().iterator().next());
}
}
}
//private EquiMap<Var, PrefixSet> varToUriPrefixes = new EquiMap<Var, PrefixSet>();
/*
private IBiSetMultimap<Var, Var> equivalences = new BiHashMultimap<Var, Var>();
private Map<Var, Node> varToNode = new HashMap<Var, Node>();
//private Multimap<Var, Constraint> varToConstraint = HashMultimap.create();
private Map<Var, PrefixSet> varToUriPrefixes = new HashMap<Var, PrefixSet>();
*/
public boolean stateRestriction(Var var, RestrictionImpl restriction) {
return stateRestriction(var, new RestrictionSetImpl(restriction));
}
public boolean stateRestriction(Var var, RestrictionSetImpl restriction) {
RestrictionSetImpl r = getOrCreateLocalRestriction(var);
if(r.stateRestriction(restriction)) {
if(r.isUnsatisfiable()) {
satisfiability = Boolean.FALSE;
} else {
check(var);
}
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.aksw.sparqlify.database.IRestrictionManager#check(org.apache.jena.sparql.core.Var)
*/
public void check(Var var) {
Collection<Var> vars = restrictions.getEquivalences(var);
check(vars);
}
public Set<Clause> getClausesForVars(Collection<Var> vars) {
Set<Clause> result = new HashSet<Clause>();
for(Var var : vars) {
Set<Clause> tmp = cnf.getClausesByVar(var);
if(tmp != null) {
result.addAll(tmp);
}
}
return result;
}
/* (non-Javadoc)
* @see org.aksw.sparqlify.database.IRestrictionManager#check(java.util.Collection)
*/
public void check(Collection<Var> vars) {
Set<Clause> clauses = getClausesForVars(vars);
checkClauses(clauses);
}
public void checkClauses(Collection<Clause> clauses) {
for(Clause clause : clauses) {
check(clause);
if(satisfiability == Boolean.FALSE) {
return;
}
}
}
public void check(Clause clause)
{
// Hm, the cnf is nested for each restriction manager, but the clauses are not nested
// Ok, so if I change a clause, I create a new one
// The old one gets removed from the cnf, the new one gets added
// The question is, do I want nesting withing clauses? Naaah, guess not
//Clause modify = null;
Set<Expr> modify = new HashSet<Expr>();
Boolean isClauseSat = true;
for(Expr expr : clause.getExprs()) {
Boolean satisfiability = determineSatisfiability(expr);
if(satisfiability == null) {
modify.add(expr);
}
else if(satisfiability == true) {
continue;
} else { // satisfiability == false
isClauseSat = false;
break;
}
}
// If one of the clauses is not satisfiable, the whole cnf is'nt
if(!isClauseSat) {
this.satisfiability = Boolean.FALSE;
return;
}
// TODO We could make nested clauses
if(modify != null) {
cnf.remove(clause);
cnf.add(new Clause(modify));
}
}
public Boolean determineSatisfiability(Expr expr) {
/*
BindingMap bindingMap = new BindingMap();
for(Entry<Var, Node> entry : binding.entrySet()) {
bindingMap.add(entry.getKey(), entry.getValue());
}*/
if(binding.keySet().containsAll(expr.getVarsMentioned())) {
try {
NodeValue value = ExprUtils.eval(expr, bindingMap);
return value.getBoolean();
} catch(Exception e) {
// Evaluation of the expression failed despite all variables were bound
// Satisfiability unknown
System.err.println(e);
return null;
}
}
else if(expr instanceof E_LogicalNot) {
Boolean tmp = determineSatisfiability(((E_LogicalNot)expr).getArg());
return tmp == null ? null : !tmp;
}
else if(expr instanceof E_Equals) {
E_Equals e = (E_Equals)expr;
RestrictionSetImpl a = getRestriction(e.getArg1());
RestrictionSetImpl b = getRestriction(e.getArg2());
return determineSatisfiabilityEquals(a, b);
} else {
return null;
}
}
public RestrictionSetImpl getRestriction(Expr expr) {
if(expr.isVariable()) {
return restrictions.get(expr.asVar());
} else {
return new RestrictionSetImpl();
//return null;
//return exprToRestriction.get(expr);
}
}
/**
*
* Supported Constraints: Constant, StartsWith1
*
* @param r
* @param c
*/
public static Boolean determineSatisfiabilityEquals(RestrictionImpl a, RestrictionImpl b) {
if(a == null || b == null) {
return null;
}
RestrictionImpl tmp = new RestrictionImpl(a);
tmp.stateRestriction(b);
if(!tmp.isConsistent()) {
return false;
} else {
return null;
}
}
public static Boolean determineSatisfiabilityEquals(RestrictionSetImpl a, RestrictionSetImpl b) {
if(a == null || b == null) {
return null;
}
RestrictionSetImpl tmp = new RestrictionSetImpl(a);
tmp.stateRestriction(b);
if(tmp.isUnsatisfiable()) {
return false;
} else {
return null;
}
}
public boolean isEqual(Var a, Var b) {
boolean e = restrictions.isEqual(a, b);
if(e) {
return true;
}
else {
return (parent != null) ? parent.isEqual(a, b) : false;
}
}
public Collection<Var> getEquivalences(Var a) {
Collection<Var> result = restrictions.getEquivalences(a);
if(result.isEmpty() && parent != null) {
return parent.getEquivalences(a);
}
return result;
}
public void stateEqual(Var a, Var b) {
boolean didCopy = false;
if(restrictions.isEqual(a, b)) {
return;
} else {
if(parent != null && parent.isEqual(a, b)) {
return;
} else {
// Copy the equivalences from the parent
Collection<Var> ae = getEquivalences(a);
RestrictionSetImpl ar = getRestriction(a);
Collection<Var> be = getEquivalences(b);
RestrictionSetImpl br = getRestriction(b);
// TODO We copy the equivalences in order to avoid ConcurrentModificationException
restrictions.stateEqual(new HashSet<Var>(ae), ar);
restrictions.stateEqual(new HashSet<Var>(be), br);
didCopy = true;
}
}
Pair<RestrictionSetImpl, RestrictionSetImpl> conflict = restrictions.stateEqual(a, b);
//Restriction r;
if(conflict != null) {
RestrictionSetImpl r = conflict.getKey();
if(didCopy) {
r = r.clone();
}
r.stateRestriction(conflict.getValue());
restrictions.stateEqual(a, b, r);
}
/*
else {
r = restrictions.get(a);
}*/
// Recheck clauses with variable a (which is now equal to b)
check(a);
}
public RestrictionSetImpl getRestriction(Var a) {
RestrictionSetImpl result = restrictions.get(a);
if(result == null && parent != null) {
return parent.getRestriction(a);
}
return result;
}
public RestrictionSetImpl getOrCreateLocalRestriction(Var a) {
RestrictionSetImpl result = restrictions.get(a);
if(result == null && parent != null) {
RestrictionSetImpl toCopy = parent.getRestriction(a);
if(toCopy != null) {
result = toCopy.clone();
}
}
if(result == null) {
result = new RestrictionSetImpl();
restrictions.put(a, result);
}
return result;
}
public void stateType(Var a, RdfTermType type) {
RestrictionSetImpl r = getOrCreateLocalRestriction(a);
if(r.stateType(type)) {
if(r.isUnsatisfiable()) {
this.satisfiability = false;
} else {
check(a);
}
}
}
public void stateNode(Var a, Node b) {
RestrictionSetImpl r = getOrCreateLocalRestriction(a);
if(r.stateNode(b)) {
if(r.isUnsatisfiable()) {
satisfiability = Boolean.FALSE;
return;
}
check(a);
if(!(satisfiability == Boolean.FALSE)) {
for(Var v : restrictions.getEquivalences(a)) {
binding.put(v, b);
bindingMap.add(v, b);
}
}
}
}
public void stateUri(Var a, String uri) {
stateNode(a, NodeFactory.createURI(uri));
}
public void stateLiteral(Var a, NodeValue b) {
stateNode(a, b.asNode());
}
public void stateLexicalValuePrefixes(Var a, org.aksw.jena_sparql_api.views.PrefixSet prefixes) {
RestrictionSetImpl r = getOrCreateLocalRestriction(a);
if(r.stateUriPrefixes(prefixes)) {
check(a);
}
}
/**
* States a new expression, which is treated as conjuncted with previous expressions.
*
* This means that the restrictions are monotone in regard to adding new expressions.
*
* Given (?a = b) && (?a = x || ?a = y)
*
* Note: We are only interested in 'global' restrictions, we are not dealing with alternate
* varible assignments here (e.g. ?a = x OR ?a = y)
*
*
* @param expr
*/
public void stateExpr(Expr expr) {
NestedNormalForm newCnf = toCnf(expr);
stateCnf(newCnf);
}
public static NestedNormalForm toCnf(Expr expr) {
Set<Set<Expr>> ss = CnfUtils.toSetCnf(expr);
Set<Clause> clauses = new HashSet<Clause>();
for(Set<Expr> s : ss) {
clauses.add(new Clause(s));
}
return new NestedNormalForm(clauses);
}
public void stateCnf(NestedNormalForm newCnf) {
deriveRestrictions(newCnf);
if(satisfiability == Boolean.FALSE) {
return;
}
cnf.addAll(newCnf);
checkClauses(newCnf);
}
public void stateNonEqual(Var a, Var b) {
throw new NotImplementedException();
}
// TODO I need this method due to the lack of suppert for CNF lookups on tables right now
// Also, it does not use nesting
public Set<Clause> getEffectiveDnf(Collection<Var> vars) {
List<Clause> clauses = new ArrayList<Clause>(getClausesForVars(vars));
// Order the clauses by number of expressions
Collections.sort(clauses, new Comparator<Clause>() {
@Override
public int compare(Clause a, Clause b) {
return a.size() - b.size();
}
});
Set<Clause> result = new HashSet<Clause>();
getEffectiveDnf(0, clauses, null, result);
return result;
}
/**
* I use this method for getting constraints for finding view candidates
*
*
* @param dnfs
* @param index
* @param dnfIndex
* @param blacklist
* @param depth
* @param parentClause
* @param result
*/
public void getEffectiveDnf(int index, List<Clause> cnfs, Clause parentClause, Set<Clause> result) {
if(index >= cnfs.size()) {
if(parentClause != null) {
result.add(parentClause);
}
return;
}
Clause clause = cnfs.get(index);
for(Expr expr : clause.getExprs()) {
Set<Expr> exprs = new HashSet<Expr>();
if(parentClause != null) {
exprs.addAll(parentClause.getExprs());
}
exprs.add(expr);
Clause merged = new Clause(exprs);
getEffectiveDnf(index + 1, cnfs, merged, result);
}
}
@Override
public String toString() {
if(satisfiability == Boolean.FALSE) {
return "inconsistent";
} else {
return restrictions + " " + cnf.toString();
}
}
public void stateUriPrefixes(Var a, org.aksw.jena_sparql_api.views.PrefixSet prefixes) {
RestrictionSetImpl r = getOrCreateLocalRestriction(a);
if(r.stateUriPrefixes(prefixes)) {
if(r.isUnsatisfiable()) {
satisfiability = Boolean.FALSE;
return;
}
check(a);
}
}
public boolean isUnsatisfiable() {
return satisfiability == Boolean.FALSE;
}
/**
* How to create unions of CNFs?
*
* (a AND (b OR c)) OR (A AND (b OR c))
*
* The good thing: It should be easy to figure out whether a clause already exists in the CNF
* Actually: Can we even separate the restrictions from the CNF? I guess so.
* Actually, thats why I have the copy on write stuff anyway.
*
*
* Ok: First step: Create a union of the restrictions per variable
*
*
*
*
* @param rms
* @return
*/
public static RestrictionManager2 createUnion(Collection<RestrictionManager2> rms) {
// TODO Actually we just want the query variables - and not all (which includes view vars)
Set<Var> vars = new HashSet<Var>();
for(RestrictionManager2 rm : rms) {
if(rm.isUnsatisfiable()) {
continue;
}
vars.addAll(rm.getVariables());
}
RestrictionManager2 result = new RestrictionManager2();
// TODO: How to deal with the equivalences and the CNFs?
for(Var var : vars) {
RestrictionSetImpl newRs = new RestrictionSetImpl();
for(RestrictionManager2 rm : rms) {
if(rm.isUnsatisfiable()) {
continue;
}
RestrictionSetImpl rs = rm.getRestriction(var);
if(rs == null || rs.isUnsatisfiable()) {
continue;
}
for(RestrictionImpl r : rs.getRestrictions()) {
newRs.addAlternative(r);
}
}
result.stateRestriction(var, newRs);
}
return result;
}
}