/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.components.evaluationmemory.execution.internal;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import de.rcenvironment.components.evaluationmemory.common.EvaluationMemoryComponentConstants;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.TypedDatum;
import de.rcenvironment.core.datamodel.api.TypedDatumSerializer;
import de.rcenvironment.core.utils.common.StringUtils;
/**
* Default implementation of {@link EvaluationMemoryAccess}.
*
* @author Doreen Seider
*/
public class EvaluationMemoryFileAccessImpl implements EvaluationMemoryAccess {
private static final String VERSION_NUMBER = "1";
private static final String TYPE = "type";
private static final String VERSION = "version";
private static final String OUTPUTS = "outputs";
private static final String INPUTS = "inputs";
private static final List<DataType> ALWAYS_VALID_OUTPUT_DATATYPES = new ArrayList<>();
private final File evalMemoryFile;
private TypedDatumSerializer typedDatumSerializer;
static {
ALWAYS_VALID_OUTPUT_DATATYPES.add(DataType.NotAValue);
}
public EvaluationMemoryFileAccessImpl(String memoryFilePath) {
evalMemoryFile = new File(memoryFilePath);
}
@Override
public synchronized void setInputsOutputsDefinition(SortedMap<String, DataType> inputs, SortedMap<String, DataType> outputs)
throws IOException {
Properties evalMemory = loadEvaluationMemory();
addInputsDefinition(inputs, evalMemory);
addOutputsDefinition(outputs, evalMemory);
storeEvaluationMemory(evalMemory);
}
@Override
public synchronized void addEvaluationValues(SortedMap<String, TypedDatum> inputValues,
SortedMap<String, TypedDatum> outputValues) throws IOException {
Properties evalMemory = loadEvaluationMemory();
validateInputs(evalMemory, getEndpoints(inputValues));
validateOutputs(evalMemory, getEndpoints(outputValues));
String evalMemoryKey = createEvaluationMemoryKeyForInputValues(inputValues);
List<String> evalMemoryValues = new ArrayList<>();
for (TypedDatum value : outputValues.values()) {
evalMemoryValues.add(typedDatumSerializer.serialize(value));
}
evalMemory.put(evalMemoryKey, StringUtils.escapeAndConcat(evalMemoryValues));
storeEvaluationMemory(evalMemory);
}
private void storeEvaluationMemory(Properties evalMemory) throws FileNotFoundException, IOException {
evalMemory.put(VERSION, VERSION_NUMBER);
evalMemory.put(TYPE, EvaluationMemoryComponentConstants.COMPONENT_ID);
try (FileOutputStream memoryFileOutputStream = new FileOutputStream(evalMemoryFile)) {
evalMemory.store(memoryFileOutputStream, null);
}
}
@Override
public synchronized SortedMap<String, TypedDatum> getEvaluationResult(SortedMap<String, TypedDatum> inputValues,
SortedMap<String, DataType> outputs) throws IOException {
Properties evalMemory = loadEvaluationMemory();
validateInputs(evalMemory, getEndpoints(inputValues));
validateOutputs(evalMemory, outputs);
SortedMap<String, TypedDatum> outputValues = null;
String evalMemoryKey = createEvaluationMemoryKeyForInputValues(inputValues);
if (evalMemory.containsKey(evalMemoryKey)) {
String[] serializedEvaluationResult = StringUtils.splitAndUnescape(evalMemory.getProperty(evalMemoryKey));
outputValues = new TreeMap<>();
int i = 0;
for (String output : outputs.keySet()) {
outputValues.put(output, typedDatumSerializer.deserialize(serializedEvaluationResult[i++]));
}
}
return outputValues;
}
@Override
public synchronized void validateEvaluationMemory(SortedMap<String, DataType> inputs, SortedMap<String, DataType> outputs)
throws IOException {
Properties evalMemory = loadEvaluationMemory();
validateVersionAndType(evalMemory);
validateInputs(evalMemory, inputs);
validateOutputs(evalMemory, outputs);
validateEvaluationMemoryEntries(inputs, outputs, evalMemory);
}
private void addInputsDefinition(SortedMap<String, DataType> inputs, Properties evalMemory) {
evalMemory.put(INPUTS, createEndpointDefinitionEntry(inputs));
}
private void addOutputsDefinition(SortedMap<String, DataType> outputs, Properties evalMemory) {
evalMemory.put(OUTPUTS, createEndpointDefinitionEntry(outputs));
}
private String createEndpointDefinitionEntry(Map<String, DataType> endpoints) {
List<String> parts = new ArrayList<>();
for (String output: endpoints.keySet()) {
parts.add(output);
parts.add(endpoints.get(output).name());
}
return StringUtils.escapeAndConcat(parts);
}
private void validateEvaluationMemoryEntries(SortedMap<String, DataType> inputs, SortedMap<String, DataType> outputs,
Properties evalMemory) throws IOException {
for (Object key : evalMemory.keySet()) {
if (!key.equals(INPUTS) && !key.equals(OUTPUTS) && !key.equals(VERSION) && !key.equals(TYPE)) {
validateEvaluationMemoryEntry(inputs, (String) key);
validateEvaluationMemoryEntry(outputs, evalMemory.getProperty((String) key));
}
}
}
private void validateEvaluationMemoryEntry(SortedMap<String, DataType> endpoints, String evalMemoryEntry) throws IOException {
String[] typedDatumParts = StringUtils.splitAndUnescape(evalMemoryEntry);
List<TypedDatum> typedDatums = new ArrayList<>();
for (String value : typedDatumParts) {
try {
typedDatums.add(typedDatumSerializer.deserialize(value));
} catch (IllegalArgumentException e) {
throw new IOException("Failed to read values from evaluation memory file", e);
}
}
if (typedDatums.size() != endpoints.size()) {
throwIOException(endpoints, typedDatums);
}
int i = 0;
for (String endpointName : endpoints.keySet()) {
if (ALWAYS_VALID_OUTPUT_DATATYPES.contains(typedDatums.get(i).getDataType())) {
continue;
}
if (endpoints.get(endpointName) != typedDatums.get(i).getDataType()) {
throwIOException(endpoints, typedDatums);
}
i++;
}
}
private void throwIOException(SortedMap<String, DataType> endpoints, List<TypedDatum> tuple) throws IOException {
throw new IOException(StringUtils.format("Input/output data type(s) don't match input/output data type(s) "
+ "in evaluation memory file - expected: %s actual: %s",
endpoints, tuple));
}
private void validateVersionAndType(Properties evalMemory) throws IOException {
if (evalMemory.getProperty(VERSION) == null) {
throw new IOException("Version information is missing");
}
if (!evalMemory.getProperty(VERSION).equals(VERSION_NUMBER)) {
throw new IOException(StringUtils.format("Version '%s' not supported; expected version: %s",
evalMemory.getProperty(VERSION), VERSION_NUMBER));
}
if (evalMemory.getProperty(TYPE) == null) {
throw new IOException("Type information is missing");
}
if (!evalMemory.getProperty(TYPE).equals(EvaluationMemoryComponentConstants.COMPONENT_ID)) {
throw new IOException(StringUtils.format("Type '%s' not supported; expected type: %s",
evalMemory.getProperty(TYPE), EvaluationMemoryComponentConstants.COMPONENT_ID));
}
}
private void validateInputs(Properties evalMemory, Map<String, DataType> inputs) throws IOException {
validateEndoints(getEndpoints(evalMemory, INPUTS), inputs, true);
}
private void validateOutputs(Properties evalMemory, Map<String, DataType> outputs) throws IOException {
validateEndoints(getEndpoints(evalMemory, OUTPUTS), outputs, false);
}
private void validateEndoints(Map<String, DataType> endpointsExpected, Map<String, DataType> actualEndpoints, boolean inputs)
throws IOException {
if (!areEndpointsEqual(endpointsExpected, actualEndpoints, inputs)) {
throw new IOException(StringUtils.format("Input(s)/output(s) don't match input(s)/output(s) in evaluation memory file"
+ " - expected: %s actual: %s", endpointsExpected, actualEndpoints));
}
}
private boolean areEndpointsEqual(Map<String, DataType> endpointsExpected, Map<String, DataType> actualEndpoints, boolean inputs) {
boolean equals = endpointsExpected.size() == actualEndpoints.size();
if (equals) {
for (String endpoint : actualEndpoints.keySet()) {
if (!inputs && ALWAYS_VALID_OUTPUT_DATATYPES.contains(actualEndpoints.get(endpoint))) {
continue;
}
if (!endpointsExpected.containsKey(endpoint)
|| !endpointsExpected.get(endpoint).equals(actualEndpoints.get(endpoint))) {
equals = false;
break;
}
}
}
return equals;
}
private Properties loadEvaluationMemory() throws IOException {
Properties evalMemory = new Properties();
if (!evalMemoryFile.exists()) {
throw new FileNotFoundException(
"Evaluation memory file not found; either deleted or not created due to invalid file name");
}
try (FileInputStream memFileInputStream = new FileInputStream(evalMemoryFile)) {
evalMemory.load(memFileInputStream);
}
return evalMemory;
}
private Map<String, DataType> getEndpoints(Properties evalMemory, String key) throws IOException {
String endpointsEntry = evalMemory.getProperty(key);
if (endpointsEntry == null) {
throw new IOException(StringUtils.format("'%s' definition is missing in evaluation memory file: %s (it is required to ensure"
+ " correct evaluation memory handling, is written by the component, and must not be removed)",
key, evalMemoryFile));
}
String[] parts = StringUtils.splitAndUnescape(endpointsEntry);
Map<String, DataType> endpoints = new HashMap<>();
for (int i = 0; i < parts.length; i = i + 2) {
endpoints.put(parts[i], DataType.valueOf(parts[i + 1]));
}
return endpoints;
}
private SortedMap<String, DataType> getEndpoints(SortedMap<String, TypedDatum> endpoints) {
SortedMap<String, DataType> endpointsWithDataType = new TreeMap<>();
for (String endpoint : endpoints.keySet()) {
endpointsWithDataType.put(endpoint, endpoints.get(endpoint).getDataType());
}
return endpointsWithDataType;
}
private String createEvaluationMemoryKeyForInputValues(SortedMap<String, TypedDatum> valuesToEvaluate) {
List<String> serializedValues = new ArrayList<>();
Iterator<TypedDatum> valuesIterator = valuesToEvaluate.values().iterator();
while (valuesIterator.hasNext()) {
serializedValues.add(typedDatumSerializer.serialize(valuesIterator.next()));
}
return StringUtils.escapeAndConcat(serializedValues);
}
protected void setTypedDatumSerializer(TypedDatumSerializer serializer) {
this.typedDatumSerializer = serializer;
}
}