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)];
}
}
}