/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.timeseries.precise.zdt;
import java.io.Serializable;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.timeseries.ObjectTimeSeries;
import com.opengamma.timeseries.ObjectTimeSeriesOperators;
import com.opengamma.timeseries.ObjectTimeSeriesOperators.BinaryOperator;
import com.opengamma.timeseries.ObjectTimeSeriesOperators.UnaryOperator;
import com.opengamma.timeseries.precise.AbstractPreciseObjectTimeSeries;
import com.opengamma.timeseries.precise.PreciseObjectTimeSeries;
/**
* Standard immutable implementation of {@code ZonedDateTimeObjectTimeSeries}.
*
* @param <V> the value being viewed over time
*/
public final class ImmutableZonedDateTimeObjectTimeSeries<V>
extends AbstractPreciseObjectTimeSeries<ZonedDateTime, V>
implements ZonedDateTimeObjectTimeSeries<V>, Serializable {
/** Serialization version. */
private static final long serialVersionUID = -43654613865187568L;
/**
* The time-zone.
*/
private final ZoneId _zone;
/**
* The times in the series.
*/
private final long[] _times;
/**
* The values in the series.
*/
private final V[] _values;
//-------------------------------------------------------------------------
/**
* Creates an empty builder, used to create time-series.
* <p>
* The builder has methods to create and modify a time-series.
*
* @param <V> the value being viewed over time
* @param zone the time-zone, not null
* @return the time-series builder, not null
*/
public static <V> ZonedDateTimeObjectTimeSeriesBuilder<V> builder(ZoneId zone) {
return new ImmutableZonedDateTimeObjectTimeSeriesBuilder<V>(zone);
}
//-------------------------------------------------------------------------
/**
* Obtains a time-series from a single date and value.
*
* @param <V> the value being viewed over time
* @param zone the time-zone, not null
* @return the time-series, not null
*/
@SuppressWarnings("unchecked")
public static <V> ImmutableZonedDateTimeObjectTimeSeries<V> ofEmpty(ZoneId zone) {
Objects.requireNonNull(zone, "zone");
return new ImmutableZonedDateTimeObjectTimeSeries<>(new long[0], (V[]) new Object[0], zone);
}
/**
* Obtains a time-series from a single instant and value.
*
* @param <V> the value being viewed over time
* @param instant the singleton instant, not null
* @param value the singleton value
* @return the time-series, not null
*/
public static <V> ImmutableZonedDateTimeObjectTimeSeries<V> of(ZonedDateTime instant, V value) {
Objects.requireNonNull(instant, "instant");
long[] timesArray = new long[] {ZonedDateTimeToLongConverter.convertToLong(instant)};
@SuppressWarnings("unchecked")
V[] valuesArray = (V[]) new Object[] {value};
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, instant.getZone());
}
/**
* Obtains a time-series from matching arrays of instants and values.
*
* @param <V> the value being viewed over time
* @param instants the instant array, not null
* @param values the value array, not null
* @param zone the time-zone, may be null if the arrays are non-empty
* @return the time-series, not null
* @throws IllegalArgumentException if the arrays are of different lengths
*/
public static <V> ImmutableZonedDateTimeObjectTimeSeries<V> of(ZonedDateTime[] instants, V[] values, ZoneId zone) {
long[] timesArray = convertToLongArray(instants);
V[] valuesArray = values.clone();
validate(timesArray, valuesArray);
zone = (zone != null ? zone : instants[0].getZone());
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, zone);
}
/**
* Obtains a time-series from matching arrays of instants and values.
*
* @param <V> the value being viewed over time
* @param instants the instant array, not null
* @param values the value array, not null
* @param zone the time-zone, not null
* @return the time-series, not null
* @throws IllegalArgumentException if the arrays are of different lengths
*/
public static <V> ImmutableZonedDateTimeObjectTimeSeries<V> of(long[] instants, V[] values, ZoneId zone) {
validate(instants, values);
Objects.requireNonNull(zone, "zone");
long[] timesArray = instants.clone();
V[] valuesArray = values.clone();
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, zone);
}
/**
* Obtains a time-series from matching arrays of instants and values.
*
* @param <V> the value being viewed over time
* @param instants the instant list, not null
* @param values the value list, not null
* @param zone the time-zone, may be null if the collections are non-empty
* @return the time-series, not null
* @throws IllegalArgumentException if the collections are of different lengths
*/
public static <V> ImmutableZonedDateTimeObjectTimeSeries<V> of(Collection<ZonedDateTime> instants, Collection<V> values, ZoneId zone) {
long[] timesArray = convertToLongArray(instants);
@SuppressWarnings("unchecked")
V[] valuesArray = (V[]) values.toArray();
validate(timesArray, valuesArray);
zone = (zone != null ? zone : instants.iterator().next().getZone());
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, zone);
}
/**
* Obtains a time-series from another time-series.
*
* @param <V> the value being viewed over time
* @param timeSeries the time-series, not null
* @param zone the time-zone, not null
* @return the time-series, not null
*/
@SuppressWarnings("unchecked")
public static <V> ImmutableZonedDateTimeObjectTimeSeries<V> of(PreciseObjectTimeSeries<?, V> timeSeries, ZoneId zone) {
Objects.requireNonNull(zone, "zone");
if (timeSeries instanceof ImmutableZonedDateTimeObjectTimeSeries &&
((ImmutableZonedDateTimeObjectTimeSeries<V>) timeSeries).getZone().equals(zone)) {
return (ImmutableZonedDateTimeObjectTimeSeries<V>) timeSeries;
}
PreciseObjectTimeSeries<?, V> other = (PreciseObjectTimeSeries<?, V>) timeSeries;
long[] timesArray = other.timesArrayFast();
V[] valuesArray = other.valuesArray();
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, zone);
}
//-------------------------------------------------------------------------
/**
* Obtains a time-series from another time-series.
*
* @param <V> the value being viewed over time
* @param timeSeries the time-series, not null
* @param zone the time-zone, not null
* @return the time-series, not null
*/
public static <V> ImmutableZonedDateTimeObjectTimeSeries<V> from(ObjectTimeSeries<ZonedDateTime, V> timeSeries, ZoneId zone) {
Objects.requireNonNull(zone, "zone");
if (timeSeries instanceof PreciseObjectTimeSeries) {
return of((PreciseObjectTimeSeries<ZonedDateTime, V>) timeSeries, zone);
}
long[] timesArray = convertToLongArray(timeSeries.timesArray());
V[] valuesArray = timeSeries.valuesArray();
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, zone);
}
//-------------------------------------------------------------------------
/**
* Validates the data before creation.
*
* @param <V> the value being viewed over time
* @param instants the times, not null
* @param values the values, not null
*/
private static <V> void validate(long[] instants, V[] values) {
if (instants == null || values == null) {
throw new NullPointerException("Array must not be null");
}
// check lengths
if (instants.length != values.length) {
throw new IllegalArgumentException("Arrays are of different sizes: " + instants.length + ", " + values.length);
}
// check dates are ordered
long maxTime = Long.MIN_VALUE;
for (long time : instants) {
if (time < maxTime) {
throw new IllegalArgumentException("ZonedDateTimes must be ordered");
}
maxTime = time;
}
}
/**
* Creates an instance.
*
* @param instants the times, not null
* @param values the values, not null
* @param zone the time-zone, not null
*/
ImmutableZonedDateTimeObjectTimeSeries(long[] instants, V[] values, ZoneId zone) {
_times = instants;
_values = values;
_zone = zone;
}
//-------------------------------------------------------------------------
static long[] convertToLongArray(Collection<ZonedDateTime> instants) {
long[] timesArray = new long[instants.size()];
int i = 0;
for (ZonedDateTime instant : instants) {
timesArray[i++] = ZonedDateTimeToLongConverter.convertToLong(instant);
}
return timesArray;
}
static long[] convertToLongArray(ZonedDateTime[] instants) {
long[] timesArray = new long[instants.length];
for (int i = 0; i < timesArray.length; i++) {
timesArray[i] = ZonedDateTimeToLongConverter.convertToLong(instants[i]);
}
return timesArray;
}
static <V> Entry<ZonedDateTime, V> makeMapEntry(ZonedDateTime key, V value) {
return new SimpleImmutableEntry<ZonedDateTime, V>(key, value);
}
//-------------------------------------------------------------------------
@Override
protected long convertToLong(ZonedDateTime instant) {
return ZonedDateTimeToLongConverter.convertToLong(instant);
}
@Override
protected ZonedDateTime convertFromLong(long instant) {
return ZonedDateTimeToLongConverter.convertToZonedDateTime(instant, getZone());
}
@Override
protected ZonedDateTime[] createArray(int size) {
return new ZonedDateTime[size];
}
//-------------------------------------------------------------------------
@Override
public ZoneId getZone() {
return _zone;
}
@Override
public ZonedDateTimeObjectTimeSeries<V> withZone(ZoneId zone) {
Objects.requireNonNull(zone, "zone");
if (zone.equals(_zone)) {
return this;
}
// immutable, so can share arrays
return new ImmutableZonedDateTimeObjectTimeSeries<V>(_times, _values, zone);
}
//-------------------------------------------------------------------------
@Override
public int size() {
return _times.length;
}
//-------------------------------------------------------------------------
@Override
public boolean containsTime(long instant) {
int binarySearch = Arrays.binarySearch(_times, instant);
return (binarySearch >= 0);
}
@Override
public V getValue(long instant) {
int binarySearch = Arrays.binarySearch(_times, instant);
if (binarySearch >= 0) {
return _values[binarySearch];
} else {
return null;
}
}
@Override
public long getTimeAtIndexFast(int index) {
return _times[index];
}
@Override
public V getValueAtIndex(int index) {
return _values[index];
}
//-------------------------------------------------------------------------
@Override
public long getEarliestTimeFast() {
try {
return _times[0];
} catch (IndexOutOfBoundsException ex) {
throw new NoSuchElementException("Series is empty");
}
}
@Override
public V getEarliestValue() {
try {
return _values[0];
} catch (IndexOutOfBoundsException ex) {
throw new NoSuchElementException("Series is empty");
}
}
@Override
public long getLatestTimeFast() {
try {
return _times[_times.length - 1];
} catch (IndexOutOfBoundsException ex) {
throw new NoSuchElementException("Series is empty");
}
}
@Override
public V getLatestValue() {
try {
return _values[_values.length - 1];
} catch (IndexOutOfBoundsException ex) {
throw new NoSuchElementException("Series is empty");
}
}
//-------------------------------------------------------------------------
@Override
public long[] timesArrayFast() {
return _times.clone();
}
@Override
public V[] valuesArray() {
return _values.clone();
}
//-------------------------------------------------------------------------
@Override
public ZonedDateTimeObjectEntryIterator<V> iterator() {
return new ZonedDateTimeObjectEntryIterator<V>() {
private int _index = -1;
@Override
public boolean hasNext() {
return (_index + 1) < size();
}
@Override
public Entry<ZonedDateTime, V> next() {
if (hasNext() == false) {
throw new NoSuchElementException("No more elements in the iteration");
}
_index++;
long date = ImmutableZonedDateTimeObjectTimeSeries.this.getTimeAtIndexFast(_index);
V value = ImmutableZonedDateTimeObjectTimeSeries.this.getValueAtIndex(_index);
return makeMapEntry(ImmutableZonedDateTimeObjectTimeSeries.this.convertFromLong(date), value);
}
@Override
public long nextTimeFast() {
if (hasNext() == false) {
throw new NoSuchElementException("No more elements in the iteration");
}
_index++;
return ImmutableZonedDateTimeObjectTimeSeries.this.getTimeAtIndexFast(_index);
}
@Override
public ZonedDateTime nextTime() {
return ImmutableZonedDateTimeObjectTimeSeries.this.convertFromLong(nextTimeFast());
}
@Override
public long currentTimeFast() {
if (_index < 0) {
throw new IllegalStateException("Iterator has not yet been started");
}
return ImmutableZonedDateTimeObjectTimeSeries.this.getTimeAtIndexFast(_index);
}
@Override
public ZonedDateTime currentTime() {
return ImmutableZonedDateTimeObjectTimeSeries.this.convertFromLong(currentTimeFast());
}
@Override
public V currentValue() {
if (_index < 0) {
throw new IllegalStateException("Iterator has not yet been started");
}
return ImmutableZonedDateTimeObjectTimeSeries.this.getValueAtIndex(_index);
}
@Override
public int currentIndex() {
return _index;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Immutable iterator");
}
};
}
//-------------------------------------------------------------------------
@Override
public ZonedDateTimeObjectTimeSeries<V> subSeries(ZonedDateTime startZonedDateTime, ZonedDateTime endZonedDateTime) {
return subSeriesFast(convertToLong(startZonedDateTime), true, convertToLong(endZonedDateTime), false);
}
@Override
public ZonedDateTimeObjectTimeSeries<V> subSeries(ZonedDateTime startZonedDateTime, boolean includeStart, ZonedDateTime endZonedDateTime, boolean includeEnd) {
return subSeriesFast(convertToLong(startZonedDateTime), includeStart, convertToLong(endZonedDateTime), includeEnd);
}
@Override
public ZonedDateTimeObjectTimeSeries<V> subSeriesFast(long startZonedDateTime, long endZonedDateTime) {
return subSeriesFast(startZonedDateTime, true, endZonedDateTime, false);
}
@Override
public ZonedDateTimeObjectTimeSeries<V> subSeriesFast(long startZonedDateTime, boolean includeStart, long endZonedDateTime, boolean includeEnd) {
if (endZonedDateTime < startZonedDateTime) {
throw new IllegalArgumentException("Invalid subSeries: endTime < startTime");
}
// special case for start equals end
if (startZonedDateTime == endZonedDateTime) {
if (includeStart && includeEnd) {
int pos = Arrays.binarySearch(_times, startZonedDateTime);
if (pos >= 0) {
return new ImmutableZonedDateTimeObjectTimeSeries<V>(new long[] {startZonedDateTime}, Arrays.copyOfRange(_values, pos, pos + 1), _zone);
}
}
return ofEmpty(_zone);
}
// special case when this is empty
if (isEmpty()) {
return ofEmpty(_zone);
}
// normalize to include start and exclude end
if (includeStart == false) {
startZonedDateTime++;
}
if (includeEnd) {
if (endZonedDateTime != Long.MAX_VALUE) {
endZonedDateTime++;
}
}
// calculate
int startPos = Arrays.binarySearch(_times, startZonedDateTime);
startPos = startPos >= 0 ? startPos : -(startPos + 1);
int endPos = Arrays.binarySearch(_times, endZonedDateTime);
endPos = endPos >= 0 ? endPos : -(endPos + 1);
if (includeEnd && endZonedDateTime == Long.MAX_VALUE) {
endPos = _times.length;
}
long[] timesArray = Arrays.copyOfRange(_times, startPos, endPos);
V[] valuesArray = Arrays.copyOfRange(_values, startPos, endPos);
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, _zone);
}
//-------------------------------------------------------------------------
@Override
public ZonedDateTimeObjectTimeSeries<V> head(int numItems) {
if (numItems == size()) {
return this;
}
long[] timesArray = Arrays.copyOfRange(_times, 0, numItems);
V[] valuesArray = Arrays.copyOfRange(_values, 0, numItems);
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, _zone);
}
@Override
public ZonedDateTimeObjectTimeSeries<V> tail(int numItems) {
int size = size();
if (numItems == size) {
return this;
}
long[] timesArray = Arrays.copyOfRange(_times, size - numItems, size);
V[] valuesArray = Arrays.copyOfRange(_values, size - numItems, size);
return new ImmutableZonedDateTimeObjectTimeSeries<V>(timesArray, valuesArray, _zone);
}
@Override
@SuppressWarnings("unchecked")
public ZonedDateTimeObjectTimeSeries<V> lag(int days) {
long[] times = timesArrayFast();
V[] values = valuesArray();
if (days == 0) {
return new ImmutableZonedDateTimeObjectTimeSeries<V>(times, values, _zone);
} else if (days < 0) {
if (-days < times.length) {
long[] resultTimes = new long[times.length + days]; // remember days is -ve
System.arraycopy(times, 0, resultTimes, 0, times.length + days);
V[] resultValues = (V[]) new Object[times.length + days];
System.arraycopy(values, -days, resultValues, 0, times.length + days);
return new ImmutableZonedDateTimeObjectTimeSeries<V>(resultTimes, resultValues, _zone);
} else {
return ImmutableZonedDateTimeObjectTimeSeries.ofEmpty(_zone);
}
} else { // if (days > 0) {
if (days < times.length) {
long[] resultTimes = new long[times.length - days]; // remember days is +ve
System.arraycopy(times, days, resultTimes, 0, times.length - days);
V[] resultValues = (V[]) new Object[times.length - days];
System.arraycopy(values, 0, resultValues, 0, times.length - days);
return new ImmutableZonedDateTimeObjectTimeSeries<V>(resultTimes, resultValues, _zone);
} else {
return ImmutableZonedDateTimeObjectTimeSeries.ofEmpty(_zone);
}
}
}
//-------------------------------------------------------------------------
@Override
public ImmutableZonedDateTimeObjectTimeSeries<V> newInstance(ZonedDateTime[] dates, V[] values) {
return of(dates, values, _zone);
}
//-------------------------------------------------------------------------
@Override
public ZonedDateTimeObjectTimeSeries<V> operate(UnaryOperator<V> operator) {
V[] valuesArray = valuesArray();
for (int i = 0; i < valuesArray.length; i++) {
valuesArray[i] = operator.operate(valuesArray[i]);
}
return new ImmutableZonedDateTimeObjectTimeSeries<V>(_times, valuesArray, _zone); // immutable, so can share times
}
@Override
public ZonedDateTimeObjectTimeSeries<V> operate(V other, BinaryOperator<V> operator) {
V[] valuesArray = valuesArray();
for (int i = 0; i < valuesArray.length; i++) {
valuesArray[i] = operator.operate(valuesArray[i], other);
}
return new ImmutableZonedDateTimeObjectTimeSeries<V>(_times, valuesArray, _zone); // immutable, so can share times
}
@Override
@SuppressWarnings("unchecked")
public ZonedDateTimeObjectTimeSeries<V> operate(PreciseObjectTimeSeries<?, V> other, BinaryOperator<V> operator) {
long[] aTimes = timesArrayFast();
V[] aValues = valuesArray();
int aCount = 0;
long[] bTimes = other.timesArrayFast();
V[] bValues = other.valuesArray();
int bCount = 0;
long[] resTimes = new long[aTimes.length + bTimes.length];
V[] resValues = (V[]) new Object[resTimes.length];
int resCount = 0;
while (aCount < aTimes.length && bCount < bTimes.length) {
if (aTimes[aCount] == bTimes[bCount]) {
resTimes[resCount] = aTimes[aCount];
resValues[resCount] = operator.operate(aValues[aCount], bValues[bCount]);
resCount++;
aCount++;
bCount++;
} else if (aTimes[aCount] < bTimes[bCount]) {
aCount++;
} else { // if (aTimes[aCount] > bTimes[bCount]) {
bCount++;
}
}
long[] trimmedTimes = new long[resCount];
V[] trimmedValues = (V[]) new Object[resCount];
System.arraycopy(resTimes, 0, trimmedTimes, 0, resCount);
System.arraycopy(resValues, 0, trimmedValues, 0, resCount);
return new ImmutableZonedDateTimeObjectTimeSeries<V>(trimmedTimes, trimmedValues, _zone);
}
@SuppressWarnings("unchecked")
@Override
public ZonedDateTimeObjectTimeSeries<V> unionOperate(PreciseObjectTimeSeries<?, V> other, BinaryOperator<V> operator) {
long[] aTimes = timesArrayFast();
V[] aValues = valuesArray();
int aCount = 0;
long[] bTimes = other.timesArrayFast();
V[] bValues = other.valuesArray();
int bCount = 0;
long[] resTimes = new long[aTimes.length + bTimes.length];
V[] resValues = (V[]) new Object[resTimes.length];
int resCount = 0;
while (aCount < aTimes.length || bCount < bTimes.length) {
if (aCount >= aTimes.length) {
int bRemaining = bTimes.length - bCount;
System.arraycopy(bTimes, bCount, resTimes, resCount, bRemaining);
System.arraycopy(bValues, bCount, resValues, resCount, bRemaining);
resCount += bRemaining;
break;
} else if (bCount >= bTimes.length) {
int aRemaining = aTimes.length - aCount;
System.arraycopy(aTimes, aCount, resTimes, resCount, aRemaining);
System.arraycopy(aValues, aCount, resValues, resCount, aRemaining);
resCount += aRemaining;
break;
} else if (aTimes[aCount] == bTimes[bCount]) {
resTimes[resCount] = aTimes[aCount];
resValues[resCount] = operator.operate(aValues[aCount], bValues[bCount]);
resCount++;
aCount++;
bCount++;
} else if (aTimes[aCount] < bTimes[bCount]) {
resTimes[resCount] = aTimes[aCount];
resValues[resCount] = aValues[aCount];
resCount++;
aCount++;
} else { // if (aTimes[aCount] > bTimes[bCount]) {
resTimes[resCount] = bTimes[bCount];
resValues[resCount] = bValues[bCount];
resCount++;
bCount++;
}
}
long[] trimmedTimes = new long[resCount];
V[] trimmedValues = (V[]) new Object[resCount];
System.arraycopy(resTimes, 0, trimmedTimes, 0, resCount);
System.arraycopy(resValues, 0, trimmedValues, 0, resCount);
return new ImmutableZonedDateTimeObjectTimeSeries<V>(trimmedTimes, trimmedValues, _zone);
}
//-------------------------------------------------------------------------
@Override
public ZonedDateTimeObjectTimeSeries<V> intersectionFirstValue(PreciseObjectTimeSeries<?, V> other) {
return operate(other, ObjectTimeSeriesOperators.<V>firstOperator());
}
@Override
public ZonedDateTimeObjectTimeSeries<V> intersectionSecondValue(PreciseObjectTimeSeries<?, V> other) {
return operate(other, ObjectTimeSeriesOperators.<V>secondOperator());
}
@Override
public ZonedDateTimeObjectTimeSeries<V> noIntersectionOperation(PreciseObjectTimeSeries<?, V> other) {
return unionOperate(other, ObjectTimeSeriesOperators.<V>noIntersectionOperator());
}
//-------------------------------------------------------------------------
@Override
public ZonedDateTimeObjectTimeSeriesBuilder<V> toBuilder() {
return ImmutableZonedDateTimeObjectTimeSeries.<V>builder(_zone).putAll(this);
}
//-------------------------------------------------------------------------
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof ImmutableZonedDateTimeObjectTimeSeries) {
ImmutableZonedDateTimeObjectTimeSeries<?> other = (ImmutableZonedDateTimeObjectTimeSeries<?>) obj;
return Arrays.equals(_times, other._times) &&
Arrays.equals(_values, other._values);
}
if (obj instanceof PreciseObjectTimeSeries) {
PreciseObjectTimeSeries<?, ?> other = (PreciseObjectTimeSeries<?, ?>) obj;
return Arrays.equals(timesArrayFast(), other.timesArrayFast()) &&
Arrays.equals(valuesArray(), other.valuesArray());
}
return false;
}
@Override
public int hashCode() {
return Arrays.hashCode(timesArrayFast()) ^ Arrays.hashCode(valuesArray());
}
}