/** * Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT * All rights reserved. Use is subject to license terms. See LICENSE.TXT */ package org.diirt.datasource.extra; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.SortedMap; import java.util.TreeMap; import org.diirt.datasource.ReadFunction; import org.diirt.vtype.Display; import org.diirt.vtype.VNumber; import org.diirt.util.array.ArrayDouble; import org.diirt.util.array.CollectionNumbers; import org.diirt.util.array.ListNumber; import org.diirt.util.time.TimeInterval; /** * * @author carcassi */ public class DoubleArrayTimeCacheFromVDoubles implements DoubleArrayTimeCache { private NavigableMap<Instant, ArrayDouble> cache = new TreeMap<Instant, ArrayDouble>(); private List<? extends ReadFunction<? extends List<? extends VNumber>>> functions; private Display display; private Duration tolerance = Duration.ofMillis(1); public DoubleArrayTimeCacheFromVDoubles(List<? extends ReadFunction<? extends List<? extends VNumber>>> functions) { this.functions = functions; } public class Data implements DoubleArrayTimeCache.Data { private List<Instant> times = new ArrayList<Instant>(); private List<ArrayDouble> arrays = new ArrayList<ArrayDouble>(); private Instant begin; private Instant end; private Data(SortedMap<Instant, ArrayDouble> subMap, Instant begin, Instant end) { this.begin = begin; this.end = end; for (Map.Entry<Instant, ArrayDouble> en : subMap.entrySet()) { times.add(en.getKey()); arrays.add(en.getValue()); } } @Override public Instant getBegin() { return begin; } @Override public Instant getEnd() { return end; } @Override public int getNArrays() { return times.size(); } @Override public ListNumber getArray(int index) { return arrays.get(index); } @Override public Instant getTimestamp(int index) { return times.get(index); } } /** * Finds the array in the cache that is within the tolerance from the * given Instant. If not found, it creates a new array and adds * it to the cache. * * @param Instant a time * @return the array for that time */ private ArrayDouble arrayFor(Instant Instant) { // Try to find the array at the exact time ArrayDouble array = cache.get(Instant); if (array != null) return array; // See if the array after the Instant is in range Instant newTime = cache.higherKey(Instant); if (newTime != null && newTime.minus(tolerance).compareTo(Instant) <= 0) { return cache.get(newTime); } // See if the array before the Instant is in range newTime = cache.lowerKey(Instant); if (newTime != null && newTime.plus(tolerance).compareTo(Instant) >= 0) { return cache.get(newTime); } // Nothing found. Create a new array and initialize it with // the previous data (if any) if (newTime != null) { array = new ArrayDouble(Arrays.copyOf(CollectionNumbers.wrappedDoubleArray(cache.get(newTime)), functions.size()), false); } else { double[] blank = new double[functions.size()]; Arrays.fill(blank, Double.NaN); array = new ArrayDouble(blank, false); } cache.put(Instant, array); return array; } @Override public DoubleArrayTimeCache.Data getData(Instant begin, Instant end) { // Let's do it in a crappy way first... for (int n = 0; n < functions.size(); n++) { List<? extends VNumber> vDoubles = functions.get(n).readValue(); for (VNumber vNumber : vDoubles) { if (display == null) display = vNumber; ArrayDouble array = arrayFor(vNumber.getTimestamp()); double oldValue = array.getDouble(n); array.setDouble(n, vNumber.getValue().doubleValue()); // Fix the following values for (Map.Entry<Instant, ArrayDouble> en : cache.tailMap(vNumber.getTimestamp().plus(tolerance)).entrySet()) { // If no value or same value as before, replace it if (Double.isNaN(en.getValue().getDouble(n)) || en.getValue().getDouble(n) == oldValue) en.getValue().setDouble(n, vNumber.getValue().doubleValue()); } } } if (cache.isEmpty()) return null; Instant newBegin = cache.lowerKey(begin); if (newBegin == null) newBegin = cache.firstKey(); deleteBefore(begin); return data(newBegin, end); } private List<TimeInterval> update() { // Let's do it in a crappy way first... // Only keep track of first and last change Instant firstChange = null; Instant lastChange = null; for (int n = 0; n < functions.size(); n++) { List<? extends VNumber> vNumbers = functions.get(n).readValue(); for (VNumber vNumber : vNumbers) { if (display == null) display = vNumber; ArrayDouble array = arrayFor(vNumber.getTimestamp()); double oldValue = array.getDouble(n); array.setDouble(n, vNumber.getValue().doubleValue()); if (firstChange == null) { firstChange = vNumber.getTimestamp(); } if (lastChange == null) { lastChange = vNumber.getTimestamp(); } firstChange = min(firstChange, vNumber.getTimestamp()); lastChange = max(lastChange, vNumber.getTimestamp()); // Fix the following values for (Map.Entry<Instant, ArrayDouble> en : cache.tailMap(vNumber.getTimestamp().plus(tolerance)).entrySet()) { // If no value or same value as before, replace it if (Double.isNaN(en.getValue().getDouble(n)) || en.getValue().getDouble(n) == oldValue) en.getValue().setDouble(n, vNumber.getValue().doubleValue()); } } } if (firstChange == null) { return Collections.emptyList(); } return Collections.singletonList(TimeInterval.between(firstChange.minus(tolerance), lastChange)); } private void deleteBefore(Instant Instant) { if (cache.isEmpty()) return; // This we want to keep as we need to draw the area // from the Instant to the first new value Instant firstEntryBeforeInstant = cache.lowerKey(Instant); if (firstEntryBeforeInstant == null) return; // This is the last entry we want to delete Instant lastToDelete = cache.lowerKey(firstEntryBeforeInstant); if (lastToDelete == null) return; Instant firstKey = cache.firstKey(); while (firstKey.compareTo(lastToDelete) <= 0) { cache.remove(firstKey); firstKey = cache.firstKey(); } } private DoubleArrayTimeCache.Data data(Instant begin, Instant end) { return new Data(cache.subMap(begin, end), begin, end); } private <T extends Comparable<T>> T max(T a, T b) { if (a.compareTo(b) > 0) { return a; } else { return b; } } private <T extends Comparable<T>> T min(T a, T b) { if (a.compareTo(b) < 0) { return a; } else { return b; } } @Override public List<DoubleArrayTimeCache.Data> newData(Instant beginUpdate, Instant endUpdate, Instant beginNew, Instant endNew) { List<TimeInterval> updates = update(); if (updates.isEmpty()) return Collections.singletonList(data(cache.lowerKey(beginNew), endNew)); TimeInterval updateInterval = updates.get(0); Instant newBegin = max(beginUpdate, updateInterval.getStart()); newBegin = min(newBegin, beginNew); deleteBefore(beginUpdate); return Collections.singletonList(data(newBegin, endNew)); } @Override public Display getDisplay() { return display; } }