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

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

package jadex.rules.state.javaimpl;

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

import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.Tuple;
import jadex.commons.beans.PropertyChangeEvent;
import jadex.commons.beans.PropertyChangeListener;
import jadex.commons.collection.IdentityHashSet;
import jadex.commons.concurrent.ISynchronizator;
import jadex.rules.state.IOAVState;
import jadex.rules.state.IOAVStateListener;
import jadex.rules.state.IProfiler;
import jadex.rules.state.OAVAttributeType;
import jadex.rules.state.OAVJavaType;
import jadex.rules.state.OAVObjectType;
import jadex.rules.state.OAVTypeModel;

/**
 *  An object holding the state as
 *  OAV triples (object, attribute, value).
 */
public abstract class OAVAbstractState	implements IOAVState
{
	// #ifndef MIDP
	//-------- constants --------
	
	/** The argument types for property change listener adding/removal (cached for speed). */
	protected static final Class[]	PCL	= new Class[]{PropertyChangeListener.class};

	// #endif

	/** The type identifier. */
	protected static final String TYPE = ":::INTERNAL_TYPE";
	
	//-------- attributes --------
	
	/** The type models. */
	protected OAVTypeModel tmodel;
	
	/** The root objects (will not be cleaned up when usages==0) (oids + java objects). */
	protected Set rootobjects;
	
	/** The objects table (oid -> content map). */
//	protected Map objects;
	
	/** The deleted objects (only available in event notifications) (oid -> content map). */
	protected Map deletedobjects;
	
	/** The java objects set. */
	protected Set javaobjects;
	
	/** The id generator. */
	protected IOAVIdGenerator generator;
	
	/** The flag to disable type checking. */
	protected boolean nocheck;
	
	/** The usages of object ids (object id -> usages[map] (objectusage -> cnt)). */
	protected Map objectusages;
	
	/** The Java beans property change listeners. */
	protected Map pcls;
	
	/** The OAV event handler. */
	protected OAVEventHandler	eventhandler;
	
	/**	List of substates (if any). */
	protected IOAVState[]	substates;
	
	/** The synchronizator (if any). */
	protected ISynchronizator synchronizator;
	
	/** Counter for number of registered bean listeners. */
	protected int beanlistenercnt;
	
	/** Flag to enable identity handling of java objects (instead of equality). */
	protected boolean javaidentity;
	
	// #ifndef MIDP
	/** The profiler. */
	// Hack???
	protected IProfiler	profiler = new IProfiler()
	{
		public void	start(String type, Object item)
		{
		}

		public void	stop(String type, Object item)
		{
		}
		
		public ProfilingInfo[] getProfilingInfos(int start)
		{
			return new ProfilingInfo[0];
		}
	};
	// #endif
	
	//-------- constructors --------
	
	/**
	 *  Create a new empty OAV state representation.
	 */
	public OAVAbstractState(OAVTypeModel tmodel)
	{
		this.tmodel = tmodel;
		this.javaidentity = true;
		
		// OID data structures
		this.deletedobjects	= new LinkedHashMap();
//		this.objects	= new LinkedHashMap();
//		this.objects	= new CheckedMap(new LinkedHashMap());
		
		// Java object data structures (todo: repeatability/ordering for identity map)
		this.javaobjects	= javaidentity ? (Set)new IdentityHashSet() : new LinkedHashSet();

		// Mixed data structures (oids + java objects)  (todo: repeatability/ordering for identity map)
		this.objectusages = javaidentity ? (Map)new IdentityHashMap() : new LinkedHashMap();
		this.rootobjects = javaidentity ? (Set)new IdentityHashSet() : new LinkedHashSet();

		this.eventhandler	= new OAVEventHandler(this); 

		this.generator = createIdGenerator();
				
//		this.nocheck = true;
	}
	
	/**
	 *  Create an id generator.
	 *  @return The id generator.
	 */
	public IOAVIdGenerator createIdGenerator()
	{
		return new OAVDebugIdGenerator();
		
//		return new OAVNameIdGenerator();
//		return new OAVLongIdGenerator();
//		return new OAVObjectIdGenerator();
	}
	
