NestedCache.java
package org.opentrafficsim.core.gtu;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import nl.tudelft.simulation.language.Throw;
/**
* Utility class to cache data based on a variable (between cache instances) number of keys of any type. This replaces nested
* {@code Map}s.
* <p>
* Copyright (c) 2013-2018 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 apr. 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 <T> value type
*/
public class NestedCache<T>
{
/** Key types. */
private final Class<?>[] types;
/** Map with cache. */
private final Map<Object, Object> map = new LinkedHashMap<>();
/**
* Constructor.
* @param types Class...; types
*/
public NestedCache(final Class<?>... types)
{
this.types = types;
}
/**
* Returns a value.
* @param supplier Supplier; supplier of {@code T} for if it wasn't cached yet
* @param keys List; list of key objects
* @return T; value
*/
public T getValue(final Supplier<T> supplier, final Object... keys)
{
return getValue(supplier, Arrays.asList(keys));
}
/**
* Returns a value. This uses a {@code List} rather than an array to allow flexible inner workings.
* @param supplier Supplier; supplier of {@code T} for if it wasn't cached yet
* @param keys List; list of key objects
* @return T; value
*/
@SuppressWarnings("unchecked")
private T getValue(final Supplier<T> supplier, final List<Object> keys)
{
Throw.when(keys.size() != this.types.length, IllegalArgumentException.class, "Incorrect number of keys.");
Throw.when(keys.get(0) != null && !this.types[0].isAssignableFrom(keys.get(0).getClass()),
IllegalArgumentException.class, "Key %s is not of %s.", keys.get(0), this.types[0]);
Object sub = this.map.get(keys.get(0));
if (this.types.length == 1)
{
if (sub == null)
{
sub = supplier.get();
this.map.put(keys.get(0), sub);
}
return (T) sub;
}
if (sub == null)
{
// create sub-NestedCache with 1 less key
Class<Object>[] subTypes = new Class[this.types.length - 1];
System.arraycopy(this.types, 1, subTypes, 0, this.types.length - 1);
sub = new NestedCache<T>(subTypes);
this.map.put(keys.get(0), sub);
}
// return from sub-NestedCache with 1 less key
return ((NestedCache<T>) sub).getValue(supplier, keys.subList(1, keys.size()));
}
/**
* Return set of key objects on this level.
* @return Set; set of key objects on this level
*/
public Set<Object> getKeys()
{
return this.map.keySet();
}
/**
* Return branch for key.
* @param key Object; key
* @return NestedCache; branch for key
* @throws IllegalStateException if this is not a branch level
*/
@SuppressWarnings("unchecked")
public NestedCache<T> getChild(final Object key) throws IllegalStateException
{
Throw.when(this.types.length < 2, IllegalStateException.class, "Children can only be obtained on branch levels.");
return (NestedCache<T>) this.map.get(key);
}
/**
* Return value for key.
* @param key Object; key
* @return T; value for key
* @throws IllegalStateException if this is not a leaf level
*/
@SuppressWarnings("unchecked")
public T getValue(final Object key) throws IllegalStateException
{
Throw.when(this.types.length != 1, IllegalStateException.class, "Values can only be obtained on leaf levels.");
return (T) this.map.get(key);
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "NestedCache [types=" + Arrays.toString(this.types) + ", map=" + this.map + "]";
}
}