blob: f864eab2aa9a82dd955f73c8f919e5032e25599c [file] [log] [blame]
/*
* 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.* ;
/**
* Checks a triple against the grounded matches and intra-triple matches
* for a single rule clause. If the match passes it creates a binding
* environment token and passes it on the RETE network itself. The checks
* and bindings are implemented using a simple byte-coded interpreter.
*/
public class RETEClauseFilter implements RETESourceNode {
/** Contains the set of byte-coded instructions and argument pointers */
protected byte[] instructions;
/** Contains the object arguments referenced from the instructions array */
protected Object[] args;
/** The network node to receive any created tokens */
protected RETESinkNode continuation;
/** Instruction code: Check triple entry (arg1) against literal value (arg2). */
public static final byte TESTValue = 0x01;
/** Instruction code: Check literal value is a functor of name arg1 */
public static final byte TESTFunctorName = 0x02;
/** Instruction code: Cross match two triple entries (arg1, arg2) */
public static final byte TESTIntraMatch = 0x03;
/** Instruction code: Create a result environment of length arg1. */
public static final byte CREATEToken = 0x04;
/** Instruction code: Bind a node (arg1) to a place in the rules token (arg2). */
public static final byte BIND = 0x05;
/** Instruction code: Final entry - dispatch to the network. */
public static final byte END = 0x06;
/** Argument addressing code: triple subject */
public static final byte ADDRSubject = 0x10;
/** Argument addressing code: triple predicate */
public static final byte ADDRPredicate = 0x20;
/** Argument addressing code: triple object as a whole */
public static final byte ADDRObject = 0x30;
/** Argument addressing code: triple object functor node, offset in
* low nibble, only usable after a successful TestFunctorName. */
public static final byte ADDRFunctorNode = 0x40;
/**
* Constructor.
* @param instructions the set of byte-coded instructions and argument pointers.
* @param args the object arguments referenced from the instructions array.
*/
public RETEClauseFilter(byte[] instructions, Object[] args) {
this.instructions = instructions;
this.args = args;
}
/**
* Create a filter node from a rule clause.
* Clause complexity is limited to less than 50 args in a Functor.
* @param clause the rule clause
* @param envLength the size of binding environment that should be created on successful matches
* @param varList a list to which all clause variables will be appended
*/
public static RETEClauseFilter compile(TriplePattern clause, int envLength, List<Node> varList) {
byte[] instructions = new byte[300];
byte[] bindInstructions = new byte[100];
ArrayList<Object> args = new ArrayList<>();
int pc = 0;
int bpc = 0;
// Pass 0 - prepare env creation statement
bindInstructions[bpc++] = CREATEToken;
bindInstructions[bpc++] = (byte)envLength;
// Pass 1 - check literal values
Node n = clause.getSubject();
if ( !n.isVariable() ) {
instructions[pc++] = TESTValue;
instructions[pc++] = ADDRSubject;
instructions[pc++] = (byte)args.size();
args.add( n );
} else {
bindInstructions[bpc++] = BIND;
bindInstructions[bpc++] = ADDRSubject;
bindInstructions[bpc++] = (byte)((Node_RuleVariable)n).getIndex();
varList.add(n);
}
n = clause.getPredicate();
if ( !n.isVariable() ) {
instructions[pc++] = TESTValue;
instructions[pc++] = ADDRPredicate;
instructions[pc++] = (byte)args.size();
args.add( clause.getPredicate() );
} else {
bindInstructions[bpc++] = BIND;
bindInstructions[bpc++] = ADDRPredicate;
bindInstructions[bpc++] = (byte)((Node_RuleVariable)n).getIndex();
varList.add(n);
}
n = clause.getObject();
if ( !n.isVariable() ) {
if (Functor.isFunctor(n)) {
// Pass 2 - check functor
Functor f = (Functor)n.getLiteralValue();
instructions[pc++] = TESTFunctorName;
instructions[pc++] = (byte)args.size();
args.add(f.getName());
Node[] fargs = f.getArgs();
for (int i = 0; i < fargs.length; i++) {
Node fn = fargs[i];
byte addr = (byte) (ADDRFunctorNode | (0x0f & i));
if ( !fn.isVariable() ) {
instructions[pc++] = TESTValue;
instructions[pc++] = addr;
instructions[pc++] = (byte)args.size();
args.add( fn );
} else {
bindInstructions[bpc++] = BIND;
bindInstructions[bpc++] = addr;
bindInstructions[bpc++] = (byte)((Node_RuleVariable)fn).getIndex();
varList.add(fn);
}
}
} else {
instructions[pc++] = TESTValue;
instructions[pc++] = ADDRObject;
instructions[pc++] = (byte)args.size();
args.add( n );
}
} else {
bindInstructions[bpc++] = BIND;
bindInstructions[bpc++] = ADDRObject;
bindInstructions[bpc++] = (byte)((Node_RuleVariable)n).getIndex();
varList.add(n);
}
bindInstructions[bpc++] = END;
// Pass 4 - Pack instructions
byte[] packed = new byte[pc + bpc];
System.arraycopy(instructions, 0, packed, 0, pc);
System.arraycopy(bindInstructions, 0, packed, pc, bpc);
Object[] packedArgs = args.toArray();
return new RETEClauseFilter(packed, packedArgs);
}
/**
* Set the continuation node for this node.
*/
@Override
public void setContinuation(RETESinkNode continuation) {
this.continuation = continuation;
}
/**
* Insert or remove a triple into the network.
* @param triple the triple to process.
* @param isAdd true if the triple is being added to the working set.
*/
public void fire(Triple triple, boolean isAdd) {
Functor lastFunctor = null; // bound by TESTFunctorName
BindingVector env = null; // bound by CREATEToken
Node n = null; // Temp workspace
for (int pc = 0; pc < instructions.length; ) {
switch(instructions[pc++]) {
case TESTValue:
// Check triple entry (arg1) against literal value (arg2)
if (! getTripleValue(triple, instructions[pc++], lastFunctor)
.sameValueAs(args[instructions[pc++]])) return;
break;
case TESTFunctorName:
// Check literal value is a functor of name arg1.
// Side effect: leaves a loop variable pointing to functor
// for possible later functor argument accesses
n = triple.getObject();
if ( !n.isLiteral() ) return;
if ( n.getLiteralDatatype() != FunctorDatatype.theFunctorDatatype) return;
lastFunctor = (Functor)n.getLiteralValue();
if ( !lastFunctor.getName().equals(args[instructions[pc++]]) ) return;
break;
case CREATEToken:
// Create a result environment of length arg1
env = new BindingVector(new Node[instructions[pc++]]);
break;
case BIND:
// Bind a node (arg1) to a place in the rules token (arg2)
n = getTripleValue(triple, instructions[pc++], lastFunctor);
if ( !env.bind(instructions[pc++], n) ) return;
break;
case END:
// Success, fire the continuation
continuation.fire(env, isAdd);
}
}
}
/**
* Helpful function. Return the node from the argument triple
* corresponding to the byte code address.
*/
private Node getTripleValue(Triple triple, byte address, Functor lastFunctor) {
switch (address & 0xf0) {
case ADDRSubject:
return triple.getSubject();
case ADDRPredicate:
return triple.getPredicate();
case ADDRObject:
return triple.getObject();
case ADDRFunctorNode:
return lastFunctor.getArgs()[address & 0x0f];
}
return null;
}
/**
* Clone this node in the network.
* @param netCopy a map from RETENode to cloned instance
* @param context the new context to which the network is being ported
*/
@Override
public RETENode clone(Map<RETENode, RETENode> netCopy, RETERuleContext context) {
RETEClauseFilter clone = (RETEClauseFilter)netCopy.get(this);
if (clone == null) {
clone = new RETEClauseFilter(instructions, args);
clone.setContinuation((RETESinkNode)continuation.clone(netCopy, context));
netCopy.put(this, clone);
}
return clone;
}
}