/*
* 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.six;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.buffer.AbstractActivationBuffer;
import org.jactr.core.buffer.BufferUtilities;
import org.jactr.core.buffer.event.ActivationBufferEvent;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.logging.Logger;
import org.jactr.core.model.IModel;
import org.jactr.core.module.IModule;
import org.jactr.core.production.condition.CannotMatchException;
import org.jactr.core.production.request.SlotBasedRequest;
import org.jactr.core.slot.DefaultMutableSlot;
import org.jactr.core.slot.IMutableSlot;
import org.jactr.core.slot.ISlot;
import org.jactr.core.slot.ISlotOwner;
/**
* template for an activation buffer that handles it's own status and inserts
* only copies of chunks
*
* @author developer
*/
public abstract class AbstractActivationBuffer6 extends
AbstractActivationBuffer implements IStatusBuffer
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(AbstractActivationBuffer6.class);
final private Map<String, IMutableSlot> _statusSlotMap;
private IChunk _requestedChunk;
private IChunk _unrequestedChunk;
private IChunk _freeChunk;
private IChunk _busyChunk;
private IChunk _fullChunk;
private IChunk _emptyChunk;
private IChunk _errorChunk;
private ISlotOwner _slotListener;
public AbstractActivationBuffer6(String name, IModule module)
{
super(name, module.getModel(), module);
_statusSlotMap = new TreeMap<String, IMutableSlot>();
_slotListener = new ISlotOwner() {
public void valueChanged(ISlot slot, Object oldValue, Object newValue)
{
boolean changed = oldValue != null && !oldValue.equals(newValue)
|| newValue != null && !newValue.equals(oldValue);
if (changed)
{
IModel model = getModel();
if (LOGGER.isDebugEnabled() || Logger.hasLoggers(model))
{
String msg = String.format("%s.%s=%s (was %s)", getName(), slot
.getName(), newValue, oldValue);
if (LOGGER.isDebugEnabled()) LOGGER.debug(msg);
if (Logger.hasLoggers(model))
Logger.log(model, Logger.Stream.BUFFER, msg);
}
if (getEventDispatcher().hasListeners())
getEventDispatcher().fire(
new ActivationBufferEvent(AbstractActivationBuffer6.this,
ActivationBufferEvent.Type.STATUS_SLOT_CHANGED, slot
.getName(), oldValue, newValue));
}
}
};
/*
* add default status slots
*/
addSlot(new DefaultMutableSlot(BUFFER_SLOT, null));
addSlot(new DefaultMutableSlot(STATE_SLOT, null));
}
@Override
public void dispose()
{
super.dispose();
try
{
getLock().writeLock().lock();
_statusSlotMap.clear();
_busyChunk = null;
_emptyChunk = null;
_errorChunk = null;
_freeChunk = null;
_fullChunk = null;
_requestedChunk = null;
_unrequestedChunk = null;
}
finally
{
getLock().writeLock().unlock();
}
}
/**
* clear the status slots too
*
* @see org.jactr.core.buffer.AbstractActivationBuffer#clear()
*/
@Override
protected Collection<IChunk> clearInternal()
{
try
{
return super.clearInternal();
}
finally
{
setStateChunk(getFreeChunk());
setBufferChunk(getEmptyChunk());
}
}
public void addSlot(ISlot slot)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Adding status slot " + slot.getName());
try
{
getLock().writeLock().lock();
DefaultMutableSlot actualSlot = new DefaultMutableSlot(slot);
actualSlot.setOwner(_slotListener);
_statusSlotMap.put(slot.getName().toLowerCase(), actualSlot);
}
finally
{
getLock().writeLock().unlock();
}
}
public void removeSlot(ISlot slot)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Removing status slot " + slot.getName());
try
{
getLock().writeLock().lock();
_statusSlotMap.remove(slot.getName().toLowerCase());
}
finally
{
getLock().writeLock().unlock();
}
}
public ISlot getSlot(String name)
{
try
{
getLock().readLock().lock();
return _statusSlotMap.get(name.toLowerCase());
}
finally
{
getLock().readLock().unlock();
}
}
public Collection<? extends ISlot> getSlots()
{
try
{
getLock().readLock().lock();
return Collections.unmodifiableCollection(_statusSlotMap.values());
}
finally
{
getLock().readLock().unlock();
}
}
public Collection<ISlot> getSlots(Collection<ISlot> slots)
{
if (slots == null) slots = new ArrayList<ISlot>();
try
{
getLock().readLock().lock();
slots.addAll(_statusSlotMap.values());
return slots;
}
finally
{
getLock().readLock().unlock();
}
}
/**
* return true if this chunk should be copied, currently it just returns
* {@link IChunk#isEncoded()} or
* {@link BufferUtilities#getContainingBuffers(IChunk, boolean)}.size()!=0. In
* other words, copy the chunk if its been encoded or is currently in another
* buffer
*
* @param chunk
* @return
*/
protected boolean shouldCopyOnInsertion(IChunk chunk)
{
return chunk.isEncoded()
|| BufferUtilities.getContainingBuffers(chunk, true).size() != 0;
}
public boolean isStateFree()
{
return checkStatusSlotContent(STATE_SLOT, getFreeChunk());
}
public boolean isStateBusy()
{
return checkStatusSlotContent(STATE_SLOT, getBusyChunk());
}
public boolean isStateError()
{
return checkStatusSlotContent(STATE_SLOT, getErrorChunk());
}
public boolean isBufferUnrequested()
{
return checkStatusSlotContent(BUFFER_SLOT, getUnrequestedChunk());
}
public boolean isBufferRequested()
{
return checkStatusSlotContent(BUFFER_SLOT, getRequestedChunk());
}
public boolean isBufferFull()
{
return checkStatusSlotContent(BUFFER_SLOT, getFullChunk());
}
/**
* this actually checks the contents of the buffer slot which might not be the
* best test in the case of buffers that share a common set of state slots
* (i.e. visual and visual-location)
*
* @see org.jactr.core.buffer.six.IStatusBuffer#isBufferEmpty()
*/
public boolean isBufferEmpty()
{
return checkStatusSlotContent(BUFFER_SLOT, getEmptyChunk());
}
protected boolean checkStatusSlotContent(String slotName, Object value)
{
try
{
getLock().readLock().lock();
ISlot slot = getSlot(slotName);
if (slot != null && value != null) return value.equals(slot.getValue());
if (slot != null) return slot.getValue() == null;
return false;
}
finally
{
getLock().readLock().unlock();
}
}
protected void setStatusSlotContent(String slotName, Object value)
{
IMutableSlot ms = (IMutableSlot) getSlot(slotName);
// Object oldValue = null;
// boolean changed = false;
// try
// {
// getLock().writeLock().lock();
// ms = ;
//
// oldValue = ms.getValue();
// ms.setValue(value);
//
// changed = oldValue != null && !oldValue.equals(value) || value != null
// && !value.equals(oldValue);
//
// }
// finally
// {
// getLock().writeLock().unlock();
// }
if (ms != null)
ms.setValue(value);
}
public void setStateChunk(IChunk chunk)
{
setStatusSlotContent(STATE_SLOT, chunk);
}
public void setBufferChunk(IChunk bufferState)
{
setStatusSlotContent(BUFFER_SLOT, bufferState);
}
public IChunk getFreeChunk()
{
/*
* non-locking because even if these values get clobbered, the chunk will
* always be the same
*/
if (_freeChunk == null)
_freeChunk = getModel().getDeclarativeModule().getFreeChunk();
return _freeChunk;
}
public IChunk getBusyChunk()
{
/*
* non-locking because even if these values get clobbered, the chunk will
* always be the same
*/
if (_busyChunk == null)
_busyChunk = getModel().getDeclarativeModule().getBusyChunk();
return _busyChunk;
}
public IChunk getEmptyChunk()
{
/*
* non-locking because even if these values get clobbered, the chunk will
* always be the same
*/
if (_emptyChunk == null)
_emptyChunk = getModel().getDeclarativeModule().getEmptyChunk();
return _emptyChunk;
}
public IChunk getFullChunk()
{
/*
* non-locking because even if these values get clobbered, the chunk will
* always be the same
*/
if (_fullChunk == null)
_fullChunk = getModel().getDeclarativeModule().getFullChunk();
return _fullChunk;
}
public IChunk getErrorChunk()
{
/*
* non-locking because even if these values get clobbered, the chunk will
* always be the same
*/
if (_errorChunk == null)
_errorChunk = getModel().getDeclarativeModule().getErrorChunk();
return _errorChunk;
}
public IChunk getRequestedChunk()
{
/*
* non-locking because even if these values get clobbered, the chunk will
* always be the same
*/
if (_requestedChunk == null)
_requestedChunk = getModel().getDeclarativeModule().getRequestedChunk();
return _requestedChunk;
}
public IChunk getUnrequestedChunk()
{/*
* non-locking because even if these values get clobbered, the chunk will
* always be the same
*/
if (_unrequestedChunk == null)
_unrequestedChunk = getModel().getDeclarativeModule()
.getUnrequestedChunk();
return _unrequestedChunk;
}
/**
* must be called after the model has started so that we can ensure that the
* status slot values are properly initialized
*/
@Override
public void initialize()
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("About to run, ensuring the status buffer slots");
setStateChunk(getFreeChunk());
setBufferChunk(getEmptyChunk());
}
/**
* ensure that all chunks added are copies
*
* @see org.jactr.core.buffer.IActivationBuffer#addSourceChunk(org.jactr.core.chunk.IChunk)
*/
@Override
public IChunk addSourceChunk(IChunk sourceChunk)
{
if (sourceChunk == null) return null;
if (LOGGER.isDebugEnabled())
LOGGER.debug("Attempting to addSourceChunk " + sourceChunk);
/*
* unlikely, but let's log it.
*/
if (sourceChunk.hasBeenDisposed())
{
LOGGER.warn(getName() + ".addSourceChunk() " + sourceChunk
+ " has been disposed of, setting error");
setStateChunk(getErrorChunk());
return null;
}
/*
* do we already have it?
*/
IChunk tmp = contains(sourceChunk);
if (tmp != null) return tmp;
if (shouldCopyOnInsertion(sourceChunk))
/*
* we only copy slotted chunks, zero slot chunks get inserted as they are
* this keeps us from making unnnecessary copies of status chunks (error)
*/
if (sourceChunk.getSymbolicChunk().getSlots().size() > 0)
try
{
if (LOGGER.isDebugEnabled())
LOGGER
.debug("sourceChunk exists and has been encoded, creating a copy instead");
sourceChunk = getModel().getDeclarativeModule()
.copyChunk(sourceChunk).get();
}
catch (Exception e)
{
LOGGER.error("Failed to copy " + sourceChunk, e);
return null;
}
// this will check addSourceChunkInternal
return super.addSourceChunk(sourceChunk);
}
// /**
// * check to see if any of the contained chunks match this pattern and
// binding
// * combination.
// */
// @Override
// protected IChunk matchesInternal(ChunkPattern retr,
// Map<String, Object> bindings) throws CannotMatchException
// {
// Collection<IChunk> sourceChunks = getSourceChunks();
//
// if (LOGGER.isDebugEnabled())
// LOGGER.debug("Attempting to match " + retr + " against " + sourceChunks
// + " bindings:" + bindings);
//
// if (sourceChunks.size() == 0)
// throw new CannotMatchException("No source chunks in " + getName()
// + " to match against");
//
// IModel model = getModel();
//
// Map<String, Object> originalBindings = bindings;
//
// // bind all the currently defined variables
// retr = (ChunkPattern) retr.bind(model, originalBindings);
//
// // first we check on any status queries
// Collection<IConditionalSlot> querySlots = matchesStatus(retr
// .getConditionalSlots(), originalBindings, false);
//
// // if we get this far, we've passed, however, the chunk pattern might
// // contain query slots, which arent actual slots of this chunks chunktype
// // so we have to remove them
// for (IConditionalSlot cSlot : querySlots)
// retr.removeSlot(cSlot);
//
// if (LOGGER.isDebugEnabled())
// LOGGER.debug("Resolved pattern to match against : " + retr);
//
// StringBuilder failedMessage = new StringBuilder();
//
// for (IChunk source : sourceChunks)
// {
// /*
// * since each source chunk might result in different bindings, we copy the
// * original bindings, test against the copy. If we are good to go, we
// * modified the original binding with the contents of the copied bindings.
// * If we did not do this then the first chunk in the buffer would change
// * the bindings for the subsequent chunks resulting in some really strange
// * behavior.
// */
// Map<String, Object> copiedBindings = new TreeMap<String, Object>(
// originalBindings);
//
// /*
// * copy the retrieval since we are going to be using multiple times..
// */
// ChunkPattern retrCopy = (ChunkPattern) retr.bind(model, copiedBindings);
//
// if (LOGGER.isDebugEnabled())
// LOGGER.debug("Checking against " + source + " bindings:"
// + copiedBindings);
//
// try
// {
// // now we can use the standard match test
// retrCopy.matches(source, copiedBindings);
//
// // we've passed, replace the contents of the bindings
// originalBindings.clear();
// originalBindings.putAll(copiedBindings);
//
// if (LOGGER.isDebugEnabled())
// LOGGER.debug("Returning " + source + " with bindings:"
// + originalBindings);
//
// return source;
// }
// catch (CannotMatchException cme)
// {
// failedMessage.append("(").append(cme.getMessage()).append(")");
// }
// }
//
// throw new CannotMatchException(getName() + " : cant match "
// + failedMessage.toString());
// }
public int bind(SlotBasedRequest request, Map<String, Object> bindings,
boolean isIterative) throws CannotMatchException
{
return request.bind(getModel(), getName(), this, bindings, isIterative);
}
//
// /**
// * check the set of conditional slots against the status slots, handles ":"
// * prefixes automatically. called by matches();
// *
// * @param slots
// * @param pureStatusCheck
// * if there are no chunk slots and only
// * @return a collection of slots that can be removed from the original
// * querying collection - i.e. the slots that only match against the
// * status slots
// * @throws CannotMatchException
// */
// public Collection<IConditionalSlot> matchesStatus(
// Collection<IConditionalSlot> slots, Map<String, Object> bindings,
// boolean pureStatusCheck) throws CannotMatchException
// {
// try
// {
// getLock().readLock().lock();
// if (LOGGER.isDebugEnabled())
// LOGGER.debug("Checking conditional slots against buffer status : "
// + slots);
//
// Collection<IConditionalSlot> slotsToRemove = new
// ArrayList<IConditionalSlot>(
// slots.size());
//
// for (IConditionalSlot originalSlot : slots)
// {
// IConditionalSlot slot = originalSlot;
//
// if (!pureStatusCheck)
// {
// // if the name is :prefixed, we need to make a temp copy
// if (originalSlot.getName().startsWith(":"))
// {
// slot = new DefaultConditionalSlot(slot.getName().substring(1,
// slot.getName().length()), slot.getCondition(), slot.getValue());
// if (LOGGER.isDebugEnabled())
// LOGGER.debug(originalSlot.getName()
// + " is status conditional, stripping prefix to "
// + slot.getName());
// /*
// * if pure, we remove all : prefixed slots, regardless of whether
// * they are actually status slots
// */
// Object testValue = testStatusSlot(slot);
//
// if (originalSlot.isVariable())
// bindings.put((String) originalSlot.getValue(), testValue);
//
// slotsToRemove.add(originalSlot);
// }
// }
// else
// {
// Object testValue = testStatusSlot(slot);
//
// if (originalSlot.isVariable())
// bindings.put((String) originalSlot.getValue(), testValue);
// slotsToRemove.add(slot);
// }
// }
// return slotsToRemove;
// }
// finally
// {
// getLock().readLock().unlock();
// }
// }
//
// private Object testStatusSlot(IConditionalSlot slot)
// throws CannotMatchException
// {
// ISlot statusSlot = getStatusSlot(slot.getName());
// // we have something named this..
// if (!slot.matchesCondition(statusSlot.getValue()))
// {
// if (LOGGER.isDebugEnabled())
// LOGGER.debug(getName() + " : " + statusSlot
// + " doesn't match the conditional slot " + slot);
// throw new CannotMatchException(getName() + " : " + statusSlot
// + " doesn't match the conditional slot " + slot);
// }
// if (LOGGER.isDebugEnabled())
// LOGGER.debug(getName() + " : " + statusSlot
// + " matches conditional slot " + slot);
//
// return statusSlot.getValue();
// }
}