blob: 6ef4e2cbf8012bf2b5a440bf9de907b51e7357fe [file] [log] [blame]
// CSVLogWriter
package org.eclipse.stem.util.loggers.views;
/*******************************************************************************
* Copyright (c) 2007,2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.IItemPropertySource;
import org.eclipse.stem.adapters.time.TimeProvider;
import org.eclipse.stem.core.STEMObjectPool;
import org.eclipse.stem.core.Utility;
import org.eclipse.stem.core.common.CommonPackage;
import org.eclipse.stem.core.graph.DynamicLabel;
import org.eclipse.stem.core.graph.IntegrationLabel;
import org.eclipse.stem.core.graph.IntegrationLabelValue;
import org.eclipse.stem.core.graph.Node;
import org.eclipse.stem.core.graph.NodeLabel;
import org.eclipse.stem.core.model.Decorator;
import org.eclipse.stem.core.model.IntegrationDecorator;
import org.eclipse.stem.core.model.STEMTime;
import org.eclipse.stem.definitions.adapters.relativevalue.RelativeValueProvider;
import org.eclipse.stem.definitions.adapters.relativevalue.RelativeValueProviderAdapter;
import org.eclipse.stem.definitions.adapters.relativevalue.RelativeValueProviderAdapterFactory;
import org.eclipse.stem.diseasemodels.standard.DiseaseModel;
import org.eclipse.stem.diseasemodels.standard.DiseaseModelLabel;
import org.eclipse.stem.jobs.simulation.ISimulation;
import org.eclipse.stem.populationmodels.standard.PopulationModel;
import org.eclipse.stem.populationmodels.standard.PopulationModelLabel;
import org.eclipse.stem.util.loggers.Activator;
import org.eclipse.stem.util.loggers.preferences.PreferenceConstants;
/**
* A log writer writing CSV files containing data for a single state of the
* simulation. Columns contain location IDs as well as simulation time and
* iteration id.
*/
public class NewCSVLogWriter extends LogWriter {
// The buffer size (per log file) to use.
private final static int BUFSZ = 1024 * 1024; // 1 MB
private static final String CSV_EXT = ".csv";
private static final String AIR_TRANSPORT_URI_DUPLICATE_FEATURE = "node/transport/pipe";
/**
*
*/
private String directoryName;
/**
*
*/
private String logDataObjectName;
// private final static String DUBLIN_CORE_CLASS = "DublinCore";
private static SimpleDateFormat dateFormat = new SimpleDateFormat(
"EEE d MMM yy", Locale.getDefault());
/**
* CSV Header Label for time column
*/
public static final String TIME_LABEL = "time";
/**
* CSV Header Label for iteration column
*/
public static final String ITERATION_LABEL = "iteration";
/**
* Name of all run parameter files (one per scenario folder)
*/
public static final String RUN_PARAMETER_FILE_NAME = "runparameters.csv";
/**
* File name for the EMF serialized decorator
*/
public static final String DECORATOR_FILE_NAME = "decorator.XMI";
/**
* Header in runparameters.csv file for column containing decorator class name
*/
public static final String DECORATOR_RUNPARAM_COLUMN_HEADER = "decorator";
private int icount = 0;
/**
* FileWriters are stored here, organized by state (e.g. S, E, I, R) and
* geographic level
*/
private final Map<StateLevelMap, BufferedWriter> fileWriters;
private boolean logIntegers;
// Pool string builders.
STEMObjectPool sbPool = new STEMObjectPool(5, 5) {
@Override
protected Object createNewObject() {
return new StringBuilder();
}
};
// Keep a map to determine what type of relative value provider each label value needs (avoids creating a new one for each label value!)
Map<IntegrationLabelValue, RelativeValueProviderAdapter> labelRelativeValueProviderMap = new HashMap<IntegrationLabelValue, RelativeValueProviderAdapter>();
/**
*
* @param dirName
* @param simulation
* @param dm
* @param logParameters
*/
public NewCSVLogWriter(final String dirName, final ISimulation simulation,
IntegrationDecorator dm, boolean logParameters) {
logIntegers = Activator.getDefault().getPreferenceStore()
.getBoolean(PreferenceConstants.LOG_INTEGER_PREFERENCE);
fileWriters = new HashMap<StateLevelMap, BufferedWriter>();
this.directoryName = dirName;
needsHeader = true;
done = false;
icount = 0;
// append the disease name or population type
if (dm instanceof DiseaseModel) {
logDataObjectName = ((DiseaseModel) dm).getDiseaseName().trim();
} else if (dm instanceof PopulationModel) {
logDataObjectName = ((PopulationModel) dm).getName().trim();
}
if (logDataObjectName == null) {
Activator
.logError(
"Failed to find a disease or population model. Cannot create CSV Logger",
null);
return;
}
this.directoryName += logDataObjectName + sep;
// remove illegal strings
directoryName = directoryName.replaceAll("\"", "");
final File dir = new File(this.directoryName);
if ((!dir.exists()) || (!dir.isDirectory())) {
// create it.
boolean success = dir.mkdirs();
if (!success) {
Activator.logError(
"Failed to Create Driectory" + directoryName,
new IOException("Failed to Create Driectory"
+ directoryName));
}
}
if (logParameters) {
logRunParameters(dm);
}
} // NewCSVLogWriter
/**
* Must implement this method to log data in run thread
*
* @param rvp
*
*/
@Override
public void logHeader(final RelativeValueProviderAdapter rvp) {
/*
* TODO do we need a header?
*/
}// log header
/**
* logHeader New method
*
* @param sim
* Simulation to log
* @param dm
* Disease Model
* @param nodeLevels
* Level information for each node in the simulation
* @param timeProvider
* Time provider
*/
@Override
public void logHeader(ISimulation sim, IntegrationDecorator dm,
Map<Node, Integer> nodeLevels, TimeProvider timeProvider) {
int headerItemCount = 0;
String dirs = this.directoryName;
File dir = new File(dirs);
if (!dir.exists()) {
boolean success = dir.mkdirs();
if (!success) {
Activator.logError(
"Failed to Create Driectory" + directoryName,
new IOException("Failed to Create Driectory"
+ directoryName));
}
}
// Find min/max level
int minLevel = Integer.MAX_VALUE;
int maxLevel = -1;
for (int level : nodeLevels.values()) {
if (level < minLevel)
minLevel = level;
if (level > maxLevel)
maxLevel = level;
}
try {
List<String> populationIdentifiers = getPopulationIdentifiers(dm);
for (String pid : populationIdentifiers) {
// Get the first node in the canonical graph and look at the
// labels.
// We will generate one log file for each label
// Find the first node that has some labels and use those as
// representative labels
// for every node
IntegrationLabel label = null;
Iterator<DynamicLabel> it = ((Decorator) dm)
.getLabelsToUpdate().iterator();
while (it.hasNext()) {
DynamicLabel l = it.next();
if (l instanceof IntegrationLabel) {
label = (IntegrationLabel) getMatchingLabel(dm, ((IntegrationLabel) l).getNode(), pid);
break;
}
}
if (label == null) {
// We couldn't find any labels. PANIC!
Activator.logError(
"Cannot log, no label found for decorator!",
new Exception());
return;
}
IntegrationLabelValue lval = (IntegrationLabelValue)label.getCurrentValue();
final RelativeValueProviderAdapter rvp = (RelativeValueProviderAdapter) RelativeValueProviderAdapterFactory.INSTANCE
.adapt(lval, RelativeValueProvider.class);
rvp.setTarget(lval);
List<IItemPropertyDescriptor> properties = rvp.getProperties();
lval.eAdapters().clear(); // Get rid of notifications being generated!
if (properties == null)
Activator.logError("Cannot retrieve properties for "
+ label, null);
for (IItemPropertyDescriptor property : properties) {
for (int level = minLevel; level <= maxLevel; ++level) {
boolean fileHasData = false;
StateLevelMap slm = new StateLevelMap(pid,
property.getDisplayName(property), level);
File pdir = new File(dirs + pid + sep);
if (!pdir.exists()) {
if (!pdir.mkdir()) {
throw new IOException(
"Error creating log store directories: "
+ pdir.getAbsolutePath());
}
}
String file = dirs + pid + sep
+ property.getDisplayName(property) + "_"
+ level + CSV_EXT;
BufferedWriter fw = new BufferedWriter(new FileWriter(
file), BUFSZ); // 10 MB
List<Node> nodeList = this.getNodeList(
level, nodeLevels);
// Iterate over nodes for a given resolution
fw.write(ITERATION_LABEL);
fw.write(",");
fw.write(TIME_LABEL);
int nodeCount = 0;
for(int i=0;i<nodeList.size();++i) {
Node node = nodeList.get(i);
NodeLabel mLabel = getMatchingLabel(dm, node, pid);
if (mLabel == null)
continue;
labelRelativeValueProviderMap.put((IntegrationLabelValue)mLabel.getCurrentValue(), rvp);
String nodeURI = node.getURI().toString();
if (nodeURI
.indexOf(AIR_TRANSPORT_URI_DUPLICATE_FEATURE) >= 0)
continue;
fileHasData = true;
String id = this.filterLocationId(node.getURI()
.toString());
fw.write(",");
fw.write(id);
headerItemCount++;
nodeCount++;
}
fw.write("\n");
if (fileHasData) {
// register the writer
fileWriters.put(slm, fw);
} else {
// don't ever write empty files
fw.flush();
fw.close();
File toDelete = new File(file);
toDelete.delete();
fw = null;
}
}
}
}// all properties
needsHeader = false;
} catch (IOException ioe) {
Activator.logError("Error writing log header ", ioe);
}
}
/**
* Returns true if the node has a label that is updated by the decorator.
*
* @param dm
* @param n
* @param popId
* @return The label of the correct type matching the population identifier,
* or null if not found
*/
private NodeLabel getMatchingLabel(IntegrationDecorator dm, Node n,
String popId) {
for (NodeLabel label : n.getLabels())
if (dm instanceof DiseaseModel
&& label instanceof DiseaseModelLabel
&& ((DiseaseModelLabel) label).getDecorator().equals(dm)
&& ((DiseaseModelLabel) label).getPopulationModelLabel()
.getPopulationIdentifier().equals(popId))
return label;
else if (dm instanceof PopulationModel
&& label instanceof PopulationModelLabel
&& ((PopulationModelLabel) label).getDecorator().equals(dm)
&& ((PopulationModelLabel) label).getPopulationIdentifier()
.equals(popId))
return label;
return null;
}
/**
* Return all the population identifiers the decorator manages
*
* @param id
* @return ArrayList<String> The population identifiers
*/
private List<String> getPopulationIdentifiers(IntegrationDecorator id) {
if (id instanceof DiseaseModel) {
return ((DiseaseModel) id).getAllLabelIdentifiers();
} else if (id instanceof PopulationModel) {
return ((PopulationModel) id).getAllLabelIdentifiers();
}
return null;
}
/**
* Creates a log file for run parameters. Only one of these is created per
* diseaseFolder
*
* @param dm
* Disease Model
*/
public void logRunParameters(IntegrationDecorator dm) {
if (dm == null) { // check
return;
}
try {
final FileWriter fwp = new FileWriter(directoryName
+ RUN_PARAMETER_FILE_NAME);
final ComposedAdapterFactory itemProviderFactory = new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
final IItemPropertySource propertySource = (IItemPropertySource) itemProviderFactory
.adapt(dm, IItemPropertySource.class);
final List<IItemPropertyDescriptor> properties = propertySource
.getPropertyDescriptors(dm);
StringBuilder header = new StringBuilder();
StringBuilder values = new StringBuilder();
for (int i = 0; i < properties.size(); ++i) {
IItemPropertyDescriptor descriptor = properties.get(i);
final EStructuralFeature feature = (EStructuralFeature) descriptor
.getFeature(null);
EClass containingClass = feature.getEContainingClass();
if (containingClass.equals(CommonPackage.eINSTANCE
.getDublinCore()))
continue;
Object value = ((EObject) dm).eGet(feature);
header.append(feature.getName());
header.append(",");
value = value.toString().replace(',', ' ');
value = value.toString().replace('\n', ' ');
values.append(value.toString().replace(',', ' '));
values.append(",");
}
header.setLength(header.length() - 1); // drop last ,
values.setLength(values.length() - 1); // drop last ,
// Insert first column containing class name of the decorator
header.insert(0, DECORATOR_RUNPARAM_COLUMN_HEADER+",");
String className = dm.getClass().getName();
values.insert(0, className+",");
fwp.write(header.toString());
fwp.write("\n");
fwp.write(values.toString());
fwp.flush();
fwp.close();
// Serialize the decorator using EMF serialization as well
// to capture the complete picture
URI serializationURI = URI.createFileURI(directoryName+DECORATOR_FILE_NAME);
Decorator copyDM = EcoreUtil.copy(dm);
Utility.serializeIdentifiableKeepDefaults(copyDM, serializationURI);
copyDM = null;
} catch (final IOException e) {
// debugging for now
Activator.logError("Error creating file writer for RUNPARAMETER ",
e);
}
}// logRunParameters
/**
* Must implement this method to log data in run thread Need to implement
* TimeProviderAdapterFactory.INSTANCE.adapt().....only Graph will work
* right now....later in CORE
*
* @param rvp
* @param timeProvider
*/
@Override
public void logData(final RelativeValueProviderAdapter rvp,
final TimeProvider timeProvider) {
// Not used
}// log data
// We need to do some caching here to speed up execution
Map<Node, Integer> currentNodeLevels = null;
List<Integer> currentResolutionList = null;
Map<Integer, List<Node>> currentSortedNodeListMap = null;
/**
* Log data from from a simulation after cycle
*
* @param sim
* Simulation
* @param dm
* DiseaseModel
* @param nodeLevels
* Node levels
* @param timeProvider
* @param beforeStart
*/
@SuppressWarnings("boxing")
@Override
public void logData(ISimulation sim, IntegrationDecorator dm,
Map<Node, Integer> nodeLevels, TimeProvider timeProvider,
boolean beforeStart) {
// Increment iteration count
this.icount++;
List<String> decoratorPopulations = this.getPopulationIdentifiers(dm);
Set<StateLevelMap> firstNode = new HashSet<StateLevelMap>();
firstNode.addAll(fileWriters.keySet());
try {
// Iterate over the map resolutions
List<Integer> resolutionList = this.getResolutionList(nodeLevels);
for(int i=0;i<resolutionList.size();++i) {
int resolution = resolutionList.get(i);
List<Node> nodeList = this.getNodeList(resolution,
nodeLevels);
// Iterate over nodes for a given resolution
for(int j=0;j<nodeList.size();++j) {
Node n = nodeList.get(j);
for (String popId : decoratorPopulations) {
NodeLabel mLabel = getMatchingLabel(dm, n, popId);
if (mLabel == null)
continue;
if (!(mLabel instanceof IntegrationLabel))
continue; // Check
IntegrationLabel dmLabel = (IntegrationLabel) mLabel;
IntegrationLabelValue dmlv = (IntegrationLabelValue) dmLabel.getCurrentValue();
final RelativeValueProviderAdapter rvp = labelRelativeValueProviderMap.get(dmlv);
if(rvp == null) {
Activator.logError("Error no rvp found for label "+mLabel, new Exception());
}
// final RelativeValueProviderAdapter rvp = (RelativeValueProviderAdapter) RelativeValueProviderAdapterFactory.INSTANCE
// .adapt(mLabel, RelativeValueProvider.class);
rvp.setTarget(dmlv);
List<IItemPropertyDescriptor> properties = rvp
.getProperties();
dmlv.eAdapters().clear();
StringBuilder sb = (StringBuilder)sbPool.get();
for(int k=0;k<properties.size();++k) {
sb.setLength(0);
IItemPropertyDescriptor itemDescriptor = properties.get(k);
StateLevelMap slm = new StateLevelMap(popId,
itemDescriptor.getDisplayName(itemDescriptor),
resolution);
BufferedWriter fw = fileWriters.get(slm);
if (fw == null) {
Activator.logError(
"Error, no file writer found for "
+ slm, null);
continue;
}
// Before writing the values for the first location,
// add iteration, time columns
if (firstNode.contains(slm)) {
firstNode.remove(slm);
sb.append(this.icount);
sb.append(",");
STEMTime time = timeProvider.getTime();
if (time == null) {
time = sim.getScenario().getSequencer()
.getStartTime();
}
if (!beforeStart)
// Add increment
time = time.addIncrement(sim.getScenario()
.getSequencer().getTimeDelta());
String timeString;
timeString = dateFormat.format(time.getTime());
sb.append(timeString);
}
EStructuralFeature feature = (EStructuralFeature) itemDescriptor
.getFeature(null);
double value =dmlv.get((EAttribute)feature);
sb.append(",");
if (logIntegers)
sb.append(Math.rint(value));
else
sb.append(value);
int sbLength = sb.length();
for(int l=0;l<sbLength;++l)
fw.write(sb.charAt(l)); // Avoids toString() creating uncessecary objects
} // For each property in label
sbPool.release(sb);
} // For each node label
} // For each node
} // For each node resolution
// We need to add the end-of-line characters to each log file
resolutionList = this.getResolutionList(nodeLevels);
for(int i=0;i<resolutionList.size();++i) {
int resolution = resolutionList.get(i);
List<Node> nodeList = this.getNodeList(resolution,
nodeLevels);
for(int j=0;j<nodeList.size();++j) {
Node n = nodeList.get(j);
boolean wroteEOL = false;
for (String popId : decoratorPopulations) {
NodeLabel mLabel = getMatchingLabel(dm, n, popId);
if (mLabel == null)
continue;
if (!(mLabel instanceof IntegrationLabel))
continue; // Check
IntegrationLabelValue lval = (IntegrationLabelValue)mLabel.getCurrentValue();
final RelativeValueProviderAdapter rvp = labelRelativeValueProviderMap.get(lval);
if(rvp == null) {
Activator.logError("Error no rvp found for label "+mLabel, new Exception());
}
rvp.setTarget(lval);
List<IItemPropertyDescriptor> properties = rvp
.getProperties();
for(int k=0;k<properties.size();++k) {
StateLevelMap slm = new StateLevelMap(popId,
properties.get(k).getDisplayName(properties.get(k)),
resolution);
BufferedWriter fw = fileWriters.get(slm);
fw.write("\n");
}
wroteEOL = true;
}
if (wroteEOL)
break; // We only need a sample of a node to find the
// file writers
}
}
needsHeader = false;
} catch (IOException ioe) {
Activator.logError("Error writing log header ", ioe);
}
// Safe to set the current node levels here
currentNodeLevels = nodeLevels;
}
/*
* private Graph getGraph(final Node node) { return (Graph)
* node.eContainer().eContainer(); } // getGraph
*/
/**
* Flush and Close the file
*/
@Override
public void flushLoggerData() {
Collection<BufferedWriter> writers = fileWriters.values();
for (BufferedWriter writer : writers) {
try {
writer.flush();
} catch (IOException ioe) {
//Activator.logError("Cannot flush log file", ioe);
}
}
} // flush All Data
/**
* Flush and Close the file
*/
@Override
public void closeLoggerData() {
Collection<BufferedWriter> writers = fileWriters.values();
for (BufferedWriter writer : writers) {
try {
writer.close();
} catch (IOException ioe) {
Activator.logError("Cannot close log file", ioe);
}
}
} // closeLoggerData
/**
*
* @param nodeLevels
* @return
*/
@SuppressWarnings("boxing")
private List<Integer> getResolutionList(
Map<Node, Integer> nodeLevels) {
if (nodeLevels == currentNodeLevels)
return currentResolutionList;
ArrayList<Integer> list = new ArrayList();
Collection<Integer> vals = nodeLevels.values();
for (int level : vals) {
if (!list.contains(level))
list.add(level);
}
currentResolutionList = list;
return list;
}
// Return all nodes at the same level
// Sort by the name alphabetically for convenience
@SuppressWarnings("boxing")
private List<Node> getNodeList(int level,
Map<Node, Integer> nodeLevels) {
if (nodeLevels == currentNodeLevels)
return currentSortedNodeListMap.get(level);
ArrayList<Node> list = new ArrayList<Node>();
// Set<Node>ns = nodeLevels.keySet();
// for(Node n:ns) {
for (Entry<Node, Integer> entry : nodeLevels.entrySet()) {
if (entry.getValue() == level) {
boolean write = false;
for (NodeLabel s : entry.getKey().getLabels()) {
if (s instanceof IntegrationLabel) {
write = true;
break;
}
}
if (!write) {
continue;
}
// Skip air transport nodes
if (entry.getKey().getURI().toString()
.indexOf(AIR_TRANSPORT_URI_DUPLICATE_FEATURE) == -1)
list.add(entry.getKey());
}
}
Collections.sort(list, new Comparator<Node>() {
public int compare(Node n1, Node n2) {
String s1 = n1.getURI().toString();
String s2 = n2.getURI().toString();
return s1.compareTo(s2);
}
});
if (currentSortedNodeListMap == null)
currentSortedNodeListMap = new HashMap<Integer, List<Node>>();
currentSortedNodeListMap.put(level, list);
return list;
}
/**
* filters the location id prefix from the beginning of a nodes ID for
* generation of a file name
*
* @param unfiltered
* @return the filtered file name using location id.
*/
private String filterLocationId(String unfiltered) {
int last = unfiltered.indexOf(LOCATIONID_PREFIX);
if (last >= 0) {
last += LOCATIONID_PREFIX.length();
return unfiltered.substring(last, unfiltered.length());
}
return unfiltered;
}
/**
* Used as key in map with FileWriters. FileWriters are key'd by the label
* (i.e. disease state) and by
*/
private static class StateLevelMap {
private final String popId;
private final String state;
private final int level;
public StateLevelMap(String popId, String state, int level) {
this.popId = popId;
this.state = state;
this.level = level;
}
public String getPopulationId() {
return this.popId;
}
public int getLevel() {
return this.level;
}
public String getState() {
return this.state;
}
@Override
public int hashCode() {
return state.hashCode() + popId.hashCode() + level; // ugh
}
@Override
public boolean equals(Object o) {
if (!(o instanceof StateLevelMap))
return false;
StateLevelMap slm = (StateLevelMap) o;
return (slm.getState().equals(this.state)
&& slm.getPopulationId().equals(this.popId) && slm
.getLevel() == level);
}
@Override
public String toString() {
return this.popId + " - " + this.state + "_" + this.level;
}
}
/**
* get the output folder name
*
* @return the log directory name
*/
public String getDirectoryName() {
return directoryName;
}
/**
* name of the data object being logged. e.g. The Disease Name
*
* @return the logged object name
*/
public String getLogDataObjectName() {
return logDataObjectName;
}
}