org.jactr.modules.pm.common.memory.map.DefaultFINSTFeatureMap.java Source code

Java tutorial

Introduction

Here is the source code for org.jactr.modules.pm.common.memory.map.DefaultFINSTFeatureMap.java

Source

/*
 * Created on Jun 26, 2007 Copyright (C) 2001-2007, 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.modules.pm.common.memory.map;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javolution.util.FastSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.commonreality.identifier.IIdentifier;
import org.commonreality.object.IAfferentObject;
import org.commonreality.object.delta.IObjectDelta;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.event.ACTREventDispatcher;
import org.jactr.core.model.IModel;
import org.jactr.core.production.request.ChunkTypeRequest;
import org.jactr.core.queue.timedevents.AbstractTimedEvent;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.core.slot.BasicSlot;
import org.jactr.core.slot.IConditionalSlot;
import org.jactr.core.slot.ISlot;
import org.jactr.modules.pm.common.memory.IPerceptualMemory;

/**
 * FINST tracking feature map that is used by both the aural and visual modules.
 * Unlike all the other {@link IFeatureMap}s, this one is accessed by both the
 * common reality and model threads. As such, it is necessary to use a
 * {@link ReentrantReadWriteLock} to protect the data. Commonreality accesses
 * this feature map (like all the other feature maps) through the
 * {@link #afferentObjectAdded(IAfferentObject)},
 * {@link #afferentObjectRemoved(IAfferentObject)},
 * {@link #afferentObjectUpdated(IAfferentObject, IObjectDelta)} methods. The
 * model thread accesses it indirectly through the {@link FlagAsOld} timed event
 * which is posted by any call to
 * {@link #flagAsAttended(IIdentifier, IChunk, double)},
 * {@link #flagAsNew(IIdentifier, IChunk, double)}, or
 * {@link #flagAsOld(IIdentifier, IChunk)}.<br>
 * <br>
 * Without this thread synchronization, deadlock or concurrent modifications
 * would occur.<br>
 * <br>
 * There is still much improvement that can be made in terms of the granularity
 * and speed, the current thread safety is course and rather slow. [the old,
 * unprotected version ran allowed models to run at 500x real time, this version
 * brings it down to 250x]
 * <br>
 * <br>
 * Note: this does not fire events
 * 
 * @author developer
 */
public class DefaultFINSTFeatureMap implements IFINSTFeatureMap {
    /**
     * logger definition
     */
    static private final Log LOGGER = LogFactory.getLog(DefaultFINSTFeatureMap.class);

    private int _maximumFINSTs;

    private String _attendedSlotName;

    IModel _model;

    IChunk _newChunk;

    Map<IIdentifier, FINST> _attendedIdentifiers;

    Map<IIdentifier, FINST> _oldIdentifiers;

    Map<IIdentifier, FINST> _newIdentifiers;

    @SuppressWarnings("unchecked")
    private ACTREventDispatcher<IFeatureMap, IFeatureMapListener> _dispatcher = new ACTREventDispatcher<IFeatureMap, IFeatureMapListener>();

    private IPerceptualMemory _memory;

    private ReentrantReadWriteLock _lock = new ReentrantReadWriteLock();

    public DefaultFINSTFeatureMap(IModel model, String attendedSlotName) {
        _attendedSlotName = attendedSlotName;
        _model = model;
        _newChunk = model.getDeclarativeModule().getNewChunk();

        _newIdentifiers = new HashMap<IIdentifier, FINST>();
        _oldIdentifiers = new HashMap<IIdentifier, FINST>();
        _attendedIdentifiers = new HashMap<IIdentifier, FINST>();
    }

    public void addListener(IFeatureMapListener listener, Executor executor) {
        _dispatcher.addListener(listener, executor);
    }

    public void removeListener(IFeatureMapListener listener) {
        _dispatcher.removeListener(listener);
    }

    protected boolean hasListeners() {
        return _dispatcher.hasListeners();
    }

    protected void dispatch(FeatureMapEvent event) {
        _dispatcher.fire(event);
    }

    public void setPerceptualMemory(IPerceptualMemory memory) {
        _memory = memory;
    }

    public IPerceptualMemory getPerceptualMemory() {
        return _memory;
    }

    /**
     * @return
     * @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#getNew()
     */
    public void getNew(Set<IIdentifier> destination) {
        try {
            _lock.readLock().lock();
            destination.addAll(_newIdentifiers.keySet());
        } finally {
            _lock.readLock().unlock();
        }
    }

    public boolean isNew(IIdentifier identifier) {
        try {
            _lock.readLock().lock();
            return _newIdentifiers.containsKey(identifier);
        } finally {
            _lock.readLock().unlock();
        }
    }

