package com.activequant.timeseries;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.activequant.domainmodel.TimeStamp;
/**
* Trivial container class, even without getters and setters.
*
* @author GhostRider
* @author BrownChipmunk
*
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class TSContainer2 {
// New implementation started here
private List<TypedColumn> columns = new ArrayList<TypedColumn>();
private List<TimeStamp> timeStamps = new ArrayList<TimeStamp>();
private List<String> columnHeaders = new ArrayList<String>();
// to specify a series granularity, 0 for no time-framing.
private long resolutionInNanoseconds = 0;
/**
* contains the series ID.
*/
private final String seriesId;
private int windowSize = 0;
public TSContainer2(final String seriesId){
this.seriesId = seriesId;
}
public TSContainer2(final String seriesId, List<String> columnHeaders, List<TypedColumn> columns) {
this(seriesId, columnHeaders, columns, 0L);
}
public TSContainer2(final String seriesId, List<String> columnHeaders, List<TypedColumn> columns,
long resolutionInNanos) {
// initialize the column headers. Copying into an array list to get rid
// of possible immutable classes.
this.columnHeaders = new ArrayList<String>(columnHeaders);
// initialize the data lists.
this.columns = new ArrayList<TypedColumn>();
for (TypedColumn tc : columns) {
this.columns.add((TypedColumn) tc.clone());
}
//
this.seriesId = seriesId;
this.resolutionInNanoseconds = resolutionInNanos;
}
// It is not possible to change SeriesId after creation of the time series
public String getSeriesId() {
return seriesId;
}
public int getNumRows() {
if (this.columns.size() == 0)
return 0;
return this.columns.get(0).size();
}
// returns number of lists including timestamps list
public int getNumColumns() {
return this.columns.size();
}
public TypedColumn getColumn(String headerName) {
if (headerName == null) {
throw new IllegalArgumentException("Header name is null");
}
//
int index = getColumnIndex(headerName);
//
if (index > -1)
return columns.get(index);
return null;
}
public int getColumnIndex(String headerName) {
for (int i = 0; i < this.columnHeaders.size(); i++) {
if (this.columnHeaders.get(i).equals(headerName))
return i;
}
// not found.
return -1;
}
public void addColumn(String headerName, TypedColumn tc) {
if (headerName == null) {
throw new IllegalArgumentException("Header name is null");
}
// 1. add header.
this.columnHeaders.add(headerName);
// 2. add column
this.columns.add(tc);
if (tc.size() != getNumRows()) {
tc.clear();
}
// initialize column with null values.
for (int i = 0; i < getNumRows(); i++) {
tc.add(null);
}
}
public void deleteColumn(String headerName) {
int index = getColumnIndex(headerName);
if (index > -1) {
this.columns.remove(index);
this.columnHeaders.remove(index);
}
}
/**
* Set a value at a timestamp. if the timestamp does not exist, it will be
* inserted. Otherwise, it will overwrite the existing values.
*
* @param ts
* @param values
*/
public void setRow(TimeStamp tsIn, Object... values) {
TimeStamp ts = tsIn;
// check if a resolution has been set.
if (resolutionInNanoseconds != 0) {
// ok, we must snap it to the next resolution.
long ns = (long) (Math.ceil((double) (tsIn.getNanoseconds() / resolutionInNanoseconds)) * resolutionInNanoseconds);
ts = new TimeStamp(ns);
}
if (ts == null) {
throw new IllegalArgumentException("Timestamp is null");
}
if (values.length != getNumColumns()) {
throw new IllegalArgumentException("Values has length " + values.length + " but expected "
+ this.columns.size());
}
int targetIndex = Collections.binarySearch(timeStamps, ts);
boolean overwrite = false;
if (targetIndex >= 0 && timeStamps.get(targetIndex).equals(ts))
overwrite = true;
if (overwrite) {
for (int i = 0; i < values.length; i++) {
columns.get(i).set(targetIndex, values[i]);
}
} else {
targetIndex = Math.abs(targetIndex + 1);
// checking if the list is at the maximum window size.
if (windowSize == timeStamps.size() && timeStamps.size() > 0) {
if (targetIndex == 0) {
delete(getNumRows() - 1);
} else {
delete(0);
targetIndex--;
}
}
timeStamps.add(targetIndex, ts);
for (int i = 0; i < values.length; i++) {
columns.get(i).add(targetIndex, values[i]);
}
}
}
public void setValue(String headerName, int rowIdx, Double value) {
int colIdx = getColumnIndex(headerName);
if (colIdx == -1) {
// add a column.
addColumn(headerName, new DoubleColumn());
}
// check if we find that timestamp.
getColumn(headerName).set(rowIdx, value);
}
public void setValue(int colIdx, int rowIdx, Double value) {
getColumns().get(colIdx).set(rowIdx, value);
}
/**
* Set a value in a column for a specific timestamp. Will insert a double
* column if not found. Will insert timestamp if not found. Rows at newly
* inserted Timestamp will be initialized with null.
*
* @param headerName
* @param ts
* @param value
*/
public void setValue(String headerName, TimeStamp tsIn, Double value) {
int colIdx = getColumnIndex(headerName);
if (colIdx == -1) {
// add a column.
addColumn(headerName, new DoubleColumn());
}
setValue(getColumnIndex(headerName), tsIn, value);
}
public void setValue(int colIdx, TimeStamp tsIn, Double value) {
TimeStamp ts = tsIn;
// check if a resolution has been set.
if(resolutionInNanoseconds!=0){
// ok, we must snap it to the next resolution.
long ns = (long) (Math.ceil((double)(tsIn.getNanoseconds()/resolutionInNanoseconds)) * resolutionInNanoseconds);
ts = new TimeStamp(ns);
}
// check if we find that timestamp.
int rowIdx = getIndex(ts);
if (rowIdx < 0) {
// add a row.
setRow(ts, new Object[getNumColumns()]);
rowIdx = getIndex(ts);
}
columns.get(colIdx).set(rowIdx, value);
}
public Object getValue(String headerName, TimeStamp tsIn) {
if (resolutionInNanoseconds != 0) {
// ok, we must snap it to the next resolution.
long ns = (long) (Math.ceil((double) (tsIn.getNanoseconds() / resolutionInNanoseconds)) * resolutionInNanoseconds);
tsIn = new TimeStamp(ns);
}
int colIdx = getColumnIndex(headerName);
if (colIdx == -1)
return null;
// check if we find that timestamp.
int rowIdx = getIndex(tsIn);
if (rowIdx < 0) {
// add a row.
return null;
}
return getColumn(headerName).get(rowIdx);
}
public void delete(TimeStamp ts) {
if (ts == null) {
throw new IllegalArgumentException("Timestamp is null");
}
int index = Collections.binarySearch(this.timeStamps, ts);
if (index < 0)
return;
if (this.timeStamps.get(index).equals(ts)) {
delete(index);
}
}
public void delete(int rowIndex) {
timeStamps.remove(rowIndex);
for (int i = 0; i < columns.size(); i++) {
columns.get(i).remove(rowIndex);
}
}
public void delete(TimeStamp from, TimeStamp to) {
int indexFrom = getIndex(from);
int count = getIndex(to) - indexFrom + 1;
for (int i = 0; i < count; i++) {
delete(indexFrom);
}
}
public Object[] getRow(TimeStamp ts) {
int index = getIndex(ts);
Object[] data = new Object[getNumColumns()];
for (int i = 0; i < getNumColumns(); i++) {
data[i] = columns.get(i).get(index);
}
return data;
}
public TimeStamp getTime(int index) {
return (TimeStamp) this.timeStamps.get(index);
}
public int getIndex(TimeStamp ts) {
if (ts == null) {
throw new IllegalArgumentException("Timestamp is null");
}
int index = Collections.binarySearch(timeStamps, ts);
if (index < 0) {
return -1;
}
return index;
}
/**
* if there is a match - return index, else - return index of the closest
* timestamp before given one
*
* @param ts
* @return
*/
public int getIndexBeforeOrEqual(TimeStamp ts) {
int targetIndex = Collections.binarySearch(timeStamps, ts);
if (targetIndex < 0) {
return Math.abs(targetIndex + 2);
}
return targetIndex;
}
public int getIndexBefore(TimeStamp ts) {
int targetIndex = Collections.binarySearch(timeStamps, ts);
if (targetIndex < 0) {
return Math.abs(targetIndex + 2);
}
return targetIndex - 1;
}
// if there is a match, then index + 1 is returned
// if there is not match, then closest timestamp before + 1 is returned
public int getIndexAfter(TimeStamp ts) {
int targetIndex = Collections.binarySearch(timeStamps, ts);
if (targetIndex < 0) {
return Math.abs(targetIndex + 1);
}
return targetIndex + 1;
}
// the Object should implement Comparable interface
// or class can maintain two lists of max/min values
public Object getMax(String headerName) {
List l = getColumn(headerName);
Object o = l.get(0);
if (o instanceof Comparable) {
return Collections.max(l);
}
return null;
}
// not sure if this needed
// could be implemented as Collections.min(timeSeries.getColumn("Ask"));
// the Object should implement Comparable interface
// or class can maintain two lists of max/min values
public Object getMin(String headerName) {
List l = getColumn(headerName);
Object o = l.get(0);
if (o instanceof Comparable) {
return Collections.min(l);
}
return null;
}
public TimeStamp getMinTimestamp() {
return timeStamps.get(0);
}
public TimeStamp getMaxTimestamp() {
return timeStamps.get(timeStamps.size() - 1);
}
public void setMaxWindow(int windowSize) {
this.windowSize = windowSize;
}
public int getMaxWindow() {
return windowSize;
}
public List<TypedColumn> getColumns() {
return columns;
}
public void setColumns(List<TypedColumn> columns) {
this.columns = columns;
}
public List<TimeStamp> getTimeStamps() {
return timeStamps;
}
public void setTimeStamps(List<TimeStamp> timeStamps) {
this.timeStamps = timeStamps;
}
// delete inclusive
public void deleteBefore(TimeStamp ts) {
deleteBefore(getIndex(ts));
}
// delete inclusive
public void deleteBefore(int index) {
if (index < 0 || index > this.timeStamps.size()) {
throw new IllegalArgumentException("index is out of range");
}
for (int i = 0; i <= index; i++) {
delete(0);
}
}
// delete inclusive
public void deleteAfter(int index) {
if (index < 0 || index > this.timeStamps.size()) {
throw new IllegalArgumentException("index is out of range");
}
int count = this.timeStamps.size() - index;
for (int i = 0; i < count; i++) {
delete(index);
}
}
// delete inclusive
public void deleteAfter(TimeStamp ts) {
deleteAfter(getIndex(ts));
}
private void addValuesToColumns(TimeStamp ts, Object... values) {
int rowIndex = getIndex(ts);
for (int i = 0; i < columns.size(); i++) {
columns.get(i).set(rowIndex, (Double) columns.get(i).get(rowIndex) + (Double) values[i]);
}
}
// not specified why we need boolean param
public void addTimeSeries(TSContainer2 timeSeries, boolean param) {
for (TimeStamp ts : timeSeries.getTimeStamps()) {
// do sum of values
if (this.timeStamps.contains(ts)) {
int rowIndex = getIndex(ts);
this.addValuesToColumns(ts, timeSeries.getRow(ts));
} else {
// add a new row
this.setRow(ts, timeSeries.getRow(ts));
}
}
}
public List<String> getColumnHeaders() {
return columnHeaders;
}
public void setColumnHeaders(List<String> columnHeaders) {
this.columnHeaders = columnHeaders;
}
public TSContainer2 getTimeFrame(TimeStamp tsFrom, TimeStamp tsTo) throws ClassNotFoundException,
InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException,
IllegalArgumentException, InvocationTargetException {
if (tsFrom.getMilliseconds() > tsTo.getMilliseconds()) {
throw new IllegalArgumentException("From time stamp should be less than To time stamp");
}
List<TypedColumn> l = getColumns();
List<TypedColumn> l_ret = new ArrayList<TypedColumn>();
int indexFrom = getIndexBeforeOrEqual(tsFrom);
int indexTo = getIndexBeforeOrEqual(tsTo);
if (indexFrom == -1 || indexTo == -1) {
return null;
}
for (int i = 0; i < l.size(); i++) {
Class ctClass = l.get(i).getClass();
Constructor constructor = ctClass.getConstructor(new Class[] { List.class });
Object ll = constructor.newInstance(new Object[] { l.get(i).subList(indexFrom, indexTo) });
l_ret.add(i, (TypedColumn) ll);
}
TSContainer2 tsc_ret = new TSContainer2(getSeriesId() + ":" + tsFrom.getMilliseconds() + ":"
+ tsTo.getMilliseconds(), getColumnHeaders(), l_ret);
tsc_ret.setTimeStamps(getTimeStamps().subList(indexFrom, indexTo));
return tsc_ret;
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
DecimalFormat dc = new DecimalFormat("#.##########");
// output header
str.append("Date\t\t\t\tMilliseconds\t\t");
for (String headerName : columnHeaders) {
str.append(headerName + "\t");
}
str.append("\n");
// output data rows
for (TimeStamp ts : timeStamps) {
str.append(ts.getDate() + "\t" + ts.toString() + "\t");
for (int i = 0; i < this.columns.size(); i++) {
Object obj = columns.get(i).get(getIndex(ts));
if (obj != null && obj.getClass().isAssignableFrom(Double.class))
str.append(dc.format(obj) + "\t");
else
str.append(obj + "\t");
}
str.append("\n");
}
return str.toString();
}
/**
* empties everything.
*/
public void emptyColumns() {
for (TypedColumn tc : this.columns)
tc.clear();
this.timeStamps.clear();
}
public long getResolutionInNanoseconds() {
return resolutionInNanoseconds;
}
public void setResolutionInNanoseconds(long resolutionInNanoseconds) {
this.resolutionInNanoseconds = resolutionInNanoseconds;
}
}