/*
* 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 javolution.util.FastList;
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.buffer.six.DefaultSourceActivationSpreader;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.concurrent.ExecutorServices;
import org.jactr.core.event.ACTREventDispatcher;
import org.jactr.core.logging.IMessageBuilder;
import org.jactr.core.logging.Logger;
import org.jactr.core.model.IModel;
import org.jactr.core.model.event.IModelListener;
import org.jactr.core.model.event.ModelEvent;
import org.jactr.core.model.event.ModelListenerAdaptor;
import org.jactr.core.module.IModule;
import org.jactr.core.utils.DefaultAdaptable;
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 extends DefaultAdaptable
implements IActivationBuffer, IParameterized
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(AbstractActivationBuffer.class);
final private IModel _model;
final private IModelListener _modelListener;
final private IModule _module;
private ISourceActivationSpreader _activationSpreader;
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;
_eventDispatcher = new ACTREventDispatcher<IActivationBuffer, IActivationBufferListener>();
_modelListener = new ModelListenerAdaptor() {
/*
* at the top of each cycle, recalculate spreading activation.
* (non-Javadoc)
* @see
* org.jactr.core.model.event.ModelListenerAdaptor#cycleStarted(org.jactr
* .core.model.event.ModelEvent)
*/
@Override
public void cycleStarted(ModelEvent me)
{
ISourceActivationSpreader spreader = getActivationSpreader();
spreader.clearSourceActivation();
spreader.spreadSourceActivation();
}
@Override
public void cycleStopped(ModelEvent me)
{
}
@Override
public void modelStarted(ModelEvent me)
{
grabReferences();
}
@Override
public void modelStopped(ModelEvent me)
{
ISourceActivationSpreader spreader = getActivationSpreader();
spreader.clearSourceActivation();
}
};
}
public void dispose()
{
try
{
getLock().writeLock().lock();
_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()))
{
IMessageBuilder mb = Logger.messageBuilder();
mb.append(getName()).append(" cleared");
Logger.log(getModel(), Logger.Stream.BUFFER, mb);
}
}
}
/**
* called within the lock
*
* @return the chunks that were removed
*/
protected Collection<IChunk> clearInternal()
{
Collection<IChunk> sources = getSourceChunks();
for (IChunk chunk : sources)
if (removeSourceChunkInternal(chunk))
BufferUtilities.unmarkContained(chunk, this);
// now handled by model listener
// calculateAndSpreadSourceActivation();
if (LOGGER.isDebugEnabled()) LOGGER.debug("cleared " + sources);
return sources;
}
public void setActivationSpreader(ISourceActivationSpreader spreader)
{
// make sure we clean up just in case
if (_activationSpreader != null)
_activationSpreader.clearSourceActivation();
_activationSpreader = spreader;
}
public ISourceActivationSpreader getActivationSpreader()
{
return _activationSpreader;
}
/**
* 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 = null;
// IChunk added = contains(c);
// if (added != null) return added;
/*
* moved event firing out of synchronization
*/
try
{
_lock.writeLock().lock();
added = addSourceChunkInternal(c);
// now handled by the model listener
// 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()))
{
IMessageBuilder mb = Logger.messageBuilder();
mb.append(getName()).append(" added ")
.append(added.getSymbolicChunk().getName());
Logger.log(getModel(), Logger.Stream.BUFFER, mb);
}
}
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?
*/
shouldRemove = containsExact(c) != null;
if (!shouldRemove) return;
/*
* remove firing from synch
*/
try
{
_lock.writeLock().lock();
shouldRemove = removeSourceChunkInternal(c);
// now handled by the model listener
// 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()))
{
IMessageBuilder mb = Logger.messageBuilder();
mb.append(getName()).append(" removed ")
.append(c.getSymbolicChunk().getName());
Logger.log(getModel(), Logger.Stream.BUFFER, mb);
}
}
}
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()
{
return getSourceChunks(null);
}
final public Collection<IChunk> getSourceChunks(Collection<IChunk> container)
{
if (container == null) container = new ArrayList<IChunk>();
try
{
_lock.readLock().lock();
return getSourceChunksInternal(container);
}
finally
{
_lock.readLock().unlock();
}
}
abstract protected Collection<IChunk> getSourceChunksInternal(
Collection<IChunk> container);
public IChunk contains(IChunk c)
{
FastList<IChunk> sources = FastList.newInstance();
try
{
for (IChunk chunk : getSourceChunks(sources))
if (chunk.equalsSymbolic(c)) return chunk;
return null;
}
finally
{
FastList.recycle(sources);
}
}
public IChunk containsExact(IChunk c)
{
FastList<IChunk> sources = FastList.newInstance();
try
{
for (IChunk chunk : getSourceChunks(sources))
if (chunk.equals(c)) return chunk;
return null;
}
finally
{
FastList.recycle(sources);
}
}
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()
{
if (getActivationSpreader() == null)
setActivationSpreader(new DefaultSourceActivationSpreader(this));
_model.addListener(_modelListener, ExecutorServices.INLINE_EXECUTOR);
}
/**
* called once the model is started. this is the hook to grab references to
* chunks & types
*/
protected void grabReferences()
{
}
@Override
public String toString()
{
return getName();
}
}