blob: 56cc41f71a0f423b0656adeef58df00be3005531 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007, 2008, 2011 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
*******************************************************************************/
package org.eclipse.stem.loggers.csv.logger;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
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.adapters.time.TimeProviderAdapterFactory;
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.loggers.csv.Activator;
import org.eclipse.stem.loggers.csv.CSVLogger;
import org.eclipse.stem.populationmodels.standard.DemographicPopulationModel;
import org.eclipse.stem.populationmodels.standard.PopulationGroup;
import org.eclipse.stem.populationmodels.standard.PopulationModel;
import org.eclipse.stem.populationmodels.standard.PopulationModelLabel;
/**
* STEM Decorator logger that writes the output values of node labels for the
* decorator's properties to a entry-delimeted log file. This class can be
* used to create various types of "separated-value" log files, including
* comma and tab separated.
*
*/
public class DelimetedFileDecoratorLogger
{
public static final Comparator<Node> NODE_SORTER = new Comparator<Node>() {
public int compare(Node n1, Node n2) {
String s1 = n1.getURI().toString();
String s2 = n2.getURI().toString();
return s1.compareTo(s2);
}
};
private final List<String> EXCLUDED_NODES = new ArrayList<String>(Arrays.asList(Constants.EXCLUDED_NODE_DEFAULTS));
private final Map<StateLevelMap, DelimetedWriter> logWriters = new HashMap<StateLevelMap, DelimetedWriter>();
private final Map<Integer, List<Node>> sortedNodeList = new HashMap<Integer, List<Node>>();
private final DelimetedFileSimulationLogger simulationLogger;
private final IntegrationDecorator decorator;
private final CSVLogger logger;
private File logDirectory;
private Map<Node,Integer> nodeLevels;
private String decoratorName;
private TimeProvider timeProvider;
private int interationCount = 0;
private boolean logIntegers = false;
private boolean beforeStart = false;
private List<Integer> resolutionLevels;
private List<String> populationIdentifiers = null;
public DelimetedFileDecoratorLogger(
DelimetedFileSimulationLogger simulationLogger,
IntegrationDecorator decorator) {
this.simulationLogger = simulationLogger;
this.decorator = decorator;
this.logger = simulationLogger.getLogger();
}
/**
* Starts the decorator logger. If enabled, writes the
* simulation parameters then sets up each log writer for the given properties
*/
public void start()
{
if (logger.isLogRunParameters()) {
writeRunParameters();
}
setupPropertyLoggers();
}
/**
* Stops the decorator logger. Flushes and closes each log writer
*/
public void stop()
{
flush();
for (DelimetedWriter fw : logWriters.values()) {
FileUtils.safeClose(fw);
}
logWriters.clear();
nodeLevels = null;
}
/**
* Flushes the buffer on each of the log writers
*/
public void flush()
{
for (DelimetedWriter fw : logWriters.values()) {
try {
fw.flush();
} catch (IOException e) {
}
}
}
/**
* Writes the current label property values for each node at the given simulation step time
*
* @param event
*/
public void log()
{
interationCount++;
List<String> decoratorPopulations = getPopulationIdentifiers();
for (Integer resolution : getResolutionLevels()) {
for (Node node : getSortedNodesForAdminlevel(resolution, nodeLevels)) {
for (String populationIdentifier : decoratorPopulations) {
NodeLabel label = getMatchingLabel(node, populationIdentifier);
if(label == null || !(label instanceof IntegrationLabel)) {
continue;
}
IntegrationLabel dmLabel = (IntegrationLabel)label;
IntegrationLabelValue labelValue = (IntegrationLabelValue)dmLabel.getCurrentValue();
for (IItemPropertyDescriptor itemDescriptor : getPropertiesForLabel(dmLabel)) {
StateLevelMap key = new StateLevelMap(populationIdentifier, itemDescriptor.getDisplayName(itemDescriptor), resolution);
DelimetedWriter fw = logWriters.get(key);
if(fw == null) {
continue;
}
try {
// Before writing the values for the first location,
// add iteration, time columns
if(fw.isEmptyLine()) {
fw.writeEntry(interationCount);
fw.writeEntry(Constants.LOG_DATE_FORMATTER.format(getScenarioTime().getTime()));
}
// Get the feature and label value for the feature
EStructuralFeature feature = (EStructuralFeature) itemDescriptor.getFeature(null);
double value = ((Double)labelValue.eGet(feature)).doubleValue();
if(logIntegers) {
fw.writeEntry((int)Math.rint(value));
} else {
fw.writeEntry(value);
}
} catch (IOException ioe) {
// If an IO Exception occurs, then close the log file
Activator.logError("Error writing to log file", ioe);
FileUtils.safeClose(fw);
logWriters.remove(key);
}
} // For each property in label
} // For each population identifier
} // For each node
} // For each node resolution
// After all iteration data is written, add a new line to the log files where needed
appendLineToLogWriters();
}
/**
* Adds a new line character to each log file writer if there is content on the line
*/
private void appendLineToLogWriters()
{
Iterator<Entry<StateLevelMap, DelimetedWriter>> writers = logWriters.entrySet().iterator();
// Make sure we're at a new line for each log writer that was modified
while (writers.hasNext()) {
DelimetedWriter fw = writers.next().getValue();
if (!fw.isEmptyLine()) {
try {
fw.newLine();
} catch (IOException ioe) {
// If an IO Exception occurs, then close the log file
Activator.logError("Error writing to log file", ioe);
FileUtils.safeClose(fw);
writers.remove();
}
}
}
}
/**
* @return The human readable name of the decorator
*/
private String getDecoratorName()
{
if (decoratorName == null) {
if(decorator instanceof DiseaseModel) {
decoratorName = ((DiseaseModel)decorator).getDiseaseName().trim();
} else if (decorator instanceof PopulationModel) {
decoratorName = ((PopulationModel)decorator).getName().trim();
} else {
decoratorName = decorator.eClass().toString();
}
}
return decoratorName;
}
/**
* @return The base directory for all log files for this simulation & decorator
*/
protected File getLogDirectory()
{
if (logDirectory == null) {
logDirectory = new File(simulationLogger.getLogDirectory(), getDecoratorName());
if (!logDirectory.exists() && !logDirectory.mkdirs()) {
Activator.logError("Unable to create log directory for decorator "+ getDecoratorName(), new IOException("Error creating directory "+ logDirectory.getAbsolutePath()));
}
}
return logDirectory;
}
/**
* @return The path to the file containing simulation parameters
*/
private File getRunParametersFile()
{
return new File(getLogDirectory(), Constants.RUN_PARAMETER_FILE_NAME);
}
/**
* @return The correct sequencer time for the simulation
*/
private STEMTime getScenarioTime()
{
STEMTime time = getTimeProvider().getTime();
if(time == null) {
time = simulationLogger.getSimulation().getScenario().getSequencer().getStartTime();
}
if(!beforeStart) {
time = time.addIncrement(simulationLogger.getSimulation().getScenario().getSequencer().getTimeDelta());
}
return time;
}
/**
* Create and write the parameters for this simulation
*/
private void writeRunParameters()
{
if (decorator == null) {
return;
}
DelimetedWriter fwp = null;
try {
fwp = new DelimetedWriter(logger.getDelimeter(), logger.getBufferSize(), new FileWriter(getRunParametersFile()));
final ComposedAdapterFactory itemProviderFactory = new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
final IItemPropertySource propertySource = (IItemPropertySource) itemProviderFactory
.adapt(decorator, IItemPropertySource.class);
final List<IItemPropertyDescriptor> properties = propertySource
.getPropertyDescriptors(decorator);
List<String> header = new ArrayList<String>();
List<String> values = new ArrayList<String>();
for (IItemPropertyDescriptor descriptor : properties) {
final EStructuralFeature feature = (EStructuralFeature) descriptor.getFeature(null);
// Omit Dublin Core values from the log file
if (CommonPackage.eINSTANCE.getDublinCore().equals(feature.getEContainingClass())) {
continue;
}
header.add(feature.getName());
values.add(((EObject) decorator).eGet(feature).toString());
}
fwp.writeLine(header);
fwp.writeLine(values);
} catch (final IOException e) {
Activator.logError("Error will writing to the run parameters log file", e);
} finally {
FileUtils.safeClose(fwp);
}
}
/**
* Get the path to the log file to write for the simulation/population/decorator property/admin level
*
* @param populationIdentifier
* @param decoratoryPropertyName
* @param adminLevel
* @return
*/
private File getLogFileForProperty(String populationIdentifier, String decoratoryPropertyName, int adminLevel)
{
File populationDirectory = new File(getLogDirectory(), populationIdentifier);
if (!populationDirectory.exists() && !populationDirectory.mkdirs()) {
Activator.logWarning("Failed to create log directory "+ populationDirectory.toString(), null);
}
StringBuilder fileName = new StringBuilder();
fileName.append(decoratoryPropertyName);
fileName.append("_");
fileName.append(adminLevel);
fileName.append(".");
fileName.append(Constants.LOG_FILE_SUFFIX);
return new File(populationDirectory, fileName.toString());
}
/**
* Creates a log file writer for a Property and Population for a given admin level. Creates the
* log file and
* @param populationIdentifier
* @param propertyDisplayName
* @param adminLevel
* @return
*/
private DelimetedWriter createLogger(String populationIdentifier, String propertyDisplayName, int adminLevel)
{
File propertyLogFile = getLogFileForProperty(populationIdentifier, propertyDisplayName, adminLevel);
DelimetedWriter fw = null;
boolean fileHasData = false;
try {
fw = new DelimetedWriter(logger.getDelimeter(), logger.getBufferSize(), new FileWriter(propertyLogFile));
fw.writeEntry(Constants.ITERATION_LABEL);
fw.writeEntry(Constants.TIME_LABEL);
for (Node node : getSortedNodesForAdminlevel(adminLevel, nodeLevels)) {
NodeLabel labelToWrite = getMatchingLabel(node, populationIdentifier);
if(labelToWrite != null) {
fileHasData = true;
fw.writeEntry(filterLocationId(node.getURI().toString()));
}
}
fw.newLine();
} catch (IOException ioe) {
Activator.logError("Exception while setting up the log file "+ propertyLogFile.toString(), ioe);
fileHasData = false;
} finally {
if (!fileHasData) {
FileUtils.safeClose(fw);
fw = null;
try {
propertyLogFile.delete();
} catch (Throwable t) {
// do nothing
}
}
}
return fw;
}
/**
* Setup and create log file writers for each individual property in
* the decorator that contains data to write.
*/
private void setupPropertyLoggers()
{
nodeLevels = SimulationUtils.getNodeLevelsForScenario(simulationLogger.getSimulation().getScenario());
// Get the first label we'll write for the decorator
IntegrationLabel label = getFirstLabelForDecorator();
if (label == null) {
Activator.logError("No labels found for decorator "+ getDecoratorName(), new Exception());
return;
}
// Get the properties for that label
List<IItemPropertyDescriptor>properties = getPropertiesForLabel(label);
if (properties == null) {
Activator.logError("Cannot retrieve properties for "+label, null);
return;
}
// Get the population identifiers for the label
List<String> populationIdentifiers = getPopulationIdentifiers();
if (populationIdentifiers == null) {
Activator.logError("No populations found for decorator "+ getDecoratorName(), null);
return;
}
// The log file is setup as:
// Population / Level / Decorator
// Iterate through the individual lists to setup the individual log files
for (IItemPropertyDescriptor decoratorProperty : properties) {
for (Integer level : getResolutionLevels()) {
for (String populationIdentifier : populationIdentifiers) {
String propertyName = decoratorProperty.getDisplayName(decoratorProperty);
DelimetedWriter fw = createLogger(populationIdentifier, propertyName, level);
if (fw != null) {
logWriters.put(new StateLevelMap(populationIdentifier, propertyName, level), fw);
}
}
}
}
}
/**
* @return The first log-able label for the decorator
*/
private IntegrationLabel getFirstLabelForDecorator()
{
for (DynamicLabel label : ((Decorator)decorator).getLabelsToUpdate()) {
if(label instanceof IntegrationLabel) {
return (IntegrationLabel)label;
}
}
return null;
}
/**
* Gets the relative value properties for the given label
* @param label
* @return
*/
private List<IItemPropertyDescriptor> getPropertiesForLabel(DynamicLabel label)
{
final RelativeValueProviderAdapter rvp = (RelativeValueProviderAdapter) RelativeValueProviderAdapterFactory.INSTANCE.adapt(label, RelativeValueProvider.class);
return rvp.getProperties();
}
/**
* @return The population identifiers for the decorator
*/
private List<String> getPopulationIdentifiers() {
if (decorator instanceof DiseaseModel) {
return ((DiseaseModel) decorator).getAllLabelIdentifiers();
} else if (decorator instanceof PopulationModel) {
return ((PopulationModel) decorator).getAllLabelIdentifiers();
}
return new ArrayList<String>();
}
/**
* @return The list of admin levels in the list of nodes
*/
private List<Integer> getResolutionLevels() {
if(resolutionLevels == null) {
resolutionLevels = new ArrayList<Integer>();
for(Integer level : nodeLevels.values()) {
if(!resolutionLevels.contains(level)) {
resolutionLevels.add(level);
}
}
}
return resolutionLevels;
}
/**
* @param node The node to test
* @return Whether the node is to be excluded from logging
*/
private boolean isExcludedNode(Node node)
{
for (String key : EXCLUDED_NODES) {
if (node.getURI().toString().contains(key)) {
return true;
}
}
return false;
}
/**
* Gets and sorts all nodes assigned to a specific administrative level.
*
* @param level The admin level to get nodes for
* @param nodeLevels The node level list to scan
* @return
*/
private List<Node> getSortedNodesForAdminlevel(int level, Map<Node, Integer>nodeLevels)
{
List<Node> list = sortedNodeList.get(level);
if (list == null) {
list = new ArrayList<Node>();
// Iterate through the list of nodes
for (Entry<Node,Integer> entry : nodeLevels.entrySet()) {
// If node level is equal to the current level
if (entry.getValue() == level) {
Node node = entry.getKey();
// Look through the labels to determin if we're going to write this node
for (NodeLabel s : node.getLabels()) {
if (s instanceof IntegrationLabel) {
// If the node contains an integration label and is
// not in the exclusion list, then add to the list for writing
if (!isExcludedNode(node)) {
list.add(entry.getKey());
}
break;
}
}
}
}
// Sort the list by applying the comparator
Collections.sort(list, NODE_SORTER);
sortedNodeList.put(level, list);
}
return list;
}
/**
* Gets the correct label for the given node, decorator, and population identifier
*
* @param n Node to get label from
* @param popId Population identifier to test
* @return The matching node label
*/
private NodeLabel getMatchingLabel(Node n, String popId) {
for(NodeLabel label:n.getLabels()) {
if (decorator instanceof DiseaseModel
&& label instanceof DiseaseModelLabel
&& ((DiseaseModelLabel)label).getDecorator().equals(decorator)
&& ((DiseaseModelLabel)label).getPopulationModelLabel().getPopulationIdentifier().equals(popId)) {
return label;
} else if (decorator instanceof PopulationModel
&& label instanceof PopulationModelLabel
&& ((PopulationModelLabel)label).getDecorator().equals(decorator)
&& ((PopulationModelLabel)label).getPopulationIdentifier().equals(popId)) {
return label;
}
}
return null;
}
/**
* 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(Constants.LOCATION_ID_PREFIX);
if (last >= 0) {
last += Constants.LOCATION_ID_PREFIX.length();
return unfiltered.substring(last, unfiltered.length());
}
return unfiltered;
}
/**
* @return The time provider for the canonical graph
*/
private TimeProvider getTimeProvider()
{
if (timeProvider == null) {
timeProvider = (TimeProvider) TimeProviderAdapterFactory.INSTANCE
.adapt(simulationLogger.getSimulation().getScenario().getCanonicalGraph(), TimeProvider.class);
}
return timeProvider;
}
/**
* 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;
}
}
}