/* Copyright 2008, 2009, 2010 by the Oxford University Computing Laboratory
This file is part of HermiT.
HermiT is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
HermiT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with HermiT. If not, see <http://www.gnu.org/licenses/>.
*/
package org.semanticweb.HermiT.blocking;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.semanticweb.HermiT.blocking.ValidatedSingleDirectBlockingChecker.ValidatedBlockingObject;
import org.semanticweb.HermiT.model.AtomicRole;
import org.semanticweb.HermiT.model.Concept;
import org.semanticweb.HermiT.model.DLClause;
import org.semanticweb.HermiT.model.DLOntology;
import org.semanticweb.HermiT.model.DataRange;
import org.semanticweb.HermiT.model.Variable;
import org.semanticweb.HermiT.monitor.TableauMonitor;
import org.semanticweb.HermiT.tableau.DLClauseEvaluator;
import org.semanticweb.HermiT.tableau.ExtensionManager;
import org.semanticweb.HermiT.tableau.Node;
import org.semanticweb.HermiT.tableau.NodeType;
import org.semanticweb.HermiT.tableau.Tableau;
public class AnywhereValidatedBlocking implements BlockingStrategy {
protected final DirectBlockingChecker m_directBlockingChecker;
protected final ValidatedBlockersCache m_currentBlockersCache;
protected BlockingValidator m_permanentBlockingValidator;
protected BlockingValidator m_additionalBlockingValidator;
protected Tableau m_tableau;
protected ExtensionManager m_extensionManager;
protected Node m_firstChangedNode;
protected Node m_lastValidatedUnchangedNode;
protected boolean m_useSimpleCore;
protected final boolean m_hasInverses;
public AnywhereValidatedBlocking(DirectBlockingChecker directBlockingChecker,boolean hasInverses,boolean useSimpleCore) {
m_directBlockingChecker=directBlockingChecker;
m_currentBlockersCache=new ValidatedBlockersCache(m_directBlockingChecker);
m_hasInverses=hasInverses;
m_useSimpleCore=useSimpleCore;
}
public void initialize(Tableau tableau) {
m_tableau=tableau;
m_directBlockingChecker.initialize(tableau);
m_extensionManager=m_tableau.getExtensionManager();
m_permanentBlockingValidator=new BlockingValidator(m_tableau,m_tableau.getPermanentDLOntology().getDLClauses());
updateAdditionalBlockingValidator();
}
public void additionalDLOntologySet(DLOntology additionalDLOntology) {
updateAdditionalBlockingValidator();
}
public void additionalDLOntologyCleared() {
updateAdditionalBlockingValidator();
}
protected void updateAdditionalBlockingValidator() {
if (m_tableau.getAdditionalHyperresolutionManager()==null)
m_additionalBlockingValidator=null;
else
m_additionalBlockingValidator=new BlockingValidator(m_tableau,m_tableau.getAdditionalDLOntology().getDLClauses());
}
public void clear() {
m_currentBlockersCache.clear();
m_firstChangedNode=null;
m_directBlockingChecker.clear();
m_lastValidatedUnchangedNode=null;
m_permanentBlockingValidator.clear();
if (m_additionalBlockingValidator!=null)
m_additionalBlockingValidator.clear();
}
public void computeBlocking(boolean finalChance) {
if (finalChance) {
validateBlocks();
}
else {
computePreBlocking();
}
}
public void computePreBlocking() {
if (m_firstChangedNode!=null) {
Node node=m_firstChangedNode;
while (node!=null) {
m_currentBlockersCache.removeNode(node);
node=node.getNextTableauNode();
}
node=m_firstChangedNode;
while (node!=null) {
if (node.isActive() && (m_directBlockingChecker.canBeBlocked(node) || m_directBlockingChecker.canBeBlocker(node))) {
if (m_directBlockingChecker.hasBlockingInfoChanged(node) || !node.isDirectlyBlocked() || node.getBlocker().getNodeID()>=m_firstChangedNode.getNodeID()) {
Node parent=node.getParent();
if (parent==null)
node.setBlocked(null,false);
else if (parent.isBlocked())
node.setBlocked(parent,false);
else {
Node blocker=null;
if (m_lastValidatedUnchangedNode==null)
blocker=m_currentBlockersCache.getBlocker(node);
else {
// after a validation has been done, only re-block if something has been modified
Node previousBlocker=node.getBlocker();
boolean nodeModified=m_directBlockingChecker.hasChangedSinceValidation(node);
for (Node possibleBlocker : m_currentBlockersCache.getPossibleBlockers(node)) {
if (nodeModified || m_directBlockingChecker.hasChangedSinceValidation(possibleBlocker) || previousBlocker==possibleBlocker) {
blocker=possibleBlocker;
break;
}
}
}
node.setBlocked(blocker,blocker!=null);
}
}
if (!node.isBlocked() && m_directBlockingChecker.canBeBlocker(node))
m_currentBlockersCache.addNode(node);
}
m_directBlockingChecker.clearBlockingInfoChanged(node);
node=node.getNextTableauNode();
}
m_firstChangedNode=null;
}
}
public void validateBlocks() {
// statistics for debugging:
boolean debuggingMode=false;
int checkedBlocks=0;
int invalidBlocks=0;
TableauMonitor monitor=m_tableau.getTableauMonitor();
if (monitor!=null)
monitor.blockingValidationStarted();
Node node;
node=m_lastValidatedUnchangedNode==null ? m_tableau.getFirstTableauNode() : m_lastValidatedUnchangedNode;
Node firstValidatedNode=node;
while (node!=null) {
m_currentBlockersCache.removeNode(node);
node=node.getNextTableauNode();
}
node=firstValidatedNode;
if (debuggingMode)
System.out.print("Model size: "+(m_tableau.getNumberOfNodesInTableau()-m_tableau.getNumberOfMergedOrPrunedNodes())+" Current ID:");
Node firstInvalidlyBlockedNode=null;
while (node!=null) {
if (node.isActive()) {
if (node.isBlocked()) { // && node.hasUnprocessedExistentials()
checkedBlocks++;
// check whether the block is a correct one
if ((node.isDirectlyBlocked() && (m_directBlockingChecker.hasChangedSinceValidation(node) || m_directBlockingChecker.hasChangedSinceValidation(node.getParent()) || m_directBlockingChecker.hasChangedSinceValidation(node.getBlocker()))) || !node.getParent().isBlocked()) {
Node validBlocker=null;
Node currentBlocker=node.getBlocker();
if (node.isDirectlyBlocked() && currentBlocker!=null) {
// try the old blocker fist
if (isBlockValid(node))
validBlocker=currentBlocker;
}
if (validBlocker==null) {
for (Node possibleBlocker : m_currentBlockersCache.getPossibleBlockers(node)) {
if (possibleBlocker!=currentBlocker) {
node.setBlocked(possibleBlocker,true);
m_permanentBlockingValidator.blockerChanged(node); // invalidate cache
if (m_additionalBlockingValidator!=null)
m_additionalBlockingValidator.blockerChanged(node);
if (isBlockValid(node)) {
validBlocker=possibleBlocker;
break;
}
}
}
}
if (validBlocker==null && node.hasUnprocessedExistentials()) {
invalidBlocks++;
if (firstInvalidlyBlockedNode==null)
firstInvalidlyBlockedNode=node;
}
node.setBlocked(validBlocker,validBlocker!=null);
}
}
m_lastValidatedUnchangedNode=node;
if (!node.isBlocked() && m_directBlockingChecker.canBeBlocker(node))
m_currentBlockersCache.addNode(node);
}
node=node.getNextTableauNode();
}
node=firstValidatedNode;
while (node!=null) {
if (node.isActive()) {
m_directBlockingChecker.setHasChangedSinceValidation(node,false);
ValidatedBlockingObject blockingObject=(ValidatedBlockingObject)node.getBlockingObject();
blockingObject.setBlockViolatesParentConstraints(false);
blockingObject.setHasAlreadyBeenChecked(false);
}
node=node.getNextTableauNode();
}
// if set to some node, then computePreblocking will be asked to check from that node onwards in case of invalid blocks
m_firstChangedNode=firstInvalidlyBlockedNode;
if (monitor!=null)
monitor.blockingValidationFinished(invalidBlocks);
if (debuggingMode) {
System.out.println("");
System.out.println("Checked "+checkedBlocks+" blocked nodes of which "+invalidBlocks+" were invalid.");
}
}
protected boolean isBlockValid(Node node) {
if (m_permanentBlockingValidator.isBlockValid(node)) {
if (m_additionalBlockingValidator!=null)
return m_additionalBlockingValidator.isBlockValid(node);
else
return true;
}
else
return false;
}
public boolean isPermanentAssertion(Concept concept,Node node) {
return true;
}
public boolean isPermanentAssertion(DataRange range,Node node) {
return true;
}
protected void validationInfoChanged(Node node) {
if (node!=null) {
if (m_lastValidatedUnchangedNode!=null && node.getNodeID()<m_lastValidatedUnchangedNode.getNodeID())
m_lastValidatedUnchangedNode=node;
m_directBlockingChecker.setHasChangedSinceValidation(node,true);
}
}
public void assertionAdded(Concept concept,Node node,boolean isCore) {
updateNodeChange(m_directBlockingChecker.assertionAdded(concept,node,isCore));
validationInfoChanged(node);
validationInfoChanged(node.getParent());
}
public void assertionCoreSet(Concept concept,Node node) {
updateNodeChange(m_directBlockingChecker.assertionAdded(concept,node,true));
validationInfoChanged(node);
validationInfoChanged(node.getParent());
}
public void assertionRemoved(Concept concept,Node node,boolean isCore) {
updateNodeChange(m_directBlockingChecker.assertionRemoved(concept,node,isCore));
validationInfoChanged(node);
validationInfoChanged(node.getParent());
}
public void assertionAdded(DataRange range,Node node,boolean isCore) {
updateNodeChange(m_directBlockingChecker.assertionAdded(range,node,isCore));
validationInfoChanged(node);
validationInfoChanged(node.getParent());
}
public void assertionCoreSet(DataRange range,Node node) {
updateNodeChange(m_directBlockingChecker.assertionAdded(range,node,true));
validationInfoChanged(node);
validationInfoChanged(node.getParent());
}
public void assertionRemoved(DataRange range,Node node,boolean isCore) {
updateNodeChange(m_directBlockingChecker.assertionRemoved(range,node,isCore));
validationInfoChanged(node);
validationInfoChanged(node.getParent());
}
public void assertionAdded(AtomicRole atomicRole,Node nodeFrom,Node nodeTo,boolean isCore) {
if (isCore)
updateNodeChange(nodeFrom);
if (isCore)
updateNodeChange(nodeTo);
validationInfoChanged(nodeFrom);
validationInfoChanged(nodeTo);
}
public void assertionCoreSet(AtomicRole atomicRole,Node nodeFrom,Node nodeTo) {
updateNodeChange(m_directBlockingChecker.assertionAdded(atomicRole,nodeFrom,nodeTo,true));
validationInfoChanged(nodeFrom);
validationInfoChanged(nodeTo);
}
public void assertionRemoved(AtomicRole atomicRole,Node nodeFrom,Node nodeTo,boolean isCore) {
updateNodeChange(m_directBlockingChecker.assertionRemoved(atomicRole,nodeFrom,nodeTo,true));
validationInfoChanged(nodeFrom);
validationInfoChanged(nodeTo);
}
public void nodesMerged(Node mergeFrom,Node mergeInto) {
Node parent=mergeFrom.getParent();
if (parent!=null && (m_directBlockingChecker.canBeBlocker(parent) || m_directBlockingChecker.canBeBlocked(parent)))
validationInfoChanged(parent);
}
public void nodesUnmerged(Node mergeFrom,Node mergeInto) {
Node parent=mergeFrom.getParent();
if (parent!=null && (m_directBlockingChecker.canBeBlocker(parent) || m_directBlockingChecker.canBeBlocked(parent)))
validationInfoChanged(parent);
}
public void nodeStatusChanged(Node node) {
updateNodeChange(node);
validationInfoChanged(node);
validationInfoChanged(node.getParent());
}
protected final void updateNodeChange(Node node) {
if (node!=null) {
if (m_firstChangedNode==null || node.getNodeID()<m_firstChangedNode.getNodeID())
m_firstChangedNode=node;
}
}
public void nodeInitialized(Node node) {
m_directBlockingChecker.nodeInitialized(node);
}
public void nodeDestroyed(Node node) {
m_currentBlockersCache.removeNode(node);
m_directBlockingChecker.nodeDestroyed(node);
if (m_firstChangedNode!=null && m_firstChangedNode.getNodeID()>=node.getNodeID())
m_firstChangedNode=null;
if (m_lastValidatedUnchangedNode!=null && node.getNodeID()<m_lastValidatedUnchangedNode.getNodeID())
m_lastValidatedUnchangedNode=node;
}
public void modelFound() {
}
protected final class ViolationStatistic implements Comparable<ViolationStatistic> {
public final String m_violatedConstraint;
public final Integer m_numberOfViolations;
public ViolationStatistic(String violatedConstraint,Integer numberOfViolations) {
m_violatedConstraint=violatedConstraint;
m_numberOfViolations=numberOfViolations;
}
public int compareTo(ViolationStatistic that) {
if (this==that)
return 0;
if (that==null)
throw new NullPointerException("Comparing to a null object is illegal. ");
if (this.m_numberOfViolations==that.m_numberOfViolations)
return m_violatedConstraint.compareTo(that.m_violatedConstraint);
else
return that.m_numberOfViolations-this.m_numberOfViolations;
}
public String toString() {
return m_numberOfViolations+": "+m_violatedConstraint.replaceAll("http://www.co-ode.org/ontologies/galen#","");
}
}
public boolean isExact() {
return false;
}
public void dlClauseBodyCompiled(List<DLClauseEvaluator.Worker> workers,DLClause dlClause,List<Variable> variables,Object[] valuesBuffer,boolean[] coreVariables) {
if (m_useSimpleCore) {
for (int i=0;i<coreVariables.length;i++) {
coreVariables[i]=false;
}
}
else {
if (dlClause.getHeadLength()==0)
return;
if (dlClause.getHeadLength()>1) {
for (int i=0;i<coreVariables.length;i++) {
coreVariables[i]=true;
}
}
else {
for (int i=0;i<coreVariables.length;i++) {
coreVariables[i]=false;
}
if (dlClause.isAtomicConceptInclusion() && variables.size()>1) {
workers.add(new ComputeCoreVariables(dlClause,variables,valuesBuffer,coreVariables));
}
}
}
}
protected static final class ComputeCoreVariables implements DLClauseEvaluator.Worker,Serializable {
private static final long serialVersionUID=899293772370136783L;
protected final DLClause m_dlClause;
protected final List<Variable> m_variables;
protected final Object[] m_valuesBuffer;
protected final boolean[] m_coreVariables;
public ComputeCoreVariables(DLClause dlClause,List<Variable> variables,Object[] valuesBuffer,boolean[] coreVariables) {
m_dlClause=dlClause;
m_variables=variables;
m_valuesBuffer=valuesBuffer;
m_coreVariables=coreVariables;
}
public void clear() {
}
public int execute(int programCounter) {
Node potentialNonCore=null;
// find the root of the subtree induced by the mapped nodes that node cannot be core
for (int variableIndex=m_coreVariables.length-1;variableIndex>=0;--variableIndex) {
Node node=(Node)m_valuesBuffer[variableIndex];
if (node.getNodeType()==NodeType.TREE_NODE && (potentialNonCore==null || node.getTreeDepth()<potentialNonCore.getTreeDepth())) {
potentialNonCore=node;
}
}
if (potentialNonCore!=null) {
for (int variableIndex=m_coreVariables.length-1;variableIndex>=0;--variableIndex) {
Node node=(Node)m_valuesBuffer[variableIndex];
if (!node.isRootNode() && potentialNonCore!=node && potentialNonCore.getTreeDepth()<node.getTreeDepth())
m_coreVariables[variableIndex]=true;
}
}
return programCounter+1;
}
public String toString() {
return "Compute core variables";
}
}
}
class ValidatedBlockersCache {
protected Tableau m_tableau;
protected final DirectBlockingChecker m_directBlockingChecker;
protected CacheEntry[] m_buckets;
protected int m_numberOfElements;
protected int m_threshold;
protected CacheEntry m_emptyEntries;
public ValidatedBlockersCache(DirectBlockingChecker directBlockingChecker) {
m_directBlockingChecker=directBlockingChecker;
clear();
}
public boolean isEmpty() {
return m_numberOfElements==0;
}
public void clear() {
m_buckets=new CacheEntry[1024];
m_threshold=(int)(m_buckets.length*0.75);
m_numberOfElements=0;
m_emptyEntries=null;
}
public boolean removeNode(Node node) {
// Check addNode() for an explanation of why we associate the entry with the node.
ValidatedBlockersCache.CacheEntry removeEntry=(ValidatedBlockersCache.CacheEntry)node.getBlockingCargo();
if (removeEntry!=null) {
int bucketIndex=getIndexFor(removeEntry.m_hashCode,m_buckets.length);
CacheEntry lastEntry=null;
CacheEntry entry=m_buckets[bucketIndex];
while (entry!=null) {
if (entry==removeEntry) {
if (node==entry.m_nodes.get(0)) {
// the whole entry needs to be removed
for (Node n : entry.m_nodes) {
n.setBlockingCargo(null);
}
if (lastEntry==null)
m_buckets[bucketIndex]=entry.m_nextEntry;
else
lastEntry.m_nextEntry=entry.m_nextEntry;
entry.m_nextEntry=m_emptyEntries;
entry.m_nodes=new ArrayList<Node>();
entry.m_hashCode=0;
m_emptyEntries=entry;
m_numberOfElements--;
}
else {
if (entry.m_nodes.contains(node)) {
for (int i=entry.m_nodes.size()-1;i>=entry.m_nodes.indexOf(node);i--) {
entry.m_nodes.get(i).setBlockingCargo(null);
}
entry.m_nodes.subList(entry.m_nodes.indexOf(node),entry.m_nodes.size()).clear();
}
else {
throw new IllegalStateException("Internal error: entry not in cache!");
}
}
return true;
}
lastEntry=entry;
entry=entry.m_nextEntry;
}
throw new IllegalStateException("Internal error: entry not in cache!");
}
return false;
}
public void addNode(Node node) {
int hashCode=m_directBlockingChecker.blockingHashCode(node);
int bucketIndex=getIndexFor(hashCode,m_buckets.length);
CacheEntry entry=m_buckets[bucketIndex];
while (entry!=null) {
if (hashCode==entry.m_hashCode && m_directBlockingChecker.isBlockedBy(entry.m_nodes.get(0),node)) {
if (!entry.m_nodes.contains(node)) {
entry.add(node);
node.setBlockingCargo(entry);
return;
}
else {
throw new IllegalStateException("Internal error: node already in the cache!");
}
}
entry=entry.m_nextEntry;
}
if (m_emptyEntries==null)
entry=new CacheEntry();
else {
entry=m_emptyEntries;
m_emptyEntries=m_emptyEntries.m_nextEntry;
}
entry.initialize(node,hashCode,m_buckets[bucketIndex]);
m_buckets[bucketIndex]=entry;
// When a node is added to the cache, we record with the node the entry.
// This is used to remove nodes from the cache. Note that changes to a node
// can affect its label. Therefore, we CANNOT remove a node by taking its present
// blocking hash-code, as this can be different from the hash-code used at the
// time the node has been added to the cache.
node.setBlockingCargo(entry);
m_numberOfElements++;
if (m_numberOfElements>=m_threshold)
resize(m_buckets.length*2);
}
protected void resize(int newCapacity) {
CacheEntry[] newBuckets=new CacheEntry[newCapacity];
for (int i=0;i<m_buckets.length;i++) {
CacheEntry entry=m_buckets[i];
while (entry!=null) {
CacheEntry nextEntry=entry.m_nextEntry;
int newIndex=getIndexFor(entry.m_hashCode,newCapacity);
entry.m_nextEntry=newBuckets[newIndex];
newBuckets[newIndex]=entry;
entry=nextEntry;
}
}
m_buckets=newBuckets;
m_threshold=(int)(newCapacity*0.75);
}
public Node getBlocker(Node node) {
if (m_directBlockingChecker.canBeBlocked(node)) {
int hashCode=m_directBlockingChecker.blockingHashCode(node);
int bucketIndex=getIndexFor(hashCode,m_buckets.length);
CacheEntry entry=m_buckets[bucketIndex];
while (entry!=null) {
if (hashCode==entry.m_hashCode && m_directBlockingChecker.isBlockedBy(entry.m_nodes.get(0),node)) {
if (node.getBlocker()!=null && entry.m_nodes.contains(node.getBlocker())) {
// don't change the blocker unnecessarily, the blocking validation code will change the blocker if necessary
return node.getBlocker();
}
else {
return entry.m_nodes.get(0);
}
}
entry=entry.m_nextEntry;
}
}
return null;
}
public List<Node> getPossibleBlockers(Node node) {
if (m_directBlockingChecker.canBeBlocked(node)) {
int hashCode=m_directBlockingChecker.blockingHashCode(node);
int bucketIndex=getIndexFor(hashCode,m_buckets.length);
CacheEntry entry=m_buckets[bucketIndex];
while (entry!=null) {
if (hashCode==entry.m_hashCode && m_directBlockingChecker.isBlockedBy(entry.m_nodes.get(0),node)) {
assert !entry.m_nodes.contains(node); // we try to block a node that is in the cache
return entry.m_nodes;
}
entry=entry.m_nextEntry;
}
}
return new ArrayList<Node>();
}
protected static int getIndexFor(int hashCode,int tableLength) {
hashCode+=~(hashCode<<9);
hashCode^=(hashCode>>>14);
hashCode+=(hashCode<<4);
hashCode^=(hashCode>>>10);
return hashCode&(tableLength-1);
}
public String toString() {
String buckets="";
for (int i=0;i<m_buckets.length;i++) {
CacheEntry entry=m_buckets[i];
if (entry!=null) {
buckets+="Bucket "+i+": ["+entry.toString()+"] ";
}
}
return buckets;
}
public static class CacheEntry implements Serializable {
private static final long serialVersionUID=-7047487963170250200L;
protected List<Node> m_nodes;
protected int m_hashCode;
protected CacheEntry m_nextEntry;
public void initialize(Node node,int hashCode,CacheEntry nextEntry) {
m_nodes=new ArrayList<Node>();
add(node);
m_hashCode=hashCode;
m_nextEntry=nextEntry;
}
public boolean add(Node node) {
for (Node n : m_nodes) {
assert n.getNodeID()<=node.getNodeID();
}
return m_nodes.add(node);
}
public String toString() {
String nodes="HashCode: "+m_hashCode+" Nodes: ";
for (Node n : m_nodes) {
nodes+=n.getNodeID()+" ";
}
return nodes;
}
}
}