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

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

Go to download

Jadex Rules is a small lightweight rule engine, which currently employs the well-known Rete algorithm for highly efficient rule matching. Jadex rules is therefore similar to other rule engines like JESS and Drools. Despite the similarities there are also important differences between these systems: * Jadex Rules is very small and intended to be used as component of other software. Even though rules can be specified in a Java dialect as well as (a small variation of) the CLIPS language its primary usage is on the API level. Jadex Rules is currently the core component of the Jadex BDI reasoning engine. * Jadex Rules cleanly separates between state and rule representation. This allows the state implementation as well as the matcher to be flexibly exchanged. Some experiments have e.g. been conducted with a Jena representation. Regarding the matcher, it is planned to support also the Treat algorithm, which has a lower memory footprint than Rete. * Jadex Rules pays close attention to rule debugging. The state as well as the rete engine can be observed at runtime. The rule debugger provides functionalities to execute a rule program stepwise and also use rule breakpoints to stop the execution at those points.

There is a newer version: 2.4
Show newest version
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;

/* $if !android $ */
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/* $else $
import javaa.beans.PropertyChangeEvent;
import javaa.beans.PropertyChangeListener;
$endif $ */
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 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 = 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)
							{
								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 - 2024 Weber Informatics LLC | Privacy Policy