View Javadoc
1   package org.opentrafficsim.core.gtu;
2   
3   import java.util.Arrays;
4   import java.util.LinkedHashMap;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.Set;
8   import java.util.function.Supplier;
9   
10  import nl.tudelft.simulation.language.Throw;
11  
12  /**
13   * Utility class to cache data based on a variable (between cache instances) number of keys of any type. This replaces nested
14   * {@code Map}s.
15   * <p>
16   * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
17   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
18   * <p>
19   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 20 apr. 2018 <br>
20   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
21   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
22   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
23   * @param <T> value type
24   */
25  public class NestedCache<T>
26  {
27  
28      /** Key types. */
29      private final Class<?>[] types;
30  
31      /** Map with cache. */
32      private final Map<Object, Object> map = new LinkedHashMap<>();
33  
34      /**
35       * Constructor.
36       * @param types Class...; types
37       */
38      public NestedCache(final Class<?>... types)
39      {
40          this.types = types;
41      }
42  
43      /**
44       * Returns a value.
45       * @param supplier Supplier; supplier of {@code T} for if it wasn't cached yet
46       * @param keys List; list of key objects
47       * @return T; value
48       */
49      public T getValue(final Supplier<T> supplier, final Object... keys)
50      {
51          return getValue(supplier, Arrays.asList(keys));
52      }
53  
54      /**
55       * Returns a value. This uses a {@code List} rather than an array to allow flexible inner workings.
56       * @param supplier Supplier; supplier of {@code T} for if it wasn't cached yet
57       * @param keys List; list of key objects
58       * @return T; value
59       */
60      @SuppressWarnings("unchecked")
61      private T getValue(final Supplier<T> supplier, final List<Object> keys)
62      {
63          Throw.when(keys.size() != this.types.length, IllegalArgumentException.class, "Incorrect number of keys.");
64          Throw.when(keys.get(0) != null && !this.types[0].isAssignableFrom(keys.get(0).getClass()),
65                  IllegalArgumentException.class, "Key %s is not of %s.", keys.get(0), this.types[0]);
66          Object sub = this.map.get(keys.get(0));
67          if (this.types.length == 1)
68          {
69              if (sub == null)
70              {
71                  sub = supplier.get();
72                  this.map.put(keys.get(0), sub);
73              }
74              return (T) sub;
75          }
76          if (sub == null)
77          {
78              // create sub-NestedCache with 1 less key
79              Class<Object>[] subTypes = new Class[this.types.length - 1];
80              System.arraycopy(this.types, 1, subTypes, 0, this.types.length - 1);
81              sub = new NestedCache<T>(subTypes);
82              this.map.put(keys.get(0), sub);
83          }
84          // return from sub-NestedCache with 1 less key
85          return ((NestedCache<T>) sub).getValue(supplier, keys.subList(1, keys.size()));
86      }
87  
88      /**
89       * Return set of key objects on this level.
90       * @return Set; set of key objects on this level
91       */
92      public Set<Object> getKeys()
93      {
94          return this.map.keySet();
95      }
96  
97      /**
98       * Return branch for key.
99       * @param key Object; key
100      * @return NestedCache; branch for key
101      * @throws IllegalStateException if this is not a branch level
102      */
103     @SuppressWarnings("unchecked")
104     public NestedCache<T> getChild(final Object key) throws IllegalStateException
105     {
106         Throw.when(this.types.length < 2, IllegalStateException.class, "Children can only be obtained on branch levels.");
107         return (NestedCache<T>) this.map.get(key);
108     }
109 
110     /**
111      * Return value for key.
112      * @param key Object; key
113      * @return T; value for key
114      * @throws IllegalStateException if this is not a leaf level
115      */
116     @SuppressWarnings("unchecked")
117     public T getValue(final Object key) throws IllegalStateException
118     {
119         Throw.when(this.types.length != 1, IllegalStateException.class, "Values can only be obtained on leaf levels.");
120         return (T) this.map.get(key);
121     }
122 
123     /** {@inheritDoc} */
124     @Override
125     public String toString()
126     {
127         return "NestedCache [types=" + Arrays.toString(this.types) + ", map=" + this.map + "]";
128     }
129 
130 }