package org.jactr.extensions.cached.procedural.internal;
/*
* default logging
*/
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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.IActivationBuffer;
import org.jactr.core.model.IModel;
import org.jactr.core.production.CannotInstantiateException;
import org.jactr.core.production.IProduction;
import org.jactr.core.production.condition.CannotMatchException;
import org.jactr.core.production.condition.IBufferCondition;
import org.jactr.core.production.condition.ICondition;
import org.jactr.core.production.condition.match.IMatchFailure;
import org.jactr.core.production.condition.match.SlotMatchFailure;
import org.jactr.core.production.condition.match.UnresolvedVariablesMatchFailure;
import org.jactr.core.slot.IConditionalSlot;
import org.jactr.core.slot.INotifyingSlotContainer;
import org.jactr.core.slot.ISlot;
import org.jactr.core.slot.IUniqueSlotContainer;
import org.jactr.core.slot.NotifyingSlot;
import org.jactr.extensions.cached.procedural.invalidators.BufferInvalidator;
import org.jactr.extensions.cached.procedural.invalidators.IInvalidator;
import org.jactr.extensions.cached.procedural.invalidators.SlotInvalidator;
/**
* stores productions that cannot be instantiated given the current state, the
* {@link CannotInstantiateException}, and a set of Invalidators
*
* @author harrison
*/
public class InstantiationCache
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(InstantiationCache.class);
private Map<IProduction, CannotInstantiateException> _failedProductions;
private Map<IProduction, FastList<IInvalidator>> _invalidators;
private ListenerHub _listenerHub;
private final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock();
public InstantiationCache(IModel model)
{
_failedProductions = new HashMap<IProduction, CannotInstantiateException>();
_invalidators = new HashMap<IProduction, FastList<IInvalidator>>();
_listenerHub = new ListenerHub(model);
}
public void dispose()
{
try
{
_lock.writeLock().lock();
_listenerHub.dispose();
_invalidators.clear();
_failedProductions.clear();
}
finally
{
_lock.writeLock().unlock();
}
}
public Set<IProduction> getProductions(Set<IProduction> container)
{
try
{
_lock.readLock().lock();
container.addAll(_failedProductions.keySet());
}
finally
{
_lock.readLock().unlock();
}
return container;
}
public boolean contains(IProduction production)
{
boolean contained = false;
try
{
_lock.readLock().lock();
contained = _failedProductions.containsKey(production);
}
finally
{
_lock.readLock().unlock();
}
return contained;
}
public void remove(IProduction production)
{
FastList<IInvalidator> invalidators = null;
boolean shouldUnregister = false;
try
{
_lock.writeLock().lock();
/*
* do we need to unregister?
*/
if (_failedProductions.remove(production) != null)
{
shouldUnregister = true;
invalidators = _invalidators.remove(production);
}
}
finally
{
_lock.writeLock().unlock();
}
if (shouldUnregister)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Marking %s as potentially instantiable",
production));
unregisterAll(production, invalidators);
}
if (invalidators != null) FastList.recycle(invalidators);
}
public void add(IProduction production, CannotInstantiateException cie)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Testing %s after %s", production, cie));
FastList<IInvalidator> invalidators = FastList.newInstance();
boolean canCache = registerAll(production, cie, invalidators);
if (canCache)
{
try
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String
.format("Marking %s as uninstantiable", production));
_lock.writeLock().lock();
_failedProductions.put(production, cie);
_invalidators.put(production, invalidators);
}
finally
{
_lock.writeLock().unlock();
}
/*
* no need for this to be in the write lock
*/
// lack of thread safety here.
for (IInvalidator invalidator : invalidators)
invalidator.register(_listenerHub);
}
else
{
if (LOGGER.isDebugEnabled())
LOGGER
.debug(String
.format(
"%s could not be associated with any invalidators, it will be available for instantiation",
production));
FastList.recycle(invalidators);
}
}
public CannotInstantiateException get(IProduction production)
{
CannotInstantiateException cie = null;
try
{
_lock.readLock().lock();
cie = _failedProductions.get(production);
}
finally
{
_lock.readLock().unlock();
}
return cie;
}
public void throwIfCached(IProduction production)
throws CannotInstantiateException
{
CannotInstantiateException cie = null;
try
{
_lock.readLock().lock();
cie = _failedProductions.get(production);
}
finally
{
_lock.readLock().unlock();
}
if (cie != null)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("%s has a cached CIE %s", production, cie));
throw cie;
}
}
/**
* @param production
*/
protected void unregisterAll(IProduction production,
Collection<IInvalidator> invalidators)
{
if (invalidators == null) return;
for (IInvalidator invalidator : invalidators)
if (invalidator != null) invalidator.unregister(_listenerHub);
}
/**
* analyze the cause of the exception and possibly install invalidators. if no
* invalidators can be created, this CIE cannot be cached.
*
* @param production
* @param cie
* @return
*/
protected boolean registerAll(IProduction production,
CannotInstantiateException cie, Collection<IInvalidator> invalidators)
{
/*
* so long as any CME has an IMatchFailure that can be associated with an
* Invalidator, we are good.
*/
try
{
for (CannotMatchException cme : cie.getExceptions())
{
IMatchFailure mf = cme.getMismatch();
if (mf == null) continue;
ICondition condition = mf.getCondition();
/*
* anything that matches against a buffer gets pegged to the buffer
* invalidator, but not exclusively
*/
if (condition instanceof IBufferCondition)
{
String name = ((IBufferCondition) mf.getCondition()).getBufferName();
invalidators.add(new BufferInvalidator(this, production, name));
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Creating invalidator for buffer %s",
name));
}
/*
* if there is an unresolved variable and it has a container, we use a
* slot listener for each slot name
*/
if (mf instanceof UnresolvedVariablesMatchFailure)
{
UnresolvedVariablesMatchFailure unmf = (UnresolvedVariablesMatchFailure) mf;
IUniqueSlotContainer container = unmf.getContainer();
/*
* if we cant track it, abort all tracking
*/
if (!(container instanceof INotifyingSlotContainer))
throw new IllegalStateException(String.format(
"Cannot track slot container %s for unresolved variables %s",
container, unmf.getUnresolvedSlots()));
/*
* attach
*/
for (IConditionalSlot cSlot : unmf.getUnresolvedSlots())
invalidators.add(new SlotInvalidator(this, production,
(INotifyingSlotContainer) container, cSlot.getName()));
}
/*
* for slot match failures, we attach to the slot container and listen
* to changes in that slot. If the slot refers to a variable, we need to
* find all references to that variable and listen to all of those slots
*/
if (mf instanceof SlotMatchFailure)
processSlotMatchFailure(production, (SlotMatchFailure) mf,
invalidators);
}
}
catch (IllegalStateException ise)
{
// thrown is something requires that this production not be cached.
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"Could not cache instantiation failure for %s", production), ise);
invalidators.clear();
}
return invalidators.size() > 0;
}
protected void processSlotMatchFailure(IProduction production,
SlotMatchFailure smf, Collection<IInvalidator> invalidators)
throws IllegalStateException
{
/*
* first the easy bit, we need an invalidator for the exact slot and
* container. If this value is null, then the slot does not exist in the
* container at all and we can't do anything else
*/
IConditionalSlot cSlot = smf.getConditionalSlot();
ISlot mSlot = smf.getMismatchedSlot();
if (mSlot == null)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"%s is not contained in %s, can never match", cSlot.getName(),
smf.getSlotContainer()));
return;
}
if (mSlot instanceof NotifyingSlot)
{
/*
* we use the slot's container, and not the one used in the test since it
* could be a proxy/delegate container that wraps multiple sources (i.e.,
* the motor buffer and associated muscle). By using the actual container,
* we are sure we are listening to the right piece
*/
INotifyingSlotContainer container = ((NotifyingSlot) mSlot)
.getContainer();
invalidators.add(new SlotInvalidator(this, production, container, mSlot
.getName()));
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Creating invalidator for %s.%s", container,
mSlot.getName()));
}
/*
* now we need to check for variable dependencies. That is, the original
* (unresolved) conditional slot may have referenced a variable. If so,
* SlotMatchFailure will contain a source. That source could be a
* IUniqueSlotContainer (and it a INotifyingSlotContainer we can track it),
* or a buffer.
*/
if (smf.involvedVariableValue())
{
Object variableSource = smf.getVariableDefinition();
if (variableSource == null)
throw new IllegalStateException(
"Could not find source of variable binding, cannot track");
if (variableSource instanceof IActivationBuffer)
{
String bufferName = ((IActivationBuffer) variableSource).getName();
invalidators.add(new BufferInvalidator(this, production, bufferName));
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"Creating invalidator to listen for variable change of %s",
bufferName));
}
else if (variableSource instanceof ISlot)
{
/*
* if it is a slot, we have to have a INotifyingSlotContainer, if not,
* we can't do anything..
*/
if (!(variableSource instanceof NotifyingSlot))
throw new IllegalStateException(
String
.format(
"Source of variable binding %s isn't a slot notifier, cannot track",
variableSource));
NotifyingSlot ns = (NotifyingSlot) variableSource;
INotifyingSlotContainer container = ns.getContainer();
invalidators.add(new SlotInvalidator(this, production, container, ns
.getName()));
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Creating invalidator for %s.%s",
container, ns.getName()));
}
}
}
}