All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jadex.rules.state.javaimpl.OAVWeakState Maven / Gradle / Ivy

package jadex.rules.state.javaimpl;

import java.lang.ref.ReferenceQueue;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

// #ifndef MIDP
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.beans.PropertyChangeEvent;
import jadex.commons.beans.PropertyChangeListener;
import jadex.commons.collection.WeakEntry;
import jadex.commons.concurrent.ISynchronizator;
import jadex.rules.state.IOAVState;
import jadex.rules.state.IOAVStateListener;
import jadex.rules.state.IProfiler;
import jadex.rules.state.OAVAttributeType;
import jadex.rules.state.OAVJavaType;
import jadex.rules.state.OAVObjectType;
import jadex.rules.state.OAVTypeModel;
import jadex.rules.state.javaimpl.OAVWeakIdGenerator.OAVExternalObjectId;
import jadex.rules.state.javaimpl.OAVWeakIdGenerator.OAVInternalObjectId;


/**
 *  An object holding the state as
 *  OAV triples (object, attribute, value).
 */
public class OAVWeakState	implements IOAVState
{
	//-------- constants --------
	
	/** The argument types for property change listener adding/removal (cached for speed). */
	protected static Class[]	PCL	= new Class[]{PropertyChangeListener.class};
	
	//-------- attributes --------
	
	/** The type models. */
	protected OAVTypeModel tmodel;
	
	/** The objects table (id -> map). */
	protected Map objects;
	
	/** The object types (object -> type). */
	protected Map types;
	
	/** The id generator. */
	protected IOAVIdGenerator generator;
	
	/** The flag to disable type checking. */
	protected boolean nocheck;
	
	/** The usages of object ids (object id -> usages). */
	protected Map objectusages;
	
	/** The root objects (will not be cleaned up when usages==0). */
	protected Set rootobjects;
	
	/** The Java beans property change listeners. */
	protected Map pcls;
	
	/** The OAV event handler. */
	protected OAVEventHandler eventhandler;
	
	/** The reference queue for stale objects. */
	protected ReferenceQueue queue;
	
	/** The synchronizator (if any). */
	protected ISynchronizator synchronizator;
	
	/** The profiler. */
	// Hack???
	protected IProfiler	profiler = new IProfiler()
	{
		public void	start(String type, Object item)
		{
		}

		public void	stop(String type, Object item)
		{
		}
		
		public ProfilingInfo[] getProfilingInfos(int start)
		{
			return new ProfilingInfo[0];
		}
	};
	
	//-------- constructors --------
	
	/**
	 *  Create a new empty OAV state representation.
	 */
	public OAVWeakState(OAVTypeModel tmodel)
	{
		this.tmodel = tmodel;
		this.objects	= new LinkedHashMap();
		this.types = new LinkedHashMap();
		this.queue = new ReferenceQueue();
		this.generator = new OAVWeakIdGenerator(queue);
//		this.generator = new OAVNameIdGenerator();
//		this.generator = new OAVLongIdGenerator();
		this.objectusages = new LinkedHashMap();
//		this.objectusages = new IdentityHashMap();
		this.rootobjects = new LinkedHashSet();
		this.eventhandler	= new OAVEventHandler(this); 
//		this.nocheck = true;
	}
	
