/* * This software is Copyright 2005,2006,2007,2008 Langdale Consultants. * Langdale Consultants can be contacted at: http://www.langdale.com.au */ package au.com.langdale.inference; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.hp.hpl.jena.graph.Graph; import com.hp.hpl.jena.graph.Node; import com.hp.hpl.jena.graph.Triple; import com.hp.hpl.jena.mem.GraphMem; import com.hp.hpl.jena.reasoner.TriplePattern; import com.hp.hpl.jena.reasoner.rulesys.ClauseEntry; import com.hp.hpl.jena.reasoner.rulesys.Functor; /** * A rule-directed graph transformer. * * The primary input to the transformation is a split model (which can be very large). * The transformation is defined by a schema graph, rule list, and a set of functors. * The result is another Graph. When used as a validator, the result may be a * diagnostic graph. * */ public class Extractor { /** * Defines the effect of a functor when it appears as a rule clause. */ public interface FunctorActions { /** * A functor appearing as a body clause tests its arguments. * * @param nodes: the functor argument list * @param model: the input model * @param axioms: the schema, which should be treated as immutable * @param state: a positive result is reported by calling dispatch() on this object */ public void match(Node[] nodes, AsyncModel model, Graph axioms, RuleState state); /** * A functor appearing as a head clause can contribute to the result graph. * * @param nodes: the functor argument list * @param model: the result graph, to which functor effects should be applied. * @param axioms: the schema, which should be treated as immutable * @param state: the final state of the rule. this is provided for information only. */ public void apply(Node[] nodes, Graph model, Graph axioms, RuleState state); } /** * Throwable used internally to unwind and stop execution. */ public static class TerminateExtractor extends Error { private static final long serialVersionUID = 9117510621543001428L; } /** * Track the progress of rule. */ public static class RuleMonitor { private int entered = 1; private int exited = 0; private int fired = 0; public void enterState() { entered ++; } public void leaveState() { exited ++; if( entered == exited ) completed(); } public void fire() { fired ++; } public int getFired() { return fired; } public int getTotalStates() { return entered; } public int getPendingStates() { return entered - exited; } protected void completed() { } } private class Guard extends RuleMonitor { private final Functor block; Guard( Functor block ) { this.block = block; blocks.add(block); } @Override protected void completed() { blocks.remove(block); } } private AsyncModel reader; private List rules; private Map functors; private Map functions; private Set blocks; private Graph result; private Graph axioms; /** * Instantiate. * * @param reader: the (large) model to be transformed * @param axioms: the schema, which is treated as immutable * @param rules: the rules to be applied * @param functors: the a map of functor names to FunctorAction instances */ public Extractor(AsyncModel reader, Graph axioms, List rules, Map functors) { this.reader = reader; this.rules = rules; this.functors = functors; this.axioms = axioms; this.functions = new HashMap(); this.blocks = new HashSet(); registerFunctions(); } private void registerFunctions() { for (Iterator it = rules.iterator(); it.hasNext();) { CompoundRule rule = (CompoundRule) it.next(); if( rule.isFunction() ) { ClauseEntry key = rule.getBodyElement(0); if(functions.put(key, rule) != null) System.out.println("Multiple definitions for " + key); if(functors.containsKey(((Functor)key).getName())) System.out.println("Builtin conflicts with function definition " + key); } } } /** * Execute the transformation. * @throws IOException */ public void run() throws IOException { result = new GraphMem(); for (Iterator it = rules.iterator(); it.hasNext();) { CompoundRule rule = (CompoundRule) it.next(); invoke(rule); } try { reader.run(); } catch (TerminateExtractor e) { // enough already } } private void invoke(CompoundRule rule) { if( rule.isFunction() ) return; RuleState state = new RuleState(rule); state.dispatch(); } private void invoke(CompoundRule rule, Node[] args) { if( ! rule.isFunction() ) return; Functor func = (Functor) rule.getBodyElement(0); Node[] formals = func.getArgs(); Functor block = new Functor(func.getName(), args); if( blocks.contains(block)) return; PartialBinding bindings = bind(formals, args, rule.getNumVars()); if( bindings == null ) return; RuleState state = new RuleState(rule, bindings, new Guard(block)); state.dispatch(); } private PartialBinding bind(Node[] formals, Node[] args, int numVars) { if(formals.length != args.length ) return null; PartialBinding bindings = new PartialBinding(numVars); for( int ix = 0; ix < args.length; ix++ ) { if(args[ix].isConcrete()) { bindings.bind(formals[ix], args[ix]); } else { return null; } } return bindings; } /** * * @return: the result graph */ public Graph getResult() { return result; } /** * Represents a partially matched rule. */ public class RuleState { private final CompoundRule rule; private final PartialBinding bindings; private final RuleMonitor monitor; private final int clause; private RuleState(CompoundRule rule, PartialBinding bindings, RuleMonitor monitor) { this.rule = rule; this.bindings = bindings; this.clause = 1; this.monitor = monitor; } private RuleState(CompoundRule rule) { this.rule = rule; this.bindings = new PartialBinding(rule.getNumVars()); this.clause = 0; this.monitor = new Alternative(); } private RuleState(RuleState parent, PartialBinding bindings) { this.rule = parent.rule; this.bindings = bindings; this.clause = parent.clause + 1; this.monitor = parent.monitor; monitor.enterState(); } private RuleState(RuleState parent, CompoundRule rule) { this.rule = rule; this.bindings = new PartialBinding(parent.bindings, rule.getNumVars()); this.clause = 0; this.monitor = new Alternative(); } private RuleState(RuleState parent) { this(parent, parent.bindings); } /** * * @return: the Rule being processed. */ public CompoundRule getRule() { return rule; } /** * * @return: the body clause number being processed */ public int getClause() { return clause; } /** * Bind a variable */ public boolean bind(Node var, Node value) { return bindings.bind(var, value); } /** * Indicate that the current clause matches. */ public void dispatch() { if(clause >= rule.bodyLength()) fire(); else match(reader, rule.getBodyElement(clause)); } /** * Indicates that the current clause does not match anything. */ public void cancel() { monitor.leaveState(); } private void fire() { ClauseEntry[] entries = rule.getHead(); for (int i = 0; i < entries.length; i++) { ClauseEntry entry = entries[i]; if( entry instanceof TriplePattern) { if( rule.bodyLength() == 0) apply((TriplePattern) entry, axioms); else apply((TriplePattern) entry, result); } else if( entry instanceof Functor) apply((Functor)entry); else if( entry instanceof CompoundRule) apply((CompoundRule)entry); } monitor.fire(); monitor.leaveState(); } private void match(AsyncModel context, ClauseEntry entry) { if( entry instanceof TriplePattern) match(context, (TriplePattern)entry); else if( entry instanceof Functor) match(context, (Functor)entry); else if( entry instanceof QuoteClause) match(context, (QuoteClause)entry); } private void match(AsyncModel context, Functor functor) { FunctorActions actions = (FunctorActions) functors.get(functor.getName()); if( actions != null ) actions.match(functor.getBoundArgs(bindings), context, axioms, new RuleState(this)); monitor.leaveState(); } private void match(AsyncModel context, TriplePattern pattern) { context.find(bindings.partInstantiate(pattern), new Matcher(pattern)); } private void match(AsyncModel outer, QuoteClause entry) { Node quote = bindings.getGroundVersion(entry.getQuote()); AsyncModel context; try { context = outer.getQuote(quote); } catch (IOException e) { throw new RuntimeException(e); } if( context != null ) { match(context, entry.getClause()); } } private void apply(CompoundRule rule) { RuleState state = new RuleState(this, rule); state.dispatch(); } private void apply(Functor functor) { FunctorActions actions = (FunctorActions) functors.get(functor.getName()); if( actions == null) { CompoundRule target = (CompoundRule) functions.get(functor); if( target != null ) { invoke( target, functor.getBoundArgs(bindings)); } } else actions.apply(functor.getBoundArgs(bindings), result, axioms, this); } private void apply(TriplePattern pattern, Graph target) { target.add(bindings.instantiate(pattern)); } // public RuleState bind(Node variable, Node value) { // PartialBinding revised = new PartialBinding(bindings); // revised.bind(variable, value); // return new RuleState(this, revised); // } private class Alternative extends RuleMonitor { @Override protected void completed() { if( getFired() == 0 && rule.getAlternative() != null) { RuleState state = new RuleState(RuleState.this, rule.getAlternative()); state.dispatch(); } } } private class Matcher implements AsyncResult { private TriplePattern pattern; // private int count = 0; public Matcher(TriplePattern pattern) { this.pattern = pattern; } public boolean add(Triple result) { // count ++; PartialBinding revised = new PartialBinding(bindings); revised.bind(pattern.getSubject(), result.getSubject()); revised.bind(pattern.getPredicate(), result.getPredicate()); revised.bind(pattern.getObject(), result.getObject()); RuleState state = new RuleState(RuleState.this, revised); state.dispatch(); return true; } public void close() { monitor.leaveState(); } } } }