    public void getOld(Set<IIdentifier> destination) {
        try {
            _lock.readLock().lock();

            destination.addAll(_oldIdentifiers.keySet());
        } finally {
            _lock.readLock().unlock();
        }
    }

    public boolean isOld(IIdentifier identifier) {
        try {
            _lock.readLock().lock();

            return _oldIdentifiers.containsKey(identifier);
        } finally {
            _lock.readLock().unlock();
        }
    }

    public void getAttended(Set<IIdentifier> destination) {
        try {
            _lock.readLock().lock();

            destination.addAll(_attendedIdentifiers.keySet());
        } finally {
            _lock.readLock().unlock();
        }
    }

    public boolean isAttended(IIdentifier identifier) {
        try {
            _lock.readLock().lock();

            return _attendedIdentifiers.containsKey(identifier);
        } finally {
            _lock.readLock().unlock();
        }
    }

    protected FINST getFINST(IIdentifier identifier) {
        try {
            _lock.readLock().lock();

            FINST finst = _newIdentifiers.get(identifier);
            if (finst == null)
                finst = _oldIdentifiers.get(identifier);
            if (finst == null)
                finst = _attendedIdentifiers.get(identifier);
            return finst;
        } finally {
            _lock.readLock().unlock();
        }
    }

    public int getMaximumFINSTs() {
        return _maximumFINSTs;
    }

    public void setMaximumFINSTs(int max) {
        _maximumFINSTs = max;
    }

    private void assignFINST(FINST finst) {
        IIdentifier identifier = finst.getIdentifier();
        if (isAttended(identifier)) {
            if (LOGGER.isDebugEnabled())
                LOGGER.debug(identifier + " already has a finst assigned");
            // do nothing..
            return;
        }

        try {
            _lock.writeLock().lock();

            while (_attendedIdentifiers.size() >= getMaximumFINSTs()) {
                FINST oldFinst = findOldestFINST();
                if (oldFinst != null) {
                    if (LOGGER.isDebugEnabled())
                        LOGGER.debug("FINST limit reached, removing oldest : " + oldFinst.getIdentifier());
                    flagAsOld(oldFinst.getIdentifier(), oldFinst.getAfferentChunk());
                }
            }

            _attendedIdentifiers.put(identifier, finst);
        } finally {
            _lock.writeLock().unlock();
        }

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("assigning finst to " + identifier);
    }

    private FINST findOldestFINST() {
        try {
            _lock.readLock().lock();

            FINST oldest = null;
            double time = Double.POSITIVE_INFINITY;
            for (FINST finst : _attendedIdentifiers.values())
                if (finst.getTime() < time)
                    oldest = finst;
            return oldest;
        } finally {
            _lock.readLock().unlock();
        }
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#flagAsAttended(org.commonreality.identifier.IIdentifier,
     *      org.jactr.core.chunk.IChunk)
     */
    public void flagAsAttended(IIdentifier identifier, IChunk chunk, double duration) {
        FINST finst = null;

        try {
            _lock.writeLock().lock();

            finst = removeFINST(identifier);

            if (finst == null)
                finst = _newIdentifiers.remove(identifier);

            // already attended?
            if (finst == null)
                finst = removeFINST(identifier);

            if (finst == null) {
                LOGGER.warn("Flagging " + identifier + " as attended, but it was never new or old");
                finst = new FINST(identifier, chunk);
            }

            finst.setAfferentChunk(chunk);

            assignFINST(finst);

            if (LOGGER.isDebugEnabled())
                LOGGER.debug("Old: " + _oldIdentifiers.keySet() + " New: " + _newIdentifiers.keySet() + " Att:"
                        + _attendedIdentifiers.keySet());
        } finally {
            _lock.writeLock().unlock();
        }

        FlagAsOld fao = this.new FlagAsOld(identifier, chunk, duration);
        finst.setFlagAsOld(fao);

        // add the timed event to expire the attended
        _model.getTimedEventQueue().enqueue(fao);

        if (hasListeners())
            dispatch(new FeatureMapEvent(this,
                    ACTRRuntime.getRuntime().getClock(getPerceptualMemory().getModule().getModel()).getTime(),
                    FeatureMapEvent.Type.UPDATED, Collections.singleton(identifier)));
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#flagAsNew(org.commonreality.identifier.IIdentifier,
     *      org.jactr.core.chunk.IChunk)
     */
    public void flagAsNew(IIdentifier identifier, IChunk chunk, double duration) {
        FINST finst = null;
        try {
            _lock.writeLock().lock();

            finst = removeFINST(identifier);

            if (finst == null) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("Created new finst object to track " + identifier);
                finst = this.new FINST(identifier, chunk);
            } else
                finst.setAfferentChunk(chunk);

            _newIdentifiers.put(identifier, finst);

            if (LOGGER.isDebugEnabled())
                LOGGER.debug("Old: " + _oldIdentifiers.keySet() + " New: " + _newIdentifiers.keySet() + " Att:"
                        + _attendedIdentifiers.keySet());

        } finally {
            _lock.writeLock().unlock();
        }

