package jadex.rules.rulesystem.rete.nodes;
import jadex.commons.SReflect;
import jadex.rules.rulesystem.AbstractAgenda;
import jadex.rules.rulesystem.rete.extractors.AttributeSet;
import jadex.rules.rulesystem.rete.extractors.IValueExtractor;
import jadex.rules.state.IOAVState;
import jadex.rules.state.IProfiler;
import jadex.rules.state.OAVAttributeType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A split node has the purpose of generating virtual facts
* for multislot bindings that use a non-multi variable or
* variable patterns.
*/
public class SplitNode extends AbstractNode implements IObjectConsumerNode, IObjectSourceNode
{
//-------- attributes --------
/** The constant for a multi variable. */
public static String MULTI = "multi";
/** The constant for a single variable. */
public static String SINGLE = "single";
/** The constant for a dummy multi variable. */
public static String MULTI_DUMMY = "multi_dummy";
//-------- attributes --------
/** The object source. */
protected IObjectSourceNode osource;
/** The object consumers. */
protected IObjectConsumerNode[] oconsumers;
/** The set of relevant attributes. */
protected AttributeSet relevants;
/** The set of indirect attributes. */
protected AttributeSet indirects;
/** The values extractor. */
// Needed as long as multifield extractor is based on attribute
protected OAVAttributeType attr;
/** The values extractor. */
protected IValueExtractor extractor;
/** The splitpattern (multi, single or multi dummy). */
protected String[] splitpattern;
/** The minimum number of required values. */
protected int min_values;
//-------- constructors --------
/**
* Create a new node.
* @param state The state.
*/
public SplitNode(int nodeid, IValueExtractor extractor, OAVAttributeType attr, String[] splitpattern)
{
super(nodeid);
assert extractor!=null;
assert attr!=null;
assert !OAVAttributeType.NONE.equals(attr.getMultiplicity());
assert splitpattern.length>0;
this.extractor = extractor;
this.attr = attr;
this.min_values = 0;
for(int i=0; i<splitpattern.length; i++)
if(splitpattern[i].equals(SINGLE))
min_values++;
// Are there no multi values?
if(min_values==splitpattern.length)
{
// Make first and last dummy multi
this.splitpattern = new String[splitpattern.length+2];
System.arraycopy(splitpattern, 0, this.splitpattern, 1, splitpattern.length);
this.splitpattern[0] = MULTI_DUMMY;
this.splitpattern[this.splitpattern.length-1] = MULTI_DUMMY;
}
else
{
this.splitpattern = splitpattern;
}
}
//-------- object consumer interface --------
/**
* Send a new object to this node.
* @param object The object.
*/
public void addObject(Object object, IOAVState state, ReteMemory mem, AbstractAgenda agenda)
{
//System.out.println("Add object called: "+this+" "+object);
state.getProfiler().start(IProfiler.TYPE_NODE, this);
state.getProfiler().start(IProfiler.TYPE_NODEEVENT, IProfiler.NODEEVENT_OBJECTADDED);
Map smem = (Map)mem.getNodeMemory(this);
assert !smem.containsKey(object) : object;
// if(!smem.containsKey(object))
{
Collection vfs = generateVirtualFacts(object, state);
smem.put(object, vfs);
//System.out.println("ADD: Object splitted to: "+object+" "+vfs);
for(Iterator it=vfs.iterator(); it.hasNext(); )
{
propagateAdditionToObjectConsumers(it.next(), state, mem, agenda);
}
}
state.getProfiler().stop(IProfiler.TYPE_NODEEVENT, IProfiler.NODEEVENT_OBJECTADDED);
state.getProfiler().stop(IProfiler.TYPE_NODE, this);
}
/**
* Send a removed object to this node.
* @param object The object.
*/
public void removeObject(Object object, IOAVState state, ReteMemory mem, AbstractAgenda agenda)
{
//System.out.println("Remove object called: "+this+" "+object);
state.getProfiler().start(IProfiler.TYPE_NODE, this);
state.getProfiler().start(IProfiler.TYPE_NODEEVENT, IProfiler.NODEEVENT_OBJECTREMOVED);
assert mem.hasNodeMemory(this);
// if(mem.hasNodeMemory(this))
{
Map smem = (Map)mem.getNodeMemory(this);
Collection vfs = (Collection)smem.remove(object);
if(vfs!=null)
{
//System.out.println("REM: Object splitted to: "+object+" "+vfs);
for(Iterator it=vfs.iterator(); it.hasNext(); )
{
propagateRemovalToObjectConsumers(it.next(), state, mem, agenda);
}
}
}
state.getProfiler().stop(IProfiler.TYPE_NODEEVENT, IProfiler.NODEEVENT_OBJECTREMOVED);
state.getProfiler().stop(IProfiler.TYPE_NODE, this);
}
/**
* Propagate an object change to this node.
* @param object The new object.
*/
public void modifyObject(Object object, OAVAttributeType type, Object oldvalue, Object newvalue, IOAVState state, ReteMemory mem, AbstractAgenda agenda)
{
state.getProfiler().start(IProfiler.TYPE_NODE, this);
state.getProfiler().start(IProfiler.TYPE_NODEEVENT, IProfiler.NODEEVENT_OBJECTMODIFIED);
if(getRelevantAttributes().contains(type))
{
// Check if modification changes node memory.
boolean affected = isAffected(type);
Collection before = mem.hasNodeMemory(this) ? (Collection)((Map)mem.getNodeMemory(this)).get(object) : null;
if(affected)
{
Collection after = generateVirtualFacts(object, state);
//System.out.println("MOD: Object splitted to: "+object+" "+before+" "+after);
// if(before==null)
// System.out.println("a");
//if(before!=null)
{
for(Iterator it=before.iterator(); it.hasNext(); )
{
Object o = it.next();
// Remove a fact that was contained and is not anymore
if(!contains(state, after, o))
{
it.remove();
propagateRemovalToObjectConsumers(o, state, mem, agenda);
}
}
}
//if(after!=null)
{
for(Iterator it=after.iterator(); it.hasNext(); )
{
Object o = it.next();
// if(o instanceof List)
// System.out.println("shit");
// Add a fact that was not contained is now
if(!before.contains(o))
{
before.add(o);
propagateAdditionToObjectConsumers(o, state, mem, agenda);
}
}
}
}
else if(before!=null)
{
for(Iterator it=before.iterator(); it.hasNext(); )
{
propagateModificationToObjectConsumers(it.next(), type, oldvalue, newvalue, state, mem, agenda);
}
}
}
state.getProfiler().stop(IProfiler.TYPE_NODEEVENT, IProfiler.NODEEVENT_OBJECTMODIFIED);
state.getProfiler().stop(IProfiler.TYPE_NODE, this);
}
/**
* Propagate an indirect object change to this node.
* @param object The changed object.
*/
public void modifyIndirectObject(Object object, OAVAttributeType type, Object oldvalue, Object newvalue, IOAVState state, ReteMemory mem, AbstractAgenda agenda)
{
throw new UnsupportedOperationException("Unsupported method.");
}
/**
* Set the object source of this node.
* @param node The object source node.
*/
public void setObjectSource(IObjectSourceNode node)
{
this.osource = node;
}
/**
* Get the object source of this node.
* @return The object source node.
*/
public IObjectSourceNode getObjectSource()
{
return osource;
}
//-------- object source interface --------
/**
* Add an object consumer node.
* @param node A new consumer node.
*/
public void addObjectConsumer(IObjectConsumerNode node)
{
if(oconsumers==null)
{
oconsumers = new IObjectConsumerNode[]{node};
}
else
{
IObjectConsumerNode[] tmp = new IObjectConsumerNode[oconsumers.length+1];
System.arraycopy(oconsumers, 0, tmp, 0, oconsumers.length);
tmp[oconsumers.length] = node;
oconsumers = tmp;
}
relevants = null; // Will be recalculated on next access;
}
/**
* Remove an object consumer.
* @param node The consumer node.
*/
public void removeObjectConsumer(IObjectConsumerNode node)
{
if(oconsumers!=null)
{
for(int i=0; i<oconsumers.length; i++)
{
if(oconsumers[i].equals(node))
{
if(oconsumers.length==1)
{
oconsumers = null;
}
else
{
IObjectConsumerNode[] tmp = new IObjectConsumerNode[oconsumers.length-1];
if(i>0)
System.arraycopy(oconsumers, 0, tmp, 0, i);
if(i<oconsumers.length-1)
System.arraycopy(oconsumers, i+1, tmp, i, oconsumers.length-1-i);
oconsumers = tmp;
}
break;
}
}
}
}
/**
* Get the memory for this node.
* @return The memory.
*/
public Collection getNodeMemory(ReteMemory mem)
{
Collection ret = null;
if(mem.hasNodeMemory(this))
{
// todo: can storage be optimized?! -> flattened multi-collection
ret = new ArrayList();
for(Iterator it=((Map)mem.getNodeMemory(this)).values().iterator(); it.hasNext(); )
ret.addAll((Collection)it.next());
}
return ret;
}
/**
* Get all object consumer nodes.
* @return All object consumer nodes.
*/
public IObjectConsumerNode[] getObjectConsumers()
{
return oconsumers;
}
//-------- methods --------
/**
* Create the node memory.
* @param state The state.
* @return The node memory.
*/
public Object createNodeMemory(IOAVState state)
{
return new LinkedHashMap();
}
//-------- helper methods --------
/**
* Propagate a new object to all object consumers.
* @param object The new object.
*/
protected void propagateAdditionToObjectConsumers(Object object, IOAVState state, ReteMemory mem, AbstractAgenda agenda)
{
IObjectConsumerNode[] ocon = oconsumers;
for(int i=0; ocon!=null && i<ocon.length; i++)
ocon[i].addObject(object, state, mem, agenda);
}
/**
* Propagate a removed object to all object consumers.
* @param object The new object.
*/
protected void propagateRemovalToObjectConsumers(Object object, IOAVState state, ReteMemory mem, AbstractAgenda agenda)
{
IObjectConsumerNode[] ocon = oconsumers;
for(int i=0; ocon!=null && i<ocon.length; i++)
ocon[i].removeObject(object, state, mem, agenda);
}
/**
* Propagate a modified object to all object consumers.
* @param object The new object.
*/
protected void propagateModificationToObjectConsumers(Object object, OAVAttributeType type, Object oldvalue,
Object newvalue, IOAVState state, ReteMemory mem, AbstractAgenda agenda)
{
IObjectConsumerNode[] ocon = oconsumers;
for(int i=0; ocon!=null && i<ocon.length; i++)
ocon[i].modifyObject(object, type, oldvalue, newvalue, state, mem, agenda);
}
/**
* Test if the node is affected from a modification.
* @param type The attribute type.
* @return True, if possibly affected.
*/
public boolean isAffected(OAVAttributeType attr)
{
return extractor.isAffected(-1, attr);
}
/**
* Get the set of relevant attribute types.
* @return The relevant attribute types.
*/
public AttributeSet getRelevantAttributes()
{
if(relevants==null)
{
synchronized(this)
{
if(relevants==null)
{
relevants = new AttributeSet();
relevants.addAll(extractor.getRelevantAttributes());
for(int i=0; oconsumers!=null && i<oconsumers.length; i++)
{
relevants.addAll(oconsumers[i].getRelevantAttributes());
}
}
}
}
return relevants;
}
/**
* Get the set of indirect attribute types.
* I.e. attributes of objects, which are not part of an object conditions
* (e.g. for chained extractors)
* @return The relevant attribute types.
*/
public AttributeSet getIndirectAttributes()
{
if(indirects==null)
{
synchronized(this)
{
if(indirects==null)
{
indirects = new AttributeSet();
indirects.addAll(extractor.getIndirectAttributes());
}
}
}
return indirects;
}
/**
* Get the attribute.
* @return The attribute.
*/
public OAVAttributeType getAttribute()
{
return attr;
}
/**
* Generate virtual facts.
* @param object The object.
* @param state The state.
*/
protected Collection generateVirtualFacts(Object object, IOAVState state)
{
List ret = new ArrayList();
Object vals = extractor.getValue(null, object, null, state);
Object[] values = (Object[])SReflect.getArray(vals); // Hack! assumes Object[]
if(values!=null && values.length>=min_values)
generateBindings(values.length, 0, new int[splitpattern.length], object, values, ret);
return ret;
}
/**
* Generate all possible bindings for a list of values.
* @param weight The number of values to distribute on variables.
* @param cur The current variable number.
* @param binding Results are stored in this binding array (contains
* for each variable how many values it should store).
* @param values The values to distribute.
* @param ret The result list containing all found bindings (in form of virtual facts).
*/
protected void generateBindings(int weight, int cur, int[] binding, Object object,
Object[] values, List ret)
{
if(cur==binding.length-1)
{
binding[cur] = weight;
//System.out.println("Found binding: "+Srules.arrayToString(binding));
ret.add(generateVirtualFact(object, binding, values));
}
else
{
if(splitpattern[cur]!=SINGLE)
{
for(int i=0; i<=weight-min_values; i++)
{
binding[cur] = i;
generateBindings(weight-i, cur+1, binding, object, values, ret);
}
}
else
{
binding[cur] = 1;
generateBindings(weight-1, cur+1, binding, object, values, ret);
}
}
}
/**
* Generate a virtual fact for a found binding.
* @param binding The number of values for each variable.
* @param values The multislot values.
* @return A virtual fact with one binding.
*/
protected VirtualFact generateVirtualFact(Object object, int[] binding, Object[] values)
{
int off = 0;
int bcnt = 0;
List splitvals = new ArrayList();
for(int i=0; i<splitpattern.length; i++)
{
if(!splitpattern[i].equals(MULTI_DUMMY))
{
if(binding[bcnt]>1)
{
List vals = new ArrayList();
for(int j=off; j<off+binding[bcnt]; j++)
vals.add(values[j]);
splitvals.add(vals);
}
else if(binding[bcnt]==1)
{
if(splitpattern[i].equals(SINGLE))
{
splitvals.add(values[off]);
}
else
{
List vals = new ArrayList();
vals.add(values[off]);
splitvals.add(vals);
}
}
else if(binding[bcnt]==0)
{
splitvals.add(Collections.EMPTY_LIST);
}
}
off += binding[bcnt++];
}
return new VirtualFact(object, getAttribute(), splitvals);
}
/**
* Get the split pattern.
*/
public String[] getSplitPattern()
{
return splitpattern;
}
/**
* Get the string representation.
* @return The string representation.
*/
public String toString()
{
return toString(", attribute="+attr+" split in: "+splitpattern.length);
// return toString(", extractor="+extractor+" split in: "+splitpattern.length);
}
//-------- cloneable --------
/**
* Do clone makes a deep clone without regarding cycles.
* Method is overridden by subclasses to actually incorporate their attributes.
* @param theclone The clone.
*/
protected void doClone(Object theclone)
{
SplitNode ret = (SplitNode)theclone;
// Deep clone tuple consumers
ret.oconsumers = new IObjectConsumerNode[oconsumers.length];
for(int i=0; i<oconsumers.length; i++)
ret.oconsumers[i] = (IObjectConsumerNode)oconsumers[i].clone();
// Don't change the source, will be done by the source
ret.osource = (IObjectSourceNode)osource.clone();
// Shallow copy the relevant attributes
if(relevants!=null)
ret.relevants = (AttributeSet)((AttributeSet)relevants).clone();
// Keep extractor
// Keep the attribute
// Keep the split pattern
// Keep the min_values
}
/**
* Main for testing.
* @param args The arguments.
* /
public static void main(String[] args)
{
/*List vals = new ArrayList();
for(int i=0; i<10; i++)
vals.add(new Integer(i));
List ret = new ArrayList();
generateBindings(5, 0, new int[3], null, vals.toArray(),
ret, new boolean[]{true,false,true}, 1);* /
IOAVState state = OAVStateFactory.createOAVState();
ReteMemory mem = new ReteMemory();
Object b1 = state.createObject(OAVBlockMetamodel.block_type);
Object b2 = state.createObject(OAVBlockMetamodel.block_type);
Object b3 = state.createObject(OAVBlockMetamodel.block_type);
Object b4 = state.createObject(OAVBlockMetamodel.block_type);
Object b5 = state.createObject(OAVBlockMetamodel.block_type);
state.addAttributeValue(b1, OAVBlockMetamodel.block_has_on, b2);
state.addAttributeValue(b1, OAVBlockMetamodel.block_has_on, b3);
state.addAttributeValue(b1, OAVBlockMetamodel.block_has_on, b4);
state.addAttributeValue(b1, OAVBlockMetamodel.block_has_on, b5);
// $?x ?y $?z
SplitNode sn = new SplitNode(OAVBlockMetamodel.block_has_on,
new int[]{SINGLE});
sn.addObject(b1, state, mem, null);
}*/
/**
* Check if an object is contained in a collection.
* Avoid the need for an "IdentityArrayList".
*/
protected boolean contains(IOAVState state, Collection coll, Object o)
{
boolean ret = false;
for(Iterator it=coll.iterator(); !ret && it.hasNext(); )
{
Object val = it.next();
if(val instanceof ArrayList && o instanceof ArrayList)
{
ArrayList l1 = (ArrayList) val;
ArrayList l2 = (ArrayList) o;
if(l1.size()==l2.size())
{
ret = true;
for(int i=0; ret && i<l1.size(); i++)
ret = state.equals(l1.get(i), l2.get(i));
}
}
else
{
ret = state.equals(val, o);
}
}
return ret;
}
}