AbstractPerception.java

package org.opentrafficsim.core.gtu.perception;

import java.util.LinkedHashMap;
import java.util.Map;

import org.opentrafficsim.core.gtu.Gtu;
import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;

/**
 * Implements {@code Perception} by housing categories.
 * <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 <G> GTU type
 */
public abstract class AbstractPerception<G extends Gtu> implements Perception<G>
{

    /** */
    private static final long serialVersionUID = 20160729L;

    /** Set of available perception categories. */
    private final Map<Class<? extends PerceptionCategory<?, ?>>, PerceptionCategory<?, ?>> perceptionCategories =
            new LinkedHashMap<>();

    /** Cache, avoiding loop and isAssignableFrom. */
    private final Map<Class<? extends PerceptionCategory<?, ?>>, PerceptionCategory<?, ?>> cachedCategories =
            new LinkedHashMap<>();

    /** GTU. */
    private G gtu;

    /**
     * Construct perception.
     * @param gtu G; GTU
     */
    public AbstractPerception(final G gtu)
    {
        this.gtu = gtu;
    }

    /** {@inheritDoc} */
    @Override
    @SuppressWarnings("checkstyle:designforextension")
    public G getGtu()
    {
        return this.gtu;
    }

    /** {@inheritDoc} */
    @SuppressWarnings("unchecked")
    @Override
    public final <T extends PerceptionCategory<?, ?>> void addPerceptionCategory(final T perceptionCategory)
    {
        // guarantees correct combination of class and perception category
        this.perceptionCategories.put((Class<T>) perceptionCategory.getClass(), perceptionCategory);
        this.cachedCategories.clear();
    }

    /** {@inheritDoc} */
    @Override
    public final <T extends PerceptionCategory<?, ?>> boolean contains(final Class<T> category)
    {
        if (this.cachedCategories.containsKey(category))
        {
            return true;
        }
        for (Class<?> clazz : this.perceptionCategories.keySet())
        {
            if (category.isAssignableFrom(clazz))
            {
                // isAssignableFrom takes care of implementation of the category
                this.cachedCategories.put(category, this.perceptionCategories.get(clazz));
                return true;
            }
        }
        return false;
    }

    /** {@inheritDoc} */
    @Override
    public final <T extends PerceptionCategory<?, ?>> T getPerceptionCategory(final Class<T> category)
            throws OperationalPlanException
    {
        T cat = getPerceptionCategoryOrNull(category);
        if (cat != null)
        {
            return cat;
        }
        throw new OperationalPlanException("Perception category" + category + " is not present.");
    }

    /** {@inheritDoc} */
    @Override
    @SuppressWarnings("unchecked")
    public final <T extends PerceptionCategory<?, ?>> T getPerceptionCategoryOrNull(final Class<T> category)
    {
        T implementation = (T) this.cachedCategories.get(category);
        if (implementation != null)
        {
            return implementation;
        }
        for (Class<?> clazz : this.perceptionCategories.keySet())
        {
            if (category.isAssignableFrom(clazz))
            {
                // addPerceptionCategory guarantees correct combination of class and perception category
                // isAssignableFrom takes care of implementation of the category
                implementation = (T) this.perceptionCategories.get(clazz);
                this.cachedCategories.put(category, implementation);
                return implementation;
            }
        }
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public final void removePerceptionCategory(final PerceptionCategory<?, ?> perceptionCategory)
    {
        for (Class<?> category : this.perceptionCategories.keySet())
        {
            if (perceptionCategory.getClass().isAssignableFrom(category))
            {
                // addPerceptionCategory guarantees correct combination of class and perception category
                // isAssignableFrom takes care of implementation of the category
                this.perceptionCategories.remove(perceptionCategory.getClass());
                this.cachedCategories.clear();
                return;
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    @SuppressWarnings("checkstyle:designforextension")
    public String toString()
    {
        StringBuilder s = new StringBuilder("Perception [");
        String sep = "";
        for (PerceptionCategory<?, ?> cat : this.perceptionCategories.values())
        {
            s.append(sep);
            s.append(cat);
            sep = ", ";
        }
        s.append("]");
        return s.toString();
    }

}