AbstractHistoricalMap.java

package org.opentrafficsim.core.perception.collections;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.djunits.value.vdouble.scalar.Time;
import org.djutils.exceptions.Throw;
import org.opentrafficsim.core.perception.AbstractHistorical;
import org.opentrafficsim.core.perception.HistoryManager;
import org.opentrafficsim.core.perception.collections.AbstractHistoricalMap.EventMap;

/**
 * Map-valued historical state. The current map is always maintained, and past states of the map are obtained by applying the
 * events between now and the requested time in reverse.<br>
 * <br>
 * The set views returned by this class are unmodifiable.
 * <p>
 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
 * </p>
 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
 * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 * @param <K> key type
 * @param <V> value type
 * @param <M> map type
 */
public abstract class AbstractHistoricalMap<K, V, M extends Map<K, V>> extends AbstractHistorical<K, EventMap<K, V, M>>
        implements HistoricalMap<K, V>
{

    /** Current map. */
    private final M current;

    /**
     * Constructor.
     * @param historyManager HistoryManager; history manager
     * @param map M; initial map
     */
    protected AbstractHistoricalMap(final HistoryManager historyManager, final M map)
    {
        super(historyManager);
        Throw.when(!map.isEmpty(), IllegalArgumentException.class, "The initial map should be empty.");
        this.current = map;
    }

    /**
     * Returns the internal map.
     * @return M; internal map
     */
    protected M getMap()
    {
        return this.current;
    }

    /**
     * Fill map with the current map.
     * @param map M; map to fill
     * @return M; input map filled
     */
    protected M fill(final M map)
    {
        map.putAll(this.current);
        return map;
    }

    /**
     * Fill map with the map at the given simulation time.
     * @param time Time; time
     * @param map M; map to fill
     * @return M; input map filled
     */
    protected M fill(final Time time, final M map)
    {
        // copy all current elements and decrement per event
        map.putAll(this.current);
        for (EventMap<K, V, M> event : getEvents(time))
        {
            event.restore(map);
        }
        return map;
    }

    // Altering Map methods

    /** {@inheritDoc} */
    @Override
    public void clear()
    {
        new LinkedHashSet<>(this.current.keySet()).forEach(this::remove);
    }

    /** {@inheritDoc} */
    @Override
    public V put(final K key, final V value)
    {
        boolean contained = this.current.containsKey(key);
        V previousValue = contained ? this.current.get(key) : null;
        if (!contained || !Objects.equals(previousValue, value))
        {
            addEvent(new EventMap<>(now().si, key, contained, previousValue));
            return this.current.put(key, value);
        }
        return previousValue;
    }

    /** {@inheritDoc} */
    @Override
    public void putAll(final Map<? extends K, ? extends V> m)
    {
        m.forEach(this::put);
    }

    /** {@inheritDoc} */
    @Override
    @SuppressWarnings("unchecked")
    public V remove(final Object key)
    {
        boolean contained = this.current.containsKey(key);
        if (contained)
        {
            V previousValue = this.current.get(key);
            addEvent(new EventMap<>(now().si, (K) key, contained, previousValue)); // contains, so safe cast
            return this.current.remove(key);
        }
        return null;
    }

    // Non-altering Map methods

    /** {@inheritDoc} */
    @Override
    public int size()
    {
        return this.current.size();
    }

    /** {@inheritDoc} */
    @Override
    public boolean isEmpty()
    {
        return this.current.isEmpty();
    }

    /** {@inheritDoc} */
    @Override
    public boolean containsKey(final Object key)
    {
        return this.current.containsKey(key);
    }

    /** {@inheritDoc} */
    @Override
    public boolean containsValue(final Object value)
    {
        return this.current.containsValue(value);
    }

    /** {@inheritDoc} */
    @Override
    public V get(final Object key)
    {
        return this.current.get(key);
    }

    /** {@inheritDoc} */
    @Override
    public Set<K> keySet()
    {
        return Collections.unmodifiableSet(this.current.keySet());
    }

    /** {@inheritDoc} */
    @Override
    public Collection<V> values()
    {
        return Collections.unmodifiableCollection(this.current.values());
    }

    /** {@inheritDoc} */
    @Override
    public Set<Entry<K, V>> entrySet()
    {
        // need to ensure that the Entries themselves allow no alterations
        return Collections.unmodifiableMap(this.current).entrySet();
    }

    // Events

    /**
     * Abstract super class for events that add or remove a value from the map.
     * <p>
     * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
     * <br>
     * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
     * </p>
     * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
     * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
     * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
     * @param <K> key type
     * @param <V> value type
     * @param <M> map type
     */
    public static class EventMap<K, V, M extends Map<K, V>> extends AbstractHistorical.EventValue<K>
    {

        /** Whether the map contained the key prior to the event. */
        private final boolean contained;

        /** Previous value in the map. */
        private final V previousValue;

        /**
         * Constructor.
         * @param time double; time of event
         * @param key K; key of event
         * @param contained boolean; whether the map contained the key prior to the event
         * @param previousValue V; previous value in the map
         */
        public EventMap(final double time, final K key, final boolean contained, final V previousValue)
        {
            super(time, key);
            this.contained = contained;
            this.previousValue = previousValue;
        }

        /**
         * Restores the map to the state of before the event.
         * @param map M; map to restore
         */
        public void restore(final M map)
        {
            if (this.contained)
            {
                map.put(getValue(), this.previousValue); // event value = map key
            }
            else
            {
                map.remove(getValue()); // event value = map key
            }
        }

        /** {@inheritDoc} */
        @Override
        public String toString()
        {
            return "EventMap [contained=" + this.contained + ", previousValue=" + this.previousValue + "]";
        }

    }

}