/* * InMemoryTS.java - Copyright(c) 2014 Joe Pasqua * Provided under the MIT License. See the LICENSE file for details. * Created: Nov 25, 2014 */ package org.noroomattheinn.timeseries; import com.google.common.collect.Range; import java.util.ArrayList; import java.util.List; import java.util.NavigableMap; import java.util.TreeMap; /** * InMemoryTS: In-Memory Time Series * * @author Joe Pasqua <joe at NoRoomAtTheInn dot org> */ public class InMemoryTS extends TSBase implements IndexedTimeSeries { /*------------------------------------------------------------------------------ * * Internal State * *----------------------------------------------------------------------------*/ private final NavigableMap<Long,Row> index; private final List<Row> rows; private final boolean forceOrdering; /*============================================================================== * ------- ------- * ------- Public Interface To This Class ------- * ------- ------- *============================================================================*/ /** * Create an In-Memory Time Series store * * @param descriptor Describes the schema of the rows in the store * @param forceOrdering If true, then all data added to the time series * will be forced to have monotonically increasing * timestamps. If a row or value is added whose time- * stamp is less than a value that has already been * added, the newer timestamp will be used. * If false, an old timestamp will result in an * IllegalArgumentException */ public InMemoryTS(RowDescriptor descriptor, boolean forceOrdering) { super(descriptor); this.index = new TreeMap<>(); this.rows = new ArrayList<>(); this.forceOrdering = forceOrdering; // The code assumes there is always a "previous" row, so create a // zero-th row that serves as a backstop. It's never presented as // part of the actual data rows.add(new Row(0, 0, descriptor.nColumns)); } /*------------------------------------------------------------------------------ * * Methods overriden from TimeSeries * *----------------------------------------------------------------------------*/ @Override public Row storeRow(Row rowToStore) throws IllegalArgumentException { Row existingRow = rows.get(rows.size() - 1); int nColumns = rowToStore.values.length; long newTime = adjustTimeIfNeeded(rowToStore.timestamp, existingRow.timestamp); if (newTime == existingRow.timestamp) { // Merge this row into existingRow logger.info("Merging rows at time: " + newTime); long bit = 1; for (int i = 0; i < nColumns; i++) { if (rowToStore.includes(bit)) { existingRow.values[i] = rowToStore.values[i]; existingRow.bitVector |= bit; } bit = bit << 1; } return existingRow; } else { // Create new row based on the existing values Row newRow = new Row(newTime, rowToStore.bitVector, existingRow.values); // Now set the values given by rowToStore long bit = 1; for (int i = 0; i < nColumns; i++) { if (rowToStore.includes(bit)) { newRow.values[i] = rowToStore.values[i]; } bit = bit << 1; } rows.add(newRow); index.put(rowToStore.timestamp, newRow); return newRow; } } @Override public void streamRows(Range<Long> period, RowCollector collector) { NavigableMap<Long,Row> subMap = getIndex(period); for (Row row : subMap.values()) { if (!collector.collect(row)) return; } } @Override public long firstTime() { return (rows.size() == 1) ? Long.MAX_VALUE : rows.get(1).timestamp; } @Override public void close() { } @Override public void flush() { } /*------------------------------------------------------------------------------ * * Methods overriden from IndexedTimeSeries * *----------------------------------------------------------------------------*/ @Override public NavigableMap<Long,Row> getIndex() { return index; } @Override public NavigableMap<Long,Row> getIndex(Range<Long> period) { long from = period.hasLowerBound() ? period.lowerEndpoint() : 0; long to = period.hasUpperBound() ? period.upperEndpoint() : Long.MAX_VALUE; return index.subMap(from, true, to, true); } /*------------------------------------------------------------------------------ * * Private Utility Methods * *----------------------------------------------------------------------------*/ private long adjustTimeIfNeeded(long newTime, long oldTime) { if (newTime >= oldTime) return newTime; if (forceOrdering) { logger.fine("Forcing timestamp: " + oldTime + ", " + newTime); return oldTime; } else { throw new IllegalArgumentException( "Timestamps out of sequence: " + oldTime + ", " + newTime); } } }