AbstractPerceptionReiterable.java

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

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Function;
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.perception.object.PerceivedObject;
import org.opentrafficsim.road.network.lane.object.LaneBasedObject;

/**
 * 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://github.com/peter-knoppers">Peter Knoppers</a>
 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
 * @param <O> perceiving object type (an {@code O} is perceiving a {@code U} as a {@code P})
 * @param <P> perceived object type (an {@code O} is perceiving a {@code U} as a {@code P})
 * @param <U> underlying object type (an {@code O} is perceiving a {@code U} as a {@code P})
 */
public abstract class AbstractPerceptionReiterable<O extends LaneBasedObject, P extends PerceivedObject, U>
        implements PerceptionCollectable<P, U>
{

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

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

    /** Primary iterator. */
    private Iterator<UnderlyingDistance<U>> primaryIterator;

    /** Perceiving object. */
    private final O perceivingObject;

    /**
     * Constructor.
     * @param perceivingObject perceiving object.
     */
    protected AbstractPerceptionReiterable(final O perceivingObject)
    {
        this.perceivingObject = perceivingObject;
    }

    /**
     * Returns the perceiving object.
     * @return perceiving object.
     */
    public O getObject()
    {
        return this.perceivingObject;
    }

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

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

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

    @Override
    public final synchronized P 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 next object with distance
     */
    final void addNext(final UnderlyingDistance<U> next)
    {
        SecondaryIteratorEntry entry = new SecondaryIteratorEntry(next);
        if (AbstractPerceptionReiterable.this.last == null)
        {
            AbstractPerceptionReiterable.this.first = entry;
            AbstractPerceptionReiterable.this.last = entry;
        }
        else
        {
            AbstractPerceptionReiterable.this.last.next = entry;
            AbstractPerceptionReiterable.this.last = entry;
        }
    }

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

    @Override
    public final Iterator<P> iterator()
    {
        return new PerceptionIterator();
    }

    @Override
    public final <C, I> C collect(final Supplier<I> identity, final PerceptionAccumulator<? super U, I> accumulator,
            final Function<I, C> 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.underlyingDistance.object(),
                        next.underlyingDistance.distance());
                intermediate.step();
                lastReturned = next;
                next = lastReturned.next;
                next = assureNext(next, lastReturned);
            }
        }
        return finalizer.apply(intermediate.getObject());
    }

    @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;

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

            @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.underlyingDistance.object();
            }
        };
    }

    @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;

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

            @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.underlyingDistance.object(),
                        this.lastReturned.underlyingDistance.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://github.com/peter-knoppers">Peter Knoppers</a>
     * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
     */
    public class PerceptionIterator implements Iterator<P>
    {

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

        /** Next entry. */
        private SecondaryIteratorEntry next;

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

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

        @Override
        public P 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 currently known next entry
     * @param lastReturned entry of last returned object or value
     * @return 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;
    }

    /**
     * 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://github.com/peter-knoppers">Peter Knoppers</a>
     * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
     */
    private class SecondaryIteratorEntry
    {
        /** Value. */
        private final UnderlyingDistance<U> underlyingDistance;

        /** Value. */
        private P value;

        /** Next entry. */
        private SecondaryIteratorEntry next;

        /**
         * Constructor.
         * @param underlyingDistance object with distance to object
         */
        SecondaryIteratorEntry(final UnderlyingDistance<U> underlyingDistance)
        {
            this.underlyingDistance = underlyingDistance;
        }

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

}