jadex.rules.parser.conditions.javagrammar.ConstraintBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jadex-rules Show documentation
Show all versions of jadex-rules Show documentation
Jadex Rules is a small lightweight rule engine, which currently
employs the well-known Rete algorithm for highly efficient rule
matching. Jadex rules is therefore similar to other rule engines
like JESS and Drools. Despite the similarities there are also
important differences between these systems:
* Jadex Rules is very small and
intended to be used as component
of other software. Even though rules can be specified in a Java
dialect as well as (a small variation of) the CLIPS language
its primary usage is on the API level. Jadex Rules is currently
the core component of the Jadex BDI reasoning engine.
* Jadex Rules cleanly separates between state and rule representation.
This allows the state implementation as well as the matcher to be
flexibly exchanged. Some experiments have e.g. been conducted with
a Jena representation. Regarding the matcher, it is planned to
support also the Treat algorithm, which has a lower memory footprint
than Rete.
* Jadex Rules pays close attention to rule debugging. The state as
well as the rete engine can be observed at runtime. The rule debugger
provides functionalities to execute a rule program stepwise and also
use rule breakpoints to stop the execution at those points.
package jadex.rules.parser.conditions.javagrammar;
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.rules.rulesystem.ICondition;
import jadex.rules.rulesystem.rules.AndCondition;
import jadex.rules.rulesystem.rules.ArraySelector;
import jadex.rules.rulesystem.rules.BoundConstraint;
import jadex.rules.rulesystem.rules.CollectCondition;
import jadex.rules.rulesystem.rules.Constant;
import jadex.rules.rulesystem.rules.ConstrainableCondition;
import jadex.rules.rulesystem.rules.FunctionCall;
import jadex.rules.rulesystem.rules.IConstraint;
import jadex.rules.rulesystem.rules.IOperator;
import jadex.rules.rulesystem.rules.LiteralConstraint;
import jadex.rules.rulesystem.rules.MethodCall;
import jadex.rules.rulesystem.rules.NotCondition;
import jadex.rules.rulesystem.rules.ObjectCondition;
import jadex.rules.rulesystem.rules.PredicateConstraint;
import jadex.rules.rulesystem.rules.TestCondition;
import jadex.rules.rulesystem.rules.Variable;
import jadex.rules.rulesystem.rules.functions.IFunction;
import jadex.rules.rulesystem.rules.functions.Identity;
import jadex.rules.rulesystem.rules.functions.MethodCallFunction;
import jadex.rules.rulesystem.rules.functions.OperatorFunction;
import jadex.rules.state.OAVAttributeType;
import jadex.rules.state.OAVJavaType;
import jadex.rules.state.OAVObjectType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* The constraint builder takes an expression
* (e.g. from the parser) and generates appropriate
* constraints and/or conditions for it.
*/
public class ConstraintBuilder
{
/**
* Build or adapt conditions for representing the given constraints.
* @param expression The expression, which contains the constraints to represent.
* @param context The build context.
* @return The generated condition.
*/
public static ICondition buildConstraints(Expression expression, BuildContext context, IParserHelper helper)
{
// Decompose expression into 1) object conditions, 2) collect conditions, 3) remaining constraints, 4) NOT conditions.
List conditions = new ArrayList();
List nots = new ArrayList();
List constraints = new ArrayList();
constraints.add(expression);
for(int i=0; i1)
{
// Todo: when adding to next condition, dummy constraints should be first!?
target = (ConstrainableCondition)lcons.get(i+1);
}
else if(i>0)
{
target = (ConstrainableCondition)lcons.get(i-1);
}
else
{
throw new UnsupportedOperationException("No object conditions produced (todo: test condition): "+expression);
}
lcons.remove(i);
for(int j=0; j1 ? new AndCondition(lcons) : (ICondition)lcons.get(0);
}
/**
* Build a constraint for a single expression.
*/
protected static void buildConstraint(Expression exp, BuildContext context, boolean invert, IParserHelper helper)
{
if(exp instanceof OperationExpression)
{
OperationExpression opex = (OperationExpression)exp;
if(opex.getOperator() instanceof IOperator)
{
IOperator operator = (IOperator)opex.getOperator();
if(invert)
{
IOperator inverse = OperationExpression.getInverseOperator0(operator);
if(inverse==null)
{
buildOperatorConstraint(exp, new LiteralExpression(Boolean.TRUE), IOperator.NOTEQUAL, context, helper);
}
else
{
buildOperatorConstraint(opex.getLeftValue(), opex.getRightValue(), inverse, context, helper);
}
}
else
{
buildOperatorConstraint(opex.getLeftValue(), opex.getRightValue(), operator, context, helper);
}
}
// else if(opex.getOperator() instanceof IFunction)
// {
// buildFunctionConstraint(opex.getLeftValue(), opex.getRightValue(), (IFunction)opex.getOperator(), context);
// }
else
{
throw new RuntimeException("Unexpected operator type: "+opex);
}
}
else if(exp instanceof UnaryExpression)
{
UnaryExpression unex = (UnaryExpression)exp;
if(unex.getOperator().equals(UnaryExpression.OPERATOR_NOT))
{
buildConstraint(unex.getValue(), context, !invert, helper);
}
else
{
throw new RuntimeException("Unexpected operator type: "+unex);
}
}
// Conditional
// Literal
// Primary
// Variable
else
{
buildOperatorConstraint(exp, new LiteralExpression(Boolean.TRUE), invert ? IOperator.NOTEQUAL : IOperator.EQUAL, context, helper);
}
}
/**
* Build an operator constraint.
*/
protected static void buildOperatorConstraint(Expression left, Expression right, IOperator op, BuildContext context, IParserHelper helper)
{
// Special treatment for unbound variables -> build test condition.
// Currently not supported by Rete builder
if(left instanceof VariableExpression && context.getConstrainableCondition0(((VariableExpression)left).getVariable())==null)
{
// Flatten right side to literal or (temporary) variable.
right = flattenToPrimary(right, context, helper);
Object rightsource = right instanceof LiteralExpression
? new Constant(((LiteralExpression)right).getValue())
: ((VariableExpression)right).getVariable();
// Build test condition for left side (variable).
TestCondition testcon = new TestCondition(new PredicateConstraint(new FunctionCall(
new OperatorFunction(op), new Object[]{((VariableExpression)left).getVariable(), rightsource})));
context.getConditions().add(testcon);
}
// Normal treatment: Find object condition + value source for left part and attach constraint based on right part.
else
{
// Get object condition and value source for left part.
Object valuesource = getObjectConditionAndValueSource(left, context, helper);
// Flatten right side to literal value or (temporary) variable.
right = flattenToPrimary(right, context, helper);
// Build literal constraint for constant values
if(right instanceof LiteralExpression)
{
// Right side of literal constraint is not a value source (i.e. Constant)
context.getCurrentCondition().addConstraint(new LiteralConstraint(valuesource, ((LiteralExpression)right).getValue(), op));
}
// Build variable constraint for other expressions
else //if(right instanceof VariableExpression)
{
context.getCurrentCondition().addConstraint(new BoundConstraint(valuesource, ((VariableExpression)right).getVariable(), op));
}
context.popCondition();
}
}
/**
* Find or create an object condition for a value and
* return the appropriate value source.
* The condition is pushed on the stack of the build context.
* When the condition is no longer required (e.g. all current constraints added) it has to be popped from the stack (manually).
* @param value The value to be obtained.
* @param lcons The existing conditions.
* @param bcons The conditions for existing variables.
* @param tmodel The type model.
* @return The value source.
*/
protected static Object getObjectConditionAndValueSource(Expression value, BuildContext context, IParserHelper helper)
{
Object valuesource;
int stacksize = context.oconstack!=null ? context.oconstack.size() : 0;
if(value instanceof VariableExpression)
{
context.pushCondition(context.getConstrainableCondition(((VariableExpression)value).getVariable()));
valuesource = context.getBoundConstraint(((VariableExpression)value).getVariable()).getValueSource();
}
else if(value instanceof PrimaryExpression)
{
Expression prim = ((PrimaryExpression)value).getPrefix();
OAVObjectType type;
if(prim instanceof VariableExpression)
{
Variable var = ((VariableExpression)prim).getVariable();
type = var.getType();
valuesource = context.getBoundConstraint(var).getValueSource();
context.pushCondition(context.getConstrainableCondition(var));
}
else if(prim instanceof LiteralExpression)
{
Constant c = new Constant(((LiteralExpression)prim).getValue());
valuesource = new FunctionCall(new Identity(), new Object[]{c});
if(c.getValue()!=null)
type = context.getTypeModel().getJavaType(c.getValue().getClass());
else
type = OAVJavaType.java_object_type;
context.pushCondition(context.getDummyCondition());
}
else if(prim instanceof StaticMethodAccess)
{
context.pushCondition(context.getDummyCondition());
StaticMethodAccess sma = (StaticMethodAccess)prim;
MethodCall mc = createMethodCall(sma.getType(), sma.getName(), sma.getParameterValues(), context, helper);
List paramsources = mc.getParameterSources();
paramsources.add(0, new Constant(null)); // first param is object to be invoked (use null for static method).
valuesource = new FunctionCall(new MethodCallFunction(mc.getMethod()), paramsources);
type = context.getTypeModel().getJavaType(mc.getMethod().getReturnType());
}
else if(prim instanceof CastExpression)
{
type = ((CastExpression)prim).getType();
valuesource = getObjectConditionAndValueSource(((CastExpression)prim).getValue(), context, helper);
// Skip pop/push of same condition.
}
else if(prim instanceof CollectExpression)
{
CollectExpression colex = (CollectExpression)prim;
BuildContext newcon = new BuildContext(context);
ICondition cond = buildConstraints(colex.getExpression(), newcon, helper);
CollectCondition colco;
if(cond instanceof ObjectCondition)
{
colco = new CollectCondition((ObjectCondition)cond);
}
else if(cond instanceof AndCondition)
{
colco = new CollectCondition(((AndCondition)cond).getConditions(), null);
}
else
{
throw new RuntimeException("Cannot collect: "+cond);
}
context.addCondition(colco);
context.pushCondition(colco);
type = OAVJavaType.java_collection_type;
valuesource = null;
}
else
{
throw new UnsupportedOperationException("Unsupported start of primary expression: "+prim);
}
List suffs = new ArrayList();
Suffix[] suffixes = ((PrimaryExpression)value).getSuffixes();
for(int i=0; i1)
{
System.out.println("Warning: Multiple matching methods found for: "+clazz.getName()+"."+name+SUtil.arrayToString(paramvalues));
}
MethodCall mc = new MethodCall(type, methods[matches[0]], params);
return mc;
}
/**
* Flatten a value to a primary value (literal or variable).
* For a complex expression, additional conditions might be created
* that bind the desired value in a new variable.
* @param value The value to be obtained.
* @param lcons The existing conditions.
* @param bcons The conditions for existing variables.
* @param tmodel The type model.
* @return The primary value (i.e. variable or literal).
*/
protected static Expression flattenToPrimary(Expression value, BuildContext context, IParserHelper helper)
{
Expression ret;
if(value instanceof VariableExpression)
{
ret = value;
}
else if(value instanceof LiteralExpression)
{
ret = value;
}
else if(value instanceof PrimaryExpression)
{
Object valuesource = getObjectConditionAndValueSource(value, context, helper);
ret = new VariableExpression(context.generateVariableBinding(context.getCurrentCondition(), valuesource));
context.popCondition();
}
else if(value instanceof OperationExpression)
{
OperationExpression opex = (OperationExpression)value;
IFunction func;
if(opex.getOperator() instanceof IFunction)
func = (IFunction)opex.getOperator();
else
func = new OperatorFunction((IOperator)opex.getOperator());
Object left = getObjectConditionAndValueSource(opex.getLeftValue(), context, helper);
Object right = flattenToPrimary(opex.getRightValue(), context, helper);
if(right instanceof VariableExpression)
{
right = ((VariableExpression)right).getVariable();
}
else //if(right instanceof LiteralExpression)
{
right = new Constant(((LiteralExpression)right).getValue());
}
Object valuesource = new FunctionCall(func, new Object[]{left, right});
ret = new VariableExpression(context.generateVariableBinding(context.getCurrentCondition(), valuesource));
context.popCondition();
}
else if(value instanceof CastExpression)
{
Expression prim = flattenToPrimary(((CastExpression)value).getValue(), context, helper);
if(prim instanceof VariableExpression)
{
VariableExpression varex = (VariableExpression)prim;
ConstrainableCondition ocon = context.getConstrainableCondition(varex.getVariable());
Object valuesource = context.getBoundConstraint(varex.getVariable()).getValueSource();
OAVObjectType type = ((CastExpression)value).getType();
ret = new VariableExpression(context.generateVariableBinding(ocon, context.generateVariableName(), type, valuesource));
}
else
{
throw new UnsupportedOperationException("Todo: flatten expressions of type: "+value);
}
}
else
{
throw new UnsupportedOperationException("Todo: flatten expressions of type: "+value);
}
return ret;
}
/**
* Shuffle conditions and constraints, such that all variables are bound
* before used.
* @param lcons The list of conditions (shuffled in place).
*/
protected static void shuffle(BuildContext context)
{
List lcons = context.getConditions();
Set boundvars = new HashSet(); // Variables, which are bound and therefore can be used.
if(context.getParent()!=null)
boundvars.addAll(context.getParent().getBoundVariables());
boolean progress = true;
int finished = 0;
while(progress)
{
progress = false;
int skipped = 0;
for(int i=finished; i