AbstractPerceptionReiterable.java

package org.opentrafficsim.road.gtu.lane.perception;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Supplier;

import org.djunits.value.vdouble.scalar.Length;
import org.djutils.exceptions.Try;
import org.opentrafficsim.base.parameters.ParameterException;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;

/**
 * This class uses a single primary iterator which a subclass defines, and makes sure that all elements are only looked up and
 * created once. It does so by storing the elements in a linked list. All calls to {@code iterator()} return an iterator which
 * iterates over the linked list. If an iterator runs to the end of the linked list, the primary iterator is requested to add an
 * element if it has one.
 * <p>
 * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
 * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
 * <p>
 * @version $Revision$, $LastChangedDate$, by $Author$, initial version 20 feb. 2018 <br>
 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
 * @param <H> headway type
 * @param <U> underlying object type
 */
public abstract class AbstractPerceptionReiterable<H extends Headway, U> implements PerceptionCollectable<H, U>
{

    /** First entry. */
    SecondaryIteratorEntry first;

    /** Last entry generated by the primary iterator. */
    SecondaryIteratorEntry last;

    /** Primary iterator. */
    private Iterator<PrimaryIteratorEntry> primaryIterator;

    /** Perceiving GTU. */
    final LaneBasedGTU gtu;

    /**
     * Constructor.
     * @param perceivingGtu LaneBasedGTU; perceiving GTU
     */
    protected AbstractPerceptionReiterable(final LaneBasedGTU perceivingGtu)
    {
        this.gtu = perceivingGtu;
    }

    /**
     * Returns the primary iterator.
     * @return Iterator; primary iterator
     */
    final Iterator<PrimaryIteratorEntry> getPrimaryIterator()
    {
        if (this.primaryIterator == null)
        {
            this.primaryIterator = primaryIterator();
        }
        return this.primaryIterator;
    }

    /**
     * Returns the primary iterator. This method is called once by AbstractPerceptionReiterable.
     * @return Iterator; primary iterator
     */
    protected abstract Iterator<PrimaryIteratorEntry> primaryIterator();

    /**
     * Returns a perceived version of the underlying object.
     * @param perceivingGtu LaneBasedGTU; perceiving GTU
     * @param object U; underlying object
     * @param distance Length; distance to the object
     * @return H; perceived version of the underlying object
     * @throws GTUException on exception
     * @throws ParameterException on invalid parameter value or missing parameter
     */
    protected abstract H perceive(LaneBasedGTU perceivingGtu, U object, Length distance)
            throws GTUException, ParameterException;

    /** {@inheritDoc} */
    @Override
    public final synchronized H first()
    {
        assureFirst();
        if (this.first == null)
        {
            return null;
        }
        return this.first.getValue();
    }

    /**
     * Assures a first SecondaryIteratorEntry is present, if the primary iterator has any elements.
     */
    private synchronized void assureFirst()
    {
        if (this.first == null && getPrimaryIterator().hasNext())
        {
            addNext(getPrimaryIterator().next());
        }
    }

    /**
     * Adds an iterator entry to the internal linked list.
     * @param next PrimaryIteratorEntry; next object
     */
    final void addNext(final PrimaryIteratorEntry next)
    {
        SecondaryIteratorEntry entry = new SecondaryIteratorEntry(next.object, next.distance);
        if (AbstractPerceptionReiterable.this.last == null)
        {
            AbstractPerceptionReiterable.this.first = entry;
            AbstractPerceptionReiterable.this.last = entry;
        }
        else
        {
            AbstractPerceptionReiterable.this.last.next = entry;
            AbstractPerceptionReiterable.this.last = entry;
        }
    }

    /** {@inheritDoc} */
    @Override
    public final boolean isEmpty()
    {
        return first() == null;
    }

    /** {@inheritDoc} */
    @Override
    public final Iterator<H> iterator()
    {
        return new PerceptionIterator();
    }

    /** {@inheritDoc} */
    @Override
    public final <C, I> C collect(final Supplier<I> identity, final PerceptionAccumulator<? super U, I> accumulator,
            final PerceptionFinalizer<C, I> finalizer)
    {
        Intermediate<I> intermediate = new Intermediate<>(identity.get());
        assureFirst();
        if (this.first != null)
        {
            SecondaryIteratorEntry lastReturned = null;
            SecondaryIteratorEntry next = this.first;
            next = assureNext(next, lastReturned);
            while (next != null && !intermediate.isStop())
            {
                intermediate = accumulator.accumulate(intermediate, next.object, next.distance);
                intermediate.step();
                lastReturned = next;
                next = lastReturned.next;
                next = assureNext(next, lastReturned);
            }
        }
        return finalizer.collect(intermediate.getObject());
    }

