| /* |
| * 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; |
| } |
| |
| } |