/**
*
*/
package vroom.common.utilities;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.Format;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import vroom.common.utilities.logging.LoggerHelper;
/**
* <code>StatCollector</code> is a class that allows the collection of statistic information and their output in a CSV
* text file.
* <p>
* Creation date: Jul 8, 2010 - 10:14:16 AM
*
* @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a
* href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a
* href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a>
* @version 1.0
*/
public class StatCollector {
/**
* Getter for this class logger
*
* @return the logger associated with this class
*/
public static LoggerHelper getLogger() {
return LoggerHelper.getLogger(StatCollector.class);
}
private static String sNumberFormat = "###0.0000";
/**
* Set the default format number to be used
*
* @param format
* a string of the form <code>###0.0000</code> defining the formating of decimal numbers
*/
public static void setDefaultNumberFormat(String format) {
sNumberFormat = format;
}
/**
* Returns the default number format to be used
*
* @return the default number format to be used
* @see #setDefaultNumberFormat(String)
*/
public static String getDefaultNumberFormat() {
return sNumberFormat;
}
public static String sCVSSeparator = ";";
public static char sDecimalSeparator = '.';
public static char sGroupingSeparator = ',';
public static String sCommentsPrefix = "#==================== COMMENTS ====================\n";
public static String sCommentsSuffix = "\n#==================================================\n\n";
private BufferedWriter mWriter;
private boolean mAutoFlush;
private final Label<?>[] mLabels;
private final List<Object[]> mValues;
private final DecimalFormat mFormat;
private final String mComment;
/**
* Creates a new <code>StatCollector</code> with no file attached
*
* @param comment
* an optional comment inserted at the beginning of the ouput file
* @param labels
* the labels for collected data
* @see #setFile(File, boolean)
*/
public StatCollector(String comment, Label<?>... labels) {
this(null, false, false, comment, labels);
}
/**
* Creates a new <code>StatCollector</code>
*
* @param output
* the file in which stats will be recorded
* @param autoFlush
* <code>true</code> if stats should be written immediatly in the file
* @param append
* <code>true</code> if statistics should be appended to the file, <code>false</code> to erase previous
* content
* @param comment
* an optional comment inserted at the beginning of the ouput file
* @param labels
* the labels for collected data
*/
public StatCollector(File output, boolean autoFlush, boolean append, String comment,
Label<?>... labels) {
if (labels == null) {
throw new IllegalArgumentException("Argument labels cannot be null");
}
mLabels = labels;
for (int i = 0; i < labels.length; i++) {
mLabels[i].id = i;
}
mValues = new LinkedList<Object[]>();
DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.getDefault());
symbols.setDecimalSeparator(sDecimalSeparator);
symbols.setGroupingSeparator(sGroupingSeparator);
mFormat = new DecimalFormat(sNumberFormat, symbols);
mValues.clear();
mComment = comment;
setFile(output, autoFlush, append);
}
/**
* Statistic collection.
* <p>
* The <code>stats</code> should be given in the same order as the labels were defined
* </p>
*
* @param stats
* the statistics to be collected
*/
public synchronized void collect(Object... stats) {
if (stats.length != mLabels.length) {
throw new IllegalArgumentException(
"The array stats should have the same length as the labels");
}
for (int i = 0; i < stats.length; i++) {
if (stats[i] != null && !mLabels[i].mValueClass.isAssignableFrom(stats[i].getClass())) {
throw new IllegalArgumentException(String.format(
"Unexpected data type for label #%s - %s: expected %s but was %s", i,
mLabels[i].mName, mLabels[i].mValueClass.getSimpleName(), stats[i]
.getClass().getSimpleName()));
}
}
mValues.add(stats);
write(false, stats);
}
/**
* Return the labels defined in this instance
*
* @return an array containing the defined labels
*/
public Label<?>[] getLabels() {
return mLabels;
}
/**
* Return the values collected for a given label
*
* @param <V>
* the type of data
* @param label
* the label to lookup
* @return an array containing the values collected so far
*/
@SuppressWarnings("unchecked")
public <V> V[] getValues(Label<V> label) {
V[] values = (V[]) new Object[mValues.size()];
int i = 0;
for (Object[] val : mValues) {
values[i++] = (V) val[label.id];
}
return values;
}
public String getSatString(Object... stats) {
if (stats.length != mLabels.length) {
throw new IllegalArgumentException(
"The array stats should have the same length as the labels");
}
StringBuilder b = new StringBuilder(stats.length * 10);
for (int i = 0; i < stats.length; i++) {
b.append(mLabels[i].getName());
b.append("=");
b.append(toString(mLabels[i], stats[i]));
if (i < stats.length - 1)
b.append(", ");
}
return b.toString();
}
/**
* Set the output file.
*
* @param output
* the file in which stats will be recorded
* @param autoflush
* <code>true</code> if stats should be written immediatly in the file
*/
public synchronized void setFile(File output, boolean autoflush, boolean append) {
boolean writeheader = (output != null) && (!output.exists() || !append);
BufferedWriter writer = null;
if (output != null) {
try {
writer = new BufferedWriter(new FileWriter(output, append));
} catch (IOException e) {
getLogger().exception("StatCollector.setFile", e);
writer = null;
}
}
mWriter = writer;
mAutoFlush = writer != null && autoflush;
if (writeheader) {
// Write comments
write("%s%s%s", sCommentsPrefix, mComment, sCommentsSuffix);
// Write column labels (headers)
write(true, (Object[]) mLabels);
}
}
/**
* Write a formated string, used for comments
*
* @param format
* @param args
*/
protected synchronized void write(String format, Object... args) {
if (mWriter == null) {
return;
}
try {
mWriter.write(String.format(format, args));
mWriter.newLine();
if (mAutoFlush) {
mWriter.flush();
}
} catch (IOException e) {
getLogger().exception("StatCollector.write", e);
}
}
/**
* Write an array of values separated by {@link #sCVSSeparator}
*
* @param header
* {@code true} if the objects are headers of the columns
* @param values
* the values to be writen
*/
protected synchronized void write(boolean header, Object... values) {
if (mWriter == null) {
return;
}
StringBuilder b = new StringBuilder();
for (int v = 0; v < values.length; v++) {
if (header)
b.append(mLabels[v].getName());
else
b.append(toString(mLabels[v], values[v]));
if (v < values.length - 1)
b.append(sCVSSeparator);
}
try {
mWriter.write(b.toString());
mWriter.newLine();
if (mAutoFlush) {
mWriter.flush();
}
} catch (IOException e) {
getLogger().exception("StatCollector.write", e);
}
}
public String toString(Label<?> label, Object val) {
if (val == null)
return "null";
if ((val instanceof Double && Double.isNaN((double) val))
|| (val instanceof Float && Float.isNaN((float) val)))
return "-";
else if (label.mFormat != null)
return label.mFormat.format(val);
else if (val instanceof Double || val instanceof Float)
return Constants.isZero(((Number) val).doubleValue()) ? mFormat.format(0) : mFormat
.format(val);
else
return val.toString();
}
/**
* Write the collected stats to the file
*/
public synchronized void flush() {
try {
mWriter.flush();
} catch (IOException e) {
getLogger().exception("StatCollector.write", e);
}
}
/**
* Close the underlying file writer
*/
public synchronized void close() {
try {
mWriter.close();
} catch (IOException e) {
getLogger().exception("StatCollector.write", e);
}
}
public static class Label<V extends Object> {
private final String mName;
private final Class<V> mValueClass;
private int id;
private final Format mFormat;
/**
* Getter for <code>name</code>
*
* @return the name
*/
public String getName() {
return mName;
}
/**
* Getter for <code>valueClass</code>
*
* @return the valueClass
*/
public Class<V> getValueClass() {
return mValueClass;
}
/**
* Creates a new <code>Label</code> using default formatting
*
* @param label
* the name of the label
* @param valueClass
* the type of data that will be collected
*/
public Label(String label, Class<V> valueClass) {
this(label, valueClass, null);
}
/**
* Creates a new <code>Label</code> using a specific formatting
*
* @param label
* the name of the label
* @param valueClass
* the type of data that will be collected
* @param format
* the format used to format the values (can be <code>null</code>)
*/
public Label(String label, Class<V> valueClass, Format format) {
mName = label;
mValueClass = valueClass;
mFormat = format;
id = -1;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return mName;
}
}
}