package org.opentrafficsim.kpi.sampling; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Spliterator; import java.util.stream.Stream; import org.djunits.Throw; /** * List implementation of {@code Table}. *

* Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* @author Alexander Verbraeck * @author Peter Knoppers * @author Wouter Schakel */ public class ListTable extends AbstractTable { /** Records. */ private List records = Collections.synchronizedList(new ArrayList<>()); /** Column numbers. */ private Map, Integer> columnNumbers = new LinkedHashMap<>(); /** Id numbers. */ private Map idNumbers = new LinkedHashMap<>(); /** * Constructor. * @param id String; id * @param description String; description * @param columns Collection<Column<?>>; columns */ public ListTable(final String id, final String description, final Collection> columns) { super(id, description, columns); for (int index = 0; index < getColumns().size(); index++) { Column column = getColumns().get(index); this.columnNumbers.put(column, index); this.idNumbers.put(column.getId(), index); } Throw.when(getNumberOfColumns() != this.idNumbers.size(), IllegalArgumentException.class, "Duplicate column ids are not allowed."); } /** * {@inheritDoc}
*
* It is imperative that the user manually synchronize on the returned list when traversing it via {@link Iterator}, * {@link Spliterator} or {@link Stream} when there is a risk of adding records while traversing the iterator: * *
     *  List list = Collections.synchronizedList(new ArrayList());
     *      ...
     *  synchronized (list) 
     *  {
     *      Iterator i = list.iterator(); // Must be in synchronized block
     *      while (i.hasNext())
     *          foo(i.next());
     *  }
     * 
* * Failure to follow this advice may result in non-deterministic behavior.
*
*/ @Override public Iterator iterator() { return this.records.iterator(); } /** {@inheritDoc} */ @Override public boolean isEmpty() { return this.records.isEmpty(); } /** * Adds a record to the table. * @param data Map<Column<?>, Object>; data with values given per column * @throws IllegalArgumentException when the size or data types in the data map do not comply to the columns */ public void addRecord(final Map, Object> data) { Throw.whenNull(data, "Data may not be null."); Throw.when(data.size() != getNumberOfColumns(), IllegalArgumentException.class, "Number of data columns doesn't match number of table columns."); Object[] dataObjects = new Object[getNumberOfColumns()]; for (int index = 0; index < getColumns().size(); index++) { Column column = getColumns().get(index); Throw.when(!data.containsKey(column), IllegalArgumentException.class, "Missing data for column %s", column.getId()); Object value = data.get(column); Throw.when(!column.getValueType().isAssignableFrom(value.getClass()), IllegalArgumentException.class, "Data value for column %s is not of type %s, but of type %s.", column.getId(), column.getValueType(), value.getClass()); dataObjects[index] = value; } this.records.add(new ListRecord(dataObjects)); } /** * Adds a record to the table. * @param data Map<String, Object>; data with values given per column id * @throws IllegalArgumentException when the size or data types in the data map do not comply to the columns */ public void addRecordByColumnIds(final Map data) { Throw.whenNull(data, "Data may not be null."); Throw.when(data.size() != getNumberOfColumns(), IllegalArgumentException.class, "Number of data columns doesn't match number of table columns."); Object[] dataObjects = new Object[getNumberOfColumns()]; for (int index = 0; index < getColumns().size(); index++) { Column column = getColumns().get(index); Throw.when(!data.containsKey(column.getId()), IllegalArgumentException.class, "Missing data for column %s", column.getId()); Object value = data.get(column.getId()); Class dataClass = value.getClass(); Throw.when(!column.getValueType().isAssignableFrom(dataClass), IllegalArgumentException.class, "Data value for column %s is not of type %s, but of type %s.", column.getId(), column.getValueType(), dataClass); dataObjects[index] = value; } this.records.add(new ListRecord(dataObjects)); } /** * 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 * the columns. * @param data Object[]; record data * @throws IllegalArgumentException when the size, order or data types in the {@code Object[]} do not comply to the columns */ public void addRecord(final Object[] data) { Throw.whenNull(data, "Data may not be null."); Throw.when(data.length != getNumberOfColumns(), IllegalArgumentException.class, "Number of data columns doesn't match number of table columns."); Object[] dataObjects = new Object[getNumberOfColumns()]; for (int index = 0; index < getColumns().size(); index++) { Column column = getColumns().get(index); Class dataClass = data[index].getClass(); Throw.when(!column.getValueType().isAssignableFrom(dataClass), IllegalArgumentException.class, "Data value for column %s is not of type %s, but of type %s.", column.getId(), column.getValueType(), dataClass); dataObjects[index] = data[index]; } this.records.add(new ListRecord(dataObjects)); } /** Record in a {@code ListTable}. */ public class ListRecord implements Record { /** Values. */ private final Object[] values; /** * Constructor. * @param values Object[]; values */ public ListRecord(final Object[] values) { this.values = values; } /** {@inheritDoc} */ @SuppressWarnings({"unchecked", "synthetic-access"}) @Override public T getValue(final Column column) { return (T) this.values[ListTable.this.columnNumbers.get(column)]; } /** {@inheritDoc} */ @SuppressWarnings("synthetic-access") @Override public Object getValue(final String id) { return this.values[ListTable.this.idNumbers.get(id)]; } } }