        FlagAsOld fao = this.new FlagAsOld(identifier, chunk, duration);
        finst.setFlagAsOld(fao);

        // add the timed event to expire the new
        _model.getTimedEventQueue().enqueue(fao);

        if (hasListeners())
            dispatch(new FeatureMapEvent(this,
                    ACTRRuntime.getRuntime().getClock(getPerceptualMemory().getModule().getModel()).getTime(),
                    FeatureMapEvent.Type.UPDATED, Collections.singleton(identifier)));
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#flagAsOld(org.commonreality.identifier.IIdentifier,
     *      org.jactr.core.chunk.IChunk)
     */
    public void flagAsOld(IIdentifier identifier, IChunk chunk) {
        try {
            _lock.writeLock().lock();

            FINST finst = removeFINST(identifier);

            if (finst == null) {
                LOGGER.warn("Flagging " + identifier + " as old, but it was never new or attended");
                finst = new FINST(identifier, chunk);
            }

            finst.setFlagAsOld(null);
            _oldIdentifiers.put(identifier, finst);

            if (LOGGER.isDebugEnabled())
                LOGGER.debug("Old: " + _oldIdentifiers.keySet() + " New: " + _newIdentifiers.keySet() + " Att:"
                        + _attendedIdentifiers.keySet());
        } finally {
            _lock.writeLock().unlock();
        }

        if (hasListeners())
            dispatch(new FeatureMapEvent(this,
                    ACTRRuntime.getRuntime().getClock(getPerceptualMemory().getModule().getModel()).getTime(),
                    FeatureMapEvent.Type.UPDATED, Collections.singleton(identifier)));
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#reset()
     */
    public void reset() {
        Collection<FINST> finsts = null;
        try {
            _lock.readLock().lock();
            finsts = new ArrayList<FINST>(_attendedIdentifiers.values());

        } finally {
            _lock.readLock().unlock();
        }

        for (FINST finst : finsts)
            flagAsOld(finst.getIdentifier(), finst.getAfferentChunk());
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFeatureMap#clear()
     */
    public void clear() {
        try {
            _lock.writeLock().lock();

            _newIdentifiers.clear();
            _oldIdentifiers.clear();
            _attendedIdentifiers.clear();
        } finally {
            _lock.writeLock().unlock();
        }
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFeatureMap#dispose()
     */
    public void dispose() {
        clear();
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFeatureMap#fillSlotValues(ChunkTypeRequest,
     *      org.commonreality.identifier.IIdentifier, IChunk, ChunkTypeRequest)
     */
    public void fillSlotValues(ChunkTypeRequest mutableRequest, IIdentifier identifier, IChunk encodedChunk,
            ChunkTypeRequest originalSearchRequest) {
        boolean isNew = false;
        boolean isAttended = false;

        FINST finst = getFINST(identifier);

        isNew = isNew(identifier);
        isAttended = isAttended(identifier);

        if (finst != null)
            if (isNew)
                mutableRequest.addSlot(new BasicSlot(_attendedSlotName, _newChunk));
            else if (isAttended)
                mutableRequest.addSlot(new BasicSlot(_attendedSlotName, Boolean.TRUE));
            else
                mutableRequest.addSlot(new BasicSlot(_attendedSlotName, null));
    }

    public FINSTState getInformation(IIdentifier identifier) {
        try {
            _lock.readLock().lock();
            if (_newIdentifiers.containsKey(identifier))
                return FINSTState.NEW;
            if (_oldIdentifiers.containsKey(identifier))
                return FINSTState.OLD;
            if (_attendedIdentifiers.containsKey(identifier))
                return FINSTState.ATTENDED;

            return FINSTState.UNKNOWN;
        } finally {
            _lock.readLock().unlock();
        }
    }

    /**
     * @see org.jactr.modules.pm.common.memory.map.IFeatureMap#getCandidateRealObjects(ChunkTypeRequest,
     *      Set)
     */
    public void getCandidateRealObjects(ChunkTypeRequest request, Set<IIdentifier> container) {

        FastSet<IIdentifier> tmp = FastSet.newInstance();
        boolean firstInsertion = true;
        for (IConditionalSlot slot : request.getConditionalSlots())
            if (slot.getName().equalsIgnoreCase(_attendedSlotName)) {
                tmp.clear();
                Object value = slot.getValue();
                switch (slot.getCondition()) {
                case IConditionalSlot.NOT_EQUALS:
                    not(value, tmp);
                    break;
                default:
                    equals(value, tmp);
                    break;
                }

                if (firstInsertion) {
                    container.addAll(tmp);
                    firstInsertion = false;
                } else
                    container.retainAll(tmp);
            }

        FastSet.recycle(tmp);
    }

    private void equals(Object value, Set<IIdentifier> container) {
        if (_newChunk.equals(value))
            getNew(container);
        else if (Boolean.TRUE.equals(value))
            getAttended(container);
        else if (Boolean.FALSE.equals(value) || value == null)
            getOld(container);

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("==" + value + " returning " + container);
    }

    private void not(Object value, Set<IIdentifier> container) {
        if (_newChunk.equals(value)) {
            // all but new
            getAttended(container);
            getOld(container);
        } else if (Boolean.TRUE.equals(value)) {
            getOld(container);
            getNew(container);
        } else if (Boolean.FALSE.equals(value) || value == null) {
            getNew(container);
            getAttended(container);
        }

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("!=" + value + " returning " + container);
    }

    /**
     * @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#afferentObjectAdded(org.commonreality.object.IAfferentObject)
     */
    public void afferentObjectAdded(IAfferentObject object) {
        // NoOp
    }

    /**
     * @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#afferentObjectRemoved(org.commonreality.object.IAfferentObject)
     */
    public void afferentObjectRemoved(IAfferentObject object) {
        IIdentifier identifier = object.getIdentifier();
        FINST finst = removeFINST(identifier);

        if (finst != null && finst.getFlagAsOld() != null)
            finst.getFlagAsOld().abort();

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("removed " + identifier + " old:" + _oldIdentifiers.keySet() + " new:"
                    + _newIdentifiers.keySet() + " att:" + _attendedIdentifiers.keySet());
    }

    /**
     * @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#afferentObjectUpdated(org.commonreality.object.IAfferentObject,
     *      org.commonreality.object.delta.IObjectDelta)
     */
    public void afferentObjectUpdated(IAfferentObject object, IObjectDelta delta) {
        // NoOp?
    }

    /**
     * @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#isInterestedIn(org.commonreality.object.IAfferentObject)
     */
    public boolean isInterestedIn(IAfferentObject object) {
        return false;
    }

    public boolean isInterestedIn(ChunkTypeRequest request) {
        for (ISlot slot : request.getSlots())
            if (slot.getName().equalsIgnoreCase(_attendedSlotName))
                return true;
        return false;
    }

    private FINST removeFINST(IIdentifier identifier) {
        try {
            _lock.writeLock().lock();

            if (isAttended(identifier))
                return _attendedIdentifiers.remove(identifier);

            if (isOld(identifier))
                return _oldIdentifiers.remove(identifier);

            if (isNew(identifier))
                return _newIdentifiers.remove(identifier);

            return null;
        } finally {
            _lock.writeLock().unlock();
        }
    }

    private class FlagAsOld extends AbstractTimedEvent {
        protected IIdentifier _identifier;

        protected IChunk _afferentChunk;

        public FlagAsOld(IIdentifier identifier, IChunk afferentChunk, double offset) {
            _identifier = identifier;
            _afferentChunk = afferentChunk;
            double start = ACTRRuntime.getRuntime().getClock(_model).getTime();
            double end = start + offset;
            setTimes(start, end);
        }

        @Override
        public void fire(double currentTime) {
            if (LOGGER.isDebugEnabled())
                LOGGER.debug("firing " + this);
            super.fire(currentTime);
            flagAsOld(_identifier, _afferentChunk);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("(");
            sb.append(getClass().getSimpleName());
            sb.append(": start=").append(getStartTime());
            sb.append(" end=").append(getEndTime());
            sb.append(" id=").append(_identifier);
            sb.append(" chunk=").append(_afferentChunk);
            sb.append(" hash=").append(hashCode());
            sb.append(")");
            return sb.toString();
        }
    }

    protected class FINST {
        protected double _time;

        protected IChunk _afferentChunk;

        protected IIdentifier _identifier;

        protected FlagAsOld _timedEvent;

        public FINST(IIdentifier identifier, IChunk afferentChunk) {
            _identifier = identifier;
            setAfferentChunk(afferentChunk);
        }

        public void setFlagAsOld(FlagAsOld timedEvent) {
            if (_timedEvent != null) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("Aborting old timed event " + _timedEvent);
                _timedEvent.abort();
            }
            _timedEvent = timedEvent;
        }

        public FlagAsOld getFlagAsOld() {
            return _timedEvent;
        }

        public IChunk getAfferentChunk() {
            return _afferentChunk;
        }

        public void setAfferentChunk(IChunk afferentChunk) {
            _afferentChunk = afferentChunk;
            _time = ACTRRuntime.getRuntime().getClock(_model).getTime();
        }

        public IIdentifier getIdentifier() {
            return _identifier;
        }

        public double getTime() {
            return _time;
        }
    }

    public void normalizeRequest(ChunkTypeRequest request) {

    }

}