/*
 * Copyright 2006-2019 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * http://www.rcenvironment.de/
 */

package de.rcenvironment.components.outputwriter.execution;

import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.notNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

import de.rcenvironment.components.outputwriter.common.OutputLocation;
import de.rcenvironment.components.outputwriter.common.OutputLocationList;
import de.rcenvironment.components.outputwriter.common.OutputWriterComponentConstants;
import de.rcenvironment.components.outputwriter.common.OutputWriterComponentConstants.HandleExistingFile;
import de.rcenvironment.core.communication.common.InstanceNodeSessionId;
import de.rcenvironment.core.component.api.ComponentException;
import de.rcenvironment.core.component.datamanagement.api.ComponentDataManagementService;
import de.rcenvironment.core.component.execution.api.Component;
import de.rcenvironment.core.component.execution.api.ComponentContext;
import de.rcenvironment.core.component.testutils.ComponentContextMock;
import de.rcenvironment.core.component.testutils.ComponentTestWrapper;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.TypedDatumFactory;
import de.rcenvironment.core.datamodel.api.TypedDatumService;
import de.rcenvironment.core.datamodel.types.api.DirectoryReferenceTD;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.TempFileService;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;

/**
 * Integration test for OutputWriter Components.
 *
 * @author Brigitte Boden
 * @author Dominik Schneider
 * @author Alexander Weinert
 */
public class OutputWriterComponentTest {

    private static final String INDENT = "\t";

    private static final String SOME_SHORT_TEXT = "short text";

    private static final String ENDPOINT_ID_TEXT = "textInput_id";

    private static final String ENDPOINT_PH_TEXT = "textI\\]nput";
    private static final String ENDPOINT_NAME_TEXT = "textI]nput";

    private static final String ENDPOINT_ID_BOOLEAN = "booleanInput_id";

    private static final String ENDPOINT_PH_BOOLEAN = "bool\\[ean\\]Input";
    private static final String ENDPOINT_NAME_BOOLEAN = "bool[ean]Input";

    private static final String ENDPOINT_ID_INT = "intInput_id";

    private static final String ENDPOINT_NAME_INT = "intInput";

    private static final String ENDPOINT_ID_FLOAT = "floatInput_id";

    private static final String ENDPOINT_PH_FLOAT = "float\\[Input";
    private static final String ENDPOINT_NAME_FLOAT = "float[Input";

    private static final String EXCEPTION_MESSAGE = "Exception while setting up componentDataManagementServiceMock";

    private static final String TARGET_NAME_FILE = "myfile";

    private static final String TARGET_NAME_DIRECTORY = "myDir";

    private static final String ENDPOINT_ID_DIRECTORY = "endpointId2";

    private static final String ENDPOINT_ID_FILE = "endpointId1";

    private static final String ENDPOINT_NAME_DIRECTORY = "dirInput";

    private static final String ENDPOINT_NAME_FILE = "fileInput";

    private static final String TARGET_SUBFOLDER_NAME = "sub";

    private static final String INPUT_DIRECTORY = "inputDirectory";

    private static final String INPUT_FILENAME = "inputFile";

    private static final String COLON = ":";

    private TempFileService tempFileService;

    private File testRootDir;

    private File inputFile;

    private File inputDirectory;

    private final Log log = LogFactory.getLog(getClass());

    private ObjectMapper mapper;

    /**
     * Context mock for Output Writer Component test.
     *
     * @author Brigitte Boden
     */
    private final class OutputWriterComponentContextMock extends ComponentContextMock {

        private static final long serialVersionUID = 4307822088604760715L;

        @Override
        public String getWorkflowInstanceName() {
            return "TestWorkflowInstanceName";
        }

        @Override
        public String getInstanceName() {
            return "TestInstanceName";
        }

        @Override
        public String getComponentName() {
            return "TestComponentName";
        }

    }

    private ComponentTestWrapper component;

    private OutputWriterComponentContextMock context;

    private TypedDatumFactory typedDatumFactory;

    private ComponentDataManagementService componentDataManagementServiceMock;

