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;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
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<roots.length; i++)
{
if(generator.isId(roots[i]))
dropObject(roots[i]);
else
removeJavaRootObject(roots[i]);
}
// Drop remaining stale objects (e.g. created from external code but never added to state). hack???
// Drop objects one at a time, as dropping might remove other unreferenced objects as well.
while(!internalGetObjects().isEmpty())
{
dropObject(internalGetObjects().iterator().next());
}
assert nocheck || beanlistenercnt == 0: getTypeModel().getName()+", "+beanlistenercnt;
}
//-------- type management --------
/**
* Get the type model.
* @return The type model.
*/
public OAVTypeModel getTypeModel()
{
return tmodel;
}
//-------- object management --------
/**
* Create an object.
* Creates an object identifier that can be used
* to store/retrieve attribute values.
* May reuse old object identifiers for performance.
* @return An object identifier.
*/
public Object createObject(OAVObjectType type)
{
return createObject(type, false);
}
/**
* Create a root object. A root object will not be automatically
* garbage collected when no references point to this object
* any longer.
* Creates an object identifier that can be used
* to store/retrieve attribute values.
* May reuse old object identifiers for performance.
* @return An object identifier.
*/
public Object createRootObject(OAVObjectType type)
{
return createObject(type, true);
}
// public MultiCollection objectspertype = new MultiCollection();
/**
* Impl of root/non-root object creation.
*/
protected Object createObject(OAVObjectType type, boolean root)
{
// #ifndef MIDP
assert nocheck || checkTypeDefined(type);
// #endif
Object ret = generator.createId(this, type);
// Map content = new LinkedHashMap();
// objects.put(ret, content);
Map content = internalCreateObject(ret);
content.put(TYPE, type);
// System.out.println("Created object of type: "+type);
eventhandler.objectAdded(ret, type, root);
if(root)
this.rootobjects.add(ret);
return ret;
}
/**
* Drop an object (oid) from the state.
* Recursively removes the object and all connected objects that are not
* referenced elsewhere.
* @param id The identifier of the object to remove.
*/
public void dropObject(Object id)
{
// System.out.println("drop: "+id);
// #ifndef MIDP
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObject(id);
// #endif
// Remove object from rootobjects
rootobjects.remove(id);
// Remove this object from all places where it is referenced
Map refs = getObjectUsages(id);
if(refs!=null && !refs.isEmpty())
{
OAVObjectUsage[] usages = (OAVObjectUsage[])refs.keySet().toArray(new OAVObjectUsage[refs.keySet().size()]);
for(int u=0; u<usages.length; u++)
{
Object uid = usages[u].getObject();
OAVAttributeType attr = usages[u].getAttribute();
if(attr.getMultiplicity().equals(OAVAttributeType.NONE))
{
setAttributeValue(uid, attr, null);
}
else
{
int cnt = ((Integer)refs.get(usages[u])).intValue();
for(int i=0; i<cnt; i++)
removeAttributeValue(uid, attr, id);
}
// refs.remove(usage); // Removed in set/remove AttributeValue
}
}
else
{
internalDropObject(id, null, false); // Required for root objects and other unreferenced objects.
}
}
/**
* Add a Java object as root object.
* @param object The Java object.
*/
public void addJavaRootObject(Object object)
{
// #ifndef MIDP
assert nocheck || !generator.isId(object);
assert nocheck || !rootobjects.contains(object);
// #endif
OAVJavaType java_type = tmodel.getJavaType(object.getClass());
if(OAVJavaType.KIND_VALUE.equals(java_type.getKind()))
throw new RuntimeException("Value types not supported for Java root objects: "+java_type+", "+object);
this.rootobjects.add(object);
if(this.javaobjects.add(object)) // Todo: java objects in nested states.
{
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
registerValue(java_type, object);
eventhandler.objectAdded(object, java_type, true);
}
}
/**
* Drop a Java object from root objects.
* @param object The Java object.
*/
public void removeJavaRootObject(Object object)
{
// #ifndef MIDP
assert nocheck || !generator.isId(object);
assert nocheck || rootobjects.contains(object) && javaobjects.contains(object);
// #endif
OAVJavaType java_type = tmodel.getJavaType(object.getClass());
if(OAVJavaType.KIND_VALUE.equals(java_type.getKind()))
throw new RuntimeException("Value types not supported for Java root objects: "+java_type+", "+object);
this.rootobjects.remove(object);
if(!objectusages.containsKey(object)) // Todo: java objects in nested states.
{
javaobjects.remove(object);
if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
deregisterValue(java_type, object);
eventhandler.objectRemoved(object, java_type);
}
}
/**
* Internal drop method for avoiding cycles in to be dropped
* objects during a recursive drop operation.
* @param id The object (oid) to be dropped.
* @param dropset A set of already dropped objects (to avoid infinite recursion).
* @param keepalive A flag indicating that at least one object in the path is externally referenced
* (object usages will not be removed, but set to external).
*/
protected void internalDropObject(Object id, Set dropset, boolean keepalive)
{
// System.out.println("internalDropObject: "+id+", "+dropset);
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
if(dropset==null)
dropset = new HashSet();
dropset.add(id);
// Use new variable because original value is needed below.
keepalive = keepalive || isExternallyUsed(id);
// Remove all used object references
// Map content = (Map)objects.get(id);
Map content = internalGetObjectContent(id);
for(Iterator it=content.keySet().iterator(); it.hasNext(); )
{
Object tmp = it.next();
if(tmp.equals(TYPE))
continue;
OAVAttributeType attribute = (OAVAttributeType)tmp;
Object value = content.get(attribute);
if(value!=null)
{
if(attribute.getMultiplicity().equals(OAVAttributeType.NONE))
{
removeObjectUsage(id, attribute, value, dropset, keepalive);
}
else
{
if(value instanceof Map)
{
Map values = (Map)value;
for(Iterator vit = values.keySet().iterator(); vit.hasNext();)
{
Object key = vit.next();
Object value1 = values.get(key);
removeObjectUsage(id, attribute, key, dropset, keepalive);
removeObjectUsage(id, attribute, value1, dropset, keepalive);
}
}
else
{
for(Iterator vit = ((Collection)value).iterator(); vit.hasNext();)
{
Object value1 = vit.next();
removeObjectUsage(id, attribute, value1, dropset, keepalive);
}
}
}
}
}
// Delete object only, when there are no direct or indirect external references.
if(!keepalive)
{
removeObject(id);
}
//System.out.print("Removing: "+object+" "+types);
// Notify listeners about removed object before removing references
// Object will be removed from types map in notifyEventListeners()
eventhandler.objectRemoved(id, (OAVObjectType)content.get(TYPE));
}
/**
* Ultimately remove an object (oid), when there are no more external or internal references.
*/
protected void removeObject(Object id)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
// Remove the object itself (needs to be done before removing its references to avoid recursion)
// Map content = (Map)objects.remove(id);
Map content = (Map)internalRemoveObject(id);
if(content==null)
throw new RuntimeException("Object not found: "+id);
deletedobjects.put(id, content);
// assert getObjectUsages(id)==null || getObjectUsages(id).isEmpty() : getObjectUsages(id);
// assert externalusages.get(id)==null : externalusages.get(id);
objectusages.remove(id);
// type will be removed in notifyEventListeners()
}
/**
* Test if the state contains a specific object (oid).
* @param id The object id.
* @return True, if contained.
*/
public boolean containsObject(Object id)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
// boolean ret = objects.containsKey(id);
boolean ret = internalContainsObject(id);
if(ret)
{
// Object is only contained when currently used
// or newly created before any usage (usages==null).
// I.e. objects, which are only externally used are not contained.
if(!rootobjects.contains(id))
{
Map usages = getObjectUsages(id);
// boolean instate = false;
// if(usages!=null && !usages.isEmpty())
// {
// for(Iterator it=usages.keySet().iterator(); it.hasNext() && !instate; )
// {
// OAVObjectUsage usage = (OAVObjectUsage)it.next();
// if(!usage.isExternal() && containsObject(usage.getObject()))
// instate = true;
// }
// }
// ret = usages==null || instate;
// Hack! usages==null means that a new object is automatically considered
// to be in state to be able to add attributes to it.
ret = usages==null || !usages.isEmpty();
}
}
else
{
// Allow event listeners to access objects that have just been deleted.
if(eventhandler.notifying)
ret = deletedobjects.containsKey(id);
// Check containment in substates.
if(!ret && substates!=null)
for(int i=0; !ret && i<substates.length; i++)
ret = substates[i].containsObject(id);
}
return ret;
}
/**
* Get the type of an object (oid or java object).
* @return The type of an object.
*/
public OAVObjectType getType(Object object)
{
if(object==null)
throw new NullPointerException();
OAVObjectType ret=null;
if(generator.isId(object))
{
// ret = (OAVObjectType)types.get(object);
Map content = (Map)getObject0(object);
ret = content!=null? (OAVObjectType)content.get(TYPE): null;
// ret = (OAVObjectType)types.get(object);
if(ret==null && substates!=null)
{
for(int i=0; ret==null && i<substates.length; i++)
{
if(substates[i].containsObject(object))
{
ret = substates[i].getType(object);
}
}
}
}
else
{
ret = tmodel.getJavaType(object.getClass());
}
if(ret==null)
{
throw new RuntimeException("Object has no type: "+object);
}
return ret;
}
/**
* Get all objects (oids and java objects) in the state.
*/
public Iterator getObjects()
{
Iterator ret;
if(!eventhandler.notifying)
{
ret = new Iterator()
{
// Iterator it1 = objects.keySet().iterator();
Iterator it1 = internalGetObjects().iterator();
Iterator it2 = javaobjects.iterator();
public boolean hasNext()
{
return it1.hasNext() || it2.hasNext();
}
public Object next()
{
Object ret = null;
if(it1.hasNext())
{
ret = it1.next();
}
else if(it2.hasNext())
{
ret = it2.next();
}
else
{
throw new RuntimeException("No next element.");
}
return ret;
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
else
{
ret = new Iterator()
{
// Iterator it1 = objects.keySet().iterator();
Iterator it1 = internalGetObjects().iterator();
Iterator it2 = deletedobjects.keySet().iterator();
Iterator it3 = javaobjects.iterator();
public boolean hasNext()
{
return it1.hasNext() || it2.hasNext() || it3.hasNext();
}
public Object next()
{
Object ret = null;
if(it1.hasNext())
{
ret = it1.next();
}
else if(it2.hasNext())
{
ret = it2.next();
}
else if(it3.hasNext())
{
ret = it3.next();
}
else
{
throw new RuntimeException("No next element.");
}
return ret;
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
return ret;
}
/**
* Get all objects (oids and java objects) in the state and its substates.
*/
public Iterator getDeepObjects()
{
if(substates==null)
{
return getObjects();
}
else
{
Set states = new HashSet();
List statelist = new ArrayList();
states.add(this);
statelist.add(this);
for(int i=0; i<statelist.size(); i++)
{
IOAVState[] subs = ((IOAVState)statelist.get(i)).getSubstates();
for(int j=0; subs!=null && j<subs.length; j++)
{
if(!states.contains(subs[j]))
{
states.add(subs[j]);
statelist.add(subs[j]);
}
}
}
final Iterator istates = states.iterator();
return new Iterator()
{
Iterator iterator = ((IOAVState)istates.next()).getObjects();
public boolean hasNext()
{
boolean ret = iterator.hasNext();
if(!ret && istates.hasNext())
{
iterator = ((IOAVState)istates.next()).getObjects();
ret = hasNext();
}
return ret;
}
public Object next()
{
if(!iterator.hasNext() && istates.hasNext())
{
iterator = ((IOAVState)istates.next()).getObjects();
return next();
}
else
{
// Throws exception in last iterator when no more elements available.
return iterator.next();
}
}
// #ifndef MIDP
public void remove()
{
throw new UnsupportedOperationException("Remove not supported.");
}
// #endif
};
}
}
/**
* Get the root objects (oids and java objects) of the state.
*/
public Iterator getRootObjects()
{
return rootobjects.iterator();
}
/**
* Get the number of objects (oids and java objects) in the state.
* Optional operation used for debugging only.
*/
public int getSize()
{
// int ret = objects.size() + javaobjects.size();
int ret = internalObjectsSize() + javaobjects.size();
if(eventhandler.notifying)
ret += deletedobjects.size();
if(substates!=null)
for(int i=0; i<substates.length; i++)
ret += substates[i].getSize();
return ret;
}
/**
* Get all unreferenced objects (oids).
* @return All unreferenced objects of the state.
*/
public Collection getUnreferencedObjects()
{
Set unreferenced = new HashSet();
// for(Iterator it=objects.keySet().iterator(); it.hasNext();)
for(Iterator it=internalGetObjects().iterator(); it.hasNext();)
{
Object id = it.next();
if(!rootobjects.contains(id) && !isReachable(id, new HashSet()))
{
// System.out.println("Found orphan: "+id);
unreferenced.add(id);
}
}
/*//Todo: support check for substates.
if(substates!=null)
{
for(int i=0; i<substates.length; i++)
unreferenced.addAll(substates[i].getUnreferencedObjects());
}
*/
return unreferenced;
}
/**
* Test if an object (oid) can be reached from some root or external object.
* @param id The object
* @param tested The objects already traversed (to avoid endless loops).
*/
protected boolean isReachable(Object id, Set tested)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
tested.add(id);
boolean ret = rootobjects.contains(id) || isExternallyUsed(id);
if(!ret)
{
Map usages = getObjectUsages(id);
if(usages!=null)
{
for(Iterator it=usages.keySet().iterator(); !ret && it.hasNext(); )
{
Object ref = it.next();
// Internal reference -> 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<subgraph.size(); i++)
{
Map theobject = getObject(subgraph.get(i));
for(Iterator keys=theobject.keySet().iterator(); cycle==null && keys.hasNext(); )
{
Object tmp = keys.next();
if(tmp instanceof OAVAttributeType)
{
OAVAttributeType attr = (OAVAttributeType)tmp;
Object value = theobject.get(attr);
if(value instanceof Collection)
{
for(Iterator refs=((Collection)value).iterator(); cycle==null && refs.hasNext(); )
{
Object next = refs.next();
if(generator.isId(next))
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), next, attr);
}
}
else if(value instanceof Map)
{
for(Iterator refs=((Map)value).values().iterator(); cycle==null && refs.hasNext(); )
{
Object next = refs.next();
if(generator.isId(next))
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), next, attr);
}
for(Iterator refs=((Map)value).keySet().iterator(); cycle==null && refs.hasNext(); )
{
Object next = refs.next();
if(generator.isId(next))
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), next, attr);
}
}
else if(generator.isId(value))
{
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), value, attr);
}
}
}
}
}
}
return cycle;
}
/**
* Step for one edge of the find cycle algorithm.
* @param current The current node (oid).
* @param next The next node (oid).
*/
protected List findCycleForValue(Collection oids, Set checked, Map edges, List subgraph, Object current, Object next, OAVAttributeType attr)
{
// #ifndef MIDP
// assert nocheck || generator.isId(current);
// assert nocheck || generator.isId(next);
// #endif
List cycle = null;
if(!generator.isId(current) || !generator.isId(next))
return cycle;
if(edges.containsKey(next))
{
// Cycle found.
edges.put(next, current);
edges.put(new Tuple(next, current), attr);
cycle = new LinkedList();
Object node = current;
cycle.add(node);
do
{
Object node1 = edges.get(node);
attr = (OAVAttributeType)edges.get(new Tuple(node, node1));
// if(attr!=null)
// System.out.println("here: "+node+" "+node1);
// prepend for expected ordering 'node, attr, ref'.
cycle.add(0, attr!=null? attr.getName(): null);
cycle.add(0, node1);
node = node1;
}
while(node!=current); // Use do-while to include node twice (for readability).
}
else if(oids.contains(next) && !checked.contains(next))
{
// Add back reference and continue search from next node.
edges.put(next, current);
edges.put(new Tuple(next, current), attr);
subgraph.add(next);
checked.add(next);
}
return cycle;
}
/**
* 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<subgraph.size(); i++)
{
Map theobject = getObject(subgraph.get(i));
for(Iterator keys=theobject.keySet().iterator(); cycle==null && keys.hasNext(); )
{
OAVAttributeType attr = (OAVAttributeType)keys.next();
Object value = theobject.get(attr);
if(value instanceof Collection)
{
for(Iterator refs=((Collection)value).iterator(); cycle==null && refs.hasNext(); )
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), refs.next(), attr);
}
else if(value instanceof Map)
{
for(Iterator refs=((Map)value).values().iterator(); cycle==null && refs.hasNext(); )
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), refs.next(), attr);
for(Iterator refs=((Map)value).keySet().iterator(); cycle==null && refs.hasNext(); )
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), refs.next(), attr);
}
else
{
cycle = findCycleForValue(oids, checked, edges, subgraph, subgraph.get(i), value, attr);
}
}
}
}
}
return cycle;
}*/
/**
* Step for one edge of the find cycle algorithm.
* @param current The current node (oid).
* @param next The next node (oid).
* /
protected List findCycleForValue(Collection oids, Set checked, Map edges, List subgraph, Object current, Object next, OAVAttributeType attr)
{
// #ifndef MIDP
assert nocheck || generator.isId(current);
assert nocheck || generator.isId(next);
// #endif
List cycle = null;
if(edges.containsKey(next))
{
// Cycle found.
edges.put(next, current);
edges.put(new Tuple(next, current), attr);
cycle = new LinkedList();
Object node = current;
cycle.add(node);
do
{
Object node1 = edges.get(node);
attr = (OAVAttributeType)edges.get(new Tuple(node, node1));
// prepend for expected ordering 'node, attr, ref'.
cycle.add(0, attr.getName());
cycle.add(0, node1);
node = node1;
}
while(node!=current); // Use do-while to include node twice (for readability).
}
else if(oids.contains(next) && !checked.contains(next))
{
// Add back reference and continue search from next node.
edges.put(next, current);
edges.put(new Tuple(next, current), attr);
subgraph.add(next);
checked.add(next);
}
return cycle;
}*/
/**
* Get those objects referencing a given object (java object or oid).
*/
public Collection getReferencingObjects(Object value)
{
Collection ret = null;
Map usages = getObjectUsages(value);
if(usages!=null)
{
ret = new ArrayList();
for(Iterator it=usages.keySet().iterator(); it.hasNext(); )
{
Object ref = it.next();
ret.add(((OAVObjectUsage)ref).getObject());
}
}
if(ret==null)
{
ret = Collections.EMPTY_SET;
}
return ret;
}
//--------- attribute management --------
/**
* Get an attribute value of an object (oid).
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @return The value (basic, object id or java object).
*/
public Object getAttributeValue(Object id, OAVAttributeType attribute)
{
// #ifndef MIDP
// if(!generator.isId(id))
// System.out.println("not an id: "+id);
assert nocheck || generator.isId(id) : id + ", " + attribute;
assert nocheck || checkValidStateObjectRead(id) : id;
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute, OAVAttributeType.NONE);
// #endif
Object ret = null;
Map theobject = getObject0(id);
if(theobject!=null)
{
ret = theobject.get(attribute);
if(ret==null && !theobject.containsKey(attribute))
ret = attribute.getDefaultValue();
}
else if(substates!=null)
{
boolean found = false;
for(int i=0; !found && i<substates.length; i++)
{
if(substates[i].containsObject(id))
{
ret = substates[i].getAttributeValue(id, attribute);
found = true;
}
}
if(!found)
throw new IllegalArgumentException("Object "+id+" does not exist.");
}
return ret;
}
/**
* Set an attribute of an object (oid) to the given value.
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @param value The value (basic, object id or java object).
*/
public void setAttributeValue(Object id, OAVAttributeType attribute, Object value)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObject(id): id+" "+attribute+" "+value;
assert nocheck || checkValidStateValue(value);
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute, OAVAttributeType.NONE);
assert nocheck || checkValueCompatibility(id, attribute, value);
// #endif
Map theobject = getObject(id);
Object oldvalue = theobject.put(attribute, value);
// Notification before removal in order to be capable to save the oldvalue reference.
if(!equals(oldvalue, value))
{
eventhandler.objectModified(id, getType(id), attribute, oldvalue, value);
removeObjectUsage(id, attribute, oldvalue, null, false);
addObjectUsage(id, attribute, value);
}
}
/**
* Get the values of an attribute of an object (oid).
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @return The values (basic, object ids or java objects).
*/
public Collection getAttributeValues(Object id, OAVAttributeType attribute)
{
// if(!generator.isId(id))
// System.out.println("dflb");
// #ifndef MIDP
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObjectRead(id);
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute,
OAVAttributeType.MULTIPLICITIES_MULT);
// #endif
Collection ret = null;
Map theobject = getObject0(id);
if(theobject!=null)
{
Object val = theobject.get(attribute);
if(val==null && !theobject.containsKey(attribute))
val = attribute.getDefaultValue();
ret = (val instanceof Map)? ((Map)val).values(): (Collection)val;
}
else if(substates!=null)
{
boolean found = false;
for(int i=0; !found && i<substates.length; i++)
{
if(substates[i].containsObject(id))
{
ret = substates[i].getAttributeValues(id, attribute);
found = true;
}
}
if(!found)
throw new IllegalArgumentException("Object "+id+" does not exist.");
}
return ret;
}
/**
* Get the keys of an attribute of an object.
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @return The keys for which values are stored.
*/
public Collection getAttributeKeys(Object id, OAVAttributeType attribute)
{
// if(!generator.isId(id))
// System.out.println("dflb");
// #ifndef MIDP
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObjectRead(id);
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute,
OAVAttributeType.MULTIPLICITIES_MAPS);
// #endif
Collection ret = null;
Map theobject = getObject0(id);
if(theobject!=null)
{
Object val = theobject.get(attribute);
if(val==null && !theobject.containsKey(attribute))
val = attribute.getDefaultValue();
ret = val!=null ? ((Map)val).keySet() : null;
}
else if(substates!=null)
{
boolean found = false;
for(int i=0; !found && i<substates.length; i++)
{
if(substates[i].containsObject(id))
{
ret = substates[i].getAttributeKeys(id, attribute);
found = true;
}
}
if(!found)
throw new IllegalArgumentException("Object "+id+" does not exist.");
}
return ret;
}
/**
* Get an attribute value of an object (oid).
* Method only applicable for map attribute type.
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @param key The key.
* @return The value (basic, object id or java object).
*/
public Object getAttributeValue(Object id, OAVAttributeType attribute, Object key)
{
// if(!generator.isId(id))
// System.out.println("not an id: "+id);
// #ifndef MIDP
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObjectRead(id);
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute,
OAVAttributeType.MULTIPLICITIES_MAPS);
// #endif
Object ret = null;
Map theobject = getObject0(id);
if(theobject!=null)
{
Map map = (Map)theobject.get(attribute);
// todo: enable check again by adding containsKey(key) method to state
// if(map==null || !map.containsKey(key))
// throw new RuntimeException("Key not available in map: "+key+" "+map);
ret = map==null? null: map.get(key);
}
else if(substates!=null)
{
boolean found = false;
for(int i=0; !found && i<substates.length; i++)
{
if(substates[i].containsObject(id))
{
ret = substates[i].getAttributeValue(id, attribute, key);
found = true;
}
}
if(!found)
throw new IllegalArgumentException("Object "+id+" does not exist.");
}
return ret;
}
/**
* Test if a key is contained in the map attribute.
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @param key The key.
* @return True if key is available.
*/
public boolean containsKey(Object id, OAVAttributeType attribute, Object key)
{
// #ifndef MIDP
// if(!generator.isId(id))
// System.out.println("no id: "+id);
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObjectRead(id);
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute,
OAVAttributeType.MULTIPLICITIES_MAPS);
// #endif
boolean ret = false;
Map theobject = getObject0(id);
if(theobject!=null)
{
Map map = (Map)theobject.get(attribute);
ret = map==null? false: map.containsKey(key);
}
else if(substates!=null)
{
boolean found = false;
for(int i=0; !found && i<substates.length; i++)
{
if(substates[i].containsObject(id))
{
ret = substates[i].containsKey(id, attribute, key);
found = true;
}
}
if(!found)
throw new IllegalArgumentException("Object "+id+" does not exist.");
}
return ret;
}
/**
* Remove all values of an attribute of an object.
* @param object The identifier of the object.
* @param attribute The attribute identifier.
* /
public void removeAllAttributeValues(Object object, OAVAttributeType attribute)
{
assert nocheck || checkTypeHasAttribute(object, attribute);
assert nocheck || checkMultiplicity(object, attribute, true);
assert nocheck || checkValidStateObject(object);
Map theobject = getObject(object);
Collection coll = (Collection)theobject.get(attribute);
if(coll!=null)
{
Object[] vals = coll.toArray();
for(int i=0; i<vals.length; i++)
removeAttributeValue(object, attribute, vals[i]);
}
}*/
/**
* Add an attribute of an object (oid) to the given value.
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @param value The value (basic, object id or java object).
*/
public void addAttributeValue(Object id, OAVAttributeType attribute, Object value)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObject(id);
assert nocheck || checkValidStateValue(value) : value;
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute, OAVAttributeType.MULTIPLICITIES_MULT);
assert nocheck || checkValueCompatibility(id, attribute, value);
// #endif
Map theobject = getObject(id);
Object tmp = theobject.get(attribute);
if(tmp == null)
{
String mult = attribute.getMultiplicity();
if(OAVAttributeType.LIST.equals(mult))
tmp = new ArrayList();
else if(OAVAttributeType.SET.equals(mult))
tmp = new LinkedHashSet();
else if(OAVAttributeType.QUEUE.equals(mult))
tmp = new LinkedList();
else if(OAVAttributeType.MAP.equals(mult))
tmp = new HashMap();
else if(OAVAttributeType.ORDEREDMAP.equals(mult))
tmp = new LinkedHashMap();
if(tmp==null)
throw new RuntimeException("Attribute has unknown multiplicity type: "+mult);
theobject.put(attribute, tmp);
}
if(tmp instanceof Collection)
{
Collection coll = (Collection)tmp;
if(!coll.add(value))
throw new RuntimeException("Could not add value: "+value);
}
else if(tmp instanceof Map)
{
Map map = (Map)tmp;
OAVAttributeType keyattr = attribute.getIndexAttribute();
if(keyattr==null)
throw new RuntimeException("Index attribute not specified: "+attribute);
Object key = getAttributeValue(value, keyattr);
if(key==null)
throw new RuntimeException("Null key not allowed: "+attribute);
map.put(key, value);
addObjectUsage(id, attribute, key);
}
addObjectUsage(id, attribute, value);
eventhandler.objectModified(id, getType(id), attribute, null, value);
}
/**
* Add an attribute of an object to the given value.
* This method is specific for map attributes.
* @param object The identifier of the object.
* @param attribute The attribute identifier.
* @param key The key.
* @param value The value (basic, object id or java object).
* /
public void putAttributeValue(Object object, OAVAttributeType attribute, Object key, Object value)
{
assert nocheck || checkTypeHasAttribute(object, attribute);
assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.MULTIPLICITIES_MAP);
assert nocheck || checkValueCompatibility(object, attribute, value);
Map theobject = getObject(object);
Map map = (Map)theobject.get(attribute);
if(map==null)
{
String mult = attribute.getMultiplicity();
if(OAVAttributeType.MAP.equals(mult))
map = new HashMap();
if(map==null)
throw new RuntimeException("Attribute has unknown multiplicity type: "+mult);
theobject.put(attribute, map);
}
// todo: what about a replacement, notify listeners?
map.put(key, value);
// throw new RuntimeException("Could not add value: "+value);
if(isNonValue(attribute, value))
{
addObjectUsage(object, attribute, value);
}
eventhandler.objectModified(object, getType(object), attribute, null, value);
}*/
/**
* Remove an attribute of an object (oid) to the given value.
* @param id The identifier of the object.
* @param attribute The attribute identifier.
* @param value The value (basic, object id or java object).
*/
public void removeAttributeValue(Object id, OAVAttributeType attribute, Object value)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
assert nocheck || checkValidStateObject(id);
assert nocheck || checkValidStateValue(value);
assert nocheck || checkTypeHasAttribute(id, attribute);
assert nocheck || checkMultiplicity(id, attribute,
OAVAttributeType.MULTIPLICITIES_MULT);
// #endif
Map theobject = getObject(id);
Object tmp = theobject.get(attribute);
if(tmp==null)
throw new RuntimeException("Value not contained in attribute: "
+id+" "+attribute+" "+value);
if(tmp instanceof Collection)
{
Collection coll = (Collection)tmp;
// Replace value with real value stored in collection
for(Iterator it=coll.iterator(); it.hasNext(); )
{
Object next = it.next();
if(SUtil.equals(next, value)) // Use equality to find original object regardless of identity settings.
{
value = next;
break;
}
}
if(!coll.remove(value))
throw new RuntimeException("Value not contained in attribute: "
+id+" "+attribute+" "+value);
if(coll.isEmpty())
theobject.remove(attribute);
// Event handler notification must be before cleanup in order to be able
// to save the object within another reference.
eventhandler.objectModified(id, getType(id), attribute, value, null);
removeObjectUsage(id, attribute, value, null, false);
}
else if(tmp instanceof Map)
{
// Value is here key!
Map map = (Map)tmp;
Object key = value;
// Replace value with real value stored in map
value = map.remove(value);
if(value==null)
throw new RuntimeException("Value not contained in attribute: "
+id+" "+attribute+" "+value);
// Event handler notification must be before cleanup in order to be able
// to save the object within another reference.
eventhandler.objectModified(id, getType(id), attribute, value, null);
removeObjectUsage(id, attribute, key, null, false);
removeObjectUsage(id, attribute, value, null, false);
}
}
/**
* Remove an attribute of an object to the given value.
* @param object The identifier of the object.
* @param attribute The attribute identifier.
* @param value The value (basic, object id or java object).
* /
public void removeAttributeValue(Object object, OAVAttributeType attribute, Object value)
{
assert nocheck || checkTypeHasAttribute(object, attribute);
assert nocheck || checkMultiplicity(object, attribute, true);
Map theobject = getObject(object);
Collection coll = (Collection)theobject.get(attribute);
if(coll==null)
throw new RuntimeException("Value not contained in attribute: "
+object+" "+attribute+" "+value);
if(!coll.remove(value))
throw new RuntimeException("Value not contained in attribute: "
+object+" "+attribute+" "+value);
if(coll.isEmpty())
theobject.remove(attribute);
if(isNonValue(attribute, value))
{
removeObjectUsage(object, attribute, value, null);
}
eventhandler.objectModified(object, getType(object), attribute, value, null);
}*/
//-------- state observers --------
/**
* Add a new state listener.
* @param listener The state listener.
* @param bunch True, for adding a bunch listener.
*/
public void addStateListener(IOAVStateListener listener, boolean bunch)
{
if(listener==null)
throw new RuntimeException("Listener must not null.");
eventhandler.addStateListener(listener, bunch);
}
/**
* Remove a state listener.
* @param listener The state listener.
*/
public void removeStateListener(IOAVStateListener listener)
{
if(listener==null)
throw new RuntimeException("Listener must not null.");
eventhandler.removeStateListener(listener);
}
/**
* Throw collected events and notify the listeners.
*/
public void notifyEventListeners()
{
eventhandler.notifyEventListeners();
// for(Iterator it=deletedobjects.keySet().iterator(); it.hasNext(); )
// types.remove(it.next());
deletedobjects.clear();
}
// #ifndef MIDP
/**
* Get the profiler.
*/
// Hack!!! Make accessible from somewhere else?
public IProfiler getProfiler()
{
return profiler;
}
/**
* Set the profiler.
*/
// Hack!!! Make accessible from somewhere else?
public void setProfiler(IProfiler profiler)
{
this.profiler = profiler;
}
// #endif
/**
* Expunge stale objects.
*/
public void expungeStaleObjects()
{
// nop? gc?
}
/**
* Run the garbage collection for deleting unreferenced objects.
*/
// public void gc()
// {
// // Perform gc in two passes, otherwise objects table changes during search.
// Set unreferenced = new HashSet();
// for(Iterator it=objects.keySet().iterator(); it.hasNext();)
// {
// Object id = it.next();
// if(!rootobjects.contains(id) && getObjectUsages(id)==null)
// {
//// System.out.println("Removing unprotected object with no references: "+id);
// unreferenced.add(id);
// }
// }
//
// for(Iterator it=unreferenced.iterator(); it.hasNext();)
// {
// Object id = it.next();
// if(containsObject(id))
// dropObject(id);
// }
// }
/**
* Set the synchronizator.
* The optional synchronizator is used to synchronize
* external modifications to the state (e.g. from bean changes).
* The synchronizator should only be set once, before
* the state is used.
*/
public void setSynchronizator(ISynchronizator synchronizator)
{
if(this.synchronizator!=null)
throw new RuntimeException("Synchronizator can be set only once.");
this.synchronizator = synchronizator;
}
/**
* Get the synchronizator (if any).
* The synchronizator (if available) can be used to synchronize
* access to the state with internal and external modifications.
*/
public ISynchronizator getSynchronizator()
{
return this.synchronizator;
}
/**
* Add an external usage of a state object (oid). This prevents
* the oav object of being garbage collected as long
* as external references are present.
* @param id The oav object id.
* @param external The user object.
*/
public abstract void addExternalObjectUsage(Object id, Object external);
/**
* Remove an external usage of a state object (oid). This allows
* the oav object of being garbage collected when no
* further external references and no internal references
* are present.
* @param id The oav object id.
* @param external The state external object.
*/
public abstract void removeExternalObjectUsage(Object id, Object external);
/**
* Get the string representation of the object.
* @return The string representation.
*/
public String toString()
{
StringBuffer ret = new StringBuffer();
ret.append("State(");
ret.append("types=");
ret.append(tmodel);
ret.append(", number of objects=");
// ret.append(objects.size());
ret.append(internalObjectsSize());
ret.append(", number of java objects=");
ret.append(javaobjects.size());
ret.append(", number of rootobjects=");
ret.append(rootobjects.size());
// #ifndef MIDP
// ret.append(", rootobjects=");
// ret.append(rootobjects);
//
// ret.append(", all objects=");
// ret.append(objects);
// #endif
if(substates!=null)
{
ret.append(", substates=");
ret.append(SUtil.arrayToString(substates));
}
ret.append(")");
return ret.toString();
}
//-------- internal helper classes --------
/**
* Get an object map for its id.
* @param id The id.
* @return The object map.
*/
protected Map getObject(Object id)
{
// #ifndef MIDP
assert nocheck || generator.isId(id);
// #endif
Map ret = getObject0(id);
if(ret==null)
throw new IllegalArgumentException("Object "+id+" does not exist.");
return ret;
}
/**
* Get an object map for its id.
* @param id The id.
* @return The object map.
*/
protected Map getObject0(Object id)
{
Map ret = null;
if(generator.isId(id))
{
// ret = (Map)objects.get(id);
ret = (Map)internalGetObjectContent(id);
if(ret==null && eventhandler.notifying && deletedobjects.containsKey(id))
ret = (Map)deletedobjects.get(id);
}
return ret;
}
/**
* Check if it is allowed to set or add an attribute value.
* For this purpose it is checked if the value is either
* a) a ObjectId -> 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)
{
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();
}