org.jactr.core.module.declarative.basic.DefaultDeclarativeModule.java Source code

Java tutorial

Introduction

Here is the source code for org.jactr.core.module.declarative.basic.DefaultDeclarativeModule.java

Source

/*
 * Created on Aug 14, 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.declarative.basic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
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.BufferUtilities;
import org.jactr.core.buffer.IActivationBuffer;
import org.jactr.core.buffer.event.ActivationBufferEvent;
import org.jactr.core.buffer.event.IActivationBufferListener;
import org.jactr.core.chunk.ChunkActivationComparator;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.chunk.ISubsymbolicChunk;
import org.jactr.core.chunk.ISymbolicChunk;
import org.jactr.core.chunk.basic.AbstractChunk;
import org.jactr.core.chunk.event.ChunkEvent;
import org.jactr.core.chunk.five.DefaultChunk5;
import org.jactr.core.chunk.five.ISubsymbolicChunk5;
import org.jactr.core.chunktype.IChunkType;
import org.jactr.core.chunktype.ISymbolicChunkType;
import org.jactr.core.chunktype.five.DefaultChunkType5;
import org.jactr.core.concurrent.ExecutorServices;
import org.jactr.core.event.IParameterEvent;
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.declarative.IDeclarativeModule;
import org.jactr.core.module.declarative.search.local.DefaultSearchSystem;
import org.jactr.core.production.request.ChunkTypeRequest;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.core.slot.ChunkSlot;
import org.jactr.core.slot.ISlot;
import org.jactr.core.utils.StringUtilities;
import org.jactr.core.utils.parameter.IParameterized;

public class DefaultDeclarativeModule extends AbstractDeclarativeModule
        implements IDeclarativeModule, IParameterized {
    /**
     * logger definition
     */
    static final Log LOGGER = LogFactory.getLog(DefaultDeclarativeModule.class);

    static private final String COPIED_FROM_KEY = "CopiedFrom";

    static private final String SUSPEND_DISPOSAL_KEY = DefaultDeclarativeModule.class + ".suspendDisposal";

    /**
     * there is a grey area between the creation of a chunk and it's use in a
     * buffer or encoding. Most never encounter it, but it can occur in the time
     * between a perceptual search (i.e. visual-location) and encoding, where the
     * system may want to dispose of the chunk (i.e. the underlying percept has
     * changed too much) in order to create a new one. However, if the encoding
     * process has already started, it is possible that the system will try to add
     * the disposed chunk to a buffer.<br>
     * This mechanism is a recommendation only, that the declarative module can
     * use to temporarily suspend disposal.
     * 
     * @param chunk
     */
    static public void setDisposalSuspended(IChunk chunk, boolean suspend) {
        if (suspend)
            chunk.setMetaData(SUSPEND_DISPOSAL_KEY, Boolean.TRUE);
        else
            chunk.setMetaData(SUSPEND_DISPOSAL_KEY, null);
    }

    static public boolean isDisposalSuspended(IChunk chunk) {
        return Boolean.TRUE.equals(chunk.getMetaData(SUSPEND_DISPOSAL_KEY));
    }

    protected ReentrantReadWriteLock _chunkTypeLock;

    protected ReentrantReadWriteLock _chunkLock;

    protected DefaultSearchSystem _searchSystem;

    protected Map<String, IChunk> _allChunks;

    protected Map<String, IChunkType> _allChunkTypes;

    protected ChunkActivationComparator _activationSorter;

    protected List<IChunk> _chunksToDispose;

    /**
     * used to encode chunks after removal
     */
    protected IActivationBufferListener _encodeChunksOnRemove;

    /**
     * delayed chunks to encode in response to buffer remove
     */
    protected List<IChunk> _chunksToEncode;

    /**
     * we actually encode the chunks at the end of the cycle
     */
    protected IModelListener _chunkEncoder;

    public DefaultDeclarativeModule() {
        super("declarative");
        _allChunks = new TreeMap<String, IChunk>();
        _allChunkTypes = new TreeMap<String, IChunkType>();
        _activationSorter = new ChunkActivationComparator();

        _chunksToDispose = FastList.newInstance();

        _searchSystem = new DefaultSearchSystem(this);

        _chunkLock = new ReentrantReadWriteLock();
        _chunkTypeLock = new ReentrantReadWriteLock();

        _chunksToEncode = FastList.newInstance();

        _encodeChunksOnRemove = new IActivationBufferListener() {

            public void chunkMatched(ActivationBufferEvent abe) {
                // noop
            }

            public void requestAccepted(ActivationBufferEvent abe) {
                // noop

            }

            public void sourceChunkAdded(ActivationBufferEvent abe) {
                // noop

            }

            public void sourceChunkRemoved(ActivationBufferEvent abe) {
                /*
                 * queue up the encoding. we dont encode it here so that any inline
                 * listeners after this one will get the actual instance of the removed
                 * chunk and not the merged version after encoding (if a merge occurs)
                 */
                try {
                    _chunkLock.writeLock().lock();

                    if (!abe.getSource().handlesEncoding())
                        _chunksToEncode.addAll(abe.getSourceChunks());
                } finally {
                    _chunkLock.writeLock().unlock();
                }
            }

            public void sourceChunksCleared(ActivationBufferEvent abe) {
                sourceChunkRemoved(abe);
            }

            public void statusSlotChanged(ActivationBufferEvent abe) {
                // noop

            }

            @SuppressWarnings("unchecked")
            public void parameterChanged(IParameterEvent pe) {
                // noop

            }

        };

        _chunkEncoder = new ModelListenerAdaptor() {

            @Override
            public void cycleStopped(ModelEvent event) {
                /**
                 * encode those that need encoding and dispose of those that need
                 * disposing
                 */
                FastList<IChunk> chunkList = FastList.newInstance();

                // assignment, check thread safety
                try {
                    _chunkLock.writeLock().lock();
                    chunkList.addAll(_chunksToEncode);
                    _chunksToEncode.clear();
                } finally {
                    _chunkLock.writeLock().unlock();
                }

                FastList<IActivationBuffer> containingBuffers = FastList.newInstance();

                // fast, destructive iterator where processing order does not matter
                for (IChunk chunk = null; !chunkList.isEmpty() && (chunk = chunkList.removeLast()) != null;) {
                    /*
                     * because this chunk might get merged, effectively changing the lock
                     * instance, we do grab a reference to the lock temporarily
                     */
                    Lock lock = chunk.getWriteLock();
                    try {
                        lock.lock();
                        if (chunk.hasBeenDisposed())
                            continue;

                        if (chunk.isEncoded())
                            chunk.getSubsymbolicChunk().accessed(event.getSimulationTime());
                        else {
                            BufferUtilities.getContainingBuffers(chunk, true, containingBuffers);
                            if (containingBuffers.size() == 0)
                                addChunk(chunk);
                        }
                    } finally {
                        lock.unlock();
                        containingBuffers.clear();
                    }
                }

                /**
                 * now for the disposal
                 */
                // assignment, check thread safety
                try {
                    _chunkLock.writeLock().lock();
                    chunkList.addAll(_chunksToDispose);
                    _chunksToDispose.clear();
                } finally {
                    _chunkLock.writeLock().unlock();
                }

                // fast, destructive iterator where processing order does not matter
                for (IChunk chunk = null; !chunkList.isEmpty() && (chunk = chunkList.removeLast()) != null;)
                    try {
                        chunk.getWriteLock().lock();

                        if (chunk.isEncoded())
                            continue;
                        if (chunk.hasBeenDisposed())
                            continue;
                        // requeue
                        if (isDisposalSuspended(chunk))
                            dispose(chunk);
                        else {
                            BufferUtilities.getContainingBuffers(chunk, true, containingBuffers);

                            if (containingBuffers.size() == 0)
                                disposeInternal(chunk);
                        }
                    } finally {
                        containingBuffers.clear();
                        chunk.getWriteLock().unlock();
                    }

                FastList.recycle(containingBuffers);
                FastList.recycle(chunkList);
            }
        };
    }

    public boolean willEncode(IChunk chunk) {
        try {
            _chunkLock.readLock().lock();
            return _chunksToEncode.contains(chunk);
        } finally {
            _chunkLock.readLock().unlock();
        }
    }

    @Override
    synchronized public void dispose() {
        for (IActivationBuffer buffer : getModel().getActivationBuffers())
            buffer.removeListener(_encodeChunksOnRemove);

        _searchSystem.clear();
        _searchSystem = null;

        try {
            _chunkTypeLock.writeLock().lock();
            // dispose of all the chunktypes (and chunks by extension)
            for (IChunkType chunkType : _allChunkTypes.values())
                chunkType.dispose();

            _allChunkTypes.clear();
        } finally {
            _chunkTypeLock.writeLock().unlock();
        }
        try {
            _chunkLock.writeLock().lock();
            _allChunks.clear();
        } finally {
            _chunkLock.writeLock().unlock();
        }

        super.dispose();
    }

    protected IChunk merge(IChunk originalChunk, IChunk newChunk) {
        /**
         * double check
         */
        if (originalChunk.equals(newChunk))
            return originalChunk;

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("Merging new chunk " + newChunk + " into existing chunk " + originalChunk);

        String msg = null;

        if (Logger.hasLoggers(getModel()))
            msg = "Merged " + newChunk + " into " + StringUtilities.toString(originalChunk);

        originalChunk.dispatch(new ChunkEvent(originalChunk, ChunkEvent.Type.MERGING_WITH, newChunk));

        newChunk.dispatch(new ChunkEvent(newChunk, ChunkEvent.Type.MERGING_INTO, originalChunk));
        try {
            _chunkLock.writeLock().lock();
            mergeChunks(originalChunk, newChunk);
        } finally {
            _chunkLock.writeLock().unlock();
        }

        if (msg != null)
            Logger.log(getModel(), Logger.Stream.DECLARATIVE, msg);

        fireChunksMerged(originalChunk, newChunk);
        return originalChunk;
    }

    /**
     * actually do the work.
     * 
     * @param originalChunk
     * @param newChunk
     */
    protected void mergeChunks(IChunk originalChunk, IChunk newChunk) {
        if (LOGGER.isDebugEnabled())
            LOGGER.debug("Replacing the guts of " + newChunk + " with " + originalChunk);

        /*
         * ok, here we do something that is a little strange.. think of meta data -
         * who's metadata (which may be different) should take precedence? the new
         * or the old? Because the perceptual modules use the metadata to keep track
         * of connections to actual objects in the environment, we actually want to
         * copy the new metadata over the existing metadata
         */
        for (String meta : newChunk.getMetaDataKeys())
            originalChunk.setMetaData(meta, newChunk.getMetaData(meta));

        if (newChunk instanceof AbstractChunk)
            ((AbstractChunk) newChunk).replaceContents(originalChunk);
    }

    /**
     * actually perform the disposal
     * 
     * @param chunk
     */
    protected void disposeInternal(IChunk chunk) {
        try {
            if (LOGGER.isDebugEnabled())
                LOGGER.debug("Disposing of " + chunk);
            chunk.dispose();
        } catch (Exception e) {
            LOGGER.error("Failed to dispose of chunk " + chunk + " ", e);
        }
    }

    @Override
    protected IChunk addChunkInternal(IChunk chunk) {

        IChunkType chunkType = chunk.getSymbolicChunk().getChunkType();

        Collection<? extends ISlot> slots = chunk.getSymbolicChunk().getSlots();

        /*
         * we don't check for duplicates when a chunk has no slots these chunks are
         * often flags for some bit of simplistic knowledge such as "new"
         */
        if (slots.size() != 0) {
            Collection<IChunk> matches = _searchSystem.findExact(new ChunkTypeRequest(chunkType, slots),
                    _activationSorter);

            if (matches.size() > 0) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("chunk " + chunk + " has yielded " + matches.size() + " matches " + matches);

                return merge(matches.iterator().next(), chunk);
            } // matches.size() == 0
        } // slots.size() > 0

        /*
         * we're here, so either we aren't checking for duplicates or there was no
         * matching chunk
         */

        double now = ACTRRuntime.getRuntime().getClock(getModel()).getTime();

        boolean added = false;
        try {
            _chunkLock.writeLock().lock();

            String name = chunk.getSymbolicChunk().getName();
            String newName = getSafeName(name, _allChunks);

            if (LOGGER.isDebugEnabled())
                LOGGER.debug("Safe name for chunk " + name + " is " + newName);

            chunk.getSymbolicChunk().setName(newName);

            /*
             * notify the chunktype of this chunk
             */
            chunk.setMetaData(COPIED_FROM_KEY, null);
            chunk.getSymbolicChunk().getChunkType().getSymbolicChunkType().addChunk(chunk);

            _allChunks.put(newName.toLowerCase(), chunk);
            added = true;
            return chunk;
        } finally {
            _chunkLock.writeLock().unlock();

            // this will fire some chunk event.. so get out of the lock
            if (added) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("Encoding " + chunk);
                chunk.encode(now);

                if (Logger.hasLoggers(getModel()))
                    Logger.log(getModel(), Logger.Stream.DECLARATIVE, "Encoded " + StringUtilities.toString(chunk));
                /*
                 * we index after since an unencoded chunk wont be indexed
                 */
                _searchSystem.index(chunk);
                fireChunkAdded(chunk);
            }
        }
    }

    /**
     * create a callable that will do all the work of adding a chunktype to the
     * model and firing the appropriate events
     * 
     * @param chunkType
     * @return
     */
    @Override
    protected IChunkType addChunkTypeInternal(IChunkType chunkType) {

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("Adding chunkType " + chunkType);

        boolean added = false;
        try {
            _chunkTypeLock.writeLock().lock();
            ISymbolicChunkType sct = chunkType.getSymbolicChunkType();
            String name = sct.getName();
            name = getSafeName(name, _allChunkTypes);
            sct.setName(name);

            _allChunkTypes.put(name.toLowerCase(), chunkType);

            IChunkType parent = chunkType.getSymbolicChunkType().getParent();
            while (parent != null) {
                parent.getSymbolicChunkType().addChild(chunkType);
                parent = parent.getSymbolicChunkType().getParent();
            }

            added = true;
            return chunkType;
        } finally {
            _chunkTypeLock.writeLock().unlock();

            // this will fire an event, so get out of the lock
            if (added) {
                chunkType.encode();

                if (Logger.hasLoggers(getModel()))
                    Logger.log(getModel(), Logger.Stream.DECLARATIVE, "Encoded " + chunkType);
                fireChunkTypeAdded(chunkType);
            }
        }
    }

    @Override
    protected IChunk createChunkInternal(IChunkType parent, String name) {
        IChunk rtn = new DefaultChunk5(parent);
        rtn.getSymbolicChunk().setName(name);
        // rtn.addListener(new ChunkListenerAdaptor() {
        // @Override
        // public void slotChanged(ChunkEvent nce)
        // {
        // /*
        // * we only index encoded chunks..
        // */
        // IChunk source = nce.getSource();
        // if (source.isEncoded() && !source.isMutable())
        // _searchSystem.update(nce.getSource(), nce.getSlotName(), nce
        // .getOldSlotValue(), nce.getNewSlotValue());
        // }
        // }, getExecutor());
        fireChunkCreated(rtn);
        return rtn;
    }

    @Override
    protected IChunkType createChunkTypeInternal(IChunkType parent, String name) {
        IModel model = getModel();
        IChunkType rtn = null;

        if (parent != null)
            rtn = new DefaultChunkType5(model, parent);
        else
            rtn = new DefaultChunkType5(model);

        rtn.getSymbolicChunkType().setName(name);

        fireChunkTypeCreated(rtn);

        return rtn;
    }

    protected Collection<IChunk> findExactMatchesInternal(ChunkTypeRequest pattern, final Comparator<IChunk> sorter,
            double activationThreshold, boolean bestOne) {
        Collection<IChunk> candidates = _searchSystem.findExact(pattern, sorter);

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("find exact matches (" + pattern + ") evaluating " + candidates.size() + " candidates");

        StringBuilder logMessage = null;

        if (Logger.hasLoggers(getModel())) {
            logMessage = new StringBuilder("Evaluating exact search matches ");
            logMessage.append(candidates).append("\n ");
        }

        ArrayList<IChunk> finalChunks = new ArrayList<IChunk>();
        /*
         * we can't be sure that the sorting used is actually relevant to us so we
         * have to zip through the entire results
         */
        double highestActivation = Double.NEGATIVE_INFINITY;
        IChunk bestChunk = null;
        for (IChunk chunk : candidates) {
            /*
             * snag the activation and see if this is the highest chunk so far
             */
            double tmpAct = chunk.getSubsymbolicChunk().getActivation();
            if (tmpAct > highestActivation) {
                bestChunk = chunk;
                highestActivation = tmpAct;
                if (logMessage != null)
                    logMessage.append(bestChunk).append(" has highest activation (").append(highestActivation)
                            .append(")\n ");
            } else if (logMessage != null)
                logMessage.append(chunk).append(" doesn't have highest activation (").append(tmpAct).append(")\n ");

            /*
             * if we are selecting the best one only, don't add it to the list
             */
            if (!bestOne && tmpAct >= activationThreshold)
                finalChunks.add(chunk);
        }

        /*
         * here's the best one, assuming we only want one
         */
        if (bestOne && bestChunk != null && highestActivation >= activationThreshold)
            finalChunks.add(bestChunk);

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("find exact matches returning " + finalChunks);

        if (logMessage != null)
            Logger.log(getModel(), Logger.Stream.DECLARATIVE, logMessage.toString());

        return finalChunks;
    }

    protected Collection<IChunk> findPartialMatchesInternal(ChunkTypeRequest pattern, Comparator<IChunk> sorter,
            double activationThreshold, boolean bestOne) {
        Collection<IChunk> candidates = _searchSystem.findFuzzy(pattern, sorter);

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("find partial matches evaluating " + candidates.size() + " candidates");
        ArrayList<IChunk> finalChunks = new ArrayList<IChunk>();
        StringBuilder logMessage = null;
        if (Logger.hasLoggers(getModel())) {
            logMessage = new StringBuilder("Evaluating partial search matches ");
            logMessage.append(candidates).append("\n ");
        }

        /*
         * we can't be sure that the sorting used is actually relevant to us so we
         * have to zip through the entire results
         */
        double highestActivation = Double.NEGATIVE_INFINITY;
        IChunk bestChunk = null;
        for (IChunk chunk : candidates) {
            /*
             * snag the activation and see if this is the highest chunk so far we need
             * to use the pattern to evaluate mismatch with activation
             */
            double tmpAct = ((ISubsymbolicChunk5) chunk.getSubsymbolicChunk()).getActivation(pattern);
            if (tmpAct > highestActivation) {
                bestChunk = chunk;
                highestActivation = tmpAct;
                if (logMessage != null)
                    logMessage.append(bestChunk).append(" has highest activation (").append(highestActivation)
                            .append(")\n ");
            } else if (logMessage != null)
                logMessage.append(chunk).append(" doesn't have highest activation (").append(tmpAct).append(")\n ");

            /*
             * if we are selecting the best one only, don't add it to the list
             */
            if (!bestOne && tmpAct >= activationThreshold)
                finalChunks.add(chunk);
        }

        /*
         * here's the best one, assuming we only want one
         */
        if (bestOne && bestChunk != null && highestActivation >= activationThreshold)
            finalChunks.add(bestChunk);

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("find partial matches returning " + finalChunks);

        if (logMessage != null)
            Logger.log(getModel(), Logger.Stream.DECLARATIVE, logMessage.toString());

        return finalChunks;
    }

    @Override
    protected IChunk getChunkInternal(String name) {
        return _allChunks.get(name.toLowerCase());
    }

    @Override
    protected IChunkType getChunkTypeInternal(String name) {
        return _allChunkTypes.get(name.toLowerCase());
    }

    @Override
    protected Collection<IChunkType> getChunkTypesInternal() {
        return new ArrayList<IChunkType>(_allChunkTypes.values());
    }

    @Override
    protected Collection<IChunk> getChunksInternal() {
        return new ArrayList<IChunk>(_allChunks.values());
    }

    public long getNumberOfChunks() {
        try {
            _chunkLock.readLock().lock();
            return _allChunks.size();
        } finally {
            _chunkLock.readLock().unlock();
        }
    }

    /**
     * here we attach a buffer listener to all the buffers and catch the removal
     * notifications to see if we should encode the chunk..
     * 
     * @see org.jactr.core.module.AbstractModule#initialize()
     */
    @Override
    public void initialize() {
        super.initialize();
        IModel model = getModel();
        model.addListener(_chunkEncoder, ExecutorServices.INLINE_EXECUTOR);

        for (IActivationBuffer buffer : model.getActivationBuffers())
            buffer.addListener(_encodeChunksOnRemove, ExecutorServices.INLINE_EXECUTOR);
    }

    /**
     * @see org.jactr.core.module.declarative.six.AbstractDeclarativeModule#copyChunkInternal(org.jactr.core.chunk.IChunk,
     *      org.jactr.core.chunk.IChunk)
     */

    @Override
    protected void copyChunkInternal(IChunk sourceChunk, IChunk destination) {
        /*
         * copy the meta data
         */
        for (String key : sourceChunk.getMetaDataKeys())
            destination.setMetaData(key, sourceChunk.getMetaData(key));

        /*
         * set the symbolic contents
         */
        ISymbolicChunk sourceSC = sourceChunk.getSymbolicChunk();
        ISymbolicChunk destinationSC = destination.getSymbolicChunk();

        String newName = sourceSC.getName();
        try {
            _chunkLock.readLock().lock();
            newName = getSafeName(newName, _allChunks);
        } finally {
            _chunkLock.readLock().unlock();
        }

        destinationSC.setName(newName);

        for (ISlot slot : sourceSC.getSlots()) {
            // this is the actual backing slot..
            ChunkSlot cs = (ChunkSlot) destinationSC.getSlot(slot.getName());
            cs.setValue(slot.getValue());
        }

        /*
         * we need to deal with the subsymbolics since it will have associative
         * links necessary for the propogation of activation when a chunk copy is in
         * a buffer...
         */
        ISubsymbolicChunk destinationSSC = destination.getSubsymbolicChunk();

        /*
         * set all the parameters this should handle the associative links as
         * well...
         */
        for (String parameterName : destinationSSC.getSetableParameters()) {
            String parameterValue = destinationSSC.getParameter(parameterName);
            try {
                destinationSSC.setParameter(parameterName, parameterValue);
            } catch (Exception e) {
                LOGGER.warn("Could not set parameter " + parameterName + " to " + parameterValue, e);
            }
        }

        if (Logger.hasLoggers(getModel()))
            Logger.log(getModel(), Logger.Stream.DECLARATIVE, "Copied " + StringUtilities.toString(destination));

        destination.setMetaData(COPIED_FROM_KEY, sourceChunk);
    }

    /**
     * @see org.jactr.core.module.declarative.IDeclarativeModule#findExactMatches(ChunkTypeRequest,
     *      java.util.Comparator, double, boolean)
     */
    public Future<Collection<IChunk>> findExactMatches(final ChunkTypeRequest request,
            final Comparator<IChunk> sorter, final double activationThreshold, final boolean bestOne) {
        return delayedFuture(new Callable<Collection<IChunk>>() {

            public Collection<IChunk> call() throws Exception {
                return findExactMatchesInternal(request, sorter, activationThreshold, bestOne);
            }

        }, getExecutor());
    }

    /**
     * @see org.jactr.core.module.declarative.IDeclarativeModule#findPartialMatches(ChunkTypeRequest,
     *      java.util.Comparator, double, boolean)
     */
    public Future<Collection<IChunk>> findPartialMatches(final ChunkTypeRequest request,
            final Comparator<IChunk> sorter, final double activationThreshold, final boolean bestOne) {
        return delayedFuture(new Callable<Collection<IChunk>>() {

            public Collection<IChunk> call() throws Exception {
                return findPartialMatchesInternal(request, sorter, activationThreshold, bestOne);
            }

        }, getExecutor());
    }

    public void dispose(IChunk chunk) {
        try {
            _chunkLock.writeLock().lock();
            _chunksToDispose.add(chunk);
        } finally {
            _chunkLock.writeLock().unlock();
        }

    }

    public String getParameter(String key) {
        return null;
    }

    public Collection<String> getPossibleParameters() {
        return getSetableParameters();
    }

    public Collection<String> getSetableParameters() {
        return Collections.emptyList();
    }

    public void setParameter(String key, String value) {

    }

    public void reset() {// noop
    }

}