/*
* DBConverter.java - Copyright(c) 2014 Joe Pasqua
* Provided under the MIT License. See the LICENSE file for details.
* Created: Nov 30, 2014
*/
package org.noroomattheinn.visibletesla.data;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import org.noroomattheinn.timeseries.PersistentTS;
import org.noroomattheinn.timeseries.Row;
import org.noroomattheinn.timeseries.RowDescriptor;
import org.noroomattheinn.timeseries.TimeSeries;
import static org.noroomattheinn.tesla.Tesla.logger;
import org.noroomattheinn.visibletesla.stats.StatsRepository;
/**
* DBConverter: Convert from an older version of the underlying DB.
* Operation is as follows:<ul>
* <ol>Create a DBConverter instance</ol>
* <ol>Call conversionRequired to see if there's any work to do</ol>
* <ol>If there is, call convert()</ol>
* </ul>
*
* @author Joe Pasqua <joe at NoRoomAtTheInn dot org>
*/
class DBConverter {
/*------------------------------------------------------------------------------
*
* Internal State & types
*
*----------------------------------------------------------------------------*/
private final File container;
private final String baseName;
private static class MapTable extends TreeMap<Long,Map<String,Double>> {
MapTable() { super(); }
MapTable(MapTable src) { super(src); }
}
/*==============================================================================
* ------- -------
* ------- Public Interface To This Class -------
* ------- -------
*============================================================================*/
DBConverter(File container, String baseName) {
this.container = container;
this.baseName = baseName;
}
static boolean conversionRequired(File dir, String name) {
if (PersistentTS.repoExistsFor(dir, name)) {
logger.fine("TimeSeries already exists");
return false;
}
File oldRepo = new File(dir, name + ".stats.log");
if (oldRepo.exists()) {
logger.info("No TimeSeries exists, but an old repo does");
return true;
}
return false;
}
void convert() throws IOException {
Map<String,String> keyConversions = new HashMap<>();
keyConversions.put("S_PWR", "L_PWR");
keyConversions.put("S_SPD", "L_SPD");
List<StatsRepository> repos = new ArrayList<>(2);
repos.add(new StatsRepository(new File(container, baseName+".locs.log")));
repos.add(new StatsRepository(new File(container, baseName+".stats.log")));
TimeSeries ts = new PersistentTS(container, baseName, VTData.schema, true);
// Load each Repo into a separate MapTable
MapTable[] tableForRepo = new MapTable[repos.size()];
for (int i = 0; i < repos.size(); i++) {
logger.info("Loading data from repo " + i);
tableForRepo[i] = new MapTable();
repos.get(i).loadExistingData(new DoRecord(tableForRepo[i], keyConversions));
}
// Merge the repos together
MapTable mergedData = new MapTable(tableForRepo[0]);
for (int i = 1; i < repos.size(); i++) {
logger.info("Merging data from repo " + i);
MapTable table = tableForRepo[i];
for (Map.Entry<Long,Map<String,Double>> entry : table.entrySet()) {
long timestamp = entry.getKey();
Map<String,Double> row = mergedData.get(timestamp);
if (row == null) {
mergedData.put(timestamp, entry.getValue());
} else {
for (Map.Entry<String,Double> val : entry.getValue().entrySet()) {
row.put(val.getKey(), val.getValue());
}
}
}
}
repos = null; tableForRepo = null; // Allow the GC to do it's thing
// Write the merged repos out to the new TimeSeries
RowDescriptor schema = ts.getSchema();
int nRowsStored = 0;
logger.info("Number of rows to store: " + mergedData.size());
for (Map.Entry<Long,Map<String,Double>> tableRow : mergedData.entrySet()) {
long timestamp = tableRow.getKey();
Map<String,Double> row = tableRow.getValue();
Row r = new Row(timestamp, 0L, schema.nColumns);
for (Map.Entry<String,Double> entry:row.entrySet()) {
r.set(schema, entry.getKey(), entry.getValue());
}
ts.storeRow(r);
nRowsStored++;
if (nRowsStored % 10000 == 0) logger.info("Number of rows stored: " + nRowsStored);
}
logger.info("Total of " + nRowsStored + " stored");
ts.close();
}
private static class DoRecord implements StatsRepository.Recorder {
private final NavigableMap<Long,Map<String,Double>> rows;
private final Map<String,String> conversions;
private int rowsRecorded = 0;
DoRecord(NavigableMap<Long,Map<String,Double>> rows,
Map<String,String> conversions) {
this.rows = rows;
this.conversions = conversions;
}
@Override public void recordElement(long time, String type, double val) {
String convertedType = conversions.get(type);
if (convertedType != null) type = convertedType;
Map<String,Double> readings = rows.get(time);
if (readings == null) {
readings = new HashMap<>();
rows.put(time, readings);
rowsRecorded++;
if (rowsRecorded % 10000 == 0)
logger.info("Rows recorded: " + rowsRecorded);
}
readings.put(type, val);
}
}
}