    /**
     * This iterator is returned to callers of the {@code iterator()} method. Multiple instances may be returned which use the
     * same linked list of {@code SecondaryIteratorEntry}. Whenever an iterator runs to the end of this list, the primary
     * iterator is requested to find the next object, if it has a next object.
     * <p>
     * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
     * <br>
     * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
     * <p>
     * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 feb. 2018 <br>
     * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
     * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
     * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
     */
    public class PerceptionIterator implements Iterator<H>
    {

        /** Last returned entry. */
        SecondaryIteratorEntry lastReturned;

        /** Next entry. */
        SecondaryIteratorEntry next;

        /** Constructor. */
        PerceptionIterator()
        {
            this.next = AbstractPerceptionReiterable.this.first;
        }

        /** {@inheritDoc} */
        @Override
        public boolean hasNext()
        {
            this.next = assureNext(this.next, this.lastReturned);
            return this.next != null;
        }

        /** {@inheritDoc} */
        @Override
        public H next()
        {
            this.next = assureNext(this.next, this.lastReturned);
            if (this.next == null)
            {
                throw new NoSuchElementException();
            }
            this.lastReturned = this.next;
            this.next = this.lastReturned.next;
            return this.lastReturned.getValue();
        }

    }

    /**
     * Helper method that assures that a next entry is available, if the primary iterator has a next value. This method may be
     * used by any process that derives from the primary iterator.
     * @param next SecondaryIteratorEntry; currently known next entry
     * @param lastReturned SecondaryIteratorEntry; entry of last returned object or value
     * @return IteratorEntry; next entry
     */
    synchronized SecondaryIteratorEntry assureNext(final SecondaryIteratorEntry next, final SecondaryIteratorEntry lastReturned)
    {
        if (next == null && getPrimaryIterator().hasNext())
        {
            addNext(getPrimaryIterator().next());
            if (lastReturned == null)
            {
                return AbstractPerceptionReiterable.this.first;
            }
            else
            {
                return lastReturned.next;
            }
        }
        return next;
    }

    /**
     * Class for {@code primaryIterator()} to return, implemented in subclasses.
     * <p>
     * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
     * <br>
     * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
     * <p>
     * @version $Revision$, $LastChangedDate$, by $Author$, initial version 28 feb. 2018 <br>
     * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
     * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
     * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
     */
    protected class PrimaryIteratorEntry implements Comparable<PrimaryIteratorEntry>
    {
        /** Object. */
        final U object;

        /** Distance to the object. */
        final Length distance;

        /**
         * Constructor.
         * @param object U; object
         * @param distance Length; distance
         */
        public PrimaryIteratorEntry(final U object, final Length distance)
        {
            this.object = object;
            this.distance = distance;
        }

        /** {@inheritDoc} */
        @Override
        public int compareTo(final PrimaryIteratorEntry o)
        {
            return this.distance.compareTo(o.distance);
        }
    }

    /**
     * Entries that make up a linked list of values for secondary iterators to iterate over.
     * <p>
     * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
     * <br>
     * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
     * <p>
     * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 feb. 2018 <br>
     * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
     * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
     * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
     */
    private class SecondaryIteratorEntry
    {
        /** Value. */
        final U object;

        /** Distance to object. */
        final Length distance;

        /** Value. */
        private H value;

        /** Next entry. */
        SecondaryIteratorEntry next;

        /**
         * Constructor.
         * @param object U; object
         * @param distance Length; distance to object
         */
        SecondaryIteratorEntry(final U object, final Length distance)
        {
            this.object = object;
            this.distance = distance;
        }

        /**
         * Returns the perceived version of the object.
         * @return H; perceived version of the object
         */
        H getValue()
        {
            if (this.value == null)
            {
                this.value = Try.assign(() -> perceive(AbstractPerceptionReiterable.this.gtu, this.object, this.distance),
                        "Exception during perception of object.");
            }
            return this.value;
        }
    }

}