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