	/**
	 *  Dispose the state.
	 */
	public void dispose()
	{
		Object[]	roots	= rootobjects.toArray();
		for(int i=0; i type check via OAVObjectType
	 *  b) a normal Java object -> type check via OAVJavaType
	 *  Additionally multiplicity is checked.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkValueCompatibility(Object object, 
		OAVAttributeType attribute, Object value)
	{
		if(value!=null)
		{
			OAVObjectType	atype	= attribute.getType();
			if(atype instanceof OAVJavaType)
			{
				if(!tmodel.getJavaType(value.getClass()).isSubtype(atype))
					throw new RuntimeException("Value not of suitable type: "+object+" "+attribute+" "+value);
			}
			else if(!getType(value).isSubtype(atype))
			{
				throw new RuntimeException("Value not of suitable type: "+object+" "+attribute+" "+value);
			}
		}
		return true;
	}
	
	/**
	 *  Ensure that a type has an attribute.
	 *  @param object The object.
	 *  @param attribute The attribute.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkTypeHasAttribute(Object object, OAVAttributeType attribute)
	{
		if(attribute==null)
			throw new IllegalArgumentException("Attribute must not null.");
		
		OAVObjectType type = attribute.getObjectType() instanceof OAVJavaType
			? tmodel.getJavaType(object.getClass())	: (OAVObjectType)types.get(object);
		if(type==null)
			throw new RuntimeException("Unknown object type of: "+object);
		OAVAttributeType attr	= type.getAttributeType(attribute.getName());
		if(!attribute.equals(attr))
			throw new RuntimeException("Attribute must belong to object type: "+attribute+", "+type);
		
		return true;
	}
	
	/**
	 *  Ensure that multiplicity is ok.
	 *  @param object The object.
	 *  @param attribute The attribute.
	 *  @param multiplicity The multiplicity.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkMultiplicity(Object object, OAVAttributeType attribute, Set allowedmults)
	{
		if(attribute==null)
			throw new IllegalArgumentException("Attribute must not null.");
		if(!allowedmults.contains(attribute.getMultiplicity()))
			throw new RuntimeException("Multiplicity violation: "+object+" "+attribute
				+" "+allowedmults+" "+attribute.getMultiplicity());

		return true;
	}
	
	/**
	 *  Ensure that multiplicity is ok.
	 *  @param object The object.
	 *  @param attribute The attribute.
	 *  @param multiplicity The multiplicity.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkMultiplicity(Object object, OAVAttributeType attribute, String allowedmult)
	{
		if(attribute==null)
			throw new IllegalArgumentException("Attribute must not null.");
		if(!allowedmult.equals(attribute.getMultiplicity()))
			throw new RuntimeException("Multiplicity violation: "+object+" "+attribute
				+" "+allowedmult+" "+attribute.getMultiplicity());

		return true;
	}
	
	/**
	 *  Test if a type is defined in one of the models.
	 *  @param type The type.
	 *  @return True, if is defined.
	 */
	protected boolean checkTypeDefined(OAVObjectType type)
	{
		if(type==null)
			throw new IllegalArgumentException("Type must not null.");
		if(type instanceof OAVJavaType)
			throw new IllegalArgumentException("Type must not be Java type: "+type);
		
		if(tmodel==null)
			throw new RuntimeException("Type model undefined for state: "+this);
		if(!tmodel.contains(type))
			throw new RuntimeException("Type undefined: "+type);
		
		return true;
	}
	
	/**
	 *  Test if the object is a valid state object, meaning
	 *  that is either a root object or a non-root object with
	 *  at least one usage.
	 *  @param object The object.
	 *  @return True, if valid.
	 */
	protected boolean checkValidStateObject(Object object)
	{
		return true;
		
//		if(object==null)
//			throw new IllegalArgumentException("Object must not null.");
//		return rootobjects.contains(object) || objectusages.get(object)!=null;
	}
	
	/**
	 *  When it is a Java object, it is not created in state,
	 *  so we have to notify object addition to listeners on first usage.
	 */
	protected void addJavaObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
	{
//		System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+id);

		// Set would be better
		Map usages = (Map)objectusages.get(value);
		if(usages==null)
		{
			usages = new HashMap();
			objectusages.put(value, usages);
			
			OAVJavaType	java_type = tmodel.getJavaType(value.getClass());
				
			if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
				registerValue(java_type, value);
			
			if(!rootobjects.contains(value))
				eventhandler.objectAdded(value, java_type, false);
		}
		
		// Add a new reference for (objectid, attribute)
		if(whichid!=null)
		{
			OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr);
			Integer cnt = (Integer)usages.get(ref);
			if(cnt!=null && whichattr.getMultiplicity().equals(OAVAttributeType.NONE))
				throw new RuntimeException("Object already there: "+value+" "+whichid+" "+whichattr);
			if(cnt==null)
				cnt = Integer.valueOf(1);
			else
				cnt = Integer.valueOf(cnt.intValue()+1);
			usages.put(ref, cnt);
		}
	}
	
