/*
* Created on Oct 12, 2006 Copyright (C) 2001-6, 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.module.retrieval.six;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
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.chunk.ChunkActivationComparator;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.event.ACTREventDispatcher;
import org.jactr.core.logging.Logger;
import org.jactr.core.module.AbstractModule;
import org.jactr.core.module.IllegalModuleStateException;
import org.jactr.core.module.declarative.IDeclarativeModule;
import org.jactr.core.module.declarative.four.IDeclarativeModule4;
import org.jactr.core.module.declarative.search.filter.ActivationFilter;
import org.jactr.core.module.declarative.search.filter.ActivationPolicy;
import org.jactr.core.module.declarative.search.filter.IChunkFilter;
import org.jactr.core.module.declarative.search.filter.ILoggedChunkFilter;
import org.jactr.core.module.declarative.search.filter.PartialMatchActivationFilter;
import org.jactr.core.module.retrieval.IRetrievalModule;
import org.jactr.core.module.retrieval.buffer.DefaultRetrievalBuffer6;
import org.jactr.core.module.retrieval.buffer.RetrievalRequestDelegate;
import org.jactr.core.module.retrieval.event.IRetrievalModuleListener;
import org.jactr.core.module.retrieval.event.RetrievalModuleEvent;
import org.jactr.core.module.retrieval.four.IRetrievalModule4;
import org.jactr.core.module.retrieval.time.DefaultRetrievalTimeEquation;
import org.jactr.core.module.retrieval.time.IRetrievalTimeEquation;
import org.jactr.core.production.request.ChunkRequest;
import org.jactr.core.production.request.ChunkTypeRequest;
import org.jactr.core.slot.IConditionalSlot;
import org.jactr.core.slot.ISlot;
import org.jactr.core.utils.collections.SkipListSetFactory;
import org.jactr.core.utils.parameter.IParameterized;
import org.jactr.core.utils.parameter.ParameterHandler;
/**
* default retrieval buffer
*
* @see http://jactr.org/node/33
* @author harrison
*/
public class DefaultRetrievalModule6 extends AbstractModule implements
IRetrievalModule4, IParameterized
{
/**
* logger definition
*/
static private final Log LOGGER = LogFactory
.getLog(DefaultRetrievalModule6.class);
static public final String INDEXED_RETRIEVALS_ENABLED_PARAM = "EnableIndexedRetrievals";
static public final String RECENTLY_RETRIEVED_SLOT = ":recently-retrieved";
static public final String RETRIEVAL_THRESHOLD_SLOT = ":retrievalThreshold";
static public final String PARTIAL_MATCH_SLOT = ":partialMatch";
static public final String ACCESSIBILITY_SLOT = ":accessibility";
static public final String RETRIEVAL_TIME_SLOT = ":retrievalTime";
static public final String INDEXED_RETRIEVAL_SLOT = ":indexedRetrieval";
private double _retrievalThreshold = Double.NEGATIVE_INFINITY;
private double _latencyFactor = 1;
private double _latencyExponent = 1;
private boolean _indexedRetrievalsEnabled = false;
private ACTREventDispatcher<IRetrievalModule, IRetrievalModuleListener> _eventDispatcher;
private DefaultRetrievalBuffer6 _retrievalBuffer;
private IRetrievalTimeEquation _retrievalTimeEquation;
private DeclarativeFINSTManager _finstManager;
private IChunk _resetChunk;
static final protected ChunkActivationComparator _activationSorter = new ChunkActivationComparator();
public DefaultRetrievalModule6()
{
this(IActivationBuffer.RETRIEVAL);
}
protected DefaultRetrievalModule6(String moduleName)
{
super(moduleName);
_eventDispatcher = new ACTREventDispatcher<IRetrievalModule, IRetrievalModuleListener>();
_retrievalTimeEquation = new DefaultRetrievalTimeEquation(this);
setFINSTManager(new DeclarativeFINSTManager(this));
}
@Override
public void dispose()
{
super.dispose();
_eventDispatcher.clear();
_retrievalBuffer.dispose();
_retrievalTimeEquation = null;
}
protected @Override Collection<IActivationBuffer> createBuffers()
{
_retrievalBuffer = new DefaultRetrievalBuffer6(getName(), this);
return Collections.singleton((IActivationBuffer) _retrievalBuffer);
}
public DeclarativeFINSTManager getFINSTManager()
{
return _finstManager;
}
public void setFINSTManager(DeclarativeFINSTManager manager)
{
if (manager == null)
throw new IllegalArgumentException("FINST manager cannot be null");
_finstManager = manager;
}
public boolean hasBeenRetrieved(IChunk chunk)
{
return _finstManager.hasBeenRetrieved(chunk);
}
public boolean isIndexedRetrievalEnabled()
{
return _indexedRetrievalsEnabled;
}
public void setIndexedRetrievalEnabled(boolean enabled)
{
_indexedRetrievalsEnabled = enabled;
}
public double getRetrievalThreshold()
{
return _retrievalThreshold;
}
private boolean isPartialMatchingEnabled(IDeclarativeModule dm,
Collection<? extends ISlot> slots)
{
dm = dm.getAdapter(IDeclarativeModule4.class);
if (dm instanceof IDeclarativeModule4)
return RetrievalRequestDelegate.isPartialMatchEnabled(
(IDeclarativeModule4) dm, slots);
return false;
}
private double getThreshold(Collection<? extends ISlot> slots)
{
return RetrievalRequestDelegate.getThreshold(this, slots);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected IChunk retrieveChunkInternal(IDeclarativeModule dm,
ChunkTypeRequest pattern) throws ExecutionException, InterruptedException
{
Future<Collection<IChunk>> fromDM = null;
fireInitiated(pattern);
FastList<ISlot> slots = FastList.newInstance();
pattern.getSlots(slots);
// this must be vestigial code, but from when?
// for (IConditionalSlot cSlot : pattern.getConditionalSlots())
// if (cSlot.getName().equals(RECENTLY_RETRIEVED_SLOT)) break;
ChunkTypeRequest cleanedPattern = cleanPattern(pattern);
_activationSorter.setChunkTypeRequest(null);
IChunkFilter filter = null;
double threshold = getThreshold(slots);
ActivationPolicy accessibility = RetrievalRequestDelegate
.getActivationPolicy(ACCESSIBILITY_SLOT, slots);
boolean wasIndexed = RetrievalRequestDelegate.isIndexRetrievalEnabled(
dm.getAdapter(DefaultRetrievalModule6.class), slots);
if (isPartialMatchingEnabled(dm, slots))
{
/**
* set the reference pattern for partial matching discounting, which is
* actually done in the comparator.
*/
_activationSorter.setChunkTypeRequest(cleanedPattern);
filter = new PartialMatchActivationFilter(accessibility, cleanedPattern,
threshold, Logger.hasLoggers(getModel()));
fromDM = dm.findPartialMatches(cleanedPattern, _activationSorter, filter);
}
else
{
filter = new ActivationFilter(accessibility, threshold,
Logger.hasLoggers(getModel()));
fromDM = dm.findExactMatches(cleanedPattern, _activationSorter, filter);
}
FastList.recycle(slots);
Collection<IChunk> results = fromDM.get();
/*
* snag the message from the filter.
*/
if (filter instanceof ILoggedChunkFilter)
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.RETRIEVAL,
((ILoggedChunkFilter) filter).getMessageBuilder().toString());
IChunk retrievalResult = selectRetrieval(results, dm.getErrorChunk(),
pattern, cleanedPattern);
// we should check to see if this is an indexed, as their time is immediate
double retrievalTime = 0;
if (!wasIndexed)
retrievalTime = getRetrievalTimeEquation().computeRetrievalTime(
retrievalResult, pattern);
fireCompleted(pattern, retrievalResult, retrievalTime, results);
// now we can recycle the collection
if (results instanceof ConcurrentSkipListSet)
SkipListSetFactory.recycle((ConcurrentSkipListSet) results);
return retrievalResult;
}
/**
* strip pattern of all the meta-slots
*
* @param pattern
* @return
*/
private ChunkTypeRequest cleanPattern(ChunkTypeRequest pattern)
{
FastList<ISlot> slots = FastList.newInstance();
pattern.getSlots(slots);
FastList<ISlot> cleanSlots = FastList.newInstance();
for (ISlot cSlot : slots)
if (!cSlot.getName().startsWith(":")) cleanSlots.add(cSlot);
if (cleanSlots.size() != slots.size())
if (pattern instanceof ChunkTypeRequest)
pattern = new ChunkTypeRequest(pattern.getChunkType(), cleanSlots);
else if (pattern instanceof ChunkRequest)
pattern = new ChunkRequest(((ChunkRequest) pattern).getChunk(),
cleanSlots);
return pattern;
}
/**
* choose the best matching result from the colleciton
*
* @param results
* @param errorChunk
* @param originalRequest
* @return
*/
protected IChunk selectRetrieval(Collection<IChunk> results,
IChunk errorChunk, ChunkTypeRequest originalRequest,
ChunkTypeRequest cleanedRequest)
{
if (results.size() == 0) return errorChunk;
/*
* now we need to check to see if the pattern included recently retrieved
*/
boolean recentlySpecified = false;
boolean ignoreRecent = false;
for (IConditionalSlot cSlot : originalRequest.getConditionalSlots())
if (cSlot.getName().equals(RECENTLY_RETRIEVED_SLOT))
{
recentlySpecified = true;
/*
* null, false, or != true results in ignoring recent
*/
Object value = cSlot.getValue();
/*
* crappy compatibility support for clearing.. the prefered method is to
* use +retrieval> isa clear full true
*/
if (_resetChunk.equals(value)
&& cSlot.getCondition() == IConditionalSlot.EQUALS)
{
_finstManager.clearRecentRetrievals();
recentlySpecified = false;
if (Logger.hasLoggers(getModel()))
Logger
.log(
getModel(),
Logger.Stream.RETRIEVAL,
String
.format(
"%s reset is archaic, use +retrieval> isa clear full t instead",
RECENTLY_RETRIEVED_SLOT));
}
else
{
ignoreRecent = cSlot.getCondition() == IConditionalSlot.EQUALS
&& (value == null || Boolean.FALSE.equals(value))
|| cSlot.getCondition() == IConditionalSlot.NOT_EQUALS
&& Boolean.TRUE.equals(value);
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("%s ignoring recently retrieved %s",
cSlot, ignoreRecent));
}
}
// just return the most active
if (!recentlySpecified) return results.iterator().next();
// now we have to iterate and filter
for (IChunk chunk : results)
if (ignoreRecent)
{
if (!_finstManager.hasBeenRetrieved(chunk))
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"%s has not been recently retrieved, returning", chunk));
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.RETRIEVAL, String.format(
"%s was not recently retrieved. Selecting.", chunk));
return chunk;
}
else
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"%s has been recently retrieved, ignoring", chunk));
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.RETRIEVAL,
String.format("%s was recently retrieved. Ignoring.", chunk));
}
}
else if (_finstManager.hasBeenRetrieved(chunk))
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"%s has been recently retrieved. Selecting.", chunk));
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.RETRIEVAL,
String.format("%s was recently retrieved. Selecting.", chunk));
return chunk;
}
else
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"%s has not been recently retrieved, ignoring", chunk));
if (Logger.hasLoggers(getModel()))
Logger.log(getModel(), Logger.Stream.RETRIEVAL,
String.format("%s was not recently retrieved. Ignoring.", chunk));
}
if (LOGGER.isDebugEnabled())
LOGGER
.debug(String
.format("No results matched the recently retrieved specification, returning error"));
return errorChunk;
}
public CompletableFuture<IChunk> retrieveChunk(
final ChunkTypeRequest chunkPattern)
{
return delayedFuture(new Callable<IChunk>() {
public IChunk call() throws Exception
{
return retrieveChunkInternal(getModel().getDeclarativeModule(),
chunkPattern);
}
}, getExecutor());
}
public void setRetrievalThreshold(double threshold)
{
_retrievalThreshold = threshold;
}
protected void fireInitiated(ChunkTypeRequest pattern)
{
_eventDispatcher.fire(new RetrievalModuleEvent(this, pattern));
}
protected void fireCompleted(ChunkTypeRequest pattern, IChunk chunk,
double retrievalTime, Collection<IChunk> allCandidates)
{
_eventDispatcher.fire(new RetrievalModuleEvent(this, pattern, chunk,
retrievalTime, allCandidates));
}
public void addListener(IRetrievalModuleListener listener, Executor executor)
{
_eventDispatcher.addListener(listener, executor);
}
public void removeListener(IRetrievalModuleListener listener)
{
_eventDispatcher.removeListener(listener);
}
@Override
public void initialize()
{
try
{
_resetChunk = getModel().getDeclarativeModule().getChunk("reset").get();
}
catch (Exception e)
{
throw new IllegalModuleStateException("Could not get reset chunk", e);
}
_finstManager.setErrorChunk(getModel().getDeclarativeModule()
.getErrorChunk());
}
public IRetrievalTimeEquation getRetrievalTimeEquation()
{
return _retrievalTimeEquation;
}
public double getLatencyExponent()
{
return _latencyExponent;
}
public double getLatencyFactor()
{
return _latencyFactor;
}
public void setLatencyExponent(double exp)
{
_latencyExponent = exp;
}
public void setLatencyFactor(double fact)
{
_latencyFactor = fact;
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getParameter(java.lang.String)
*/
public String getParameter(String key)
{
if (RETRIEVAL_THRESHOLD.equalsIgnoreCase(key))
return "" + getRetrievalThreshold();
if (LATENCY_EXPONENT.equalsIgnoreCase(key))
return "" + getLatencyExponent();
if (LATENCY_FACTOR.equalsIgnoreCase(key)) return "" + getLatencyFactor();
if (INDEXED_RETRIEVALS_ENABLED_PARAM.equalsIgnoreCase(key))
return "" + isIndexedRetrievalEnabled();
if (DeclarativeFINSTManager.FINST_DURATION_PARAM.equalsIgnoreCase(key))
return "" + _finstManager.getFINSTDuration();
if (DeclarativeFINSTManager.NUMBER_OF_FINSTS_PARAM.equalsIgnoreCase(key))
return "" + _finstManager.getNumberOfFINSTs();
return null;
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getPossibleParameters()
*/
public Collection<String> getPossibleParameters()
{
ArrayList<String> rtn = new ArrayList<String>();
rtn.add(RETRIEVAL_THRESHOLD);
rtn.add(LATENCY_EXPONENT);
rtn.add(LATENCY_FACTOR);
rtn.add(INDEXED_RETRIEVALS_ENABLED_PARAM);
rtn.add(DeclarativeFINSTManager.FINST_DURATION_PARAM);
rtn.add(DeclarativeFINSTManager.NUMBER_OF_FINSTS_PARAM);
return rtn;
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#getSetableParameters()
*/
public Collection<String> getSetableParameters()
{
return getPossibleParameters();
}
/**
* @see org.jactr.core.utils.parameter.IParameterized#setParameter(java.lang.String,
* java.lang.String)
*/
public void setParameter(String key, String value)
{
if (RETRIEVAL_THRESHOLD.equalsIgnoreCase(key))
setRetrievalThreshold(ParameterHandler.numberInstance().coerce(value)
.doubleValue());
else if (LATENCY_EXPONENT.equalsIgnoreCase(key))
setLatencyExponent(ParameterHandler.numberInstance().coerce(value)
.doubleValue());
else if (LATENCY_FACTOR.equalsIgnoreCase(key))
setLatencyFactor(ParameterHandler.numberInstance().coerce(value)
.doubleValue());
else if (INDEXED_RETRIEVALS_ENABLED_PARAM.equalsIgnoreCase(key))
setIndexedRetrievalEnabled(ParameterHandler.booleanInstance()
.coerce(value).booleanValue());
else if (DeclarativeFINSTManager.FINST_DURATION_PARAM.equalsIgnoreCase(key))
_finstManager.setFINSTDuration(ParameterHandler.numberInstance()
.coerce(value).doubleValue());
else if (DeclarativeFINSTManager.NUMBER_OF_FINSTS_PARAM
.equalsIgnoreCase(key))
_finstManager.setNumberOfFINSTs(ParameterHandler.numberInstance()
.coerce(value).intValue());
else if (LOGGER.isWarnEnabled())
LOGGER.warn(String.format(
"%s doesn't recognize %s. Available parameters : %s", getClass()
.getSimpleName(), key, getSetableParameters()));
}
public void reset()
{
reset(false);
}
public void reset(boolean resetFinsts)
{
_retrievalBuffer.clear();
if (resetFinsts) _finstManager.clearRecentRetrievals();
}
}