View Javadoc
1   package org.opentrafficsim.kpi.sampling;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.Collections;
6   import java.util.Iterator;
7   import java.util.LinkedHashMap;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Spliterator;
11  import java.util.stream.Stream;
12  
13  import org.djunits.Throw;
14  
15  /**
16   * List implementation of {@code Table}.
17   * <p>
18   * Copyright (c) 2020-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="https://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
20   * </p>
21   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
23   * @author <a href="https://www.transport.citg.tudelft.nl">Wouter Schakel</a>
24   */
25  public class ListTable extends AbstractTable
26  {
27  
28      /** Records. */
29      private List<Record> records = Collections.synchronizedList(new ArrayList<>());
30  
31      /** Column numbers. */
32      private Map<Column<?>, Integer> columnNumbers = new LinkedHashMap<>();
33  
34      /** Id numbers. */
35      private Map<String, Integer> idNumbers = new LinkedHashMap<>();
36  
37      /**
38       * Constructor.
39       * @param id String; id
40       * @param description String; description
41       * @param columns Collection&lt;Column&lt;?&gt;&gt;; columns
42       */
43      public ListTable(final String id, final String description, final Collection<Column<?>> columns)
44      {
45          super(id, description, columns);
46          for (int index = 0; index < getColumns().size(); index++)
47          {
48              Column<?> column = getColumns().get(index);
49              this.columnNumbers.put(column, index);
50              this.idNumbers.put(column.getId(), index);
51          }
52          Throw.when(getNumberOfColumns() != this.idNumbers.size(), IllegalArgumentException.class,
53              "Duplicate column ids are not allowed.");
54      }
55  
56      /**
57       * {@inheritDoc} <br>
58       * <br>
59       * It is imperative that the user manually synchronize on the returned list when traversing it via {@link Iterator},
60       * {@link Spliterator} or {@link Stream} when there is a risk of adding records while traversing the iterator:
61       * 
62       * <pre>
63       *  List list = Collections.synchronizedList(new ArrayList());
64       *      ...
65       *  synchronized (list) 
66       *  {
67       *      Iterator i = list.iterator(); // Must be in synchronized block
68       *      while (i.hasNext())
69       *          foo(i.next());
70       *  }
71       * </pre>
72       * 
73       * Failure to follow this advice may result in non-deterministic behavior.<br>
74       * <br>
75       */
76      @Override
77      public Iterator<Record> iterator()
78      {
79          return this.records.iterator();
80      }
81  
82      /** {@inheritDoc} */
83      @Override
84      public boolean isEmpty()
85      {
86          return this.records.isEmpty();
87      }
88  
89      /**
90       * Adds a record to the table. 
91       * @param data Map&lt;String, Object&gt;; data with values given per column
92       * @throws IllegalArgumentException when the size or data types in the data map do not comply to the columns
93       */
94      public void addRecord(final Map<Column<?>, Object> data)
95      {
96          Throw.whenNull(data, "Data may not be null.");
97          Throw.when(data.size() != getNumberOfColumns(), IllegalArgumentException.class,
98              "Number of data columns doesn't match number of table columns.");
99          Object[] dataObjects = new Object[getNumberOfColumns()];
100         for (int index = 0; index < getColumns().size(); index++)
101         {
102             Column<?> column = getColumns().get(index);
103             Throw.when(!data.containsKey(column), IllegalArgumentException.class, "Missing data for column %s", column.getId());
104             Object value = data.get(column);
105             Throw.when(!column.getValueType().isAssignableFrom(value.getClass()), IllegalArgumentException.class,
106                 "Data value for column %s is not of type %s, but of type %s.", column.getId(), column.getValueType(), value
107                     .getClass());
108             dataObjects[index] = value;
109         }
110         this.records.add(new ListRecord(dataObjects));
111     }
112 
113     /**
114      * Adds a record to the table. 
115      * @param data Map&lt;String, Object&gt;; data with values given per column id
116      * @throws IllegalArgumentException when the size or data types in the data map do not comply to the columns
117      */
118     public void addRecordByColumnIds(final Map<String, Object> data)
119     {
120         Throw.whenNull(data, "Data may not be null.");
121         Throw.when(data.size() != getNumberOfColumns(), IllegalArgumentException.class,
122             "Number of data columns doesn't match number of table columns.");
123         Object[] dataObjects = new Object[getNumberOfColumns()];
124         for (int index = 0; index < getColumns().size(); index++)
125         {
126             Column<?> column = getColumns().get(index);
127             Throw.when(!data.containsKey(column.getId()), IllegalArgumentException.class, "Missing data for column %s", column
128                 .getId());
129             Object value = data.get(column.getId());
130             Class<?> dataClass = value.getClass();
131             Throw.when(!column.getValueType().isAssignableFrom(dataClass), IllegalArgumentException.class,
132                 "Data value for column %s is not of type %s, but of type %s.", column.getId(), column.getValueType(),
133                 dataClass);
134             dataObjects[index] = value;
135         }
136         this.records.add(new ListRecord(dataObjects));
137     }
138 
139     /**
140      * Adds a record to the table. The order in which the elements in the array are offered should be the same as the order of
141      * the columns.
142      * @param data Object[]; record data
143      * @throws IllegalArgumentException when the size, order or data types in the {@code Object[]} do not comply to the columns
144      */
145     public void addRecord(final Object[] data)
146     {
147         Throw.whenNull(data, "Data may not be null.");
148         Throw.when(data.length != getNumberOfColumns(), IllegalArgumentException.class,
149             "Number of data columns doesn't match number of table columns.");
150         Object[] dataObjects = new Object[getNumberOfColumns()];
151         for (int index = 0; index < getColumns().size(); index++)
152         {
153             Column<?> column = getColumns().get(index);
154             Class<?> dataClass = data[index].getClass();
155             Throw.when(!column.getValueType().isAssignableFrom(dataClass), IllegalArgumentException.class,
156                 "Data value for column %s is not of type %s, but of type %s.", column.getId(), column.getValueType(),
157                 dataClass);
158             dataObjects[index] = data[index];
159         }
160         this.records.add(new ListRecord(dataObjects));
161     }
162 
163     /** Record in a {@code ListTable}. */
164     public class ListRecord implements Record
165     {
166 
167         /** Values. */
168         private final Object[] values;
169 
170         /**
171          * Constructor.
172          * @param values Object[]; values
173          */
174         public ListRecord(final Object[] values)
175         {
176             this.values = values;
177         }
178 
179         /** {@inheritDoc} */
180         @SuppressWarnings({"unchecked", "synthetic-access"})
181         @Override
182         public <T> T getValue(final Column<T> column)
183         {
184             return (T) this.values[ListTable.this.columnNumbers.get(column)];
185         }
186 
187         /** {@inheritDoc} */
188         @SuppressWarnings("synthetic-access")
189         @Override
190         public Object getValue(final String id)
191         {
192             return this.values[ListTable.this.idNumbers.get(id)];
193         }
194 
195     }
196 
197 }