	/**
	 *  Remove an object usage.
	 *  @param whichid The object that references the object.
	 *  @param whichattr The attribute which references the object.
	 *  @param value The object id/value to remove.
	 *  @param dropset	Already dropped objects in recursive drop (or null if none).
	 */
	protected void removeJavaObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
	{
//		System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+id);

		Map usages = getJavaObjectUsages(value);
		if(usages==null)
			throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
		OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr);
		Integer cnt = (Integer)usages.get(ref);
		if(cnt==null)
			throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
	
		if(cnt.intValue()==1)
			usages.remove(ref);
		else
			usages.put(ref, Integer.valueOf(cnt.intValue()-1));
		
		// If this was the last reference to the object and it is 
		// not a root object clean it up
		if(usages.size()==0)
		{
			objectusages.remove(value);
			
			OAVJavaType	java_type = tmodel.getJavaType(value.getClass());
			
			if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
				deregisterValue(value);
			
			eventhandler.objectRemoved(value, java_type/*, null*/);
		}
	}
	
	/**
	 *  Add an object usage. For creation this method can be called with (id, null, null).
	 *  For each occurrence of an object in a multi attribute a separate reference is added.
	 *  @param whichid The object that references the object.
	 *  @param whichattr The attribute which references the object.
	 *  @param value The value (id of the referenced object).
	 * /
	protected void addObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
	{
//		System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+id);

		// Set would be better
		Map usages = (Map)objectusages.get(value);
		if(usages==null)
		{
			usages = new HashMap();
			objectusages.put(value, usages);
			
			// When it is a Java object, it is not created in state,
			// so we have to notify object addition to listeners on first usage.
			if(whichattr.getType() instanceof OAVJavaType)
			{
				OAVJavaType	java_type = tmodel.getJavaType(value.getClass());
				
				if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
					registerValue(java_type, value);
				
				eventhandler.objectAdded(value, java_type);
			}
			
			// Add a object created event when non-root object is used first time.
			else if(!rootobjects.contains(value))
			{
				eventhandler.objectAdded(value, getType(value));
			}
		}
		
		// Add a new reference for (objectid, attribute)
		if(whichid!=null)
		{
			ObjectUsage ref = new ObjectUsage(whichid, whichattr);
			Integer cnt = (Integer)usages.get(ref);
			if(cnt!=null && whichattr.getMultiplicity().equals(OAVAttributeType.NONE))
				throw new RuntimeException("Object already there: "+value+" "+whichid+" "+whichattr);
			if(cnt==null)
				cnt = Integer.valueOf(1);
			else
				cnt = Integer.valueOf(cnt.intValue()+1);
			usages.put(ref, cnt);
		}
	}*/
	
	/**
	 *  Remove an object usage.
	 *  @param whichid The object that references the object.
	 *  @param whichattr The attribute which references the object.
	 *  @param value The object id/value to remove.
	 *  @param dropset	Already dropped objects in recursive drop (or null if none).
	 * /
	protected void removeObjectUsage(Object whichid, OAVAttributeType whichattr, Object value, Set dropset)
	{
//		System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+id);

		Map usages = getObjectUsages(value);
		if(usages==null)
			throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
		ObjectUsage ref = new ObjectUsage(whichid, whichattr);
		Integer cnt = (Integer)usages.get(ref);
		if(cnt==null)
			throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
	
		if(cnt.intValue()==1)
			usages.remove(ref);
		else
			usages.put(ref, Integer.valueOf(cnt.intValue()-1));
		
		// If this was the last reference to the object and it is 
		// not a root object clean it up
		if(usages.size()==0)
		{
			objectusages.remove(value);
			if(containsObject(value) && !rootobjects.contains(value) && (dropset==null || !dropset.contains(value)))
			{
//				System.out.println("Garbage collecting unreferenced object: "+id);
//				Thread.dumpStack();
				if(getInternalId(value).isClear())
				{
					System.out.println("Could immediately drop: "+value);
					internalDropObject(value, dropset);
				}
//				else
//				{
//					System.out.println("Could not immediately drop: "+value);
//				}
			}
			
			// When it is a Java object, it is not dropped from state,
			// so we have to notify object removal to listeners on last usage.
			else if(whichattr.getType() instanceof OAVJavaType)
			{
				OAVJavaType	java_type = tmodel.getJavaType(value.getClass());
				
				if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
					deregisterValue(value);
				
				eventhandler.objectRemoved(value, java_type, null);
			}
		}
	}*/
	
	/**
	 *  Get all object usages.
	 *  @return The usages for an object.
	 */
	protected Map getJavaObjectUsages(Object id)
	{
		return (Map)objectusages.get(id);
	}
	
	/**  
	 *  Register a value for observation.
	 *  If its an expression then add the action,
	 *  if its a bean then add the property listener.
	 */
	protected void	registerValue(final OAVJavaType type, Object value)
	{
		if(value!=null)
		{
//			System.out.println("register: "+value);
		
			if(pcls==null)
			{
				pcls = new IdentityHashMap(); // values may change, therefore identity hash map
			}
			PropertyChangeListener pcl = (PropertyChangeListener)pcls.get(value);
			
			if(pcl==null)
			{
				pcl = new PropertyChangeListener()
				{
					public void propertyChange(PropertyChangeEvent evt)
					{
						OAVAttributeType attr = type.getAttributeType(evt.getPropertyName());
						eventhandler.beanModified(evt.getSource(), type, attr, evt.getOldValue(), evt.getNewValue()); 
					}
				};
				pcls.put(value, pcl);
			}
			
			// Invoke addPropertyChangeListener on value
			try
			{
				// Do not use Class.getMethod (slow).
				Method	meth	= SReflect.getMethod(value.getClass(), "addPropertyChangeListener", PCL);
				if(meth!=null)
				{
					meth.invoke(value, new Object[]{pcl});
				}
			}
			catch(IllegalAccessException e)
			{
				System.err.println("Cannot add property change listener to OAV java bean: "+e);
			}
			catch(InvocationTargetException e)
			{
				System.err.println("Cannot add property change listener to OAV java bean: "+e);				
			}
		}
	}

	/**
	 *  Deregister a value for observation.
	 *  If its an expression then clear the action,
	 *  if its a bean then remove the property listener.
	 */
	protected void	deregisterValue(Object value)
	{
		if(value!=null)
		{
//			System.out.println("deregister: "+value);
			// Stop listening for bean events.
			if(pcls!=null)
			{
				try
				{
					PropertyChangeListener pcl = (PropertyChangeListener)pcls.remove(value);
					if(pcl!=null)
					{
						// Do not use Class.getMethod (slow).
						Method	meth	= SReflect.getMethod(value.getClass(), "removePropertyChangeListener", PCL);
						if(meth!=null)
						{
							meth.invoke(value, new Object[]{pcl});
						}
					}
				}
				catch(IllegalAccessException e)
				{
					System.err.println("Cannot remove property change listener from OAV java bean: "+e);
				}
				catch(InvocationTargetException e)
				{
					System.err.println("Cannot remove property change listener from OAV java bean: "+e);				
				}
			}
		}
	}
	
	/**
	 *  Get the internal object id.
	 *  @param id The id.
	 *  @return The internal id. 
	 */
	protected OAVInternalObjectId getInternalId(Object id)
	{
		OAVInternalObjectId ret;
		if(id instanceof OAVInternalObjectId)
			ret = (OAVInternalObjectId)id;
		else
			ret = ((OAVExternalObjectId)id).getInternalId();
		return ret;
	}

	//-------- nested states --------
	
	/**
	 *  Add a substate.
	 *  Read accesses will be transparently mapped to substates.
	 *  Write accesses to substates need not be supported and
	 *  may generate UnsupportedOperationException.
	 */
	public void addSubstate(IOAVState substate)
	{
		throw new UnsupportedOperationException("todo: substates for weak state");
	}

	/**
	 *  Get the substates.
	 */
	public IOAVState[] getSubstates()
	{
		return null;
	}

	//-------- identity vs. equality --------
	
	/**
	 *  Flag indicating that java objects are
	 *  stored by identity instead of equality.  
	 */
	public boolean	isJavaIdentity()
	{
		return false;
	}
	
	/**
	 *  Test if two values are equal
	 *  according to current identity/equality
	 *  settings. 
	 */
	public boolean	equals(Object a, Object b)
	{
		return SUtil.equals(a, b);
	}
}
// #endif




© 2015 - 2025 Weber Informatics LLC | Privacy Policy