/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.reasoner.rulesys.impl;
import java.util.*;
import org.apache.jena.graph.* ;
import org.apache.jena.reasoner.* ;
import org.apache.jena.reasoner.rulesys.* ;
import org.apache.jena.util.OneToManyMap ;
import org.apache.jena.util.PrintUtil ;
import org.apache.jena.util.iterator.* ;
import org.apache.jena.vocabulary.RDF ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The processing engine for forward production rules. It neeeds to reference
* an enclosing ForwardInfGraphI which holds the raw data and deductions.
*/
public class FRuleEngine implements FRuleEngineI {
/** The parent InfGraph which is employing this engine instance */
protected ForwardRuleInfGraphI infGraph;
/** Set of rules being used */
protected List<Rule> rules;
/** Map from predicate node to rule + clause, Node_ANY is used for wildcard predicates */
protected OneToManyMap<Node, ClausePointer> clauseIndex;
/** List of predicates used in rules to assist in fast data loading */
protected HashSet<Node> predicatesUsed;
/** Flag, if true then there is a wildcard predicate in the rule set so that selective insert is not useful */
protected boolean wildcardRule;
/** Set to true to flag that derivations should be logged */
protected boolean recordDerivations;
/** performance stats - number of rules passing initial trigger */
int nRulesTriggered = 0;
/** performance stats - number of rules fired */
long nRulesFired = 0;
/** performance stats - number of rules fired during axiom initialization */
long nAxiomRulesFired = -1;
/** True if we have processed the axioms in the rule set */
boolean processedAxioms = false;
protected static Logger logger = LoggerFactory.getLogger(FRuleEngine.class);
// =======================================================================
// Constructors
/**
* Constructor.
* @param parent the F or FB infGraph that it using this engine, the parent graph
* holds the deductions graph and source data.
* @param rules the rule set to be processed
*/
public FRuleEngine(ForwardRuleInfGraphI parent, List<Rule> rules) {
infGraph = parent;
this.rules = rules;
}
/**
* Constructor. Build an empty engine to which rules must be added
* using setRuleStore().
* @param parent the F or FB infGraph that it using this engine, the parent graph
* holds the deductions graph and source data.
*/
public FRuleEngine(ForwardRuleInfGraphI parent) {
infGraph = parent;
}
// =======================================================================
// Control methods
/**
* Process all available data. This should be called once a deductions graph
* has be prepared and loaded with any precomputed deductions. It will process
* the rule axioms and all relevant existing exiting data entries.
* @param ignoreBrules set to true if rules written in backward notation should be ignored
* @param inserts the set of triples to be processed, normally this is the
* raw data graph but may include additional deductions made by preprocessing hooks
*/
@Override
public void init(boolean ignoreBrules, Finder inserts) {
if (clauseIndex == null) compile(rules, ignoreBrules);
findAndProcessAxioms();
nAxiomRulesFired = nRulesFired;
logger.debug("Axioms fired " + nAxiomRulesFired + " rules");
fastInit(inserts);
}
/**
* Process all available data. This version expects that all the axioms
* have already be preprocessed and the clause index already exists.
* @param inserts the set of triples to be processed, normally this is the
* raw data graph but may include additional deductions made by preprocessing hooks
*/
@Override
public void fastInit(Finder inserts) {
findAndProcessActions();
// Create the reasoning context
BFRuleContext context = new BFRuleContext(infGraph);
// Insert the data
if (wildcardRule) {
for (Iterator<Triple> i = inserts.find(new TriplePattern(null, null, null)); i.hasNext(); ) {
context.addTriple(i.next());
}
} else {
for ( Node predicate : predicatesUsed )
{
for ( Iterator<Triple> i = inserts.find( new TriplePattern( null, predicate, null ) ); i.hasNext(); )
{
context.addTriple( i.next() );
}
}
}
// Run the engine
addSet(context);
}
/**
* Add one triple to the data graph, run any rules triggered by
* the new data item, recursively adding any generated triples.
*/
@Override
public synchronized void add(Triple t) {
BFRuleContext context = new BFRuleContext(infGraph);
context.addTriple(t);
addSet(context);
}
/**
* Remove one triple to the data graph.
* @return true if the effects could be correctly propagated or
* false if not (in which case the entire engine should be restarted).
*/
@Override
public synchronized boolean delete(Triple t) {
// Incremental delete not supported
return false;
}
/**
* Return the number of rules fired since this rule engine instance
* was created and initialized
*/
@Override
public long getNRulesFired() {
return nRulesFired;
}
/**
* Return true if the internal engine state means that tracing is worthwhile.
* It will return false during the axiom bootstrap phase.
*/
@Override
public boolean shouldTrace() {
// return processedAxioms;
return true;
}
/**
* Set to true to enable derivation caching
*/
@Override
public void setDerivationLogging(boolean recordDerivations) {
this.recordDerivations = recordDerivations;
}
/**
* Access the precomputed internal rule form. Used when precomputing the
* internal axiom closures.
*/
@Override
public Object getRuleStore() {
return new RuleStore(clauseIndex, predicatesUsed, wildcardRule);
}
/**
* Set the internal rule from from a precomputed state.
*/
@Override
public void setRuleStore(Object ruleStore) {
RuleStore rs = (RuleStore)ruleStore;
clauseIndex = rs.clauseIndex;
predicatesUsed = rs.predicatesUsed;
wildcardRule = rs.wildcardRule;
}
// =======================================================================
// Internal methods
/**
* Add a set of new triple to the data graph, run any rules triggered by
* the new data item, recursively adding any generated triples.
* Technically the triples having been physically added to either the
* base or deduction graphs and the job of this function is just to
* process the stack of additions firing any relevant rules.
* @param context a context containing a set of new triples to be added
*/
public void addSet(BFRuleContext context) {
Triple t;
while ((t = context.getNextTriple()) != null) {
if (infGraph.shouldTrace()) {
logger.info("Processing: " + PrintUtil.print(t));
}
// Check for rule triggers
HashSet<Rule> firedRules = new HashSet<>();
Iterator<ClausePointer> i1 = clauseIndex.getAll(t.getPredicate());
Iterator<ClausePointer> i2 = clauseIndex.getAll(Node.ANY);
Iterator<ClausePointer> i = WrappedIterator.create(i1).andThen(i2);
while (i.hasNext()) {
ClausePointer cp = i.next();
if (firedRules.contains(cp.rule)) continue;
context.resetEnv( cp.rule.getNumVars() );
TriplePattern trigger = (TriplePattern) cp.rule.getBodyElement(cp.index);
if (match(trigger, t, context.getEnvStack())) {
nRulesTriggered++;
context.setRule(cp.rule);
if (matchRuleBody(cp.index, context)) {
firedRules.add(cp.rule);
nRulesFired++;
}
}
}
}
}
/**
* Compile a list of rules into the internal rule store representation.
* @param rules the list of Rule objects
* @param ignoreBrules set to true if rules written in backward notation should be ignored
* @returns an object that can be installed into the engine using setRuleStore.
*/
public void compile(List<Rule> rules, boolean ignoreBrules) {
clauseIndex = new OneToManyMap<>();
predicatesUsed = new HashSet<>();
wildcardRule = false;
for ( Rule r : rules )
{
if ( ignoreBrules && r.isBackward() )
{
continue;
}
Object[] body = r.getBody();
for ( int j = 0; j < body.length; j++ )
{
if ( body[j] instanceof TriplePattern )
{
Node predicate = ( (TriplePattern) body[j] ).getPredicate();
ClausePointer cp = new ClausePointer( r, j );
if ( predicate.isVariable() )
{
clauseIndex.put( Node.ANY, cp );
wildcardRule = true;
}
else
{
clauseIndex.put( predicate, cp );
if ( !wildcardRule )
{
predicatesUsed.add( predicate );
}
}
}
}
}
if (wildcardRule) predicatesUsed = null;
}
/**
* Scan the rules for any axioms and insert those
*/
protected void findAndProcessAxioms() {
BFRuleContext context = new BFRuleContext(infGraph);
for ( Rule r : rules )
{
if ( r.bodyLength() == 0 )
{
// An axiom
for ( int j = 0; j < r.headLength(); j++ )
{
Object head = r.getHeadElement( j );
if ( head instanceof TriplePattern )
{
TriplePattern h = (TriplePattern) head;
Triple t = new Triple( h.getSubject(), h.getPredicate(), h.getObject() );
context.addTriple( t );
infGraph.getDeductionsGraph().add( t );
}
}
}
}
addSet(context);
processedAxioms = true;
}
/**
* Scan the rules for any actions and run those
*/
protected void findAndProcessActions() {
BFRuleContext context = new BFRuleContext(infGraph);
for ( Rule r : rules )
{
if ( r.bodyLength() == 0 )
{
// An axiom
for ( int j = 0; j < r.headLength(); j++ )
{
Object head = r.getHeadElement( j );
if ( head instanceof Functor )
{
Functor f = (Functor) head;
Builtin imp = f.getImplementor();
if ( imp != null )
{
context.setRule( r );
imp.headAction( f.getArgs(), f.getArgLength(), context );
}
else
{
throw new ReasonerException(
"Invoking undefined Functor " + f.getName() + " in " + r.toShortString() );
}
}
}
}
}
}
/**
* Match the rest of a set of rule clauses once an initial rule
* trigger has fired. Carries out any actions as a side effect.
* @param trigger the index of the clause which has already be successfully matched
* @param context a context containing a set of new triples to be added
* @return true if the rule actually fires
*/
private boolean matchRuleBody(int trigger, BFRuleContext context) {
Rule rule = context.getRule();
// Create an ordered list of body clauses to process, best at the end
ClauseEntry[] body = rule.getBody();
int len = body.length;
List<ClauseEntry> clauses = new ArrayList<>(len-1);
if (len <= 1) {
// No clauses to add, just fall through to clause matcher
} else if (len == 2) {
// Only one clause remaining, no reordering necessary
Object clause = body[trigger == 0 ? 1 : 0];
if (clause instanceof TriplePattern) {
clauses.add((TriplePattern)clause);
}
} else {
// Pick most bound remaining clause as the best one to go first
int bestscore = 0;
int best = -1;
for (int i = 0; i < len; i++) {
if (i == trigger) continue; // Skip the clause already processed
BindingStack env = context.getEnvStack();
if (body[i] instanceof TriplePattern) {
TriplePattern clause = (TriplePattern) body[i];
int score = scoreNodeBoundness(clause.getSubject(), env) * 3 +
scoreNodeBoundness(clause.getPredicate(), env) * 2 +
scoreNodeBoundness(clause.getObject(), env) * 3;
if (score > bestscore) {
bestscore = score;
best = i;
}
}
}
for (int i = 0; i < len; i++) {
if (i == trigger || i == best) continue;
if (body[i] instanceof TriplePattern) {
clauses.add(body[i]);
}
}
if (best != -1) clauses.add(body[best]);
}
// Call a recursive clause matcher in the ordered list of clauses
boolean matched = matchClauseList(clauses, context);
if (matched) {
// We have new deductions stashed which now want to be
// asserted as deductions and then added to processing stack
context.flushPending();
}
return matched;
}
/**
* Match each of a list of clauses in turn. For all bindings for which all
* clauses match check the remaining clause guards and fire the rule actions.
* @param clauses the list of clauses to match, start with last clause
* @param context a context containing a set of new triples to be added
* @return true if the rule actually fires
*/
private boolean matchClauseList(List<ClauseEntry> clauses, BFRuleContext context) {
Rule rule = context.getRule();
BindingStack env = context.getEnvStack();
int index = clauses.size() - 1;
if (index == -1) {
// Check any non-pattern clauses
for (int i = 0; i < rule.bodyLength(); i++) {
Object clause = rule.getBodyElement(i);
if (clause instanceof Functor) {
// Fire a built in
if (!((Functor)clause).evalAsBodyClause(context)) {
return false; // guard failed
}
}
}
// Now fire the rule
if (infGraph.shouldTrace()) {
logger.info("Fired rule: " + rule.toShortString() + " = " + rule.instantiate(env));
}
List<Triple> matchList = null;
if (recordDerivations) {
// Create derivation record
matchList = new ArrayList<>(rule.bodyLength());
for (int i = 0; i < rule.bodyLength(); i++) {
Object clause = rule.getBodyElement(i);
if (clause instanceof TriplePattern) {
matchList.add(env.instantiate((TriplePattern)clause));
}
}
}
for (int i = 0; i < rule.headLength(); i++) {
Object hClause = rule.getHeadElement(i);
if (hClause instanceof TriplePattern) {
Triple t = env.instantiate((TriplePattern) hClause);
if (!t.getSubject().isLiteral()) {
// Only add the result if it is legal at the RDF level.
// E.g. RDFS rules can create assertions about literals
// that we can't record in RDF
if ( ! context.contains(t) ) {
context.add(t);
if (recordDerivations) {
infGraph.logDerivation(t, new RuleDerivation(rule, t, matchList, infGraph));
}
}
}
} else if (hClause instanceof Functor) {
Functor f = (Functor)hClause;
Builtin imp = f.getImplementor();
if (imp != null) {
imp.headAction(f.getBoundArgs(env), f.getArgLength(), context);
} else {
throw new ReasonerException("Invoking undefined Functor " + f.getName() +" in " + rule.toShortString());
}
} else if (hClause instanceof Rule) {
Rule r = (Rule)hClause;
if (r.isBackward()) {
infGraph.addBRule(r.instantiate(env));
} else {
throw new ReasonerException("Found non-backward subrule : " + r);
}
}
}
return true;
}
// More clauses left to match ...
List<ClauseEntry> clausesCopy = new ArrayList<>(clauses);
TriplePattern clause = (TriplePattern) clausesCopy.remove(index);
Node objPattern = env.getBinding(clause.getObject());
if (Functor.isFunctor(objPattern)) {
// Can't search on functor patterns so leave that as a wildcard
objPattern = null;
}
Iterator<Triple> i = infGraph.findDataMatches(
env.getBinding(clause.getSubject()),
env.getBinding(clause.getPredicate()),
env.getBinding(objPattern));
boolean foundMatch = false;
while (i.hasNext()) {
Triple t = i.next();
// Add the bindings to the environment
env.push();
if (match(clause.getPredicate(), t.getPredicate(), env)
&& match(clause.getObject(), t.getObject(), env)
&& match(clause.getSubject(), t.getSubject(), env)) {
foundMatch |= matchClauseList(clausesCopy, context);
}
env.unwind();
}
return foundMatch;
}
/**
* Score a Node in terms of groundedness - heuristic.
* Treats a variable as better than a wildcard because it constrains
* later clauses. Treats rdf:type as worse than any other ground node
* because that tends to link to lots of expensive rules.
*/
public static int scoreNodeBoundness(Node n, BindingEnvironment env) {
if (n instanceof Node_ANY) {
return 0;
} else if (n instanceof Node_RuleVariable) {
Node val = env.getGroundVersion(n);
if (val == null) {
return 1;
} else if (val.equals(RDF.type.asNode())) {
return 2;
} else {
return 3;
}
} else {
return 3;
}
}
// /**
// * Instantiate a triple pattern against the current environment.
// * This version handles unbound varibles by turning them into bNodes.
// * @param clause the triple pattern to match
// * @param env the current binding environment
// * @return a new, instantiated triple
// */
// public static Triple instantiate(TriplePattern pattern, BindingStack env) {
// Node s = env.getBinding(pattern.getSubject());
// if (s == null) s = Node.createAnon();
// Node p = env.getBinding(pattern.getPredicate());
// if (p == null) p = Node.createAnon();
// Node o = env.getBinding(pattern.getObject());
// if (o == null) o = Node.createAnon();
// return new Triple(s, p, o);
// }
/**
* Test if a TriplePattern matches a Triple in the given binding
* environment. If it does then the binding environment is modified
* the reflect any additional bindings.
* @return true if the pattern matches the triple
*/
public static boolean match(TriplePattern pattern, Triple triple, BindingStack env) {
env.push();
boolean matchOK = match(pattern.getPredicate(), triple.getPredicate(), env)
&& match(pattern.getObject(), triple.getObject(), env)
&& match(pattern.getSubject(), triple.getSubject(), env);
if (matchOK) {
env.commit();
return true;
} else {
env.unwind();
return false;
}
}
/**
* Test if a pattern Node matches a Triple Node in the given binding
* environment. If it does then the binding environment is modified
* the reflect any additional bindings.
* @return true if the pattern matches the node
*/
public static boolean match(Node pattern, Node node, BindingStack env) {
if (pattern instanceof Node_RuleVariable) {
int index = ((Node_RuleVariable)pattern).getIndex();
return env.bind(index, node);
} else if (pattern instanceof Node_ANY) {
return true;
} else if (Functor.isFunctor(pattern)) {
if (!Functor.isFunctor(node)) return false;
Functor patternF = (Functor) pattern.getLiteralValue();
Functor nodeF = (Functor) node.getLiteralValue();
if (!patternF.getName().equals(nodeF.getName())) return false;
Node[] patternArgs = patternF.getArgs();
Node[] nodeArgs = nodeF.getArgs();
// if (patternF.isGround()) {
// return Arrays.equals(patternArgs, nodeArgs);
// } else {
if (patternArgs.length != nodeArgs.length) return false;
// Compatible functor shapes so bind an embedded variables in the pattern
env.push();
boolean matchOK = true;
for (int i = 0; i < patternArgs.length; i++) {
if (!match(patternArgs[i], nodeArgs[i], env)) {
matchOK = false;
break;
}
}
if (matchOK) {
env.commit();
return true;
} else {
env.unwind();
return false;
}
// }
} else {
return pattern.sameValueAs(node);
}
}
//=======================================================================
// Inner classes
/**
* Structure used in the clause index to indicate a particular
* clause in a rule. This is used purely as an internal data
* structure so we just use direct field access.
*/
protected static class ClausePointer {
/** The rule containing this clause */
protected Rule rule;
/** The index of the clause in the rule body */
protected int index;
/** constructor */
ClausePointer(Rule rule, int index) {
this.rule = rule;
this.index = index;
}
/** Get the clause pointed to */
TriplePattern getClause() {
return (TriplePattern)rule.getBodyElement(index);
}
}
/**
* Structure used to wrap up processed rule indexes.
*/
public static class RuleStore {
/** Map from predicate node to rule + clause, Node_ANY is used for wildcard predicates */
protected OneToManyMap<Node, ClausePointer> clauseIndex;
/** List of predicates used in rules to assist in fast data loading */
protected HashSet<Node> predicatesUsed;
/** Flag, if true then there is a wildcard predicate in the rule set so that selective insert is not useful */
protected boolean wildcardRule;
/** Constructor */
RuleStore(OneToManyMap<Node, ClausePointer> clauseIndex, HashSet<Node> predicatesUsed, boolean wildcardRule) {
this.clauseIndex = clauseIndex;
this.predicatesUsed = predicatesUsed;
this.wildcardRule = wildcardRule;
}
}
}