org.noroomattheinn.visibletesla.stats.StatsRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.noroomattheinn.visibletesla.stats.StatsRepository.java

Source

/*
 * StatsRepository.java - Copyright(c) 2013 Joe Pasqua
 * Provided under the MIT License. See the LICENSE file for details.
 * Created: Aug 25, 2013
 */

package org.noroomattheinn.visibletesla.stats;

import com.google.common.collect.Range;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import static org.noroomattheinn.tesla.Tesla.logger;

/**
 * StatsRepository
 *
 * @author Joe Pasqua <joe at NoRoomAtTheInn dot org>
 */

public class StatsRepository {

    /*------------------------------------------------------------------------------
     *
     * Internal State
     * 
     *----------------------------------------------------------------------------*/

    private final File statsFile;
    private final List<Stat> entriesSinceLastFlush = new ArrayList<>();

    private PrintStream statsWriter;
    //private FileLock repoLock = null;

    /*==============================================================================
     * -------                                                               -------
     * -------              Public Interface To This Class                   ------- 
     * -------                                                               -------
     *============================================================================*/

    public StatsRepository(File statsFile) throws IOException {
        this.statsFile = statsFile;
        if (!prepRepository()) {
            throw new IOException("Unable to access repository: " + statsFile);
        }
    }

    public interface Recorder {
        void recordElement(long time, String type, double val);
    }

    public synchronized void loadExistingData(Recorder r) {
        BufferedReader rdr = getReaderForFile(statsFile);
        if (rdr == null)
            return;

        String line;
        Map<String, String> lastEntries = new HashMap<>();

        while ((line = getLineFromReader(rdr)) != null) {
            String tokens[] = line.split("\\s");

            if ((tokens.length - 1) % 2 != 0) { // Malformed line
                logger.log(Level.INFO, "Malformed stats entry: Improper number of tokens: {0}", line);
                continue;
            }

            Map<String, String> merged = mergeEntries(tokens, lastEntries);
            try {
                long time = Long.valueOf(tokens[0]);
                for (Map.Entry<String, String> entry : merged.entrySet()) {
                    String type = entry.getKey();
                    double val = Double.valueOf(entry.getValue());
                    r.recordElement(time, type, val);
                }
                lastEntries = merged;
            } catch (NumberFormatException ex) {
                logger.log(Level.INFO, "Malformed stats entry", ex);
            }
        }
    }

    public synchronized void loadExistingData(final Recorder r, final Range<Long> period) {
        loadExistingData(new Recorder() {
            @Override
            public void recordElement(long time, String type, double val) {
                if (period.contains(time))
                    r.recordElement(time, type, val);
            }
        });
    }

    public synchronized void storeElement(String type, long time, double value) {
        if (statsWriter == null)
            return;
        if (Double.isNaN(value) || Double.isInfinite(value))
            value = 0.0;
        entriesSinceLastFlush.add(new Stat(time, type, value));
    }

    public synchronized void close() {
        if (statsWriter != null)
            statsWriter.close();
    }

    private final Map<String, Double> lastValForType = new HashMap<>();
    private Set<String> columnsInLastRow = new HashSet<>();

    private boolean sameColumnsAsLastTime(Map<String, Double> thisRow) {
        if (thisRow.size() != columnsInLastRow.size())
            return false;
        for (String columnName : columnsInLastRow) {
            if (thisRow.get(columnName) == null)
                return false;
        }
        return true;
    }

    public synchronized void flushElements() {
        Map<Long, Map<String, Double>> rows = new TreeMap<>();

        // It's possible that there are entries for multiple points in time
        // Create a map with a key for each unique time where the value
        // is a list of Entries with that time
        for (Stat entry : entriesSinceLastFlush) {
            Map<String, Double> row = rows.get(entry.sample.timestamp);
            if (row == null) {
                row = new HashMap<>();
                rows.put(entry.sample.timestamp, row);
            }
            row.put(entry.type, entry.sample.value);
        }

        for (Map.Entry<Long, Map<String, Double>> row : rows.entrySet()) {
            long time = row.getKey();
            Map<String, Double> thisRow = row.getValue();
            if (!sameColumnsAsLastTime(thisRow)) {
                lastValForType.clear();
            }

            StringBuilder newValues = new StringBuilder();
            boolean hasDupes = false;
            Set<String> columnsInThisRow = new HashSet<>();
            for (Map.Entry<String, Double> column : thisRow.entrySet()) {
                String type = column.getKey();
                double value = column.getValue();
                columnsInThisRow.add(type);
                Double lastValue = lastValForType.get(type);
                if (lastValue == null || lastValue != value) {
                    lastValForType.put(type, value);
                    newValues.append(" ").append(type).append(" ").append(value);
                } else {
                    hasDupes = true;
                }
            }
            statsWriter.print(time);
            if (hasDupes)
                statsWriter.print(" * *");
            statsWriter.println(newValues.toString());
            columnsInLastRow = columnsInThisRow;
        }

        statsWriter.flush();
        entriesSinceLastFlush.clear();
    }

    /*------------------------------------------------------------------------------
     *
     * PRIVATE - Utility methods that aid in the reading and writing of data
     * to the external file in a space efficient manner
     * 
     *----------------------------------------------------------------------------*/

    private Map<String, String> mergeEntries(String[] newTokens, Map<String, String> lastEntries) {
        // If the newTokens contains a "*" column, then start by copying the last
        // set of values. If not, start with an empty Map.
        Map<String, String> vals = null;
        for (String token : newTokens) {
            if (token.equals("*")) {
                vals = new HashMap<>(lastEntries);
                break;
            }
        }
        if (vals == null)
            vals = new HashMap<>();

        // Go through the new tokens and overwrite any previous values for the same type
        for (int i = 1; i < newTokens.length;) {
            String type = newTokens[i++];
            String value = newTokens[i++];
            if (!type.equals("*"))
                vals.put(type, value);
        }

        return vals;
    }

    /*------------------------------------------------------------------------------
     *
     * Private Utility Methods and Classes
     * 
     *----------------------------------------------------------------------------*/

    private boolean prepRepository() {
        releaseWriter();
        FileOutputStream fos = obtainStream();
        if (fos == null)
            return false;
        statsWriter = new PrintStream(fos);
        return (statsWriter != null);
    }

    private FileOutputStream obtainStream() {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(statsFile, true);
        } catch (IOException e) {
            logger.warning("Unable to obtain lock on StatsRepository: " + e.toString());
        }
        return fos;
    }

    private void releaseWriter() {
        if (statsWriter != null)
            statsWriter.close();
    }

    private BufferedReader getReaderForFile(File file) {
        try {
            return new BufferedReader(new FileReader(file));
        } catch (FileNotFoundException ex) {
            logger.log(Level.INFO, "Could not open file", ex);
        }
        return null;
    }

    private String getLineFromReader(BufferedReader rdr) {
        try {
            return rdr.readLine();
        } catch (IOException ex) {
            logger.log(Level.INFO, "Failed reading line", ex);
        }
        return null;
    }

}