	/**
	 *  Dispose the state.
	 */
	public void dispose()
	{
		// Drop root objects for clean disposal.
		Object[]	roots	= rootobjects.toArray();
		for(int i=0; i recursively check reachability 
					if(ref instanceof OAVObjectUsage)
					{
						OAVObjectUsage	usage	= (OAVObjectUsage)ref;
						if(!tested.contains(usage.getObject()))
							ret	= isReachable(usage.getObject(), tested);
					}

					// External reference -> reachability is true 
					else
					{
						ret	= true;
					}
				}
			}
		}
		return ret;
	}
	
	/**
	 *  Find a cycle in a given set of objects (oids).
	 */
	public List	findCycle(Collection oids)
	{
		List	cycle	= null;
		Set	checked	= new HashSet();
		
		// Find cycles starting from each object.
		for(Iterator it=oids.iterator(); cycle==null && it.hasNext(); )
		{
			Object	id	= it.next();
			// #ifndef MIDP
			assert nocheck || generator.isId(id);
			// #endif

			// Do not check again, if object was already in subgraph of other object.
			if(!checked.contains(id))
			{
				Map	edges	= new HashMap();	// Back references.
				edges.put(id, id);	// Root object of subgraph has no back reference. Cyclic edge simplifies algorithm, when cycle.length==1.
	
				// Iteratively check all objects connected to the current object.
				List	subgraph	= new ArrayList();
				subgraph.add(id);
				checked.add(id);
				for(int i=0; cycle==null && i type check via OAVObjectType
	 *  b) a normal Java object -> type check via OAVJavaType
	 *  Additionally multiplicity is checked.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkValueCompatibility(Object id, 
		OAVAttributeType attribute, Object value)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(id);
		// #endif

		if(value!=null)
		{
			OAVObjectType	atype	= attribute.getType();
			if(atype instanceof OAVJavaType)
			{
				if(!tmodel.getJavaType(value.getClass()).isSubtype(atype))
					throw new RuntimeException("Value not of suitable type: "+id+" "+attribute+" "+value);
			}
			else if(!getType(value).isSubtype(atype))
			{
				throw new RuntimeException("Value not of suitable type: "+id+" "+attribute+" "+value);
			}
		}
		return true;
	}
	
	/**
	 *  Ensure that a type has an attribute.
	 *  @param id The object (oid).
	 *  @param attribute The attribute.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkTypeHasAttribute(Object id, OAVAttributeType attribute)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(id);
		// #endif

		if(attribute==null)
			throw new IllegalArgumentException("Attribute must not null.");
		
		OAVObjectType	type	= attribute.getObjectType() instanceof OAVJavaType
			? tmodel.getJavaType(id.getClass())	: getType(id);
		OAVAttributeType attr	= type.getAttributeType(attribute.getName());
		if(!attribute.equals(attr))
			throw new RuntimeException("Attribute must belong to object type: "+attribute+", "+type);
		
		return true;
	}
	
	/**
	 *  Ensure that multiplicity is ok.
	 *  @param id The object (oid).
	 *  @param attribute The attribute.
	 *  @param multiplicity The multiplicity.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkMultiplicity(Object id, OAVAttributeType attribute, Set allowedmults)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(id);
		// #endif

		if(attribute==null)
			throw new IllegalArgumentException("Attribute must not null.");
		if(!allowedmults.contains(attribute.getMultiplicity()))
			throw new RuntimeException("Multiplicity violation: "+id+" "+attribute
				+" "+allowedmults+" "+attribute.getMultiplicity());

		return true;
	}
	
	/**
	 *  Ensure that multiplicity is ok.
	 *  @param id The object (oid).
	 *  @param attribute The attribute.
	 *  @param multiplicity The multiplicity.
	 *  @throws RuntimeException if value is not allowed.
	 */
	protected boolean checkMultiplicity(Object id, OAVAttributeType attribute, String allowedmult)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(id);
		// #endif

		if(attribute==null)
			throw new IllegalArgumentException("Attribute must not null.");
		if(!allowedmult.equals(attribute.getMultiplicity()))
			throw new RuntimeException("Multiplicity violation: "+id+" "+attribute
				+" "+allowedmult+" "+attribute.getMultiplicity());

		return true;
	}
	
	/**
	 *  Test if a type is defined in one of the models.
	 *  @param type The type.
	 *  @return True, if is defined.
	 */
	protected boolean checkTypeDefined(OAVObjectType type)
	{
		if(type==null)
			throw new IllegalArgumentException("Type must not null.");
		if(type instanceof OAVJavaType)
			throw new IllegalArgumentException("Type must not be Java type: "+type);
		
		if(tmodel==null)
			throw new RuntimeException("Type model undefined for state: "+this);
		if(!tmodel.contains(type))
			throw new RuntimeException("Type undefined: "+type);
		
		return true;
	}
	
	/**
	 *  Test if the object is a valid state object (oid).
	 *  @param id The object (oid).
	 *  @return True, if valid.
	 */
	protected boolean checkValidStateObject(Object id)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(id);
		// #endif

		return containsObject(id);
	}
	
	/**
	 *  Test if reading the object (oid) is allowed.
	 *  Reading is allowed on removed objects as long as there are external references.
	 *  @param id The object (oid).
	 *  @return True, if valid.
	 */
	protected boolean checkValidStateObjectRead(Object id)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(id);
		// #endif

		return checkValidStateObject(id) || isExternallyUsed(id);
	}
	
	/**
	 *  Test if the object is a valid state value, meaning
	 *  that is either a state object or a java value.
	 *  @param value The value.
	 *  @return True, if valid.
	 */
	protected boolean checkValidStateValue(Object value)
	{
		// No state object (i.e. Java object) or object in state.
		return !generator.isId(value) || checkValidStateObject(value);
	}
	
	/**
	 *  Add an object (oid of java object) usage.
	 *  For each occurrence of an object in a multi attribute a separate reference is added.
	 *  @param whichid The object (oid) that references the value.
	 *  @param whichattr The attribute which references the object.
	 *  @param value The value (id of the referenced object or java object).
	 */
	protected void addObjectUsage(Object whichid, OAVAttributeType whichattr, Object value)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(whichid);
		// #endif

		if(isManaged(value))
		{
//			if((""+whichid).indexOf("goal_")!=-1 || (""+value).indexOf("goal_")!=-1)
//			{
//				System.err.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+value);
//				if(whichattr.getName().equals("parameterelement_has_parameters"))
//					Thread.dumpStack();
//			}

			// Set would be better
			Map usages = (Map)objectusages.get(value);
			boolean	newobject	= usages==null;
			if(newobject)
			{
				usages = new HashMap();
				objectusages.put(value, usages);
			}
		
			// Add a new reference for (objectid, attribute)
			OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr);
			Integer cnt = (Integer)usages.get(ref);
			if(cnt!=null && whichattr.getMultiplicity().equals(OAVAttributeType.NONE))
				throw new RuntimeException("Object already there: "+value+" "+whichid+" "+whichattr);
			if(cnt==null)
				cnt = Integer.valueOf(1);
			else
				cnt = Integer.valueOf(cnt.intValue()+1);
			usages.put(ref, cnt);

			if(newobject && !generator.isId(value))
			{
				// When it is a Java object, it is not created in state,
				// so we have to notify object addition to listeners on first usage.
			
				if(javaobjects.add(value))
				{
//					System.out.println("Creating reference: "+whichid+" "+whichattr.getName()+" "+value);
					OAVJavaType	java_type = tmodel.getJavaType(value.getClass());
					
					if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
						registerValue(java_type, value);
					
					eventhandler.objectAdded(value, java_type, false);
				}
			}			
		}
	}
	
	/**
	 *  Remove an object (oid or java object) usage.
	 *  @param whichid The object that references the value.
	 *  @param whichattr The attribute which references the value.
	 *  @param value The object id/java value to remove.
	 *  @param dropset	Already dropped objects in recursive drop (or null if none).
	 *  @param keepalive	A flag indicating that at least one object in the path is externally referenced
	 *    (all contained unused objects are set to externally referenced, too).
	 */
	protected void removeObjectUsage(Object whichid, OAVAttributeType whichattr, Object value, Set dropset, boolean keepalive)
	{
		// #ifndef MIDP
		assert nocheck || generator.isId(whichid);
		// #endif

		if(isManaged(value))
		{
//			System.err.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+value);

			// Increase external usage counter, if source object is externally referenced.
			if(keepalive && generator.isId(value))
				addExternalObjectUsage(value, this);

			Map usages = getObjectUsages(value);
			if(usages==null)
				throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
			OAVObjectUsage ref = new OAVObjectUsage(whichid, whichattr);
			Integer cnt = (Integer)usages.get(ref);
			if(cnt==null)
				throw new RuntimeException("Reference not found: "+whichid+" "+whichattr.getName()+" "+value);
		
			if(cnt.intValue()==1)
				usages.remove(ref);
			else
				usages.put(ref, Integer.valueOf(cnt.intValue()-1));
			
			// If this was the last reference to the object and it is 
			// not a root object clean it up
			if(usages.size()==0)
			{
//				if(objects.containsKey(value) && !rootobjects.contains(value) && (dropset==null || !dropset.contains(value)))
				if(internalContainsObject(value) && !rootobjects.contains(value) && (dropset==null || !dropset.contains(value)))
				{
//					System.out.println("Garbage collecting unreferenced object: "+value);
//					Thread.dumpStack();
					internalDropObject(value, dropset, keepalive);
				}
				
				// When it is a Java object, it is not dropped from state,
				// so we have to notify object removal to listeners on last usage.
				else if(!generator.isId(value) && !rootobjects.contains(value))
				{
					javaobjects.remove(value);
//					System.out.println("Removing reference: "+whichid+" "+whichattr.getName()+" "+value);
					OAVJavaType	java_type	= tmodel.getJavaType(value.getClass());
					
					if(OAVJavaType.KIND_BEAN.equals(java_type.getKind()))
						deregisterValue(java_type, value);
					
					objectusages.remove(value);
					
					eventhandler.objectRemoved(value, java_type);
				}
			}
		}
	}	
	
	
	/**
	 *  Check if a value (oid or java object) is managed by the state.
	 *  Returns true for attribute values which are directly contained oav objects
	 *  or mutable java objects, e.g. not simple values such as strings or intergers. 
	 */
	protected boolean	isManaged(Object value)
	{
		// Value is a directly contained object or java bean/object (i.e. not basic value)
		return value!=null && !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)
			&& (!generator.isId(value) || internalContainsObject(value));
