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.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-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 <H> headway type
 * @param <U> underlying object type
 */
public abstract class AbstractPerceptionReiterable<H extends Headway, U> implements PerceptionCollectable<H, U>
{

    /** First entry. */
    private SecondaryIteratorEntry first;

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

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

    /** Perceiving GTU. */
    private final LaneBasedGtu gtu;

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

    /**
     * Returns the GTU.
     * @return LaneBasedGtu; GTU
     */
    protected LaneBasedGtu getGtu()
    {
        return this.gtu;
    }

    /**
     * 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
     */
    @SuppressWarnings("synthetic-access")
    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());
    }

    /** {@inheritDoc} */
    @Override
    public Iterator<U> underlying()
    {
        assureFirst();
        SecondaryIteratorEntry firstInContext = this.first;
        return new Iterator<U>()
        {
            /** Last returned iterator entry. */
            private SecondaryIteratorEntry lastReturned = null;

            /** Next iterator entry. */
            private SecondaryIteratorEntry next = firstInContext;

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

            /** {@inheritDoc} */
            @Override
            public U 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.object;

                this.lastReturned = this.next;
                this.next = this.lastReturned.next;
                this.next = assureNext(this.next, this.lastReturned);
                return this.lastReturned.object;
            }
        };
    }

    /** {@inheritDoc} */
    @Override
    public Iterator<UnderlyingDistance<U>> underlyingWithDistance()
    {
        assureFirst();
        SecondaryIteratorEntry firstInContext = this.first;
        return new Iterator<UnderlyingDistance<U>>()
        {
            /** Last returned iterator entry. */
            private SecondaryIteratorEntry lastReturned = null;

            /** Next iterator entry. */
            private SecondaryIteratorEntry next = firstInContext;

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

            /** {@inheritDoc} */
            @Override
            public UnderlyingDistance<U> next()
            {
                this.lastReturned = this.next;
                this.next = this.lastReturned.next;
                this.next = assureNext(this.next, this.lastReturned);
                return new UnderlyingDistance<>(this.lastReturned.object, this.lastReturned.distance);
            }
        };
    }

    /**
     * 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-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>
     */
    public class PerceptionIterator implements Iterator<H>
    {

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

        /** Next entry. */
        private 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)
        {
            return next;
        }
        if (lastReturned != null)
        {
            if (lastReturned.next == null)
            {
                if (getPrimaryIterator().hasNext())
                {
                    addNext(getPrimaryIterator().next());
                }
            }
            return lastReturned.next;
        }
        if (getPrimaryIterator().hasNext())
        {
            addNext(getPrimaryIterator().next());
        }
        return AbstractPerceptionReiterable.this.first;
    }

    /**
     * Class for {@code primaryIterator()} to return, implemented in subclasses.
     * <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>
     */
    protected class PrimaryIteratorEntry implements Comparable<PrimaryIteratorEntry>
    {
        /** Object. */
        private final U object;

        /** Distance to the object. */
        private 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);
        }

        /**
         * Returns the object.
         * @return U; object
         */
        protected U getObject()
        {
            return this.object;
        }
    }

    /**
     * Entries that make up a linked list of values for secondary iterators to iterate over.
     * <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>
     */
    private class SecondaryIteratorEntry
    {
        /** Value. */
        private final U object;

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

        /** Value. */
        private H value;

        /** Next entry. */
        private 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.getGtu(), this.object, this.distance),
                        "Exception during perception of object.");
                */
                try
                {
                    this.value = perceive(AbstractPerceptionReiterable.this.getGtu(), this.object, this.distance);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }

            }
            return this.value;
        }
    }

}