jadex.rules.state.javaimpl.OAVWeakState 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;
// #ifndef MIDP
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.collection.WeakEntry;
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;
import jadex.rules.state.javaimpl.OAVWeakIdGenerator.OAVExternalObjectId;
import jadex.rules.state.javaimpl.OAVWeakIdGenerator.OAVInternalObjectId;
/* $if !android $ */
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/* $else $
import javaa.beans.PropertyChangeEvent;
import javaa.beans.PropertyChangeListener;
$endif $ */
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
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 class OAVWeakState implements IOAVState
{
//-------- constants --------
/** The argument types for property change listener adding/removal (cached for speed). */
protected static Class[] PCL = new Class[]{PropertyChangeListener.class};
//-------- attributes --------
/** The type models. */
protected OAVTypeModel tmodel;
/** The objects table (id -> map). */
protected Map objects;
/** The object types (object -> type). */
protected Map types;
/** The id generator. */
protected IOAVIdGenerator generator;
/** The flag to disable type checking. */
protected boolean nocheck;
/** The usages of object ids (object id -> usages). */
protected Map objectusages;
/** The root objects (will not be cleaned up when usages==0). */
protected Set rootobjects;
/** The Java beans property change listeners. */
protected Map pcls;
/** The OAV event handler. */
protected OAVEventHandler eventhandler;
/** The reference queue for stale objects. */
protected ReferenceQueue queue;
/** The synchronizator (if any). */
protected ISynchronizator synchronizator;
/** 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];
}
};
//-------- constructors --------
/**
* Create a new empty OAV state representation.
*/
public OAVWeakState(OAVTypeModel tmodel)
{
this.tmodel = tmodel;
this.objects = new LinkedHashMap();
this.types = new LinkedHashMap();
this.queue = new ReferenceQueue();
this.generator = new OAVWeakIdGenerator(queue);
// this.generator = new OAVNameIdGenerator();
// this.generator = new OAVLongIdGenerator();
this.objectusages = new LinkedHashMap();
// this.objectusages = new IdentityHashMap();
this.rootobjects = new LinkedHashSet();
this.eventhandler = new OAVEventHandler(this);
// this.nocheck = true;
}
/**
* Dispose the state.
*/
public void dispose()
{
Object[] roots = rootobjects.toArray();
for(int i=0; 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 object,
OAVAttributeType attribute, Object value)
{
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: "+object+" "+attribute+" "+value);
}
else if(!getType(value).isSubtype(atype))
{
throw new RuntimeException("Value not of suitable type: "+object+" "+attribute+" "+value);
}
}
return true;
}
/**
* Ensure that a type has an attribute.
* @param object The object.
* @param attribute The attribute.
* @throws RuntimeException if value is not allowed.
*/
protected boolean checkTypeHasAttribute(Object object, OAVAttributeType attribute)
{
if(attribute==null)
throw new IllegalArgumentException("Attribute must not null.");
OAVObjectType type = attribute.getObjectType() instanceof OAVJavaType
? tmodel.getJavaType(object.getClass()) : (OAVObjectType)types.get(object);
if(type==null)
throw new RuntimeException("Unknown object type of: "+object);
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 object The object.
* @param attribute The attribute.
* @param multiplicity The multiplicity.
* @throws RuntimeException if value is not allowed.
*/
protected boolean checkMultiplicity(Object object, OAVAttributeType attribute, Set allowedmults)
{
if(attribute==null)
throw new IllegalArgumentException("Attribute must not null.");
if(!allowedmults.contains(attribute.getMultiplicity()))
throw new RuntimeException("Multiplicity violation: "+object+" "+attribute
+" "+allowedmults+" "+attribute.getMultiplicity());
return true;
}
/**
* Ensure that multiplicity is ok.
* @param object The object.
* @param attribute The attribute.
* @param multiplicity The multiplicity.
* @throws RuntimeException if value is not allowed.
*/
protected boolean checkMultiplicity(Object object, OAVAttributeType attribute, String allowedmult)
{
if(attribute==null)
throw new IllegalArgumentException("Attribute must not null.");
if(!allowedmult.equals(attribute.getMultiplicity()))
throw new RuntimeException("Multiplicity violation: "+object+" "+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, meaning
* that is either a root object or a non-root object with
* at least one usage.
* @param object The object.
* @return True, if valid.
*/
protected boolean checkValidStateObject(Object object)
{
return true;
// if(object==null)
// throw new IllegalArgumentException("Object must not null.");
// return rootobjects.contains(object) || objectusages.get(object)!=null;
}
/**
* When it is a Java object, it is not created in state,
* so we have to notify object addition to listeners on first usage.
*/
protected void addJavaObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
{
// System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+id);
// Set would be better
Map usages = (Map)objectusages.get(value);
if(usages==null)
{
usages = new HashMap();
objectusages.put(value, usages);
OAVJavaType java_type = tmodel.getJavaType(value.getClass());
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
registerValue(java_type, value);
if(!rootobjects.contains(value))
eventhandler.objectAdded(value, java_type, false);
}
// Add a new reference for (objectid, attribute)
if(whichid!=null)
{
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);
}
}
/**
* Remove an object usage.
* @param whichid The object that references the object.
* @param whichattr The attribute which references the object.
* @param value The object id/value to remove.
* @param dropset Already dropped objects in recursive drop (or null if none).
*/
protected void removeJavaObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
{
// System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+id);
Map usages = getJavaObjectUsages(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)
{
objectusages.remove(value);
OAVJavaType java_type = tmodel.getJavaType(value.getClass());
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
deregisterValue(value);
eventhandler.objectRemoved(value, java_type/*, null*/);
}
}
/**
* Add an object usage. For creation this method can be called with (id, null, null).
* For each occurrence of an object in a multi attribute a separate reference is added.
* @param whichid The object that references the object.
* @param whichattr The attribute which references the object.
* @param value The value (id of the referenced object).
* /
protected void addObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
{
// System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+id);
// Set would be better
Map usages = (Map)objectusages.get(value);
if(usages==null)
{
usages = new HashMap();
objectusages.put(value, usages);
// 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(whichattr.getType() instanceof OAVJavaType)
{
OAVJavaType java_type = tmodel.getJavaType(value.getClass());
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
registerValue(java_type, value);
eventhandler.objectAdded(value, java_type);
}
// Add a object created event when non-root object is used first time.
else if(!rootobjects.contains(value))
{
eventhandler.objectAdded(value, getType(value));
}
}
// Add a new reference for (objectid, attribute)
if(whichid!=null)
{
ObjectUsage ref = new ObjectUsage(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);
}
}*/
/**
* Remove an object usage.
* @param whichid The object that references the object.
* @param whichattr The attribute which references the object.
* @param value The object id/value to remove.
* @param dropset Already dropped objects in recursive drop (or null if none).
* /
protected void removeObjectUsage(Object whichid, OAVAttributeType whichattr, Object value, Set dropset)
{
// System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+id);
Map usages = getObjectUsages(value);
if(usages==null)
throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
ObjectUsage ref = new ObjectUsage(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)
{
objectusages.remove(value);
if(containsObject(value) && !rootobjects.contains(value) && (dropset==null || !dropset.contains(value)))
{
// System.out.println("Garbage collecting unreferenced object: "+id);
// Thread.dumpStack();
if(getInternalId(value).isClear())
{
System.out.println("Could immediately drop: "+value);
internalDropObject(value, dropset);
}
// else
// {
// System.out.println("Could not immediately drop: "+value);
// }
}
// 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(whichattr.getType() instanceof OAVJavaType)
{
OAVJavaType java_type = tmodel.getJavaType(value.getClass());
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
deregisterValue(value);
eventhandler.objectRemoved(value, java_type, null);
}
}
}*/
/**
* Get all object usages.
* @return The usages for an object.
*/
protected Map getJavaObjectUsages(Object id)
{
return (Map)objectusages.get(id);
}
/**
* 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)
{
if(value!=null)
{
// System.out.println("register: "+value);
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(PropertyChangeEvent evt)
{
OAVAttributeType attr = type.getAttributeType(evt.getPropertyName());
eventhandler.beanModified(evt.getSource(), type, attr, evt.getOldValue(), evt.getNewValue());
}
};
pcls.put(value, pcl);
}
// Invoke addPropertyChangeListener on value
try
{
// 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){}
catch(InvocationTargetException e){}
}
}
/**
* 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(Object value)
{
if(value!=null)
{
// System.out.println("deregister: "+value);
// Stop listening for bean events.
try
{
if(pcls!=null)
{
PropertyChangeListener pcl = (PropertyChangeListener)pcls.get(value);
if(pcl!=null)
{
// 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){}
catch(InvocationTargetException e){}
}
}
/**
* Get the internal object id.
* @param id The id.
* @return The internal id.
*/
protected OAVInternalObjectId getInternalId(Object id)
{
OAVInternalObjectId ret;
if(id instanceof OAVInternalObjectId)
ret = (OAVInternalObjectId)id;
else
ret = ((OAVExternalObjectId)id).getInternalId();
return ret;
}
//-------- 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)
{
throw new UnsupportedOperationException("todo: substates for weak state");
}
/**
* Get the substates.
*/
public IOAVState[] getSubstates()
{
return null;
}
//-------- identity vs. equality --------
/**
* Flag indicating that java objects are
* stored by identity instead of equality.
*/
public boolean isJavaIdentity()
{
return false;
}
/**
* Test if two values are equal
* according to current identity/equality
* settings.
*/
public boolean equals(Object a, Object b)
{
return SUtil.equals(a, b);
}
}
// #endif