//		return value!=null &&
//				(!generator.isId(value) && !tmodel.getJavaType(value.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)
//					|| internalContainsObject(value));

	}
	
	/**
	 *  Get all object usages.
	 *  @return The usages for an object (oid or java object).
	 */
	protected Map getObjectUsages(Object object)
	{
		return (Map)objectusages.get(object);
	}
	
//	private static int[] cnt	= new int[1];
	
	/**  
	 *  Register a value for observation.
	 *  If its an expression then add the action,
	 *  if its a bean then add the property listener.
	 */
	protected void	registerValue(final OAVJavaType type, Object value)
	{
		// #ifndef MIDP
		assert nocheck || !generator.isId(value) : value;
		// #endif

		// #ifndef MIDP
		if(value!=null)
		{
			if(pcls==null)
				pcls = new IdentityHashMap(); // values may change, therefore identity hash map
			PropertyChangeListener pcl = (PropertyChangeListener)pcls.get(value);
			
			if(pcl==null)
			{
				pcl = new PropertyChangeListener()
				{
					public void propertyChange(final PropertyChangeEvent evt)
					{
						if(synchronizator!=null)
						{
							try
							{
								synchronizator.invokeLater(new Runnable()
								{
									public void run()
									{
										try
										{
											OAVAttributeType attr = type.getAttributeType(evt.getPropertyName());
											eventhandler.beanModified(evt.getSource(), type, attr, evt.getOldValue(), evt.getNewValue());
										}
										catch(Exception e)
										{
											// Todo: use customizable logger supplied from external.
											e.printStackTrace();
										}
									}
								});
							}
							catch(Exception e)
							{
								e.printStackTrace();
								System.out.println("Synchronizer invalid: "+evt+", "+e);
							}
						}
						else
						{
							try
							{
								OAVAttributeType attr = type.getAttributeType(evt.getPropertyName());
								eventhandler.beanModified(evt.getSource(), type, attr, evt.getOldValue(), evt.getNewValue());
							}
							catch(Exception e)
							{
								// Todo: use customizable logger supplied from external.
								e.printStackTrace();
							}
						}
					}
				};
				pcls.put(value, pcl);
			}
			
			// Invoke addPropertyChangeListener on value
			try
			{
				assert nocheck || beanlistenercnt+1==++beanlistenercnt;
//				if(getTypeModel().getName().equals("BlackjackDealer_typemodel") && value.toString().indexOf("GameState")!=-1)
//					Thread.dumpStack();
//				System.out.println(getTypeModel().getName()+": Registered on: "+value);

				// Do not use Class.getMethod (slow).
				Method	meth	= SReflect.getMethod(value.getClass(),
					"addPropertyChangeListener", PCL);
				if(meth!=null)
					meth.invoke(value, new Object[]{pcl});				
			}
			catch(IllegalAccessException e){e.printStackTrace();}
			catch(InvocationTargetException e){e.printStackTrace();}
		}
		// #endif
	}

	/**
	 *  Deregister a value for observation.
	 *  If its an expression then clear the action,
	 *  if its a bean then remove the property listener.
	 */
	protected void	deregisterValue(OAVJavaType type, Object value)
	{
		// #ifndef MIDP
		assert nocheck || !generator.isId(value) : value;
		// #endif

		// #ifndef MIDP
		if(value!=null)
		{
//			synchronized(cnt)
//			{
//				cnt[0]--;
//			}
//			System.out.println("deregister ("+cnt[0]+"): "+value);
			// Stop listening for bean events.
			if(pcls!=null)
			{
				PropertyChangeListener pcl = (PropertyChangeListener)pcls.remove(value);
				if(pcl!=null)
				{
					try
					{
//						System.out.println(getTypeModel().getName()+": Deregister: "+value+", "+type);						
						assert nocheck || beanlistenercnt-1==--beanlistenercnt;
						
						// Do not use Class.getMethod (slow).
						Method	meth	= SReflect.getMethod(value.getClass(),
							"removePropertyChangeListener", PCL);
						if(meth!=null)
							meth.invoke(value, new Object[]{pcl});
					}
					catch(IllegalAccessException e){e.printStackTrace();}
					catch(InvocationTargetException e){e.printStackTrace();}
				}
			}
		}
		// #endif
	}
	
	/**
	 *  Test if an object is externally used.
	 *  @param id The id.
	 *  @return True, if externally used.
	 */
	protected abstract boolean isExternallyUsed(Object id);
	
	//-------- nested states --------
	
	/**
	 *  Add a substate.
	 *  Read accesses will be transparently mapped to substates.
	 *  Write accesses to substates need not be supported and
	 *  may generate UnsupportedOperationException.
	 */
	public void addSubstate(IOAVState substate)
	{
		if(substates==null)
		{
			substates	= new IOAVState[]{substate};
		}
		else
		{
			IOAVState[]	tmp	= new IOAVState[substates.length+1];
			System.arraycopy(substates, 0, tmp, 0, substates.length);
			substates	= tmp;
			substates[substates.length-1]	= substate;
		}
	}
	
	/**
	 *  Get the substates.
	 */
	public IOAVState[] getSubstates()
	{
		return substates;
	}
	
	//-------- identity vs. equality --------
	
	/**
	 *  Flag indicating that java objects are
	 *  stored by identity instead of equality.  
	 */
	public boolean	isJavaIdentity()
	{
		return javaidentity;
	}

	/**
	 *  Test if two values are equal
	 *  according to current identity/equality
	 *  settings. 
	 */
	public boolean	equals(Object a, Object b)
	{
		// When a!=b && javaidentity use equals() only for ids or java values.
		return a==b || a!=null && (javaidentity
			? ((generator.isId(a) || tmodel.getJavaType(a.getClass()).getKind().equals(OAVJavaType.KIND_VALUE)) && a.equals(b))
			: a.equals(b));
	}
	
	//-------- internal object handling --------
	
	/**
	 *  Internally create an object.
	 *  @param id The id.
	 *  @return The content map of the new object.
	 */
	protected abstract Map internalCreateObject(Object id);
	
	/**
	 *  Remove an object from the state objects.
	 *  @param id The id.
	 *  @return The content map of the object.
	 */
	protected abstract Map internalRemoveObject(Object id);
	
	/**
	 *  Get the object content of an object.
	 *  @param id The id.
	 *  @return The content map of the object.
	 */
	protected abstract Map internalGetObjectContent(Object id);
	
	/**
	 *  Test if an object is contained in the state.
	 *  @param id The id.
	 *  @return True, if object is contained.
	 */
	protected abstract boolean internalContainsObject(Object id);
	
	/**
	 *  Test how many object are contained in the state.
	 *  @return The number of objects.
	 */
	protected abstract int internalObjectsSize();
	
	/**
	 *  Get a set of the internal state objects.
	 *  @return A set of the state objects. 
	 */
	protected abstract Set internalGetObjects();
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy