/*
* CachedTimeSeries.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.BoundType;
import com.google.common.collect.Range;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.NavigableMap;
import java.util.logging.Logger;
/**
* CachedTimeSeries: A TimeSeries that is persistent but also has an in-memory cache.
*
* @author Joe Pasqua <joe at NoRoomAtTheInn dot org>
*/
public class CachedTimeSeries implements TimeSeries, IndexedTimeSeries {
/*------------------------------------------------------------------------------
*
* Constants and Enums
*
*----------------------------------------------------------------------------*/
protected static final Logger logger = Logger.getLogger("org.noroomattheinn.timeseries");
/*------------------------------------------------------------------------------
*
* Internal State
*
*----------------------------------------------------------------------------*/
private final RowDescriptor schema;
private final PersistentTS persistent;
private final InMemoryTS inMemory;
/*==============================================================================
* ------- -------
* ------- Public Interface To This Class -------
* ------- -------
*============================================================================*/
/**
* Create a CachedTimeSeries with an empty cache.
*
* @param container The folder containing the PersistentTimeSeries data
* @param baseName The name of the PersistentTimeSeries
* @param descriptor A Descriptor giving the schema of the rows
* @throws IOException If the PersistentTimeSeries is unavailable
*/
public CachedTimeSeries(
File container, String baseName, RowDescriptor descriptor)
throws IOException {
this(container, baseName, descriptor, 0);
}
/**
* Create a CachedTimeSeries and initialize the cache to a given period.
*
* @param container The folder containing the PersistentTimeSeries data
* @param baseName The name of the PersistentTimeSeries
* @param descriptor A Descriptor giving the schema of the rows
* @param amtToCache Cache the range (now-amtToCache, now)
* @throws IOException If the PersistentTimeSeries is unavailable
*/
public CachedTimeSeries(
File container, String baseName, RowDescriptor descriptor, long amtToCache)
throws IOException {
this(container, baseName, descriptor, Range.<Long>downTo(
System.currentTimeMillis() - amtToCache, BoundType.OPEN));
}
/**
* Create a CachedTimeSeries and initialize the cache to a given period.
*
* @param container The folder containing the PersistentTimeSeries data
* @param baseName The name of the PersistentTimeSeries
* @param descriptor A Descriptor giving the schema of the rows
* @param cacheRange The range of data to cache
* @throws IOException If the PersistentTimeSeries is unavailable
*/
public CachedTimeSeries(
File container, String baseName, RowDescriptor descriptor, Range<Long> cacheRange)
throws IOException {
this.schema = descriptor;
this.inMemory = new InMemoryTS(descriptor, true);
this.persistent = new PersistentTS(container, baseName, descriptor, true);
persistent.loadInto(inMemory, cacheRange);
}
/**
* Get the underlying CachedTimeSeries. Use this method if you want to be
* sure you're operating on only the in-memory component of this TimeSeries
* @return An IndexedTimeSeries representing the in-memory component of
* this TimeSeries
*/
public IndexedTimeSeries getCachedSeries() { return inMemory; }
/*------------------------------------------------------------------------------
*
* Methods overriden from TimeSeries
*
*----------------------------------------------------------------------------*/
@Override public Row storeRow(Row r) throws IllegalArgumentException {
Row storedRow = inMemory.storeRow(r);
return persistent.storeRow(storedRow);
}
@Override public void streamRows(Range<Long> period, RowCollector collector) {
if (period == null) period = Range.<Long>all();
tsForPeriod(period).streamRows(period, collector);
}
@Override public void loadInto(TimeSeries ts, Range<Long> period) {
tsForPeriod(period).loadInto(ts, period);
}
@Override public void streamValues(Range<Long> period, ValueCollector collector) {
tsForPeriod(period).streamValues(period, collector);
}
@Override public boolean export(
File toFile, Range<Long> period,
List<String> columns, boolean includeDerived) {
return tsForPeriod(period).export(toFile, period, columns, includeDerived);
}
@Override public void flush() {
persistent.flush();
}
@Override public void close() {
flush();
persistent.close();
}
@Override public long firstTime() {
return Math.min(persistent.firstTime(), inMemory.firstTime());
}
@Override public RowDescriptor getSchema() { return schema; }
/*------------------------------------------------------------------------------
*
* Methods overriden from IndexedTimeSeries
*
*----------------------------------------------------------------------------*/
@Override public NavigableMap<Long, Row> getIndex() {
return getIndex(Range.<Long>all());
}
@Override public NavigableMap<Long, Row> getIndex(Range<Long> period) {
if (useInMemory(period)) {
return inMemory.getIndex(period);
} else {
InMemoryTS tempTS = new InMemoryTS(schema, false);
persistent.loadInto(tempTS, period);
return tempTS.getIndex();
}
}
/*------------------------------------------------------------------------------
*
* Private Utility Methods
*
*----------------------------------------------------------------------------*/
private TimeSeries tsForPeriod(Range<Long> period) {
return useInMemory(period) ? inMemory : persistent;
}
private boolean useInMemory(Range<Long> period) {
boolean im = useInMemoryInternal(period);
logger.finest("Use InMemory: " + im);
return im;
}
private boolean useInMemoryInternal(Range<Long> period) {
long firstInMemory = inMemory.firstTime();
long firstPersistent = persistent.firstTime();
if (firstInMemory <= firstPersistent) return true;
if (period.hasLowerBound() && firstInMemory <= period.lowerEndpoint()) return true;
return false;
}
}