package jadex.rules.state.javaimpl; // #ifndef MIDP import jadex.commons.SReflect; import jadex.commons.SUtil; import jadex.commons.collection.WeakEntry; import jadex.commons.concurrent.ISynchronizator; import jadex.rules.state.IOAVState; import jadex.rules.state.IOAVStateListener; import jadex.rules.state.IProfiler; import jadex.rules.state.OAVAttributeType; import jadex.rules.state.OAVJavaType; import jadex.rules.state.OAVObjectType; import jadex.rules.state.OAVTypeModel; import jadex.rules.state.javaimpl.OAVWeakIdGenerator.OAVExternalObjectId; import jadex.rules.state.javaimpl.OAVWeakIdGenerator.OAVInternalObjectId; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.ReferenceQueue; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * An object holding the state as * OAV triples (object, attribute, value). */ public class OAVWeakState implements IOAVState { //-------- constants -------- /** The argument types for property change listener adding/removal (cached for speed). */ protected static Class[] PCL = new Class[]{PropertyChangeListener.class}; //-------- attributes -------- /** The type models. */ protected OAVTypeModel tmodel; /** The objects table (id -> map). */ protected Map objects; /** The object types (object -> type). */ protected Map types; /** The id generator. */ protected IOAVIdGenerator generator; /** The flag to disable type checking. */ protected boolean nocheck; /** The usages of object ids (object id -> usages). */ protected Map objectusages; /** The root objects (will not be cleaned up when usages==0). */ protected Set rootobjects; /** The Java beans property change listeners. */ protected Map pcls; /** The OAV event handler. */ protected OAVEventHandler eventhandler; /** The reference queue for stale objects. */ protected ReferenceQueue queue; /** The synchronizator (if any). */ protected ISynchronizator synchronizator; /** The profiler. */ // Hack??? protected IProfiler profiler = new IProfiler() { public void start(String type, Object item) { } public void stop(String type, Object item) { } public ProfilingInfo[] getProfilingInfos(int start) { return new ProfilingInfo[0]; } }; //-------- constructors -------- /** * Create a new empty OAV state representation. */ public OAVWeakState(OAVTypeModel tmodel) { this.tmodel = tmodel; this.objects = new LinkedHashMap(); this.types = new LinkedHashMap(); this.queue = new ReferenceQueue(); this.generator = new OAVWeakIdGenerator(queue); // this.generator = new OAVNameIdGenerator(); // this.generator = new OAVLongIdGenerator(); this.objectusages = new LinkedHashMap(); // this.objectusages = new IdentityHashMap(); this.rootobjects = new LinkedHashSet(); this.eventhandler = new OAVEventHandler(this); // this.nocheck = true; } /** * Dispose the state. */ public void dispose() { Object[] roots = rootobjects.toArray(); for(int i=0; i<roots.length; i++) dropObject(roots[i]); } //-------- 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) { assert nocheck || checkTypeDefined(type); OAVInternalObjectId ret = (OAVInternalObjectId)generator.createId(this, type); objects.put(ret, new LinkedHashMap()); types.put(ret, type); //System.out.println("Adding type: "+type); // Event should be thrown once the object is used somewhere in the state? eventhandler.objectAdded(ret.getPhantomExternalId(), type, false); return ret.getWeakExternalId(); } /** * 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) { Object ret = createObject(type); this.rootobjects.add(ret); eventhandler.objectAdded(((OAVExternalObjectId)ret).getInternalId().getPhantomExternalId(), type, true); assert ret instanceof OAVExternalObjectId; return ret; } /** * Drop an object from the state. * Recursively removes the object and all connected objects that are not * referenced elsewhere. * @param object The identifier of the object to remove. */ public void dropObject(Object object) { assert nocheck || checkValidStateObject(object); assert rootobjects.contains(object); assert object instanceof OAVExternalObjectId; rootobjects.remove(object); // internalDropObject(object, null); } /** * Add a Java object as root object. * @param object The Java object. */ public void addJavaRootObject(Object object) { assert nocheck || !rootobjects.contains(object); this.rootobjects.add(object); OAVJavaType java_type = tmodel.getJavaType(object.getClass()); 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) { assert nocheck || rootobjects.contains(object); this.rootobjects.remove(object); OAVJavaType java_type = tmodel.getJavaType(object.getClass()); if(OAVJavaType.KIND_BEAN.equals(java_type.getKind())) deregisterValue(object); eventhandler.objectRemoved(object, java_type); } /** * Internal drop method for avoiding cycles in to be dropped * objects during a recursive drop operation. * / protected void internalDropObject(Object object, Set dropset) { if(dropset==null) dropset = new HashSet(); dropset.add(object); // Remove all used object references Map content = (Map)objects.get(object); for(Iterator it=content.keySet().iterator(); it.hasNext(); ) { OAVAttributeType attribute = (OAVAttributeType)it.next(); Object value = content.get(attribute); if(value!=null) { if(attribute.getMultiplicity().equals(OAVAttributeType.NONE)) { if(isNonValue(value)) removeObjectUsage(object, attribute, value, dropset); } 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); if(isNonValue(key)) removeObjectUsage(object, attribute, key, dropset); if(isNonValue(value1)) removeObjectUsage(object, attribute, value1, dropset); } } else { for(Iterator vit = ((Collection)value).iterator(); vit.hasNext();) { Object value1 = vit.next(); if(isNonValue(value1)) removeObjectUsage(object, attribute, value1, dropset); } } } } } // Remove the object itself (needs to be done before removing its references to avoid recursion) Map theobject = (Map)objects.remove(object); if(theobject==null) throw new RuntimeException("Object not found: "+object); // Remove this object from all places where it is referenced Map references = getObjectUsages(object); if(references!=null) { Iterator it = references.keySet().iterator(); while(it.hasNext()) { ObjectUsage usage = (ObjectUsage)it.next(); Object id = usage.getObject(); if(!dropset.contains(id)) { OAVAttributeType attr = usage.getAttribute(); if(attr.getMultiplicity().equals(OAVAttributeType.NONE)) { setAttributeValue(id, attr, null); } else { int cnt = ((Integer)references.get(usage)).intValue(); for(int i=0; i<cnt; i++) removeAttributeValue(id, attr, object); } } references.remove(usage); } } OAVObjectType type = (OAVObjectType)types.remove(object); // System.out.print("Dropped: "+object); // Notify listeners about removed object before removing references eventhandler.objectRemoved(object, type, content); }*/ /** * Clone an object in the state (deep copy). * @param object The handle to the object to be cloned. * @param targetstate The target state in which the clone should be created. * @return The identifier of the newly created clone. */ public Object cloneObject(Object object, IOAVState targetstate) { assert object instanceof OAVExternalObjectId; Map handles = new HashMap(); List todo = new LinkedList(); Map todoafter = new HashMap(); if(rootobjects.contains(object)) handles.put(object, targetstate.createRootObject(getType(object))); else handles.put(object, targetstate.createObject(getType(object))); todo.add(object); while(!todo.isEmpty()) { Object obj = todo.remove(0); Object newobj = handles.get(obj); Map content = (Map)objects.get(obj); if(content!=null) { for(Iterator it=content.keySet().iterator(); it.hasNext(); ) { // Clone single-valued attribute. OAVAttributeType attr = (OAVAttributeType)it.next(); if(OAVAttributeType.NONE.equals(attr.getMultiplicity())) { Object oldval = content.get(attr); if(oldval==null || attr.getType() instanceof OAVJavaType) { // Todo: clone Java values also? targetstate.setAttributeValue(newobj, attr, oldval); } else { Object newval = getClonedOAVObject(targetstate, handles, todo, oldval); targetstate.setAttributeValue(newobj, attr, newval); } } // Clone multi-valued attribute. else { Collection coll = null; Object tmp = content.get(attr); if(tmp instanceof Collection) { coll = (Collection)tmp; if(attr.getType() instanceof OAVJavaType) { // Todo: clone Java values also? for(Iterator it2=coll.iterator(); it2.hasNext(); ) targetstate.addAttributeValue(newobj, attr, it2.next()); } else { for(Iterator it2=coll.iterator(); it2.hasNext(); ) { Object newval = getClonedOAVObject(targetstate, handles, todo, it2.next()); targetstate.addAttributeValue(newobj, attr, newval); } } } else if(tmp instanceof Map) { Map map = (Map)tmp; for(Iterator it2=map.keySet().iterator(); it2.hasNext(); ) { Object oldval = map.get(it2.next()); Object newval = getClonedOAVObject(targetstate, handles, todo, oldval); // if object is not read defer adding if(todo.contains(oldval)) { List mapadds = (List)todoafter.get(oldval); if(mapadds==null) { mapadds = new ArrayList(); todoafter.put(oldval, mapadds); } // System.out.println("(+) "+newobj+" "+attr+" "+newval+" "+handles.get(oldval)); mapadds.add(new Object[]{newobj, attr}); // mapadds.add(new Tuple(newobj, attr)); } else { targetstate.addAttributeValue(newobj, attr, newval); } } } } } } // Handle deferred mapadds after object is fully cloned List mapadds = (List)todoafter.get(obj); if(mapadds!=null) { // System.out.println("mapadds of: "+handles.get(obj)+" "+mapadds); for(int i=0; i<mapadds.size(); i++) { Object[] mapadd = (Object[])mapadds.get(i); // Tuple mapadd = (Tuple)mapadds.get(i); // System.out.println("+ "+mapadd[0]+" "+(OAVAttributeType)mapadd[1]+" "+handles.get(obj)); targetstate.addAttributeValue(mapadd[0], (OAVAttributeType)mapadd[1], handles.get(obj)); // targetstate.addAttributeValue(mapadd.get(0), (OAVAttributeType)mapadd.get(1), handles.get(obj)); } } } Object ret = handles.get(object); assert ret instanceof OAVExternalObjectId; return ret; } /** * Get or create a clone of an oav object. * @param targetstate The target state. * @param handles The handles. * @param todo The todo list. * @param oldval The old object. */ protected Object getClonedOAVObject(IOAVState targetstate, Map handles, List todo, Object oldval) { Object newval = handles.get(oldval); if(newval==null) { if(rootobjects.contains(oldval)) newval = targetstate.createRootObject(getType(oldval)); else newval = targetstate.createObject(getType(oldval)); handles.put(oldval, newval); todo.add(oldval); } return newval; } /** * Test if the state contains a specific object. * @param id The object id. * @return True, if contained. */ public boolean containsObject(Object id) { return objects.containsKey(id); } /** * Get the type of an object. * @return The type of an object. */ public OAVObjectType getType(Object id) { assert objects.containsKey(id): "No object id or object not contained in state: "+id; assert id instanceof OAVExternalObjectId; OAVObjectType ret = (OAVObjectType)types.get(id); if(ret==null) { throw new RuntimeException("Object has no type: "+id); } return ret; } /** * Get all objects in the state. */ public Iterator getObjects() { // todo: return objects.keySet().iterator(); } /** * Get all objects in the state. */ public Iterator getDeepObjects() { return getObjects(); } /** * Get the root objects of the state. */ public Iterator getRootObjects() { return rootobjects.iterator(); } /** * Get the number of objects in the state. * Optional operation used for debugging only. */ public int getSize() { return objects.size(); } /** * Get all unreferenced objects. * @return All unreferenced objects of the state. */ public Collection getUnreferencedObjects() { Set unreferenced = new HashSet(); for(Iterator it=objects.keySet().iterator(); it.hasNext();) { OAVInternalObjectId id = (OAVInternalObjectId)it.next(); if(!rootobjects.contains(id) // && getObjectUsages(id)==null && id.isClear()) { // System.out.println("Found orphan: "+id); unreferenced.add(id); } } return unreferenced; } /** * Find a cycle in a given set of objects. */ public List findCycle(Collection objects) { throw new UnsupportedOperationException("todo"); } /** * Get those objects referencing a given object. */ public Collection getReferencingObjects(Object value) { throw new UnsupportedOperationException("todo"); } //--------- attribute management -------- /** * Get an attribute value of an object. * @param object The identifier of the object. * @param attribute The attribute identifier. * @return The value (basic, object id or java object). */ public Object getAttributeValue(Object object, OAVAttributeType attribute) { assert nocheck || checkTypeHasAttribute(object, attribute); assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.NONE); assert nocheck || checkValidStateObject(object); assert object instanceof OAVExternalObjectId: object; Map theobject = getObject(object); Object ret = theobject.get(attribute); if(ret==null && !theobject.containsKey(attribute)) ret = attribute.getDefaultValue(); if(ret instanceof Collection) throw new IllegalArgumentException("Attribute "+attribute+" is not single valued."); return ret; } /** * Set 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 setAttributeValue(Object object, OAVAttributeType attribute, Object value) { assert nocheck || checkTypeHasAttribute(object, attribute); assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.NONE); assert nocheck || checkValueCompatibility(object, attribute, value); assert nocheck || checkValidStateObject(object): object+" "+attribute+" "+value; assert object instanceof OAVExternalObjectId; Map theobject = getObject(object); Object oldvalue = theobject.put(attribute, value); // When not value type, track usages of object. // if(isNonValue(oldvalue)) // removeObjectUsage(object, attribute, oldvalue, null); // if(isNonValue(value)) // addObjectUsage(object, attribute, value); if(isJavaNonValue(oldvalue)) removeJavaObjectUsage(object, attribute, oldvalue); if(isJavaNonValue(value)) addJavaObjectUsage(object, attribute, value); eventhandler.objectModified(((OAVExternalObjectId)object).getInternalId().getPhantomExternalId(), getType(object), attribute, oldvalue, value); } /** * Check if a value is not a value (!?). * Returns true for attribute values which are oav objects or mutable java objects, * e.g. not simple values such as strings or intergers. * / protected boolean isNonValue(OAVAttributeType attribute, Object value) { return value!=null && (!(attribute.getType() instanceof OAVJavaType) || !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)); }*/ /** * Check if a value is not a value (!?). * Returns true for attribute values which are oav objects or mutable java objects, * e.g. not simple values such as strings or intergers. * / // protected boolean isNonValue(OAVAttributeType attribute, Object value) protected boolean isNonValue(Object value) { // boolean ret2 = value!=null && (!(attribute.getType() instanceof OAVJavaType) // || !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)); OAVObjectType type = (OAVObjectType)types.get(value); boolean ret = value!=null && (type!=null || !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)); // if(ret!=ret2) // { // boolean a = attribute.getType() instanceof OAVJavaType; // System.out.println(a); // throw new RuntimeException(); // } return ret; }*/ /** * Check if an object is a java object but not a value. */ protected boolean isJavaNonValue(Object obj) { return obj!=null && types.get(obj)==null && !tmodel.getJavaType(obj.getClass()).getKind().equals(OAVJavaType.KIND_VALUE); } /** * Get the values of an attribute of an object. * @param object The identifier of the object. * @param attribute The attribute identifier. * @return The values (basic, object ids or java objects). */ public Collection getAttributeValues(Object object, OAVAttributeType attribute) { assert nocheck || checkTypeHasAttribute(object, attribute); assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.MULTIPLICITIES_MULT); assert nocheck || checkValidStateObject(object); assert object instanceof OAVExternalObjectId; Map theobject = getObject(object); Object ret = theobject.get(attribute); if(ret==null && !theobject.containsKey(attribute)) ret = attribute.getDefaultValue(); return (ret instanceof Map)? ((Map)ret).values(): (Collection)ret; } /** * Get the keys of an attribute of an object. * @param object The identifier of the object. * @param attribute The attribute identifier. * @return The keys for which values are stored. */ public Collection getAttributeKeys(Object object, OAVAttributeType attribute) { assert nocheck || checkTypeHasAttribute(object, attribute); assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.MULTIPLICITIES_MAPS); assert nocheck || checkValidStateObject(object); assert object instanceof OAVExternalObjectId; Map theobject = getObject(object); Object ret = theobject.get(attribute); if(ret==null && !theobject.containsKey(attribute)) ret = attribute.getDefaultValue(); return ((Map)ret).keySet(); } /** * Get an attribute value of an object. Method only applicable for * map attribute type. * @param object 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 object, OAVAttributeType attribute, Object key) { assert nocheck || checkTypeHasAttribute(object, attribute); assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.MULTIPLICITIES_MAPS); assert nocheck || checkValidStateObject(object); assert object instanceof OAVExternalObjectId; Map theobject = getObject(object); 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); Object ret = map==null? null: map.get(key); assert ret==null || ret instanceof OAVExternalObjectId; return ret; } /** * Test if a key is contained in the map attribute. * @param object The identifier of the object. * @param attribute The attribute identifier. * @param key The key. * @return True if key is available. */ public boolean containsKey(Object object, OAVAttributeType attribute, Object key) { assert nocheck || checkTypeHasAttribute(object, attribute); assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.MULTIPLICITIES_MAPS); assert object instanceof OAVExternalObjectId; Map theobject = getObject(object); Map map = (Map)theobject.get(attribute); return map==null? false: map.containsKey(key); } /** * 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); 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 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 addAttributeValue(Object object, OAVAttributeType attribute, Object value) { assert nocheck || checkTypeHasAttribute(object, attribute); assert nocheck || checkMultiplicity(object, attribute, OAVAttributeType.MULTIPLICITIES_MULT); assert nocheck || checkValueCompatibility(object, attribute, value); assert nocheck || checkValidStateObject(object); assert object instanceof OAVExternalObjectId; // System.out.println("Add attribute value: "+object+" "+attribute+" "+value); Map theobject = getObject(object); 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); // if(isNonValue(key)) // addObjectUsage(object, attribute, key); if(isJavaNonValue(key)) addJavaObjectUsage(object, attribute, key); } // if(isNonValue(value)) // addObjectUsage(object, attribute, value); if(isJavaNonValue(value)) addJavaObjectUsage(object, attribute, value); eventhandler.objectModified(((OAVExternalObjectId)object).getInternalId().getPhantomExternalId(), getType(object), 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 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, OAVAttributeType.MULTIPLICITIES_MULT); assert nocheck || checkValidStateObject(object); assert object instanceof OAVExternalObjectId; Map theobject = getObject(object); Object tmp = theobject.get(attribute); if(tmp==null) throw new RuntimeException("Value not contained in attribute: " +object+" "+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)) { value = next; break; } } if(!coll.remove(value)) throw new RuntimeException("Value not contained in attribute: " +object+" "+attribute+" "+value); if(coll.isEmpty()) theobject.remove(attribute); } 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: " +object+" "+attribute+" "+value); // if(isNonValue(key)) // removeObjectUsage(object, attribute, key, null); if(isJavaNonValue(key)) removeJavaObjectUsage(object, attribute, key); } // if(isNonValue(value)) // removeObjectUsage(object, attribute, value, null); if(isJavaNonValue(value)) removeJavaObjectUsage(object, attribute, value); eventhandler.objectModified(((OAVExternalObjectId)object).getInternalId().getPhantomExternalId(), getType(object), attribute, value, null); } /** * 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. */ 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(); } /** * Expunge stale objects. */ public void expungeStaleObjects() { WeakEntry entry; while((entry = (WeakEntry)queue.poll()) !=null) { OAVInternalObjectId id = (OAVInternalObjectId)entry.getArgument(); eventhandler.objectRemoved(id.getPhantomExternalId(), getType(id.getPhantomExternalId())); objects.remove(id); types.remove(id); // System.out.println("Removed: "+id); } } /** * Add an external usage of a state object. 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 void addExternalObjectUsage(Object id, Object external) { // nothing to do. } /** * Remove an external usage of a state object. 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 void removeExternalObjectUsage(Object id, Object external) { // nothing to do. } /** * 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; } /** * 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; } /** * 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(", rootobjects="); ret.append(rootobjects); ret.append(", all objects="); ret.append(objects); 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) { Map ret = (Map)objects.get(id); if(ret==null) throw new IllegalArgumentException("Object "+id+" does not exist."); 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 object, OAVAttributeType attribute, Object value) { if(value!=null) { OAVObjectType atype = attribute.getType(); if(atype instanceof OAVJavaType) { if(!tmodel.getJavaType(value.getClass()).isSubtype(atype)) throw new RuntimeException("Value not of suitable type: "+object+" "+attribute+" "+value); } else if(!getType(value).isSubtype(atype)) { throw new RuntimeException("Value not of suitable type: "+object+" "+attribute+" "+value); } } return true; } /** * Ensure that a type has an attribute. * @param object The object. * @param attribute The attribute. * @throws RuntimeException if value is not allowed. */ protected boolean checkTypeHasAttribute(Object object, OAVAttributeType attribute) { if(attribute==null) throw new IllegalArgumentException("Attribute must not null."); OAVObjectType type = attribute.getObjectType() instanceof OAVJavaType ? tmodel.getJavaType(object.getClass()) : (OAVObjectType)types.get(object); if(type==null) throw new RuntimeException("Unknown object type of: "+object); OAVAttributeType attr = type.getAttributeType(attribute.getName()); if(!attribute.equals(attr)) throw new RuntimeException("Attribute must belong to object type: "+attribute+", "+type); return true; } /** * Ensure that multiplicity is ok. * @param object The object. * @param attribute The attribute. * @param multiplicity The multiplicity. * @throws RuntimeException if value is not allowed. */ protected boolean checkMultiplicity(Object object, OAVAttributeType attribute, Set allowedmults) { if(attribute==null) throw new IllegalArgumentException("Attribute must not null."); if(!allowedmults.contains(attribute.getMultiplicity())) throw new RuntimeException("Multiplicity violation: "+object+" "+attribute +" "+allowedmults+" "+attribute.getMultiplicity()); return true; } /** * Ensure that multiplicity is ok. * @param object The object. * @param attribute The attribute. * @param multiplicity The multiplicity. * @throws RuntimeException if value is not allowed. */ protected boolean checkMultiplicity(Object object, OAVAttributeType attribute, String allowedmult) { if(attribute==null) throw new IllegalArgumentException("Attribute must not null."); if(!allowedmult.equals(attribute.getMultiplicity())) throw new RuntimeException("Multiplicity violation: "+object+" "+attribute +" "+allowedmult+" "+attribute.getMultiplicity()); return true; } /** * Test if a type is defined in one of the models. * @param type The type. * @return True, if is defined. */ protected boolean checkTypeDefined(OAVObjectType type) { if(type==null) throw new IllegalArgumentException("Type must not null."); if(type instanceof OAVJavaType) throw new IllegalArgumentException("Type must not be Java type: "+type); if(tmodel==null) throw new RuntimeException("Type model undefined for state: "+this); if(!tmodel.contains(type)) throw new RuntimeException("Type undefined: "+type); return true; } /** * Test if the object is a valid state object, meaning * that is either a root object or a non-root object with * at least one usage. * @param object The object. * @return True, if valid. */ protected boolean checkValidStateObject(Object object) { return true; // if(object==null) // throw new IllegalArgumentException("Object must not null."); // return rootobjects.contains(object) || objectusages.get(object)!=null; } /** * When it is a Java object, it is not created in state, * so we have to notify object addition to listeners on first usage. */ protected void addJavaObjectUsage(Object whichid, OAVAttributeType whichattr, Object value) { // System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+id); // Set would be better Map usages = (Map)objectusages.get(value); if(usages==null) { usages = new HashMap(); objectusages.put(value, usages); OAVJavaType java_type = tmodel.getJavaType(value.getClass()); if(OAVJavaType.KIND_BEAN.equals(java_type.getKind())) registerValue(java_type, value); if(!rootobjects.contains(value)) eventhandler.objectAdded(value, java_type, false); } // Add a new reference for (objectid, attribute) if(whichid!=null) { OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr); Integer cnt = (Integer)usages.get(ref); if(cnt!=null && whichattr.getMultiplicity().equals(OAVAttributeType.NONE)) throw new RuntimeException("Object already there: "+value+" "+whichid+" "+whichattr); if(cnt==null) cnt = new Integer(1); else cnt = new Integer(cnt.intValue()+1); usages.put(ref, cnt); } } /** * Remove an object usage. * @param whichid The object that references the object. * @param whichattr The attribute which references the object. * @param value The object id/value to remove. * @param dropset Already dropped objects in recursive drop (or null if none). */ protected void removeJavaObjectUsage(Object whichid, OAVAttributeType whichattr, Object value) { // System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+id); Map usages = getJavaObjectUsages(value); if(usages==null) throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value); OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr); Integer cnt = (Integer)usages.get(ref); if(cnt==null) throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value); if(cnt.intValue()==1) usages.remove(ref); else usages.put(ref, new Integer(cnt.intValue()-1)); // If this was the last reference to the object and it is // not a root object clean it up if(usages.size()==0) { objectusages.remove(value); OAVJavaType java_type = tmodel.getJavaType(value.getClass()); if(OAVJavaType.KIND_BEAN.equals(java_type.getKind())) deregisterValue(value); eventhandler.objectRemoved(value, java_type/*, null*/); } } /** * Add an object usage. For creation this method can be called with (id, null, null). * For each occurrence of an object in a multi attribute a separate reference is added. * @param whichid The object that references the object. * @param whichattr The attribute which references the object. * @param value The value (id of the referenced object). * / protected void addObjectUsage(Object whichid, OAVAttributeType whichattr, Object value) { // System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+id); // Set would be better Map usages = (Map)objectusages.get(value); if(usages==null) { usages = new HashMap(); objectusages.put(value, usages); // When it is a Java object, it is not created in state, // so we have to notify object addition to listeners on first usage. if(whichattr.getType() instanceof OAVJavaType) { OAVJavaType java_type = tmodel.getJavaType(value.getClass()); if(OAVJavaType.KIND_BEAN.equals(java_type.getKind())) registerValue(java_type, value); eventhandler.objectAdded(value, java_type); } // Add a object created event when non-root object is used first time. else if(!rootobjects.contains(value)) { eventhandler.objectAdded(value, getType(value)); } } // Add a new reference for (objectid, attribute) if(whichid!=null) { ObjectUsage ref = new ObjectUsage(whichid, whichattr); Integer cnt = (Integer)usages.get(ref); if(cnt!=null && whichattr.getMultiplicity().equals(OAVAttributeType.NONE)) throw new RuntimeException("Object already there: "+value+" "+whichid+" "+whichattr); if(cnt==null) cnt = new Integer(1); else cnt = new Integer(cnt.intValue()+1); usages.put(ref, cnt); } }*/ /** * Remove an object usage. * @param whichid The object that references the object. * @param whichattr The attribute which references the object. * @param value The object id/value to remove. * @param dropset Already dropped objects in recursive drop (or null if none). * / protected void removeObjectUsage(Object whichid, OAVAttributeType whichattr, Object value, Set dropset) { // System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+id); Map usages = getObjectUsages(value); if(usages==null) throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value); ObjectUsage ref = new ObjectUsage(whichid, whichattr); Integer cnt = (Integer)usages.get(ref); if(cnt==null) throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value); if(cnt.intValue()==1) usages.remove(ref); else usages.put(ref, new Integer(cnt.intValue()-1)); // If this was the last reference to the object and it is // not a root object clean it up if(usages.size()==0) { objectusages.remove(value); if(containsObject(value) && !rootobjects.contains(value) && (dropset==null || !dropset.contains(value))) { // System.out.println("Garbage collecting unreferenced object: "+id); // Thread.dumpStack(); if(getInternalId(value).isClear()) { System.out.println("Could immediately drop: "+value); internalDropObject(value, dropset); } // else // { // System.out.println("Could not immediately drop: "+value); // } } // When it is a Java object, it is not dropped from state, // so we have to notify object removal to listeners on last usage. else if(whichattr.getType() instanceof OAVJavaType) { OAVJavaType java_type = tmodel.getJavaType(value.getClass()); if(OAVJavaType.KIND_BEAN.equals(java_type.getKind())) deregisterValue(value); eventhandler.objectRemoved(value, java_type, null); } } }*/ /** * Get all object usages. * @return The usages for an object. */ protected Map getJavaObjectUsages(Object id) { return (Map)objectusages.get(id); } /** * Register a value for observation. * If its an expression then add the action, * if its a bean then add the property listener. */ protected void registerValue(final OAVJavaType type, Object value) { if(value!=null) { // System.out.println("register: "+value); if(pcls==null) pcls = new IdentityHashMap(); // values may change, therefore identity hash map PropertyChangeListener pcl = (PropertyChangeListener)pcls.get(value); if(pcl==null) { pcl = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { OAVAttributeType attr = type.getAttributeType(evt.getPropertyName()); eventhandler.beanModified(evt.getSource(), type, attr, evt.getOldValue(), evt.getNewValue()); } }; pcls.put(value, pcl); } // Invoke addPropertyChangeListener on value try { // Do not use Class.getMethod (slow). Method meth = SReflect.getMethod(value.getClass(), "addPropertyChangeListener", PCL); if(meth!=null) meth.invoke(value, new Object[]{pcl}); } catch(IllegalAccessException e){} catch(InvocationTargetException e){} } } /** * Deregister a value for observation. * If its an expression then clear the action, * if its a bean then remove the property listener. */ protected void deregisterValue(Object value) { if(value!=null) { // System.out.println("deregister: "+value); // Stop listening for bean events. try { if(pcls!=null) { PropertyChangeListener pcl = (PropertyChangeListener)pcls.get(value); if(pcl!=null) { // Do not use Class.getMethod (slow). Method meth = SReflect.getMethod(value.getClass(), "removePropertyChangeListener", PCL); if(meth!=null) meth.invoke(value, new Object[]{pcl}); } } } catch(IllegalAccessException e){} catch(InvocationTargetException e){} } } /** * Get the internal object id. * @param id The id. * @return The internal id. */ protected OAVInternalObjectId getInternalId(Object id) { OAVInternalObjectId ret; if(id instanceof OAVInternalObjectId) ret = (OAVInternalObjectId)id; else ret = ((OAVExternalObjectId)id).getInternalId(); return ret; } //-------- nested states -------- /** * Add a substate. * Read accesses will be transparently mapped to substates. * Write accesses to substates need not be supported and * may generate UnsupportedOperationException. */ public void addSubstate(IOAVState substate) { throw new UnsupportedOperationException("todo: substates for weak state"); } /** * Get the substates. */ public IOAVState[] getSubstates() { return null; } //-------- identity vs. equality -------- /** * Flag indicating that java objects are * stored by identity instead of equality. */ public boolean isJavaIdentity() { return false; } /** * Test if two values are equal * according to current identity/equality * settings. */ public boolean equals(Object a, Object b) { return SUtil.equals(a, b); } } // #endif