    /*
     * These two IAnswers are called by the mocked ComponentDataManagementService and imitate the behavior of the methods
     * copyReferenceToLocalFile() and copyDirectoryReferenceTDToLocalDirectory (on the local node). This is necessary because the "real"
     * datamanagement service is not available here, but for test purposes the output files/directories have to be really created.
     * 
     * WARNING: Therefore, this test may have to be adapted if the API for the datamanagement service changes.
     */
    private IAnswer<Void> copyReferenceToLocalFileAnswer = new IAnswer<Void>() {

        @Override
        public Void answer() throws Throwable {
            String reference = (String) EasyMock.getCurrentArguments()[0];
            File targetfile = (File) EasyMock.getCurrentArguments()[1];

            FileUtils.copyFile(new File(reference), targetfile);
            return null;
        }
    };

    private IAnswer<Void> copyDirectoryReferenceTDToLocalDirectoryAnswer = new IAnswer<Void>() {

        @Override
        public Void answer() throws Throwable {
            String reference = ((DirectoryReferenceTD) EasyMock.getCurrentArguments()[1]).getDirectoryReference();
            File targetdir = (File) EasyMock.getCurrentArguments()[2];

            FileUtils.copyDirectoryToDirectory(new File(reference), targetdir);
            return null;
        }
    };

