
jadex.rules.state.javaimpl.OAVAbstractState Maven / Gradle / Ivy
package jadex.rules.state.javaimpl;
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;
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.Tuple;
import jadex.commons.beans.PropertyChangeEvent;
import jadex.commons.beans.PropertyChangeListener;
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;
/**
* 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 final Class[] PCL = new Class[]{PropertyChangeListener.class};
// #endif
/** The type identifier. */
protected static final 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 = Integer.valueOf(1);
else
cnt = Integer.valueOf(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, Integer.valueOf(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 && !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)
&& (!generator.isId(value) || internalContainsObject(value));
// return value!=null &&
// (!generator.isId(value) && !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)
// || internalContainsObject(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 customizable 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();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy