
jadex.rules.state.javaimpl.OAVWeakState Maven / Gradle / Ivy
package jadex.rules.state.javaimpl;
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.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;
// #ifndef MIDP
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.beans.PropertyChangeEvent;
import jadex.commons.beans.PropertyChangeListener;
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;
/**
* 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 = Integer.valueOf(1);
else
cnt = Integer.valueOf(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, 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)
{
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 = Integer.valueOf(1);
else
cnt = Integer.valueOf(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, 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)
{
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)
{
System.err.println("Cannot add property change listener to OAV java bean: "+e);
}
catch(InvocationTargetException e)
{
System.err.println("Cannot add property change listener to OAV java bean: "+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.
if(pcls!=null)
{
try
{
PropertyChangeListener pcl = (PropertyChangeListener)pcls.remove(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)
{
System.err.println("Cannot remove property change listener from OAV java bean: "+e);
}
catch(InvocationTargetException e)
{
System.err.println("Cannot remove property change listener from OAV java bean: "+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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy