/*
* Created on Aug 13, 2005 Copyright (C) 2001-5, Anthony Harrison anh23@pitt.edu
* (jactr.org) This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version. This library is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU Lesser General Public License for more details. You should have
* received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jactr.core.buffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.buffer.event.ActivationBufferEvent;
import org.jactr.core.buffer.event.IActivationBufferListener;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.chunk.ISubsymbolicChunk;
import org.jactr.core.chunk.four.ISubsymbolicChunk4;
import org.jactr.core.chunk.four.Link;
import org.jactr.core.event.ACTREventDispatcher;
import org.jactr.core.logging.Logger;
import org.jactr.core.model.IModel;
import org.jactr.core.module.IModule;
import org.jactr.core.utils.parameter.IParameterized;
import org.jactr.core.utils.parameter.ParameterHandler;
/**
* basic implementation of an activation buffer that handles the spreading of
* activation to chunks. this is thread safe.
*
* @author developer
*/
public abstract class AbstractActivationBuffer implements IActivationBuffer,
IParameterized
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(AbstractActivationBuffer.class);
final private IModel _model;
final private IModule _module;
final private Collection<IChunk> _activatedChunks;
private double _activation = 0;
private double _goalValue = 0;
private boolean _strictHarvestingEnabled = true;
final private String _name;
final private ACTREventDispatcher<IActivationBuffer, IActivationBufferListener> _eventDispatcher;
final private ReentrantReadWriteLock _lock = new ReentrantReadWriteLock();
/**
* Comment for <code>ACTIVATION</code>
*/
static public final String ACTIVATION_PARAM = "Activation";
/**
* Comment for <code>GOAL_VALUE</code>
*/
static public final String GOAL_VALUE_PARAM = "G";
static public final String STRICT_HARVESTING_PARAM = "StrictHarvestingEnabled";
static private final String[] SETTABLE = {
ACTIVATION_PARAM, GOAL_VALUE_PARAM, STRICT_HARVESTING_PARAM };
static private final String[] GETTABLE = {
ACTIVATION_PARAM, GOAL_VALUE_PARAM, STRICT_HARVESTING_PARAM };
public AbstractActivationBuffer(String name, IModel model, IModule module)
{
_name = name;
_model = model;
_module = module;
_activatedChunks = new ArrayList<IChunk>();
_eventDispatcher = new ACTREventDispatcher<IActivationBuffer, IActivationBufferListener>();
}
public void dispose()
{
try
{
getLock().writeLock().lock();
_activatedChunks.clear();
_eventDispatcher.clear();
}
finally
{
getLock().writeLock().unlock();
}
}
protected ReentrantReadWriteLock getLock()
{
return _lock;
}
public void matched(IChunk chunk)
{
if (chunk == null) return;
boolean signal = false;
try
{
_lock.writeLock().lock();
signal = matchedInternal(chunk);
}
finally
{
_lock.writeLock().unlock();
}
if (signal)
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new ActivationBufferEvent(this,
ActivationBufferEvent.Type.CHUNK_MATCHED, chunk));
}
/**
* return true if the matched event should be fired
*
* @param chunk
* @return
*/
protected boolean matchedInternal(IChunk chunk)
{
return true;
}
final public void setActivation(double activation)
{
double oldAct = _activation;
try
{
_lock.writeLock().lock();
_activation = activation;
}
finally
{
_lock.writeLock().unlock();
}
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new ActivationBufferEvent(this,
ActivationBufferEvent.Type.PARAMETER_CHANGED, ACTIVATION_PARAM,
oldAct, activation));
}
final public double getActivation()
{
try
{
_lock.readLock().lock();
return _activation;
}
finally
{
_lock.readLock().unlock();
}
}
final public void setG(double g)
{
double oldValue = _goalValue;
try
{
_lock.writeLock().lock();
_goalValue = g;
}
finally
{
_lock.writeLock().unlock();
}
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new ActivationBufferEvent(this,
ActivationBufferEvent.Type.PARAMETER_CHANGED, GOAL_VALUE_PARAM,
oldValue, g));
}
final public double getG()
{
try
{
_lock.readLock().lock();
return _goalValue;
}
finally
{
_lock.readLock().unlock();
}
}
final public void setStrictHarvestingEnabled(boolean enabled)
{
try
{
getLock().writeLock().lock();
_strictHarvestingEnabled = enabled;
}
finally
{
getLock().writeLock().unlock();
}
}
final public boolean isStrictHarvestingEnabled()
{
try
{
getLock().readLock().lock();
return _strictHarvestingEnabled;
}
finally
{
getLock().readLock().unlock();
}
}
final protected ACTREventDispatcher<IActivationBuffer, IActivationBufferListener> getEventDispatcher()
{
return _eventDispatcher;
}
/**
* will call removeSourceChunkInternal for all the chunks and then recalculate
* activation
*
* @see org.jactr.core.buffer.IActivationBuffer#clear()
*/
public void clear()
{
Collection<IChunk> old = null;
try
{
_lock.writeLock().lock();
old = clearInternal();
}
catch (RuntimeException re)
{
LOGGER.error("exception while clearing buffer " + getName(), re);
throw re;
}
finally
{
_lock.writeLock().unlock();
}
// notify
if (old.size() != 0)
{
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new ActivationBufferEvent(this, old));
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.BUFFER, "cleared " + getName());
}
}
/**
* called within the lock
*
* @return the chunks that were removed
*/
protected Collection<IChunk> clearInternal()
{
Collection<IChunk> cleared = getSourceChunks();
for (IChunk chunk : cleared)
if(removeSourceChunkInternal(chunk))
BufferUtilities.unmarkContained(chunk, this);
calculateAndSpreadSourceActivation();
if (LOGGER.isDebugEnabled()) LOGGER.debug("cleared " + cleared);
return cleared;
}
/**
* spread activation to the contents, if any..
*/
final protected void calculateAndSpreadSourceActivation()
{
try
{
_lock.writeLock().lock();
clearActivatedChunks();
_activatedChunks.addAll(spreadActivation());
}
finally
{
_lock.writeLock().unlock();
}
}
/**
* spread activation to all the chunks that are connected to the source chunk
*
* @return a collection of all the chunks that have had their source
* activation incremented
*/
final protected Collection<IChunk> spreadActivation()
{
/*
* we use an array list which WILL permit duplicates. Why? shouldn't
* something appearing N times in a source chunk get N times the source
* activation?
*/
Collection<IChunk> rtn = new ArrayList<IChunk>();
Collection<Link> iLinks = new ArrayList<Link>();
/*
* we need to get each chunk that the source contains
*/
for (IChunk sourceChunk : getSourceChunks())
{
ISubsymbolicChunk sourceChunkSub = sourceChunk.getSubsymbolicChunk();
if (sourceChunkSub instanceof ISubsymbolicChunk4)
{
iLinks.clear();
((ISubsymbolicChunk4) sourceChunkSub).getIAssociations(iLinks);
/*
* this chunk is J, so we need I
*/
for (Link iLink : iLinks)
rtn.add(iLink.getIChunk());
}
if (rtn.size() != 0)
{
// now we have them all, let's apply the activation
double sourceActivation = getActivation() / rtn.size();
for (IChunk chunk : rtn)
{
// unlikely, but possible..
if (chunk.hasBeenDisposed()) continue;
ISubsymbolicChunk ssc = chunk.getSubsymbolicChunk();
double activation = ssc.getSourceActivation() + sourceActivation;
ssc.setSourceActivation(activation);
}
}
}
return rtn;
}
/**
*
*
*/
final private void clearActivatedChunks()
{
if (_activatedChunks.size() == 0) return;
double sourceActivation = getActivation() / _activatedChunks.size();
for (IChunk chunk : _activatedChunks)
{
// a chunk may have been disposed of by now...
if (chunk.hasBeenDisposed()) continue;
ISubsymbolicChunk ssc = chunk.getSubsymbolicChunk();
double activation = ssc.getSourceActivation() - sourceActivation;
ssc.setSourceActivation(activation);
}
_activatedChunks.clear();
}
/**
* return the source chunk that was actually inserted into the buffer (i.e. a
* copy of chunkToInsert if chunkToInsert has already been encoded).
*
* @param chunkToInsert
* the chunk to be inserted, will never be null nor already in the
* buffer
* @return actual chunk inserted. if null, no events are fired or activation
* spread
*/
abstract protected IChunk addSourceChunkInternal(IChunk chunkToInsert);
/**
* add chunk to the buffer. if addSourceChunkInternal(IChunk) returns not
* null, activation is spread and an event is fired
*
* @see org.jactr.core.buffer.IActivationBuffer#addSourceChunk(org.jactr.core.chunk.IChunk)
*/
public IChunk addSourceChunk(IChunk c)
{
if (c == null) return null;
IChunk added = contains(c);
if (added != null) return added;
/*
* moved event firing out of synchronization
*/
try
{
_lock.writeLock().lock();
added = addSourceChunkInternal(c);
if (added != null)
calculateAndSpreadSourceActivation();
}
finally
{
_lock.writeLock().unlock();
}
if (added != null)
{
BufferUtilities.markContained(added, this, getActivation());
if (LOGGER.isDebugEnabled())
LOGGER
.debug("addSourceChunkInternal returned true, dispatching and calculating activation");
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new ActivationBufferEvent(this,
ActivationBufferEvent.Type.SOURCE_ADDED, added));
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.BUFFER, "Added " + added + " to "
+ getName());
}
return added;
}
/**
* do the actual work of removing chunkToRemove from the buffer.
*
* @param chunkToRemove
* chunk that is in the buffer, never null
* @return true if activation is to be reset and an event fired
*/
abstract protected boolean removeSourceChunkInternal(IChunk chunkToRemove);
/**
* remove the chunk from the buffer. if removeSourceChunkInternal(IChunk)
* returns true activation is reset and event fired
*
* @see org.jactr.core.buffer.IActivationBuffer#removeSourceChunk(org.jactr.core.chunk.IChunk)
*/
public void removeSourceChunk(IChunk c)
{
if (c == null) return;
boolean shouldRemove = false;
/*
* is the chunk actually in the buffer?
*/
for (IChunk chunk : getSourceChunks())
if (chunk.equals(c))
{
shouldRemove = true;
break;
}
if (!shouldRemove) return;
/*
* remove firing from synch
*/
try
{
_lock.writeLock().lock();
shouldRemove = removeSourceChunkInternal(c);
if (shouldRemove)
calculateAndSpreadSourceActivation();
}
finally
{
_lock.writeLock().unlock();
}
if (shouldRemove)
{
BufferUtilities.unmarkContained(c, this);
if (LOGGER.isDebugEnabled())
LOGGER
.debug("removeSourceChunkInternal returned true, dispatching and calculating activation");
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new ActivationBufferEvent(this,
ActivationBufferEvent.Type.SOURCE_REMOVED, c));
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.BUFFER, "Removed " + c + " from "
+ getName());
}
}
final public IChunk getSourceChunk()
{
try
{
_lock.readLock().lock();
return getSourceChunkInternal();
}
finally
{
_lock.readLock().unlock();
}
}
/**
* return the source chunk from the internal backing store
*
* @return
*/
abstract protected IChunk getSourceChunkInternal();
final public Collection<IChunk> getSourceChunks()
{
try
{
_lock.readLock().lock();
return getSourceChunksInternal();
}
finally
{
_lock.readLock().unlock();
}
}
abstract protected Collection<IChunk> getSourceChunksInternal();
public IChunk contains(IChunk c)
{
for (IChunk chunk : getSourceChunks())
if (chunk.equalsSymbolic(c)) return chunk;
return null;
}
public void addListener(IActivationBufferListener abl, Executor executor)
{
_eventDispatcher.addListener(abl, executor);
}
public void removeListener(IActivationBufferListener abl)
{
_eventDispatcher.removeListener(abl);
}
public String getName()
{
return _name;
}
public IModel getModel()
{
return _model;
}
public boolean handlesEncoding()
{
return false;
}
public IModule getModule()
{
return _module;
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getSetableParameters()
* @return
*/
public Collection<String> getSetableParameters()
{
return Arrays.asList(SETTABLE);
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getPossibleParameters()
* @return
*/
public Collection<String> getPossibleParameters()
{
return Arrays.asList(GETTABLE);
}
/**
* @param parameter
* @see org.jactr.core.utils.parameter.IParameterized#getParameter(java.lang.String)
* @return
*/
public String getParameter(String parameter)
{
String rtn = null;
if (parameter.equalsIgnoreCase(ACTIVATION_PARAM))
rtn = ParameterHandler.numberInstance().toString(getActivation());
else if (parameter.equalsIgnoreCase(GOAL_VALUE_PARAM))
rtn = ParameterHandler.numberInstance().toString(getG());
else if (parameter.equalsIgnoreCase(STRICT_HARVESTING_PARAM))
rtn = "" + isStrictHarvestingEnabled();
return rtn;
}
/**
* @param parameter
* @param value
* @see org.jactr.core.utils.parameter.IParameterized#setParameter(java.lang.String,
* String)
*/
public void setParameter(String parameter, String value)
{
if (parameter.equalsIgnoreCase(ACTIVATION_PARAM))
setActivation(ParameterHandler.numberInstance().coerce(value)
.doubleValue());
else if (parameter.equalsIgnoreCase(GOAL_VALUE_PARAM))
setG(ParameterHandler.numberInstance().coerce(value).doubleValue());
else if (parameter.equalsIgnoreCase(STRICT_HARVESTING_PARAM))
setStrictHarvestingEnabled(ParameterHandler.booleanInstance().coerce(
value));
}
public void initialize()
{
// noop
}
public String toString()
{
return getName();
}
}