    /**
     * Set up the Output Writer test.
     * 
     * @throws Exception e
     */
    @Before
    public void setup() throws Exception {
        context = new OutputWriterComponentContextMock();
        component = new ComponentTestWrapper(new OutputWriterComponent(), context);
        typedDatumFactory = context.getService(TypedDatumService.class).getFactory();

        // Setup root directory for testing
        TempFileServiceAccess.setupUnitTestEnvironment();
        tempFileService = TempFileServiceAccess.getInstance();
        testRootDir = tempFileService.createManagedTempDir();
        log.debug("Testing in temporary directory " + testRootDir.getAbsolutePath());

        inputFile = createAndVerifyFile(testRootDir, INPUT_FILENAME);
        inputDirectory = createAndVerifySubdir(testRootDir, INPUT_DIRECTORY);
        assertEquals(2, testRootDir.listFiles().length);

        // Add some files to the input directory
        createAndVerifyFile(inputDirectory, "fileInDir1");
        createAndVerifyFile(inputDirectory, "fileInDir2");
        assertEquals(2, inputDirectory.listFiles().length);

        componentDataManagementServiceMock = EasyMock.createMock(ComponentDataManagementService.class);

        context.addService(ComponentDataManagementService.class, componentDataManagementServiceMock);
        context.setConfigurationValue(OutputWriterComponentConstants.CONFIG_KEY_ROOT, testRootDir.getAbsolutePath());

        mapper = JsonUtils.getDefaultObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY);
    }

    /**
     * Clean up temp files.
     * 
     * @throws IOException on Error.
     */
    @After
    public void cleanup() throws IOException {
        tempFileService.disposeManagedTempDirOrFile(testRootDir);
    }

    /**
     * Test with no input.
     * 
     * @throws ComponentException e
     */
    @Test
    public void testNoInputs() throws ComponentException {
        component.start();
        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
    }

    /**
     * Test file and directory inputs. Here, no previous file/directory exists (i.e. no autorename etc. is necessary).
     * 
     * @throws ComponentException e
     */
    @Test
    public void testWithFileAndDirInputs() throws ComponentException {
        try {
            EasyMock.reset(componentDataManagementServiceMock);
            // Set expectations for data management calls. Arguments should not be null, except for the NodeIdentifier, which is obtained
            // from the mock component context instead of a "real" context.
            componentDataManagementServiceMock.copyReferenceToLocalFile(notNull(String.class),
                notNull(File.class), anyObject(InstanceNodeSessionId.class));
            EasyMock.expectLastCall().andAnswer(copyReferenceToLocalFileAnswer);
            componentDataManagementServiceMock.copyDirectoryReferenceTDToLocalDirectory(notNull(ComponentContext.class),
                notNull(DirectoryReferenceTD.class),
                notNull(File.class));
            EasyMock.expectLastCall().andAnswer(copyDirectoryReferenceTDToLocalDirectoryAnswer);
            EasyMock.replay(componentDataManagementServiceMock);
        } catch (IOException e1) {
            throw new ComponentException(EXCEPTION_MESSAGE);
        }

        component.start();
        Map<String, String> metadataForFile = generateMetadata(TARGET_NAME_FILE, TARGET_SUBFOLDER_NAME);
        context.addSimulatedInput(ENDPOINT_NAME_FILE, ENDPOINT_ID_FILE, DataType.FileReference, true, metadataForFile);
        Map<String, String> metadataForDir = generateMetadata(TARGET_NAME_DIRECTORY, TARGET_SUBFOLDER_NAME);
        context.addSimulatedInput(ENDPOINT_NAME_DIRECTORY, ENDPOINT_ID_DIRECTORY, DataType.DirectoryReference, true, metadataForDir);

        // Assert that the target file and directory do not exist yet
        String path = testRootDir + File.separator + TARGET_SUBFOLDER_NAME;
        assertFalse(new File(path, TARGET_NAME_FILE).exists());
        assertFalse(new File(path, TARGET_NAME_DIRECTORY).exists());

        // Set a file input value
        String fileReference = inputFile.getAbsolutePath();
        context.setInputValue(ENDPOINT_NAME_FILE, typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();

        // Set a directory input value
        String dirReference = inputDirectory.getAbsolutePath();
        context.setInputValue(ENDPOINT_NAME_DIRECTORY, typedDatumFactory.createDirectoryReference(dirReference, inputDirectory.getName()));
        component.processInputs();

        // Assert that the target file and directory do exist now and the directory contains its two files.
        assertTrue(new File(path, TARGET_NAME_FILE).exists());
        assertTrue(new File(path, TARGET_NAME_DIRECTORY).exists());
        assertEquals(2, new File(path, TARGET_NAME_DIRECTORY).listFiles().length);

        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
    }

    /**
     * Test file and directory inputs with already existing targets. Here, the autorename option is tested.
     * 
     * @throws ComponentException e
     */
    @Test
    public void testWithFileAndDirInputsExistingTarget() throws ComponentException {
        try {
            EasyMock.reset(componentDataManagementServiceMock);
            // Set expectations for data management calls. Arguments should not be null, except for the NodeIdentifier, which is obtained
            // from the mock component context instead of a "real" context.
            componentDataManagementServiceMock.copyReferenceToLocalFile(notNull(String.class),
                notNull(File.class), anyObject(InstanceNodeSessionId.class));
            EasyMock.expectLastCall().andAnswer(copyReferenceToLocalFileAnswer);
            componentDataManagementServiceMock.copyDirectoryReferenceTDToLocalDirectory(notNull(ComponentContext.class),
                notNull(DirectoryReferenceTD.class),
                notNull(File.class));
            EasyMock.expectLastCall().andAnswer(copyDirectoryReferenceTDToLocalDirectoryAnswer);
            EasyMock.replay(componentDataManagementServiceMock);
        } catch (IOException e1) {
            throw new ComponentException(EXCEPTION_MESSAGE);
        }

        component.start();
        Map<String, String> metadataForFile = generateMetadata(TARGET_NAME_FILE, TARGET_SUBFOLDER_NAME);
        context.addSimulatedInput(ENDPOINT_NAME_FILE, ENDPOINT_ID_FILE, DataType.FileReference, true, metadataForFile);
        Map<String, String> metadataForDir = generateMetadata(TARGET_NAME_DIRECTORY, TARGET_SUBFOLDER_NAME);
        context.addSimulatedInput(ENDPOINT_NAME_DIRECTORY, ENDPOINT_ID_DIRECTORY, DataType.DirectoryReference, true, metadataForDir);

        // Create file and directory with the given names, such that the Output Writer has to use the autorename option.
        File folderForSaving = createAndVerifySubdir(testRootDir, TARGET_SUBFOLDER_NAME);
        try {
            createAndVerifyFile(folderForSaving, TARGET_NAME_FILE);
            createAndVerifySubdir(folderForSaving, TARGET_NAME_DIRECTORY);
        } catch (IOException e) {
            log.error("Error while creating test file: " + e);
        }
        assertEquals(2, folderForSaving.listFiles().length);

        // Set a file input value
        String fileReference = inputFile.getAbsolutePath();
        context.setInputValue(ENDPOINT_NAME_FILE, typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();

        // Set a directory input value
        String dirReference = inputDirectory.getAbsolutePath();
        context.setInputValue(ENDPOINT_NAME_DIRECTORY, typedDatumFactory.createDirectoryReference(dirReference, inputDirectory.getName()));
        component.processInputs();

        // Due to the autorename option, there should now be 2 files and 2 directories
        assertEquals(4, folderForSaving.listFiles().length);

        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
    }

    /**
     * Test the replacement of placeholders.
     * 
     * @throws ComponentException e
     */
    @Test
    public void testPlaceholders() throws ComponentException {
        try {
            EasyMock.reset(componentDataManagementServiceMock);
            // Set expectations for data management calls. Arguments should not be null, except for the NodeIdentifier, which is obtained
            // from the mock component context instead of a "real" context.
            componentDataManagementServiceMock.copyReferenceToLocalFile(notNull(String.class),
                notNull(File.class), anyObject(InstanceNodeSessionId.class));
            EasyMock.expectLastCall().andAnswer(copyReferenceToLocalFileAnswer).times(7);
            EasyMock.replay(componentDataManagementServiceMock);
        } catch (IOException e1) {
            throw new ComponentException(EXCEPTION_MESSAGE);
        }

        component.start();

        // Set an input for each supported placeholder
        final String end = "yyy";
        final String beginning = "xxx";
        context.addSimulatedInput("workflowname", "workflowname_id", DataType.FileReference, true,
            generateMetadata(beginning + OutputWriterComponentConstants.PH_WORKFLOWNAME + end, null));
        context.addSimulatedInput("inputname", "inputname_id", DataType.FileReference, true,
            generateMetadata(beginning + OutputWriterComponentConstants.PH_INPUTNAME + end, null));
        context.addSimulatedInput("start_ts", "start_ts_id", DataType.FileReference, true,
            generateMetadata(beginning + "starttime" + OutputWriterComponentConstants.PH_WF_START_TS + end, null));
        context.addSimulatedInput("comp_name", "comp_name_id", DataType.FileReference, true,
            generateMetadata(beginning + OutputWriterComponentConstants.PH_COMP_NAME + end, null));
        context.addSimulatedInput("comp_type", "comp_type_id", DataType.FileReference, true,
            generateMetadata(beginning + OutputWriterComponentConstants.PH_COMP_TYPE + end, null));
        context.addSimulatedInput("ts", "ts_id", DataType.FileReference, true,
            generateMetadata(beginning + "time" + OutputWriterComponentConstants.PH_TIMESTAMP + end, null));
        context.addSimulatedInput("ec", "ec_id", DataType.FileReference, true,
            generateMetadata(beginning + "execution_count" + OutputWriterComponentConstants.PH_EXECUTION_COUNT + end, null));

        // Send input file to each input and test if placeholders are replaced
        String fileReference = inputFile.getAbsolutePath();
        context.setInputValue("workflowname", typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();
        context.setInputValue("inputname", typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();
        context.setInputValue("ts", typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();
        context.setInputValue("start_ts", typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();
        context.setInputValue("comp_name", typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();
        context.setInputValue("comp_type", typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();
        context.setInputValue("ec", typedDatumFactory.createFileReference(fileReference, inputFile.getName()));
        component.processInputs();

        assertEquals(9, testRootDir.listFiles().length); // input file, input directory + 7 output files

        // Assert that filenames do not contain placeholders and the text besides the placeholders has not been changed.
        for (int i = 0; i < testRootDir.listFiles().length; i++) {
            String filename = testRootDir.listFiles()[i].getName();
            if (!filename.equals(INPUT_DIRECTORY) && !filename.equals(INPUT_FILENAME)) {
                if (!filename.endsWith(end) || !filename.startsWith(beginning)
                    || filename.contains(OutputWriterComponentConstants.PH_PREFIX)
                    || filename.contains(OutputWriterComponentConstants.PH_SUFFIX)) {
                    fail();
                }
            }
        }

        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
        EasyMock.verify(componentDataManagementServiceMock);
    }

    /**
     * Test with inputs of simple data types and without targets.
     * 
     * @throws ComponentException e
     */
    @Test
    public void testSimpleDataInputsWithoutTargets() throws ComponentException {

        EasyMock.reset(componentDataManagementServiceMock);

        component.start();
        context.addSimulatedInput(ENDPOINT_NAME_FLOAT, ENDPOINT_ID_FLOAT, DataType.Float, true, null);
        context.addSimulatedInput(ENDPOINT_NAME_INT, ENDPOINT_ID_INT, DataType.Integer, true, null);
        context.addSimulatedInput(ENDPOINT_NAME_BOOLEAN, ENDPOINT_ID_BOOLEAN, DataType.Boolean, true, null);
        context.addSimulatedInput(ENDPOINT_NAME_TEXT, ENDPOINT_ID_TEXT, DataType.Float, true, null);

        context.setInputValue(ENDPOINT_NAME_FLOAT, typedDatumFactory.createFloat(0.0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_INT, typedDatumFactory.createInteger(0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_BOOLEAN, typedDatumFactory.createBoolean(false));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_TEXT, typedDatumFactory.createShortText(SOME_SHORT_TEXT));
        component.processInputs();

        assertTrue(testRootDir.listFiles().length == 2); // This test should not produce output, thus only the input file/dir exist.

        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
    }

    /**
     * Test with inputs of simple data types and with several targets. ("Append" Option)
     * 
     * @throws ComponentException e
     * @throws IOException e
     */
    @Test
    public void testSimpleDataInputsWithTargetsAppend() throws ComponentException, IOException {

        EasyMock.reset(componentDataManagementServiceMock);

        createSimpleDataInputsAndTargets(HandleExistingFile.APPEND);

        component.start();
        context.setInputValue(ENDPOINT_NAME_FLOAT, typedDatumFactory.createFloat(0.0));
        context.setInputValue(ENDPOINT_NAME_INT, typedDatumFactory.createInteger(0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_BOOLEAN, typedDatumFactory.createBoolean(false));
        context.setInputValue(ENDPOINT_NAME_TEXT, typedDatumFactory.createShortText(SOME_SHORT_TEXT));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_FLOAT, typedDatumFactory.createFloat(0.0));
        context.setInputValue(ENDPOINT_NAME_INT, typedDatumFactory.createInteger(0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_BOOLEAN, typedDatumFactory.createBoolean(false));
        context.setInputValue(ENDPOINT_NAME_TEXT, typedDatumFactory.createShortText(SOME_SHORT_TEXT));
        component.processInputs();

        assertTrue(testRootDir.listFiles().length == 4);

        for (File file : testRootDir.listFiles()) {
            if (!file.isDirectory() && !file.getName().equals(INPUT_FILENAME)) {
                checkSimpleDataOutputFile(file);
            }
        }

        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
    }

    /**
     * Test with inputs of simple data types and with several targets. ("Override" Option)
     * 
     * @throws ComponentException e
     * @throws IOException e
     */
    @Test
    public void testSimpleDataInputsWithTargetsOverride() throws ComponentException, IOException {

        EasyMock.reset(componentDataManagementServiceMock);

        createSimpleDataInputsAndTargets(HandleExistingFile.OVERRIDE);

        component.start();
        context.setInputValue(ENDPOINT_NAME_FLOAT, typedDatumFactory.createFloat(0.0));
        context.setInputValue(ENDPOINT_NAME_INT, typedDatumFactory.createInteger(0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_BOOLEAN, typedDatumFactory.createBoolean(false));
        context.setInputValue(ENDPOINT_NAME_TEXT, typedDatumFactory.createShortText(SOME_SHORT_TEXT));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_FLOAT, typedDatumFactory.createFloat(0.0));
        context.setInputValue(ENDPOINT_NAME_INT, typedDatumFactory.createInteger(0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_BOOLEAN, typedDatumFactory.createBoolean(false));
        context.setInputValue(ENDPOINT_NAME_TEXT, typedDatumFactory.createShortText(SOME_SHORT_TEXT));
        component.processInputs();

        assertTrue(testRootDir.listFiles().length == 4);

        for (File file : testRootDir.listFiles()) {
            if (!file.isDirectory() && !file.getName().equals(INPUT_FILENAME)) {
                checkSimpleDataOutputFile(file);
            }
        }

        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
    }

    /**
     * Test with inputs of simple data types and with several targets. ("Auto-Rename" Option)
     * 
     * @throws ComponentException e
     * @throws IOException e
     */
    @Test
    public void testSimpleDataInputsWithTargetsAutorename() throws ComponentException, IOException {

        EasyMock.reset(componentDataManagementServiceMock);

        createSimpleDataInputsAndTargets(HandleExistingFile.AUTORENAME);

        component.start();
        context.setInputValue(ENDPOINT_NAME_FLOAT, typedDatumFactory.createFloat(0.0));
        context.setInputValue(ENDPOINT_NAME_INT, typedDatumFactory.createInteger(0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_BOOLEAN, typedDatumFactory.createBoolean(false));
        context.setInputValue(ENDPOINT_NAME_TEXT, typedDatumFactory.createShortText(SOME_SHORT_TEXT));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_FLOAT, typedDatumFactory.createFloat(0.0));
        context.setInputValue(ENDPOINT_NAME_INT, typedDatumFactory.createInteger(0));
        component.processInputs();
        context.setInputValue(ENDPOINT_NAME_BOOLEAN, typedDatumFactory.createBoolean(false));
        context.setInputValue(ENDPOINT_NAME_TEXT, typedDatumFactory.createShortText(SOME_SHORT_TEXT));
        component.processInputs();

        assertTrue(testRootDir.listFiles().length == 6);

        for (File file : testRootDir.listFiles()) {
            if (!file.isDirectory() && !file.getName().equals(INPUT_FILENAME)) {
                checkSimpleDataOutputFile(file);
            }
        }

        component.tearDownAndDispose(Component.FinalComponentState.FINISHED);
    }

    /**
     * Generates meta data for output writer test.
     * 
     * @param from Start value of parametric study range
     * @param to End value of parametric study range
     * @param step Step size of parametric study range
     * @return
     */
    private Map<String, String> generateMetadata(String filename, String foldername) {
        Map<String, String> metadata = new HashMap<>();
        String folderForSaving = OutputWriterComponentConstants.ROOT_DISPLAY_NAME;
        if (foldername != null && !foldername.isEmpty()) {
            folderForSaving = folderForSaving + "\\" + foldername;
        }
        metadata.put(OutputWriterComponentConstants.CONFIG_KEY_FILENAME, filename);
        metadata.put(OutputWriterComponentConstants.CONFIG_KEY_FOLDERFORSAVING, folderForSaving);
        return metadata;
    }

    private File createAndVerifySubdir(File parentDir, String name) {
        File dir = new File(parentDir, name);
        dir.mkdir();
        assertTrue(dir.isDirectory());
        return dir;
    }

    private File createAndVerifyFile(File parentDir, String name) throws IOException {
        File file = new File(parentDir, name);
        file.createNewFile();
        assertTrue(file.isFile() && file.canRead());
        return file;
    }

    private void createSimpleDataInputsAndTargets(HandleExistingFile handling) {
        context.addSimulatedInput(ENDPOINT_NAME_FLOAT, ENDPOINT_ID_FLOAT, DataType.Float, true, null);
        context.addSimulatedInput(ENDPOINT_NAME_INT, ENDPOINT_ID_INT, DataType.Integer, true, null);
        context.addSimulatedInput(ENDPOINT_NAME_BOOLEAN, ENDPOINT_ID_BOOLEAN, DataType.Boolean, true, null);
        context.addSimulatedInput(ENDPOINT_NAME_TEXT, ENDPOINT_ID_TEXT, DataType.Float, true, null);

        String header1 =
            OutputWriterComponentConstants.PH_TIMESTAMP
                + INDENT
                + OutputWriterComponentConstants.PH_EXECUTION_COUNT + COLON;

        String header2 =
            OutputWriterComponentConstants.PH_TIMESTAMP
                + OutputWriterComponentConstants.PH_LINEBREAK
                + COLON;

        String format1 =
            OutputWriterComponentConstants.PH_PREFIX + escapeSquaredBrackets(ENDPOINT_NAME_FLOAT) + OutputWriterComponentConstants.PH_SUFFIX
                + INDENT
                + OutputWriterComponentConstants.PH_PREFIX + ENDPOINT_NAME_INT
                + OutputWriterComponentConstants.PH_SUFFIX + OutputWriterComponentConstants.PH_LINEBREAK
                + OutputWriterComponentConstants.PH_TIMESTAMP;

        String format2 =
            OutputWriterComponentConstants.PH_PREFIX + ENDPOINT_PH_BOOLEAN + OutputWriterComponentConstants.PH_SUFFIX
                + INDENT + OutputWriterComponentConstants.PH_PREFIX + ENDPOINT_PH_TEXT
                + OutputWriterComponentConstants.PH_SUFFIX + OutputWriterComponentConstants.PH_LINEBREAK
                + OutputWriterComponentConstants.PH_TIMESTAMP;

        List<String> inputs1 = new ArrayList<String>();
        inputs1.add(ENDPOINT_NAME_FLOAT);
        inputs1.add(ENDPOINT_NAME_INT);
        OutputLocation ol1 =
            new OutputLocation("output1.txt", OutputWriterComponentConstants.ROOT_DISPLAY_NAME, header1, format1,
                handling,
                inputs1);
        List<String> inputs2 = new ArrayList<String>();
        inputs2.add(ENDPOINT_NAME_BOOLEAN);
        inputs2.add(ENDPOINT_NAME_TEXT);
        OutputLocation ol2 =
            new OutputLocation("output2.txt", OutputWriterComponentConstants.ROOT_DISPLAY_NAME, header2, format2,
                handling,
                inputs2);

        OutputLocationList list = new OutputLocationList();
        list.addOrReplaceOutputLocation(ol1);
        list.addOrReplaceOutputLocation(ol2);
        try {
            context.setConfigurationValue(OutputWriterComponentConstants.CONFIG_KEY_OUTPUTLOCATIONS, mapper.writeValueAsString(list));
        } catch (IOException e) {
            fail("Json Error");
        }
    }

    // Checks if the file is not empty and does not contain placeholders
    private void checkSimpleDataOutputFile(File output) throws IOException {
        String content = FileUtils.readFileToString(output);
        if (content.isEmpty()) {
            fail("Output file " + output.getName() + " is empty.");
        } else {
            List<String> inputList = new ArrayList<String>();
            inputList
                .add(OutputWriterComponentConstants.PH_PREFIX + ENDPOINT_NAME_BOOLEAN + OutputWriterComponentConstants.PH_SUFFIX);
            inputList.add(OutputWriterComponentConstants.PH_PREFIX + ENDPOINT_NAME_INT + OutputWriterComponentConstants.PH_SUFFIX);
            inputList.add(OutputWriterComponentConstants.PH_PREFIX + ENDPOINT_NAME_TEXT + OutputWriterComponentConstants.PH_SUFFIX);
            inputList.add(OutputWriterComponentConstants.PH_PREFIX + ENDPOINT_NAME_FLOAT + OutputWriterComponentConstants.PH_SUFFIX);

            List<String> classicPlaceholderList = new ArrayList<String>();

            classicPlaceholderList.add(OutputWriterComponentConstants.PH_LINEBREAK);
            classicPlaceholderList.add(OutputWriterComponentConstants.PH_TIMESTAMP);
            classicPlaceholderList.add(OutputWriterComponentConstants.PH_EXECUTION_COUNT);

            int split = content.indexOf(COLON);

            String header = content.substring(0, split);
            String format = content.substring(split + 1, content.length());

            for (String placeholder : classicPlaceholderList) {
                if (header.contains(placeholder)) {
                    fail("In output file " + output.getName() + ", not all placeholders in header were resolved.");
                }
            }

            for (String placeholder : inputList) {
                if (format.contains(placeholder)) {
                    fail("In output file " + output.getName() + ", not all placeholders in value(s) format  were resolved.");
                }
            }

        }
    }

    private String escapeSquaredBrackets(String text) {
        text = text.replaceAll("\\[", "\\\\[");
        text = text.replaceAll("\\]", "\\\\]");
        return text;
    }
}
