jadex.rules.state.javaimpl.OAVAbstractState 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.state.javaimpl;
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.Tuple;
import jadex.commons.collection.IdentityHashSet;
import jadex.commons.concurrent.ISynchronizator;
import jadex.rules.state.IOAVState;
import jadex.rules.state.IOAVStateListener;
import jadex.rules.state.IProfiler;
import jadex.rules.state.OAVAttributeType;
import jadex.rules.state.OAVJavaType;
import jadex.rules.state.OAVObjectType;
import jadex.rules.state.OAVTypeModel;
/* $if !android $ */
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/* $else $
import javaa.beans.PropertyChangeEvent;
import javaa.beans.PropertyChangeListener;
$endif $ */
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An object holding the state as
* OAV triples (object, attribute, value).
*/
public abstract class OAVAbstractState implements IOAVState
{
// #ifndef MIDP
//-------- constants --------
/** The argument types for property change listener adding/removal (cached for speed). */
protected static Class[] PCL = new Class[]{PropertyChangeListener.class};
// #endif
/** The type identifier. */
protected static String TYPE = ":::INTERNAL_TYPE";
//-------- attributes --------
/** The type models. */
protected OAVTypeModel tmodel;
/** The root objects (will not be cleaned up when usages==0) (oids + java objects). */
protected Set rootobjects;
/** The objects table (oid -> content map). */
// protected Map objects;
/** The deleted objects (only available in event notifications) (oid -> content map). */
protected Map deletedobjects;
/** The java objects set. */
protected Set javaobjects;
/** The id generator. */
protected IOAVIdGenerator generator;
/** The flag to disable type checking. */
protected boolean nocheck;
/** The usages of object ids (object id -> usages[map] (objectusage -> cnt)). */
protected Map objectusages;
/** The Java beans property change listeners. */
protected Map pcls;
/** The OAV event handler. */
protected OAVEventHandler eventhandler;
/** List of substates (if any). */
protected IOAVState[] substates;
/** The synchronizator (if any). */
protected ISynchronizator synchronizator;
/** Counter for number of registered bean listeners. */
protected int beanlistenercnt;
/** Flag to enable identity handling of java objects (instead of equality). */
protected boolean javaidentity;
// #ifndef MIDP
/** The profiler. */
// Hack???
protected IProfiler profiler = new IProfiler()
{
public void start(String type, Object item)
{
}
public void stop(String type, Object item)
{
}
public ProfilingInfo[] getProfilingInfos(int start)
{
return new ProfilingInfo[0];
}
};
// #endif
//-------- constructors --------
/**
* Create a new empty OAV state representation.
*/
public OAVAbstractState(OAVTypeModel tmodel)
{
this.tmodel = tmodel;
this.javaidentity = true;
// OID data structures
this.deletedobjects = new LinkedHashMap();
// this.objects = new LinkedHashMap();
// this.objects = new CheckedMap(new LinkedHashMap());
// Java object data structures (todo: repeatability/ordering for identity map)
this.javaobjects = javaidentity ? (Set)new IdentityHashSet() : new LinkedHashSet();
// Mixed data structures (oids + java objects) (todo: repeatability/ordering for identity map)
this.objectusages = javaidentity ? (Map)new IdentityHashMap() : new LinkedHashMap();
this.rootobjects = javaidentity ? (Set)new IdentityHashSet() : new LinkedHashSet();
this.eventhandler = new OAVEventHandler(this);
this.generator = createIdGenerator();
// this.nocheck = true;
}
/**
* Create an id generator.
* @return The id generator.
*/
public IOAVIdGenerator createIdGenerator()
{
return new OAVDebugIdGenerator();
// return new OAVNameIdGenerator();
// return new OAVLongIdGenerator();
// return new OAVObjectIdGenerator();
}
/**
* Dispose the state.
*/
public void dispose()
{
// Drop root objects for clean disposal.
Object[] roots = rootobjects.toArray();
for(int i=0; i recursively check reachability
if(ref instanceof OAVObjectUsage)
{
OAVObjectUsage usage = (OAVObjectUsage)ref;
if(!tested.contains(usage.getObject()))
ret = isReachable(usage.getObject(), tested);
}
// External reference -> reachability is true
else
{
ret = true;
}
}
}
}
return ret;
}
/**
* Find a cycle in a given set of objects (oids).
*/
public List findCycle(Collection oids)
{
List cycle = null;
Set checked = new HashSet();
// Find cycles starting from each object.
for(Iterator it=oids.iterator(); cycle==null && it.hasNext(); )
{
Object id = it.next();
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
// Do not check again, if object was already in subgraph of other object.
if(!checked.contains(id))
{
Map edges = new HashMap(); // Back references.
edges.put(id, id); // Root object of subgraph has no back reference. Cyclic edge simplifies algorithm, when cycle.length==1.
// Iteratively check all objects connected to the current object.
List subgraph = new ArrayList();
subgraph.add(id);
checked.add(id);
for(int i=0; cycle==null && i type check via OAVObjectType
* b) a normal Java object -> type check via OAVJavaType
* Additionally multiplicity is checked.
* @throws RuntimeException if value is not allowed.
*/
protected boolean checkValueCompatibility(Object id,
OAVAttributeType attribute, Object value)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
if(value!=null)
{
OAVObjectType atype = attribute.getType();
if(atype instanceof OAVJavaType)
{
if(!tmodel.getJavaType(value.getClass()).isSubtype(atype))
throw new RuntimeException("Value not of suitable type: "+id+" "+attribute+" "+value);
}
else if(!getType(value).isSubtype(atype))
{
throw new RuntimeException("Value not of suitable type: "+id+" "+attribute+" "+value);
}
}
return true;
}
/**
* Ensure that a type has an attribute.
* @param id The object (oid).
* @param attribute The attribute.
* @throws RuntimeException if value is not allowed.
*/
protected boolean checkTypeHasAttribute(Object id, OAVAttributeType attribute)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
if(attribute==null)
throw new IllegalArgumentException("Attribute must not null.");
OAVObjectType type = attribute.getObjectType() instanceof OAVJavaType
? tmodel.getJavaType(id.getClass()) : getType(id);
OAVAttributeType attr = type.getAttributeType(attribute.getName());
if(!attribute.equals(attr))
throw new RuntimeException("Attribute must belong to object type: "+attribute+", "+type);
return true;
}
/**
* Ensure that multiplicity is ok.
* @param id The object (oid).
* @param attribute The attribute.
* @param multiplicity The multiplicity.
* @throws RuntimeException if value is not allowed.
*/
protected boolean checkMultiplicity(Object id, OAVAttributeType attribute, Set allowedmults)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
if(attribute==null)
throw new IllegalArgumentException("Attribute must not null.");
if(!allowedmults.contains(attribute.getMultiplicity()))
throw new RuntimeException("Multiplicity violation: "+id+" "+attribute
+" "+allowedmults+" "+attribute.getMultiplicity());
return true;
}
/**
* Ensure that multiplicity is ok.
* @param id The object (oid).
* @param attribute The attribute.
* @param multiplicity The multiplicity.
* @throws RuntimeException if value is not allowed.
*/
protected boolean checkMultiplicity(Object id, OAVAttributeType attribute, String allowedmult)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
if(attribute==null)
throw new IllegalArgumentException("Attribute must not null.");
if(!allowedmult.equals(attribute.getMultiplicity()))
throw new RuntimeException("Multiplicity violation: "+id+" "+attribute
+" "+allowedmult+" "+attribute.getMultiplicity());
return true;
}
/**
* Test if a type is defined in one of the models.
* @param type The type.
* @return True, if is defined.
*/
protected boolean checkTypeDefined(OAVObjectType type)
{
if(type==null)
throw new IllegalArgumentException("Type must not null.");
if(type instanceof OAVJavaType)
throw new IllegalArgumentException("Type must not be Java type: "+type);
if(tmodel==null)
throw new RuntimeException("Type model undefined for state: "+this);
if(!tmodel.contains(type))
throw new RuntimeException("Type undefined: "+type);
return true;
}
/**
* Test if the object is a valid state object (oid).
* @param id The object (oid).
* @return True, if valid.
*/
protected boolean checkValidStateObject(Object id)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
return containsObject(id);
}
/**
* Test if reading the object (oid) is allowed.
* Reading is allowed on removed objects as long as there are external references.
* @param id The object (oid).
* @return True, if valid.
*/
protected boolean checkValidStateObjectRead(Object id)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
return checkValidStateObject(id) || isExternallyUsed(id);
}
/**
* Test if the object is a valid state value, meaning
* that is either a state object or a java value.
* @param value The value.
* @return True, if valid.
*/
protected boolean checkValidStateValue(Object value)
{
// No state object (i.e. Java object) or object in state.
return !generator.isId(value) || checkValidStateObject(value);
}
/**
* Add an object (oid of java object) usage.
* For each occurrence of an object in a multi attribute a separate reference is added.
* @param whichid The object (oid) that references the value.
* @param whichattr The attribute which references the object.
* @param value The value (id of the referenced object or java object).
*/
protected void addObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
{
// #ifndef MIDP
assert nocheck || generator.isId(whichid);
// #endif
if(isManaged(value))
{
// if((""+whichid).indexOf("goal_")!=-1 || (""+value).indexOf("goal_")!=-1)
// {
// System.err.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+value);
// if(whichattr.getName().equals("parameterelement_has_parameters"))
// Thread.dumpStack();
// }
// Set would be better
Map usages = (Map)objectusages.get(value);
boolean newobject = usages==null;
if(newobject)
{
usages = new HashMap();
objectusages.put(value, usages);
}
// Add a new reference for (objectid, attribute)
OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr);
Integer cnt = (Integer)usages.get(ref);
if(cnt!=null && whichattr.getMultiplicity().equals(OAVAttributeType.NONE))
throw new RuntimeException("Object already there: "+value+" "+whichid+" "+whichattr);
if(cnt==null)
cnt = new Integer(1);
else
cnt = new Integer(cnt.intValue()+1);
usages.put(ref, cnt);
if(newobject && !generator.isId(value))
{
// When it is a Java object, it is not created in state,
// so we have to notify object addition to listeners on first usage.
if(javaobjects.add(value))
{
// System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+value);
OAVJavaType java_type = tmodel.getJavaType(value.getClass());
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
registerValue(java_type, value);
eventhandler.objectAdded(value, java_type, false);
}
}
}
}
/**
* Remove an object (oid or java object) usage.
* @param whichid The object that references the value.
* @param whichattr The attribute which references the value.
* @param value The object id/java value to remove.
* @param dropset Already dropped objects in recursive drop (or null if none).
* @param keepalive A flag indicating that at least one object in the path is externally referenced
* (all contained unused objects are set to externally referenced, too).
*/
protected void removeObjectUsage(Object whichid, OAVAttributeType whichattr, Object value, Set dropset, boolean keepalive)
{
// #ifndef MIDP
assert nocheck || generator.isId(whichid);
// #endif
if(isManaged(value))
{
// System.err.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+value);
// Increase external usage counter, if source object is externally referenced.
if(keepalive && generator.isId(value))
addExternalObjectUsage(value, this);
Map usages = getObjectUsages(value);
if(usages==null)
throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr);
Integer cnt = (Integer)usages.get(ref);
if(cnt==null)
throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
if(cnt.intValue()==1)
usages.remove(ref);
else
usages.put(ref, new Integer(cnt.intValue()-1));
// If this was the last reference to the object and it is
// not a root object clean it up
if(usages.size()==0)
{
// if(objects.containsKey(value) && !rootobjects.contains(value) && (dropset==null || !dropset.contains(value)))
if(internalContainsObject(value) && !rootobjects.contains(value) && (dropset==null || !dropset.contains(value)))
{
// System.out.println("Garbage collecting unreferenced object: "+value);
// Thread.dumpStack();
internalDropObject(value, dropset, keepalive);
}
// When it is a Java object, it is not dropped from state,
// so we have to notify object removal to listeners on last usage.
else if(!generator.isId(value) && !rootobjects.contains(value))
{
javaobjects.remove(value);
// System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+value);
OAVJavaType java_type = tmodel.getJavaType(value.getClass());
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
deregisterValue(java_type, value);
objectusages.remove(value);
eventhandler.objectRemoved(value, java_type);
}
}
}
}
/**
* Check if a value (oid or java object) is managed by the state.
* Returns true for attribute values which are directly contained oav objects
* or mutable java objects, e.g. not simple values such as strings or intergers.
*/
protected boolean isManaged(Object value)
{
// Value is a directly contained object or java bean/object (i.e. not basic value)
return value!=null &&
// (objects.containsKey(value)
(internalContainsObject(value)
|| !generator.isId(value) && !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE));
}
/**
* Get all object usages.
* @return The usages for an object (oid or java object).
*/
protected Map getObjectUsages(Object object)
{
return (Map)objectusages.get(object);
}
// private static int[] cnt = new int[1];
/**
* Register a value for observation.
* If its an expression then add the action,
* if its a bean then add the property listener.
*/
protected void registerValue(final OAVJavaType type, Object value)
{
// #ifndef MIDP
assert nocheck || !generator.isId(value) : value;
// #endif
// #ifndef MIDP
if(value!=null)
{
if(pcls==null)
pcls = new IdentityHashMap(); // values may change, therefore identity hash map
PropertyChangeListener pcl = (PropertyChangeListener)pcls.get(value);
if(pcl==null)
{
pcl = new PropertyChangeListener()
{
public void propertyChange(final PropertyChangeEvent evt)
{
if(synchronizator!=null)
{
try
{
synchronizator.invokeLater(new Runnable()
{
public void run()
{
try
{
OAVAttributeType attr = type.getAttributeType(evt.getPropertyName());
eventhandler.beanModified(evt.getSource(), type, attr, evt.getOldValue(), evt.getNewValue());
}
catch(Exception e)
{
// Todo: use customizsble logger supplied from external.
e.printStackTrace();
}
}
});
}
catch(Exception e)
{
e.printStackTrace();
System.out.println("Synchronizer invalid: "+evt+", "+e);
}
}
else
{
try
{
OAVAttributeType attr = type.getAttributeType(evt.getPropertyName());
eventhandler.beanModified(evt.getSource(), type, attr, evt.getOldValue(), evt.getNewValue());
}
catch(Exception e)
{
// Todo: use customizable logger supplied from external.
e.printStackTrace();
}
}
}
};
pcls.put(value, pcl);
}
// Invoke addPropertyChangeListener on value
try
{
assert nocheck || beanlistenercnt+1==++beanlistenercnt;
// if(getTypeModel().getName().equals("BlackjackDealer_typemodel") && value.toString().indexOf("GameState")!=-1)
// Thread.dumpStack();
// System.out.println(getTypeModel().getName()+": Registered on: "+value);
// Do not use Class.getMethod (slow).
Method meth = SReflect.getMethod(value.getClass(),
"addPropertyChangeListener", PCL);
if(meth!=null)
meth.invoke(value, new Object[]{pcl});
}
catch(IllegalAccessException e){e.printStackTrace();}
catch(InvocationTargetException e){e.printStackTrace();}
}
// #endif
}
/**
* Deregister a value for observation.
* If its an expression then clear the action,
* if its a bean then remove the property listener.
*/
protected void deregisterValue(OAVJavaType type, Object value)
{
// #ifndef MIDP
assert nocheck || !generator.isId(value) : value;
// #endif
// #ifndef MIDP
if(value!=null)
{
// synchronized(cnt)
// {
// cnt[0]--;
// }
// System.out.println("deregister ("+cnt[0]+"): "+value);
// Stop listening for bean events.
if(pcls!=null)
{
PropertyChangeListener pcl = (PropertyChangeListener)pcls.remove(value);
if(pcl!=null)
{
try
{
// System.out.println(getTypeModel().getName()+": Deregister: "+value+", "+type);
assert nocheck || beanlistenercnt-1==--beanlistenercnt;
// Do not use Class.getMethod (slow).
Method meth = SReflect.getMethod(value.getClass(),
"removePropertyChangeListener", PCL);
if(meth!=null)
meth.invoke(value, new Object[]{pcl});
}
catch(IllegalAccessException e){e.printStackTrace();}
catch(InvocationTargetException e){e.printStackTrace();}
}
}
}
// #endif
}
/**
* Test if an object is externally used.
* @param id The id.
* @return True, if externally used.
*/
protected abstract boolean isExternallyUsed(Object id);
//-------- nested states --------
/**
* Add a substate.
* Read accesses will be transparently mapped to substates.
* Write accesses to substates need not be supported and
* may generate UnsupportedOperationException.
*/
public void addSubstate(IOAVState substate)
{
if(substates==null)
{
substates = new IOAVState[]{substate};
}
else
{
IOAVState[] tmp = new IOAVState[substates.length+1];
System.arraycopy(substates, 0, tmp, 0, substates.length);
substates = tmp;
substates[substates.length-1] = substate;
}
}
/**
* Get the substates.
*/
public IOAVState[] getSubstates()
{
return substates;
}
//-------- identity vs. equality --------
/**
* Flag indicating that java objects are
* stored by identity instead of equality.
*/
public boolean isJavaIdentity()
{
return javaidentity;
}
/**
* Test if two values are equal
* according to current identity/equality
* settings.
*/
public boolean equals(Object a, Object b)
{
// When a!=b && javaidentity use equals() only for ids or java values.
return a==b || a!=null && (javaidentity
? ((generator.isId(a) || tmodel.getJavaType(a.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)) && a.equals(b))
: a.equals(b));
}
//-------- internal object handling --------
/**
* Internally create an object.
* @param id The id.
* @return The content map of the new object.
*/
protected abstract Map internalCreateObject(Object id);
/**
* Remove an object from the state objects.
* @param id The id.
* @return The content map of the object.
*/
protected abstract Map internalRemoveObject(Object id);
/**
* Get the object content of an object.
* @param id The id.
* @return The content map of the object.
*/
protected abstract Map internalGetObjectContent(Object id);
/**
* Test if an object is contained in the state.
* @param id The id.
* @return True, if object is contained.
*/
protected abstract boolean internalContainsObject(Object id);
/**
* Test how many object are contained in the state.
* @return The number of objects.
*/
protected abstract int internalObjectsSize();
/**
* Get a set of the internal state objects.
* @return A set of the state objects.
*/
protected abstract Set internalGetObjects();
}