/*
* 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.PrintUtil ;
/**
* An implementation of a binding environment that maintains
* a single array of bound values for the variables in a rule.
* Stack management is done externally. This is intended for use in
* the Brule system and so also supports variable-variable bindings by
* use of reference chains.
*/
public class BindingVector implements BindingEnvironment {
/** The current binding set */
protected Node[] environment;
/**
* Constructor - create an empty binding environment
*/
public BindingVector(int size) {
environment = new Node[size];
}
/**
* Constructor - create a binding environment from a vector of bindings
*/
public BindingVector(Node [] env) {
environment = env;
}
/**
* Constructor - create a binding environment which is a copy
* of the given environment
*/
public BindingVector(BindingVector clone) {
Node[] orig = clone.environment;
environment = new Node[orig.length];
System.arraycopy(orig, 0, environment, 0, orig.length);
}
/**
* Return the current array of bindings. Useful for fast access to
* serveral bindings, not useful for doing updates.
*/
public Node[] getEnvironment() {
return environment;
}
/**
* If the node is a variable then return the current binding (null if not bound)
* otherwise return the node itself.
*/
public Node getBinding(Node node) {
if (node instanceof Node_RuleVariable) {
Node val = environment[((Node_RuleVariable)node).getIndex()];
if (val instanceof Node_RuleVariable) {
return getBinding(val);
} else {
return val;
}
} else if (node instanceof Node_ANY) {
return null;
} else if (Functor.isFunctor(node)) {
Functor functor = (Functor)node.getLiteralValue();
if (functor.isGround()) return node;
Node[] args = functor.getArgs();
List<Node> boundargs = new ArrayList<>(args.length);
for ( Node arg : args )
{
Node binding = getBinding( arg );
if ( binding == null )
{
// Not sufficently bound to instantiate functor yet
return null;
}
boundargs.add( binding );
}
Functor newf = new Functor( functor.getName(), boundargs, functor.getImplementor() );
return Functor.makeFunctorNode( newf );
} else {
return node;
}
}
/**
* Return the most ground version of the node. If the node is not a variable
* just return it, if it is a varible bound in this enviroment return the binding,
* if it is an unbound variable return the variable.
*/
@Override
public Node getGroundVersion(Node node) {
Node bind = getBinding(node);
if (bind == null) {
return node;
} else {
return bind;
}
}
/**
* Bind the ith variable in the current envionment to the given value.
* Checks that the new binding is compatible with any current binding.
* Handles aliased variables.
* @return false if the binding fails
*/
public boolean bind(int i, Node value) {
Node node = environment[i];
if (node == null) {
environment[i] = value;
return true;
} else if (node instanceof Node_RuleVariable) {
environment[i] = value;
return bind(((Node_RuleVariable)node).getIndex(), value);
} else {
return node.sameValueAs(value);
}
}
/**
* Bind a variable in the current envionment to the given value.
* Checks that the new binding is compatible with any current binding.
* @param var a Node_RuleVariable defining the variable to bind
* @param value the value to bind
* @return false if the binding fails
*/
@Override
public boolean bind(Node var, Node value) {
if (var instanceof Node_RuleVariable) {
return bind(((Node_RuleVariable)var).getIndex(), value);
} else {
return var.sameValueAs(value);
}
}
/**
* Bind the variables in a goal pattern using the binding environment, to
* generate a more specialized goal
* @param goal the TriplePattern to be instantiated
* @return a TriplePattern obtained from the goal by substituting current bindinds
*/
public TriplePattern partInstantiate(TriplePattern goal) {
return new TriplePattern(
getGroundVersion(goal.getSubject()),
getGroundVersion(goal.getPredicate()),
getGroundVersion(goal.getObject())
);
}
// Replaced by version below for consistency with stack variant
// /**
// * Instatiate a goal pattern using the binding environment
// * @param goal the TriplePattern to be instantiated
// * @return an instantiated Triple
// */
// public Triple instantiate(TriplePattern goal) {
// return new Triple(
// getGroundVersion(goal.getSubject()),
// getGroundVersion(goal.getPredicate()),
// getGroundVersion(goal.getObject())
// );
// }
/**
* Instantiate a triple pattern against the current environment.
* This version handles unbound varibles by turning them into bNodes.
* @param pattern the triple pattern to match
* @return a new, instantiated triple
*/
@Override
public Triple instantiate(TriplePattern pattern) {
Node s = getGroundVersion(pattern.getSubject());
if (s.isVariable()) s = NodeFactory.createBlankNode();
Node p = getGroundVersion(pattern.getPredicate());
if (p.isVariable()) p = NodeFactory.createBlankNode();
Node o = getGroundVersion(pattern.getObject());
if (o.isVariable()) o = NodeFactory.createBlankNode();
return new Triple(s, p, o);
}
/**
* Printable form
*/
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
for ( Node anEnvironment : environment )
{
if ( anEnvironment == null )
{
buffer.append( "-" );
}
else
{
buffer.append( PrintUtil.print( anEnvironment ) );
}
buffer.append( " " );
}
return buffer.toString();
}
/**
* Unify a goal with the head of a rule. This is a poor-man's unification,
* we should try swtiching to a more conventional global-variables-with-trail
* implementation in the future.
* @param goal the goal pattern which it being matched to a rule
* @param head the head pattern of the rule which is being instantiated
* @param numRuleVars the length of the environment to allocate.
* @return An initialized binding environment for the rule variables
* or null if the unification fails. If a variable in the environment becomes
* aliased to another variable through the unification this is represented
* by having its value in the environment be the variable to which it is aliased.
*/
public static BindingVector unify(TriplePattern goal, TriplePattern head, int numRuleVars) {
Node[] gEnv = new Node[numRuleVars]; // TODO: check
Node[] hEnv = new Node[numRuleVars];
if (!unify(goal.getSubject(), head.getSubject(), gEnv, hEnv)) {
return null;
}
if (!unify(goal.getPredicate(), head.getPredicate(), gEnv, hEnv)) {
return null;
}
Node gObj = goal.getObject();
Node hObj = head.getObject();
if (Functor.isFunctor(gObj)) {
Functor gFunctor = (Functor)gObj.getLiteralValue();
if (Functor.isFunctor(hObj)) {
Functor hFunctor = (Functor)hObj.getLiteralValue();
if ( ! gFunctor.getName().equals(hFunctor.getName()) ) {
return null;
}
Node[] gArgs = gFunctor.getArgs();
Node[] hArgs = hFunctor.getArgs();
if ( gArgs.length != hArgs.length ) return null;
for (int i = 0; i < gArgs.length; i++) {
if (! unify(gArgs[i], hArgs[i], gEnv, hEnv) ) {
return null;
}
}
} else if (hObj instanceof Node_RuleVariable) {
// temp debug ...
// Check the goal functor is fully ground
if (gFunctor.isGround(new BindingVector(gEnv))) {
if (!unify(gObj, hObj, gEnv, hEnv)) return null;
}
// ... end debug
} else {
// unifying simple ground object with functor, failure
return null;
}
} else {
if (!unify(gObj, hObj, gEnv, hEnv)) return null;
}
// Successful bind if we get here
return new BindingVector(hEnv);
}
/**
* Unify a single pair of goal/head nodes. Unification of a head var to
* a goal var is recorded using an Integer in the head env to point to a
* goal env and storing the head var in the goal env slot.
* @return true if they are unifiable, side effects the environments
*/
private static boolean unify(Node gNode, Node hNode, Node[] gEnv, Node[] hEnv) {
if (hNode instanceof Node_RuleVariable) {
int hIndex = ((Node_RuleVariable)hNode).getIndex();
if (gNode instanceof Node_RuleVariable) {
// Record variable bind between head and goal to detect aliases
int gIndex = ((Node_RuleVariable)gNode).getIndex();
if (gIndex < 0) return true;
if (gEnv[gIndex] == null) {
// First time bind so record link
gEnv[gIndex] = hNode;
} else {
// aliased var so follow trail to alias
// but ignore self-aliases
Node gVal = gEnv[gIndex];
if (hIndex != gIndex || ! (gVal instanceof Node_RuleVariable)) {
hEnv[hIndex] = gVal;
}
}
} else {
Node hVal = hEnv[hIndex];
if (hVal == null) {
hEnv[hIndex] = gNode;
} else {
// Already bound
if (hVal instanceof Node_RuleVariable) {
// Already an aliased variable, so bind both this an the alias
hEnv[((Node_RuleVariable)hVal).getIndex()] = gNode;
hEnv[hIndex] = gNode;
} else {
// Already bound to a ground node
return hVal.sameValueAs(gNode);
}
}
}
return true;
} else {
if (gNode instanceof Node_RuleVariable) {
int gIndex = ((Node_RuleVariable)gNode).getIndex();
if (gIndex < 0) return true;
Node gVal = gEnv[gIndex];
if (gVal == null) {
//. No variable alias so just record binding
gEnv[gIndex] = hNode;
} else if (gVal instanceof Node_RuleVariable) {
// Already an alias
hEnv[((Node_RuleVariable)gVal).getIndex()] = hNode;
gEnv[gIndex] = hNode;
} else {
return gVal.sameValueAs(hNode);
}
return true;
} else {
return hNode.sameValueAs(gNode);
}
}
}
/** Equality override */
@Override
public boolean equals(Object o) {
// Pass 1 - just check basic shape
if (! (o instanceof BindingVector) ) return false;
Node[] other = ((BindingVector)o).environment;
if (environment.length != other.length) return false;
for (int i = 0; i < environment.length; i++) {
Node n = environment[i];
Node no = other[i];
if (n == null) {
if (no != null) return false;
} else {
if (! n.sameValueAs(no)) return false;
}
}
return true;
}
/** hash function override */
@Override
public int hashCode() {
int hash = 0;
for ( Node n : environment )
{
hash = ( hash << 1 ) ^ ( n == null ? 0x537c : n.hashCode() );
}
return hash;
}
}