It's not too difficult to create a generic function that does this class
checking. Given that
(defrule check
?f <- (MAIN::__fact)
=>
... check ?f ...)
fires whenever there is any new or changed fact, its RHS can check the slots
of
this fact against a map of template/slot/class entries. These entries will
have to be
defined by the user, e.g., by a sequence of calls
(classcheck template slot pack.age.class)
The attached userfunction illustrates this. A call to classcheck
automatically establishes
the rule outlined above.
Moreover, classcheck can also be called explicitly on some fact.
It may be preferable to have separate check rules for individual templates,
depending
on the ratio between facts requiring such a check and those which don't.
Cheers
Wolfgang
On Thu, Feb 12, 2009 at 3:42 PM, Ernest Friedman-Hill <[email protected]>wrote:
> Jess is not a strongly-typed language; all slots and variables can contain
> any data type.
>
> If, for some reason, you can't just write your program to only put the
> correct data into the correct slot, you could always write a rule which
> checked Human-Animal facts for the proper contents of their slots.
>
package at.laune.jess;
import java.util.HashMap;
import java.util.Map;
import jess.Context;
import jess.Defrule;
import jess.Deftemplate;
import jess.Fact;
import jess.Funcall;
import jess.Group;
import jess.JessException;
import jess.Pattern;
import jess.Rete;
import jess.RU;
import jess.Userfunction;
import jess.Value;
import jess.ValueVector;
import jess.Variable;
/**
* A Jess Userfunction implementing class checking on the
* object values of a fact.
*
* (classcheck <template> <slot> <class>)
* (classcheck <fact>)
*
* Call the function's three argument form to define
* the Java class permitted for some slot. This will
* also set up a rule that will fire on new facts and
* check them accordingly.
*
* You may also call classcheck with a single argument,
* a fact, for an immediate check of a newly created fact.
*
* @author Wolfgang Laun
*/
public class ClassCheck implements Userfunction {
private static final String NAME = "classcheck";
private static final String BFACT = "fact";
private Map<String,Map<String,Class<?>>> temp2map =
new HashMap<String,Map<String,Class<?>>>();
private Defrule theRule;
/** Get the name of the Jess function.
* @see jess.Userfunction#getName()
*/
public String getName(){
return NAME;
}
private String mkMsg( Fact f, String tempName, String slotName, String msg
){
StringBuilder sb = new StringBuilder();
return tempName + " fact-" + f.getFactId() + ", slot " + slotName + ":
found " + msg;
}
/**
* Check a fact according to temp2map.
* @param fact the fact
* @param context the context
* @throws JessException if a slot doesnÄt contain what it should
*/
private void checkFact( Fact fact, Context context ) throws JessException {
Deftemplate temp = fact.getDeftemplate();
String tempName = temp.getName();
Map<String,Class<?>> slot2class = temp2map.get( tempName );
if( slot2class == null ){
return;
}
int nSlots = temp.getNSlots();
for( int iSlot = 0; iSlot < nSlots; iSlot++ ){
String slotName = temp.getSlotName( iSlot );
Class clazz = slot2class.get( slotName );
if( clazz != null ){
Value val = fact.get( iSlot );
int type = val.type();
switch( type ){
case RU.JAVA_OBJECT:
// must have class clazz
Object obj = val.javaObjectValue( context );
if( ! clazz.isInstance( obj ) ){
throw new JessException( NAME,
mkMsg( fact, tempName,
slotName, "class " + obj.getClass().getName() ),
"expected " + clazz.getName() );
}
break;
case RU.SYMBOL:
// must be nil
if( ! Funcall.NIL.equals( val ) ){
throw new JessException( NAME,
mkMsg( fact, tempName, slotName,
"SYMBOL" ),
"not nil" );
}
break;
default:
// bad value type
throw new JessException( NAME,
mkMsg( fact, tempName,
slotName, "value type " + RU.getTypeName( type ) ),
"expected " + RU.getTypeName(
RU.JAVA_OBJECT ) + " or nil" );
}
}
}
}
/** Call the userfunction
* @see jess.Userfunction#call(jess.ValueVector, jess.Context)
*/
public Value call( ValueVector vv, Context context )
throws JessException {
int narg = vv.size() - 1;
// If we have one argument, it must be a variable containing a fact.
if( narg == 1 ){
// Callback from the RHS of the installed rule - checks.
Value value = vv.get( 1 );
if( value.type() == RU.VARIABLE &&
value instanceof Variable ){
Value varval = ((Variable)value).resolveValue( context
);
if( varval.type() == RU.FACT ){
// Check the fact.
checkFact( varval.factValue( context ), context );
return Funcall.NIL;
}
}
}
/*
* Otherwise, we must have three arguments: a template name,
* a slot from that template and a Java class name.
*/
if( narg != 3 )
throw new JessException( NAME,
"invalid number of arguments",
Integer.toString( narg ) );
String tempName = vv.get( 1 ).stringValue( context );
String slotName = vv.get( 2 ).stringValue( context );
String typeName = vv.get( 3 ).stringValue( context );
Rete rete = context.getEngine();
if( tempName.indexOf( "::" ) < 0 ){
tempName = rete.getCurrentModule() + "::" + tempName;
}
Deftemplate temp = rete.findDeftemplate( tempName );
if( temp == null ){
throw new JessException( NAME, "no such deftemplate", tempName
);
}
int slotType = 0;
Class<?> clazz = null;
try {
slotType = temp.getSlotDataType( slotName );
clazz = Class.forName( typeName );
} catch( JessException je ){
throw new JessException( NAME, "no such slot in "+ tempName,
slotName );
} catch( ClassNotFoundException cnfe ){
throw new JessException( NAME, "no such class", typeName );
}
/*
* Enter template - slot - class into the map.
*/
Map<String,Class<?>> slot2class = temp2map.get( tempName );
if( slot2class == null ){
slot2class = new HashMap<String,Class<?>>();
temp2map.put( tempName, slot2class );
}
slot2class.put( slotName, clazz );
/*
* If the rule hasn't been set up yet, set it up!
*/
if( theRule == null ){
theRule = new Defrule( "MAIN::__ClassCheck", "Triggers class
check.", rete );
// LHS: ?fact <- (__fact)
Pattern fpat = new Pattern( "MAIN::__fact", rete );
fpat.setBoundName( BFACT );
Group gand = new Group( Group.AND );
gand.add( fpat );
theRule.addCE( gand, rete );
// RHS: (NAME BFACT)
Funcall f = new Funcall( NAME, rete );
f.add( rete.getValueFactory().get( BFACT, RU.VARIABLE ) );
theRule.addAction( f );
rete.addDefrule( theRule );
}
return Funcall.NIL;
}
}