/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.flink.runtime.operators.util;

import org.apache.flink.api.common.aggregators.Aggregator;
import org.apache.flink.api.common.aggregators.AggregatorWithName;
import org.apache.flink.api.common.aggregators.ConvergenceCriterion;
import org.apache.flink.api.common.distributions.DataDistribution;
import org.apache.flink.api.common.functions.Function;
import org.apache.flink.api.common.functions.Partitioner;
import org.apache.flink.api.common.operators.util.UserCodeWrapper;
import org.apache.flink.api.common.typeutils.TypeComparatorFactory;
import org.apache.flink.api.common.typeutils.TypePairComparatorFactory;
import org.apache.flink.api.common.typeutils.TypeSerializerFactory;
import org.apache.flink.api.java.operators.DeltaIteration;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.DelegatingConfiguration;
import org.apache.flink.core.memory.DataInputViewStreamWrapper;
import org.apache.flink.core.memory.DataOutputViewStreamWrapper;
import org.apache.flink.runtime.operators.Driver;
import org.apache.flink.runtime.operators.DriverStrategy;
import org.apache.flink.runtime.operators.chaining.ChainedDriver;
import org.apache.flink.runtime.operators.shipping.ShipStrategyType;
import org.apache.flink.types.Value;
import org.apache.flink.util.InstantiationUtil;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/** Configuration class which stores all relevant parameters required to set up the Pact tasks. */
public class TaskConfig implements Serializable {

    private static final long serialVersionUID = -2498884325640066272L;

    private static final String TASK_NAME = "taskname";

    // ------------------------------------ User Code ---------------------------------------------

    private static final String STUB_OBJECT = "udf";

    private static final String STUB_PARAM_PREFIX = "udf.param.";

    // -------------------------------------- Driver ----------------------------------------------

    private static final String DRIVER_CLASS = "driver.class";

    private static final String DRIVER_STRATEGY = "driver.strategy";

    private static final String DRIVER_COMPARATOR_FACTORY_PREFIX = "driver.comp.";

    private static final String DRIVER_COMPARATOR_PARAMETERS_PREFIX = "driver.comp.params.";

    private static final String DRIVER_PAIR_COMPARATOR_FACTORY = "driver.paircomp";

    // -------------------------------------- Inputs ----------------------------------------------

    private static final String NUM_INPUTS = "in.num";

    private static final String NUM_BROADCAST_INPUTS = "in.bc.num";

    /*
     * If one input has multiple predecessors (bag union), multiple
     * inputs must be grouped together. For a map or reduce there is
     * one group and "pact.size.inputGroup.0" will be equal to
     * "pact.in.num"
     *
     * In the case of a dual input pact (eg. match) there might be
     * 2 predecessors for the first group and one for the second group.
     * Hence, "pact.in.num" would be 3, "pact.size.inputGroup.0"
     * would be 2, and "pact.size.inputGroup.1" would be 1.
     */
    private static final String INPUT_GROUP_SIZE_PREFIX = "in.groupsize.";

    private static final String BROADCAST_INPUT_GROUP_SIZE_PREFIX = "in.bc.groupsize.";

    private static final String INPUT_TYPE_SERIALIZER_FACTORY_PREFIX = "in.serializer.";

    private static final String BROADCAST_INPUT_TYPE_SERIALIZER_FACTORY_PREFIX =
            "in.bc.serializer.";

    private static final String INPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX = "in.serializer.param.";

    private static final String BROADCAST_INPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX =
            "in.bc.serializer.param.";

    private static final String INPUT_LOCAL_STRATEGY_PREFIX = "in.strategy.";

    private static final String INPUT_STRATEGY_COMPARATOR_FACTORY_PREFIX = "in.comparator.";

    private static final String INPUT_STRATEGY_COMPARATOR_PARAMETERS_PREFIX =
            "in.comparator.param.";

    private static final String INPUT_DAM_PREFIX = "in.dam.";

    private static final String INPUT_REPLAYABLE_PREFIX = "in.dam.replay.";

    private static final String INPUT_DAM_MEMORY_PREFIX = "in.dam.mem.";

    private static final String BROADCAST_INPUT_NAME_PREFIX = "in.broadcast.name.";

    // -------------------------------------- Outputs ---------------------------------------------

    private static final String OUTPUTS_NUM = "out.num";

    private static final String OUTPUT_TYPE_SERIALIZER_FACTORY = "out.serializer";

    private static final String OUTPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX = "out.serializer.param.";

    private static final String OUTPUT_SHIP_STRATEGY_PREFIX = "out.shipstrategy.";

    private static final String OUTPUT_TYPE_COMPARATOR_FACTORY_PREFIX = "out.comp.";

    private static final String OUTPUT_TYPE_COMPARATOR_PARAMETERS_PREFIX = "out.comp.param.";

    private static final String OUTPUT_DATA_DISTRIBUTION_CLASS = "out.distribution.class";

    private static final String OUTPUT_DATA_DISTRIBUTION_PREFIX = "out.distribution.";

    private static final String OUTPUT_PARTITIONER = "out.partitioner.";

    // ------------------------------------- Chaining ---------------------------------------------

    private static final String CHAINING_NUM_STUBS = "chaining.num";

    private static final String CHAINING_TASKCONFIG_PREFIX = "chaining.taskconfig.";

    private static final String CHAINING_TASK_PREFIX = "chaining.task.";

    private static final String CHAINING_TASKNAME_PREFIX = "chaining.taskname.";

    // ------------------------------------ Memory & Co -------------------------------------------

    private static final String MEMORY_DRIVER = "memory.driver";

    private static final String MEMORY_INPUT_PREFIX = "memory.input.";

    private static final String FILEHANDLES_DRIVER = "filehandles.driver";

    private static final String FILEHANDLES_INPUT_PREFIX = "filehandles.input.";

    private static final String SORT_SPILLING_THRESHOLD_DRIVER = "sort-spill-threshold.driver";

    private static final String SORT_SPILLING_THRESHOLD_INPUT_PREFIX =
            "sort-spill-threshold.input.";

    private static final String USE_LARGE_RECORD_HANDLER = "sort-spill.large-record-handler";

    private static final boolean USE_LARGE_RECORD_HANDLER_DEFAULT = false;

    // ----------------------------------- Iterations ---------------------------------------------

    private static final String NUMBER_OF_ITERATIONS = "iterative.num-iterations";

    private static final String NUMBER_OF_EOS_EVENTS_PREFIX = "iterative.num-eos-events.";

    private static final String NUMBER_OF_EOS_EVENTS_BROADCAST_PREFIX =
            "iterative.num-eos-events.bc.";

    private static final String ITERATION_HEAD_ID = "iterative.head.id";

    private static final String ITERATION_WORKSET_MARKER = "iterative.is-workset";

    private static final String ITERATION_HEAD_INDEX_OF_PARTIAL_SOLUTION =
            "iterative.head.ps-input-index";

    private static final String ITERATION_HEAD_INDEX_OF_SOLUTIONSET =
            "iterative.head.ss-input-index";

    private static final String ITERATION_HEAD_BACKCHANNEL_MEMORY =
            "iterative.head.backchannel-memory";

    private static final String ITERATION_HEAD_SOLUTION_SET_MEMORY =
            "iterative.head.solutionset-memory";

    private static final String ITERATION_HEAD_FINAL_OUT_CONFIG_PREFIX = "iterative.head.out.";

    private static final String ITERATION_HEAD_SYNC_OUT_INDEX = "iterative.head.sync-index.";

    private static final String ITERATION_CONVERGENCE_CRITERION = "iterative.terminationCriterion";

    private static final String ITERATION_CONVERGENCE_CRITERION_AGG_NAME =
            "iterative.terminationCriterion.agg.name";

    private static final String ITERATION_IMPLICIT_CONVERGENCE_CRITERION =
            "iterative.implicit.terminationCriterion";

    private static final String ITERATION_IMPLICIT_CONVERGENCE_CRITERION_AGG_NAME =
            "iterative.implicit.terminationCriterion.agg.name";

    private static final String ITERATION_NUM_AGGREGATORS = "iterative.num-aggs";

    private static final String ITERATION_AGGREGATOR_NAME_PREFIX = "iterative.agg.name.";

    private static final String ITERATION_AGGREGATOR_PREFIX = "iterative.agg.data.";

    private static final String ITERATION_SOLUTION_SET_SERIALIZER = "iterative.ss-serializer";

    private static final String ITERATION_SOLUTION_SET_SERIALIZER_PARAMETERS =
            "iterative.ss-serializer.params";

    private static final String ITERATION_SOLUTION_SET_COMPARATOR = "iterative.ss-comparator";

    private static final String ITERATION_SOLUTION_SET_COMPARATOR_PARAMETERS =
            "iterative.ss-comparator.params";

    private static final String ITERATION_SOLUTION_SET_UPDATE = "iterative.ss-update";

    private static final String ITERATION_SOLUTION_SET_UPDATE_SKIP_REPROBE =
            "iterative.ss-update-fast";

    private static final String ITERATION_SOLUTION_SET_UPDATE_WAIT = "iterative.ss-wait";

    private static final String ITERATION_WORKSET_UPDATE = "iterative.ws-update";

    private static final String SOLUTION_SET_OBJECTS = "itertive.ss.obj";

    // ---------------------------------- Miscellaneous -------------------------------------------

    private static final char SEPARATOR = '.';

    // --------------------------------------------------------------------------------------------
    //                         Members, Constructors, and Accessors
    // --------------------------------------------------------------------------------------------

    protected final Configuration config; // the actual configuration holding the values

    /**
     * Creates a new Task Config that wraps the given configuration.
     *
     * @param config The configuration holding the actual values.
     */
    public TaskConfig(Configuration config) {
        this.config = config;
    }

    /**
     * Gets the configuration that holds the actual values encoded.
     *
     * @return The configuration that holds the actual values
     */
    public Configuration getConfiguration() {
        return this.config;
    }

    // --------------------------------------------------------------------------------------------
    //                                       User Code
    // --------------------------------------------------------------------------------------------

    public void setTaskName(String name) {
        if (name != null) {
            this.config.setString(TASK_NAME, name);
        }
    }

    public String getTaskName() {
        return this.config.getString(TASK_NAME, null);
    }

    public boolean hasStubWrapper() {
        return this.config.containsKey(STUB_OBJECT);
    }

    public void setStubWrapper(UserCodeWrapper<?> wrapper) {
        try {
            InstantiationUtil.writeObjectToConfig(wrapper, this.config, STUB_OBJECT);
        } catch (IOException e) {
            throw new CorruptConfigurationException(
                    "Could not write the user code wrapper " + wrapper.getClass() + " : " + e, e);
        }
    }

    @SuppressWarnings("unchecked")
    public <T> UserCodeWrapper<T> getStubWrapper(ClassLoader cl) {
        try {
            return InstantiationUtil.readObjectFromConfig(this.config, STUB_OBJECT, cl);
        } catch (ClassNotFoundException | IOException e) {
            throw new CorruptConfigurationException(
                    "Could not read the user code wrapper: " + e.getMessage(), e);
        }
    }

    public void setStubParameters(Configuration parameters) {
        this.config.addAll(parameters, STUB_PARAM_PREFIX);
    }

    public Configuration getStubParameters() {
        return new DelegatingConfiguration(this.config, STUB_PARAM_PREFIX);
    }

    public void setStubParameter(String key, String value) {
        this.config.setString(STUB_PARAM_PREFIX + key, value);
    }

    public String getStubParameter(String key, String defaultValue) {
        return this.config.getString(STUB_PARAM_PREFIX + key, defaultValue);
    }

    // --------------------------------------------------------------------------------------------
    //                                      Driver
    // --------------------------------------------------------------------------------------------

    public void setDriver(@SuppressWarnings("rawtypes") Class<? extends Driver> driver) {
        this.config.setString(DRIVER_CLASS, driver.getName());
    }

    public <S extends Function, OT> Class<? extends Driver<S, OT>> getDriver() {
        final String className = this.config.getString(DRIVER_CLASS, null);
        if (className == null) {
            throw new CorruptConfigurationException("The pact driver class is missing.");
        }

        try {
            @SuppressWarnings("unchecked")
            final Class<Driver<S, OT>> pdClazz = (Class<Driver<S, OT>>) (Class<?>) Driver.class;
            return Class.forName(className).asSubclass(pdClazz);
        } catch (ClassNotFoundException cnfex) {
            throw new CorruptConfigurationException("The given driver class cannot be found.");
        } catch (ClassCastException ccex) {
            throw new CorruptConfigurationException(
                    "The given driver class does not implement the pact driver interface.");
        }
    }

    public void setDriverStrategy(DriverStrategy strategy) {
        this.config.setInteger(DRIVER_STRATEGY, strategy.ordinal());
    }

    public DriverStrategy getDriverStrategy() {
        final int ls = this.config.getInteger(DRIVER_STRATEGY, -1);
        if (ls == -1) {
            return DriverStrategy.NONE;
        } else if (ls < 0 || ls >= DriverStrategy.values().length) {
            throw new CorruptConfigurationException(
                    "Illegal driver strategy in configuration: " + ls);
        } else {
            return DriverStrategy.values()[ls];
        }
    }

    public void setDriverComparator(TypeComparatorFactory<?> factory, int inputNum) {
        setTypeComparatorFactory(
                factory,
                DRIVER_COMPARATOR_FACTORY_PREFIX + inputNum,
                DRIVER_COMPARATOR_PARAMETERS_PREFIX + inputNum + SEPARATOR);
    }

    public <T> TypeComparatorFactory<T> getDriverComparator(int inputNum, ClassLoader cl) {
        return getTypeComparatorFactory(
                DRIVER_COMPARATOR_FACTORY_PREFIX + inputNum,
                DRIVER_COMPARATOR_PARAMETERS_PREFIX + inputNum + SEPARATOR,
                cl);
    }

    public void setDriverPairComparator(TypePairComparatorFactory<?, ?> factory) {
        final Class<?> clazz = factory.getClass();
        InstantiationUtil.checkForInstantiation(clazz);
        this.config.setString(DRIVER_PAIR_COMPARATOR_FACTORY, clazz.getName());
    }

    public <T1, T2> TypePairComparatorFactory<T1, T2> getPairComparatorFactory(ClassLoader cl) {
        final String className = this.config.getString(DRIVER_PAIR_COMPARATOR_FACTORY, null);
        if (className == null) {
            return null;
        }

        @SuppressWarnings("unchecked")
        final Class<TypePairComparatorFactory<T1, T2>> superClass =
                (Class<TypePairComparatorFactory<T1, T2>>)
                        (Class<?>) TypePairComparatorFactory.class;
        try {
            final Class<? extends TypePairComparatorFactory<T1, T2>> clazz =
                    Class.forName(className, true, cl).asSubclass(superClass);
            return InstantiationUtil.instantiate(clazz, superClass);
        } catch (ClassNotFoundException cnfex) {
            throw new RuntimeException(
                    "The class '"
                            + className
                            + "', noted in the configuration as "
                            + "pair comparator factory, could not be found. It is not part of the user code's class loader resources.");
        } catch (ClassCastException ccex) {
            throw new CorruptConfigurationException(
                    "The class noted in the configuration as the pair comparator factory "
                            + "is no subclass of TypePairComparatorFactory.");
        }
    }

    // --------------------------------------------------------------------------------------------
    //                                        Inputs
    // --------------------------------------------------------------------------------------------

    public void setInputLocalStrategy(int inputNum, LocalStrategy strategy) {
        this.config.setInteger(INPUT_LOCAL_STRATEGY_PREFIX + inputNum, strategy.ordinal());
    }

    public LocalStrategy getInputLocalStrategy(int inputNum) {
        final int ls = this.config.getInteger(INPUT_LOCAL_STRATEGY_PREFIX + inputNum, -1);
        if (ls == -1) {
            return LocalStrategy.NONE;
        } else if (ls < 0 || ls >= LocalStrategy.values().length) {
            throw new CorruptConfigurationException(
                    "Illegal local strategy in configuration: " + ls);
        } else {
            return LocalStrategy.values()[ls];
        }
    }

    public void setInputSerializer(TypeSerializerFactory<?> factory, int inputNum) {
        setTypeSerializerFactory(
                factory,
                INPUT_TYPE_SERIALIZER_FACTORY_PREFIX + inputNum,
                INPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX + inputNum + SEPARATOR);
    }

    public void setBroadcastInputSerializer(TypeSerializerFactory<?> factory, int inputNum) {
        setTypeSerializerFactory(
                factory,
                BROADCAST_INPUT_TYPE_SERIALIZER_FACTORY_PREFIX + inputNum,
                BROADCAST_INPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX + inputNum + SEPARATOR);
    }

    public <T> TypeSerializerFactory<T> getInputSerializer(int inputNum, ClassLoader cl) {
        return getTypeSerializerFactory(
                INPUT_TYPE_SERIALIZER_FACTORY_PREFIX + inputNum,
                INPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX + inputNum + SEPARATOR,
                cl);
    }

    public <T> TypeSerializerFactory<T> getBroadcastInputSerializer(int inputNum, ClassLoader cl) {
        return getTypeSerializerFactory(
                BROADCAST_INPUT_TYPE_SERIALIZER_FACTORY_PREFIX + inputNum,
                BROADCAST_INPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX + inputNum + SEPARATOR,
                cl);
    }

    public void setInputComparator(TypeComparatorFactory<?> factory, int inputNum) {
        setTypeComparatorFactory(
                factory,
                INPUT_STRATEGY_COMPARATOR_FACTORY_PREFIX + inputNum,
                INPUT_STRATEGY_COMPARATOR_PARAMETERS_PREFIX + inputNum + SEPARATOR);
    }

    public <T> TypeComparatorFactory<T> getInputComparator(int inputNum, ClassLoader cl) {
        return getTypeComparatorFactory(
                INPUT_STRATEGY_COMPARATOR_FACTORY_PREFIX + inputNum,
                INPUT_STRATEGY_COMPARATOR_PARAMETERS_PREFIX + inputNum + SEPARATOR,
                cl);
    }

    public int getNumInputs() {
        return this.config.getInteger(NUM_INPUTS, 0);
    }

    public int getNumBroadcastInputs() {
        return this.config.getInteger(NUM_BROADCAST_INPUTS, 0);
    }

    public int getGroupSize(int groupIndex) {
        return this.config.getInteger(INPUT_GROUP_SIZE_PREFIX + groupIndex, -1);
    }

    public int getBroadcastGroupSize(int groupIndex) {
        return this.config.getInteger(BROADCAST_INPUT_GROUP_SIZE_PREFIX + groupIndex, -1);
    }

    public void addInputToGroup(int groupIndex) {
        final String grp = INPUT_GROUP_SIZE_PREFIX + groupIndex;
        this.config.setInteger(grp, this.config.getInteger(grp, 0) + 1);
        this.config.setInteger(NUM_INPUTS, this.config.getInteger(NUM_INPUTS, 0) + 1);
    }

    public void addBroadcastInputToGroup(int groupIndex) {
        final String grp = BROADCAST_INPUT_GROUP_SIZE_PREFIX + groupIndex;
        if (!this.config.containsKey(grp)) {
            this.config.setInteger(
                    NUM_BROADCAST_INPUTS, this.config.getInteger(NUM_BROADCAST_INPUTS, 0) + 1);
        }
        this.config.setInteger(grp, this.config.getInteger(grp, 0) + 1);
    }

    public void setInputAsynchronouslyMaterialized(int inputNum, boolean temp) {
        this.config.setBoolean(INPUT_DAM_PREFIX + inputNum, temp);
    }

    public boolean isInputAsynchronouslyMaterialized(int inputNum) {
        return this.config.getBoolean(INPUT_DAM_PREFIX + inputNum, false);
    }

    public void setInputCached(int inputNum, boolean persistent) {
        this.config.setBoolean(INPUT_REPLAYABLE_PREFIX + inputNum, persistent);
    }

    public boolean isInputCached(int inputNum) {
        return this.config.getBoolean(INPUT_REPLAYABLE_PREFIX + inputNum, false);
    }

    public void setRelativeInputMaterializationMemory(int inputNum, double relativeMemory) {
        this.config.setDouble(INPUT_DAM_MEMORY_PREFIX + inputNum, relativeMemory);
    }

    public double getRelativeInputMaterializationMemory(int inputNum) {
        return this.config.getDouble(INPUT_DAM_MEMORY_PREFIX + inputNum, 0);
    }

    public void setBroadcastInputName(String name, int groupIndex) {
        this.config.setString(BROADCAST_INPUT_NAME_PREFIX + groupIndex, name);
    }

    public String getBroadcastInputName(int groupIndex) {
        return this.config.getString(
                BROADCAST_INPUT_NAME_PREFIX + groupIndex,
                String.format("broadcastVar%04d", groupIndex));
    }

    // --------------------------------------------------------------------------------------------
    //                                        Outputs
    // --------------------------------------------------------------------------------------------

    public void addOutputShipStrategy(ShipStrategyType strategy) {
        final int outputCnt = this.config.getInteger(OUTPUTS_NUM, 0);
        this.config.setInteger(OUTPUT_SHIP_STRATEGY_PREFIX + outputCnt, strategy.ordinal());
        this.config.setInteger(OUTPUTS_NUM, outputCnt + 1);
    }

    public int getNumOutputs() {
        return this.config.getInteger(OUTPUTS_NUM, 0);
    }

    public ShipStrategyType getOutputShipStrategy(int outputNum) {
        // check how many outputs are encoded in the config
        final int outputCnt = this.config.getInteger(OUTPUTS_NUM, -1);
        if (outputCnt < 1) {
            throw new CorruptConfigurationException(
                    "No output ship strategies are specified in the configuration.");
        }

        // sanity range checks
        if (outputNum < 0 || outputNum >= outputCnt) {
            throw new IllegalArgumentException("Invalid index for output shipping strategy.");
        }

        final int strategy = this.config.getInteger(OUTPUT_SHIP_STRATEGY_PREFIX + outputNum, -1);
        if (strategy == -1) {
            throw new CorruptConfigurationException(
                    "No output shipping strategy in configuration for output " + outputNum);
        } else if (strategy < 0 || strategy >= ShipStrategyType.values().length) {
            throw new CorruptConfigurationException(
                    "Illegal output shipping strategy in configuration for output "
                            + outputNum
                            + ": "
                            + strategy);
        } else {
            return ShipStrategyType.values()[strategy];
        }
    }

    public void setOutputSerializer(TypeSerializerFactory<?> factory) {
        setTypeSerializerFactory(
                factory, OUTPUT_TYPE_SERIALIZER_FACTORY, OUTPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX);
    }

    public <T> TypeSerializerFactory<T> getOutputSerializer(ClassLoader cl) {
        return getTypeSerializerFactory(
                OUTPUT_TYPE_SERIALIZER_FACTORY, OUTPUT_TYPE_SERIALIZER_PARAMETERS_PREFIX, cl);
    }

    public void setOutputComparator(TypeComparatorFactory<?> factory, int outputNum) {
        setTypeComparatorFactory(
                factory,
                OUTPUT_TYPE_COMPARATOR_FACTORY_PREFIX + outputNum,
                OUTPUT_TYPE_COMPARATOR_PARAMETERS_PREFIX + outputNum + SEPARATOR);
    }

    public <T> TypeComparatorFactory<T> getOutputComparator(int outputNum, ClassLoader cl) {
        return getTypeComparatorFactory(
                OUTPUT_TYPE_COMPARATOR_FACTORY_PREFIX + outputNum,
                OUTPUT_TYPE_COMPARATOR_PARAMETERS_PREFIX + outputNum + SEPARATOR,
                cl);
    }

    public void setOutputDataDistribution(DataDistribution distribution, int outputNum) {
        this.config.setString(OUTPUT_DATA_DISTRIBUTION_CLASS, distribution.getClass().getName());

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                DataOutputViewStreamWrapper out = new DataOutputViewStreamWrapper(baos)) {

            distribution.write(out);
            config.setBytes(OUTPUT_DATA_DISTRIBUTION_PREFIX + outputNum, baos.toByteArray());

        } catch (IOException e) {
            throw new RuntimeException(
                    "Error serializing the DataDistribution: " + e.getMessage(), e);
        }
    }

    public DataDistribution getOutputDataDistribution(int outputNum, final ClassLoader cl)
            throws ClassNotFoundException {
        final String className = this.config.getString(OUTPUT_DATA_DISTRIBUTION_CLASS, null);
        if (className == null) {
            return null;
        }

        final Class<? extends DataDistribution> clazz;
        try {
            clazz = Class.forName(className, true, cl).asSubclass(DataDistribution.class);
        } catch (ClassCastException ccex) {
            throw new CorruptConfigurationException(
                    "The class noted in the configuration as the data distribution "
                            + "is no subclass of DataDistribution.");
        }

        final DataDistribution distribution =
                InstantiationUtil.instantiate(clazz, DataDistribution.class);

        final byte[] stateEncoded =
                this.config.getBytes(OUTPUT_DATA_DISTRIBUTION_PREFIX + outputNum, null);
        if (stateEncoded == null) {
            throw new CorruptConfigurationException(
                    "The configuration contained the data distribution type, but no serialized state.");
        }

        final ByteArrayInputStream bais = new ByteArrayInputStream(stateEncoded);
        final DataInputViewStreamWrapper in = new DataInputViewStreamWrapper(bais);

        try {
            distribution.read(in);
            return distribution;
        } catch (Exception ex) {
            throw new RuntimeException(
                    "The deserialization of the encoded data distribution state caused an error"
                            + (ex.getMessage() == null ? "." : ": " + ex.getMessage()),
                    ex);
        }
    }

    public void setOutputPartitioner(Partitioner<?> partitioner, int outputNum) {
        try {
            InstantiationUtil.writeObjectToConfig(
                    partitioner, config, OUTPUT_PARTITIONER + outputNum);
        } catch (Throwable t) {
            throw new RuntimeException("Could not serialize custom partitioner.", t);
        }
    }

    public Partitioner<?> getOutputPartitioner(int outputNum, final ClassLoader cl)
            throws ClassNotFoundException {
        try {
            return InstantiationUtil.readObjectFromConfig(
                    config, OUTPUT_PARTITIONER + outputNum, cl);
        } catch (ClassNotFoundException e) {
            throw e;
        } catch (Throwable t) {
            throw new RuntimeException("Could not deserialize custom partitioner.", t);
        }
    }

    // --------------------------------------------------------------------------------------------
    //                       Parameters to configure the memory and I/O behavior
    // --------------------------------------------------------------------------------------------

    public void setRelativeMemoryDriver(double relativeMemorySize) {
        this.config.setDouble(MEMORY_DRIVER, relativeMemorySize);
    }

    public double getRelativeMemoryDriver() {
        return this.config.getDouble(MEMORY_DRIVER, 0);
    }

    public void setRelativeMemoryInput(int inputNum, double relativeMemorySize) {
        this.config.setDouble(MEMORY_INPUT_PREFIX + inputNum, relativeMemorySize);
    }

    public double getRelativeMemoryInput(int inputNum) {
        return this.config.getDouble(MEMORY_INPUT_PREFIX + inputNum, 0);
    }

    // --------------------------------------------------------------------------------------------

    public void setFilehandlesDriver(int filehandles) {
        if (filehandles < 2) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(FILEHANDLES_DRIVER, filehandles);
    }

    public int getFilehandlesDriver() {
        return this.config.getInteger(FILEHANDLES_DRIVER, -1);
    }

    public void setFilehandlesInput(int inputNum, int filehandles) {
        if (filehandles < 2) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(FILEHANDLES_INPUT_PREFIX + inputNum, filehandles);
    }

    public int getFilehandlesInput(int inputNum) {
        return this.config.getInteger(FILEHANDLES_INPUT_PREFIX + inputNum, -1);
    }

    // --------------------------------------------------------------------------------------------

    public void setSpillingThresholdDriver(float threshold) {
        if (threshold < 0.0f || threshold > 1.0f) {
            throw new IllegalArgumentException();
        }
        this.config.setFloat(SORT_SPILLING_THRESHOLD_DRIVER, threshold);
    }

    public float getSpillingThresholdDriver() {
        return this.config.getFloat(SORT_SPILLING_THRESHOLD_DRIVER, 0.7f);
    }

    public void setSpillingThresholdInput(int inputNum, float threshold) {
        if (threshold < 0.0f || threshold > 1.0f) {
            throw new IllegalArgumentException();
        }
        this.config.setFloat(SORT_SPILLING_THRESHOLD_INPUT_PREFIX + inputNum, threshold);
    }

    public float getSpillingThresholdInput(int inputNum) {
        return this.config.getFloat(SORT_SPILLING_THRESHOLD_INPUT_PREFIX + inputNum, 0.7f);
    }

    public void setUseLargeRecordHandler(boolean useLargeRecordHandler) {
        this.config.setBoolean(USE_LARGE_RECORD_HANDLER, useLargeRecordHandler);
    }

    public boolean getUseLargeRecordHandler() {
        return this.config.getBoolean(USE_LARGE_RECORD_HANDLER, USE_LARGE_RECORD_HANDLER_DEFAULT);
    }

    // --------------------------------------------------------------------------------------------
    //                                    Parameters for Function Chaining
    // --------------------------------------------------------------------------------------------

    public int getNumberOfChainedStubs() {
        return this.config.getInteger(CHAINING_NUM_STUBS, 0);
    }

    public void addChainedTask(
            @SuppressWarnings("rawtypes") Class<? extends ChainedDriver> chainedTaskClass,
            TaskConfig conf,
            String taskName) {
        int numChainedYet = this.config.getInteger(CHAINING_NUM_STUBS, 0);

        this.config.setString(CHAINING_TASK_PREFIX + numChainedYet, chainedTaskClass.getName());
        this.config.addAll(conf.config, CHAINING_TASKCONFIG_PREFIX + numChainedYet + SEPARATOR);
        this.config.setString(CHAINING_TASKNAME_PREFIX + numChainedYet, taskName);

        this.config.setInteger(CHAINING_NUM_STUBS, ++numChainedYet);
    }

    public TaskConfig getChainedStubConfig(int chainPos) {
        return new TaskConfig(
                new DelegatingConfiguration(
                        this.config, CHAINING_TASKCONFIG_PREFIX + chainPos + SEPARATOR));
    }

    public Class<? extends ChainedDriver<?, ?>> getChainedTask(int chainPos) {
        final String className = this.config.getString(CHAINING_TASK_PREFIX + chainPos, null);
        if (className == null) {
            throw new IllegalStateException("Chained Task Class missing");
        }

        @SuppressWarnings("unchecked")
        final Class<ChainedDriver<?, ?>> clazz =
                (Class<ChainedDriver<?, ?>>) (Class<?>) ChainedDriver.class;
        try {
            return Class.forName(className).asSubclass(clazz);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public String getChainedTaskName(int chainPos) {
        return this.config.getString(CHAINING_TASKNAME_PREFIX + chainPos, null);
    }

    // --------------------------------------------------------------------------------------------
    //                                      Iterations
    // --------------------------------------------------------------------------------------------

    public void setNumberOfIterations(int numberOfIterations) {
        if (numberOfIterations <= 0) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(NUMBER_OF_ITERATIONS, numberOfIterations);
    }

    public int getNumberOfIterations() {
        int numberOfIterations = this.config.getInteger(NUMBER_OF_ITERATIONS, 0);
        if (numberOfIterations <= 0) {
            throw new IllegalArgumentException();
        }
        return numberOfIterations;
    }

    public void setIterationHeadPartialSolutionOrWorksetInputIndex(int inputIndex) {
        if (inputIndex < 0) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(ITERATION_HEAD_INDEX_OF_PARTIAL_SOLUTION, inputIndex);
    }

    public int getIterationHeadPartialSolutionOrWorksetInputIndex() {
        int index = this.config.getInteger(ITERATION_HEAD_INDEX_OF_PARTIAL_SOLUTION, -1);
        if (index < 0) {
            throw new IllegalArgumentException();
        }
        return index;
    }

    public void setIterationHeadSolutionSetInputIndex(int inputIndex) {
        if (inputIndex < 0) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(ITERATION_HEAD_INDEX_OF_SOLUTIONSET, inputIndex);
    }

    public int getIterationHeadSolutionSetInputIndex() {
        int index = this.config.getInteger(ITERATION_HEAD_INDEX_OF_SOLUTIONSET, -1);
        if (index < 0) {
            throw new IllegalArgumentException();
        }
        return index;
    }

    public void setRelativeBackChannelMemory(double relativeMemory) {
        if (relativeMemory < 0) {
            throw new IllegalArgumentException();
        }
        this.config.setDouble(ITERATION_HEAD_BACKCHANNEL_MEMORY, relativeMemory);
    }

    public double getRelativeBackChannelMemory() {
        double relativeBackChannelMemory =
                this.config.getDouble(ITERATION_HEAD_BACKCHANNEL_MEMORY, 0);
        if (relativeBackChannelMemory <= 0) {
            throw new IllegalArgumentException();
        }
        return relativeBackChannelMemory;
    }

    public void setRelativeSolutionSetMemory(double relativeMemory) {
        if (relativeMemory < 0) {
            throw new IllegalArgumentException();
        }
        this.config.setDouble(ITERATION_HEAD_SOLUTION_SET_MEMORY, relativeMemory);
    }

    public double getRelativeSolutionSetMemory() {
        double backChannelMemory = this.config.getDouble(ITERATION_HEAD_SOLUTION_SET_MEMORY, 0);
        if (backChannelMemory <= 0) {
            throw new IllegalArgumentException();
        }
        return backChannelMemory;
    }

    public boolean isIterativeInputGate(int inputGateIndex) {
        return getNumberOfEventsUntilInterruptInIterativeGate(inputGateIndex) > 0;
    }

    public void setGateIterativeWithNumberOfEventsUntilInterrupt(
            int inputGateIndex, int numEvents) {
        if (inputGateIndex < 0) {
            throw new IllegalArgumentException();
        }
        if (numEvents <= 0) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(NUMBER_OF_EOS_EVENTS_PREFIX + inputGateIndex, numEvents);
    }

    public int getNumberOfEventsUntilInterruptInIterativeGate(int inputGateIndex) {
        if (inputGateIndex < 0) {
            throw new IllegalArgumentException();
        }
        return this.config.getInteger(NUMBER_OF_EOS_EVENTS_PREFIX + inputGateIndex, 0);
    }

    public void setBroadcastGateIterativeWithNumberOfEventsUntilInterrupt(
            int bcGateIndex, int numEvents) {
        if (bcGateIndex < 0) {
            throw new IllegalArgumentException();
        }
        if (numEvents <= 0) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(NUMBER_OF_EOS_EVENTS_BROADCAST_PREFIX + bcGateIndex, numEvents);
    }

    public int getNumberOfEventsUntilInterruptInIterativeBroadcastGate(int bcGateIndex) {
        if (bcGateIndex < 0) {
            throw new IllegalArgumentException();
        }
        return this.config.getInteger(NUMBER_OF_EOS_EVENTS_BROADCAST_PREFIX + bcGateIndex, 0);
    }

    public void setIterationId(int id) {
        if (id < 0) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(ITERATION_HEAD_ID, id);
    }

    public int getIterationId() {
        int id = this.config.getInteger(ITERATION_HEAD_ID, -1);
        if (id == -1) {
            throw new CorruptConfigurationException("Iteration head ID is missing.");
        }
        return id;
    }

    public void setIsWorksetIteration() {
        this.config.setBoolean(ITERATION_WORKSET_MARKER, true);
    }

    public boolean getIsWorksetIteration() {
        return this.config.getBoolean(ITERATION_WORKSET_MARKER, false);
    }

    public void setIterationHeadIndexOfSyncOutput(int outputIndex) {
        if (outputIndex < 0) {
            throw new IllegalArgumentException();
        }
        this.config.setInteger(ITERATION_HEAD_SYNC_OUT_INDEX, outputIndex);
    }

    public int getIterationHeadIndexOfSyncOutput() {
        int outputIndex = this.config.getInteger(ITERATION_HEAD_SYNC_OUT_INDEX, -1);
        if (outputIndex < 0) {
            throw new IllegalArgumentException();
        }
        return outputIndex;
    }

    public void setIterationHeadFinalOutputConfig(TaskConfig conf) {
        this.config.addAll(conf.config, ITERATION_HEAD_FINAL_OUT_CONFIG_PREFIX);
    }

    public TaskConfig getIterationHeadFinalOutputConfig() {
        return new TaskConfig(
                new DelegatingConfiguration(this.config, ITERATION_HEAD_FINAL_OUT_CONFIG_PREFIX));
    }

    public void setSolutionSetSerializer(TypeSerializerFactory<?> factory) {
        setTypeSerializerFactory(
                factory,
                ITERATION_SOLUTION_SET_SERIALIZER,
                ITERATION_SOLUTION_SET_SERIALIZER_PARAMETERS);
    }

    public <T> TypeSerializerFactory<T> getSolutionSetSerializer(ClassLoader cl) {
        return getTypeSerializerFactory(
                ITERATION_SOLUTION_SET_SERIALIZER,
                ITERATION_SOLUTION_SET_SERIALIZER_PARAMETERS,
                cl);
    }

    public void setSolutionSetComparator(TypeComparatorFactory<?> factory) {
        setTypeComparatorFactory(
                factory,
                ITERATION_SOLUTION_SET_COMPARATOR,
                ITERATION_SOLUTION_SET_COMPARATOR_PARAMETERS);
    }

    public <T> TypeComparatorFactory<T> getSolutionSetComparator(ClassLoader cl) {
        return getTypeComparatorFactory(
                ITERATION_SOLUTION_SET_COMPARATOR,
                ITERATION_SOLUTION_SET_COMPARATOR_PARAMETERS,
                cl);
    }

    public void addIterationAggregator(String name, Aggregator<?> aggregator) {
        int num = this.config.getInteger(ITERATION_NUM_AGGREGATORS, 0);
        this.config.setString(ITERATION_AGGREGATOR_NAME_PREFIX + num, name);
        try {
            InstantiationUtil.writeObjectToConfig(
                    aggregator, this.config, ITERATION_AGGREGATOR_PREFIX + num);
        } catch (IOException e) {
            throw new RuntimeException(
                    "Error while writing the aggregator object to the task configuration.");
        }
        this.config.setInteger(ITERATION_NUM_AGGREGATORS, num + 1);
    }

    public void addIterationAggregators(Collection<AggregatorWithName<?>> aggregators) {
        int num = this.config.getInteger(ITERATION_NUM_AGGREGATORS, 0);
        for (AggregatorWithName<?> awn : aggregators) {
            this.config.setString(ITERATION_AGGREGATOR_NAME_PREFIX + num, awn.getName());
            try {
                InstantiationUtil.writeObjectToConfig(
                        awn.getAggregator(), this.config, ITERATION_AGGREGATOR_PREFIX + num);
            } catch (IOException e) {
                throw new RuntimeException(
                        "Error while writing the aggregator object to the task configuration.");
            }
            num++;
        }
        this.config.setInteger(ITERATION_NUM_AGGREGATORS, num);
    }

    @SuppressWarnings("unchecked")
    public Collection<AggregatorWithName<?>> getIterationAggregators(ClassLoader cl) {
        final int numAggs = this.config.getInteger(ITERATION_NUM_AGGREGATORS, 0);
        if (numAggs == 0) {
            return Collections.emptyList();
        }

        List<AggregatorWithName<?>> list = new ArrayList<>(numAggs);
        for (int i = 0; i < numAggs; i++) {
            Aggregator<Value> aggObj;
            try {
                aggObj =
                        InstantiationUtil.readObjectFromConfig(
                                this.config, ITERATION_AGGREGATOR_PREFIX + i, cl);
            } catch (IOException e) {
                throw new RuntimeException(
                        "Error while reading the aggregator object from the task configuration.");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(
                        "Error while reading the aggregator object from the task configuration. "
                                + "Aggregator class not found.");
            }
            if (aggObj == null) {
                throw new RuntimeException("Missing config entry for aggregator.");
            }
            String name = this.config.getString(ITERATION_AGGREGATOR_NAME_PREFIX + i, null);
            if (name == null) {
                throw new RuntimeException("Missing config entry for aggregator.");
            }
            list.add(new AggregatorWithName<>(name, aggObj));
        }
        return list;
    }

    public void setConvergenceCriterion(
            String aggregatorName, ConvergenceCriterion<?> convCriterion) {
        try {
            InstantiationUtil.writeObjectToConfig(
                    convCriterion, this.config, ITERATION_CONVERGENCE_CRITERION);
        } catch (IOException e) {
            throw new RuntimeException(
                    "Error while writing the convergence criterion object to the task configuration.");
        }
        this.config.setString(ITERATION_CONVERGENCE_CRITERION_AGG_NAME, aggregatorName);
    }

    /**
     * Sets the default convergence criterion of a {@link DeltaIteration}
     *
     * @param aggregatorName
     * @param convCriterion
     */
    public void setImplicitConvergenceCriterion(
            String aggregatorName, ConvergenceCriterion<?> convCriterion) {
        try {
            InstantiationUtil.writeObjectToConfig(
                    convCriterion, this.config, ITERATION_IMPLICIT_CONVERGENCE_CRITERION);
        } catch (IOException e) {
            throw new RuntimeException(
                    "Error while writing the implicit convergence criterion object to the task configuration.");
        }
        this.config.setString(ITERATION_IMPLICIT_CONVERGENCE_CRITERION_AGG_NAME, aggregatorName);
    }

    @SuppressWarnings("unchecked")
    public <T extends Value> ConvergenceCriterion<T> getConvergenceCriterion(ClassLoader cl) {
        ConvergenceCriterion<T> convCriterionObj;
        try {
            convCriterionObj =
                    InstantiationUtil.readObjectFromConfig(
                            this.config, ITERATION_CONVERGENCE_CRITERION, cl);
        } catch (IOException e) {
            throw new RuntimeException(
                    "Error while reading the convergence criterion object from the task configuration.");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(
                    "Error while reading the convergence criterion object from the task configuration. "
                            + "ConvergenceCriterion class not found.");
        }
        if (convCriterionObj == null) {
            throw new NullPointerException();
        }
        return convCriterionObj;
    }

    public boolean usesConvergenceCriterion() {
        return config.getBytes(ITERATION_CONVERGENCE_CRITERION, null) != null;
    }

    public String getConvergenceCriterionAggregatorName() {
        return this.config.getString(ITERATION_CONVERGENCE_CRITERION_AGG_NAME, null);
    }

    @SuppressWarnings("unchecked")
    public <T extends Value> ConvergenceCriterion<T> getImplicitConvergenceCriterion(
            ClassLoader cl) {
        ConvergenceCriterion<T> convCriterionObj;
        try {
            convCriterionObj =
                    InstantiationUtil.readObjectFromConfig(
                            this.config, ITERATION_IMPLICIT_CONVERGENCE_CRITERION, cl);
        } catch (IOException e) {
            throw new RuntimeException(
                    "Error while reading the default convergence criterion object from the task configuration.");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(
                    "Error while reading the default convergence criterion object from the task configuration. "
                            + "ConvergenceCriterion class not found.");
        }
        if (convCriterionObj == null) {
            throw new NullPointerException();
        }
        return convCriterionObj;
    }

    public boolean usesImplicitConvergenceCriterion() {
        return config.getBytes(ITERATION_IMPLICIT_CONVERGENCE_CRITERION, null) != null;
    }

    public String getImplicitConvergenceCriterionAggregatorName() {
        return this.config.getString(ITERATION_IMPLICIT_CONVERGENCE_CRITERION_AGG_NAME, null);
    }

    public void setIsSolutionSetUpdate() {
        this.config.setBoolean(ITERATION_SOLUTION_SET_UPDATE, true);
    }

    public boolean getIsSolutionSetUpdate() {
        return this.config.getBoolean(ITERATION_SOLUTION_SET_UPDATE, false);
    }

    public void setIsSolutionSetUpdateWithoutReprobe() {
        this.config.setBoolean(ITERATION_SOLUTION_SET_UPDATE_SKIP_REPROBE, true);
    }

    public boolean getIsSolutionSetUpdateWithoutReprobe() {
        return this.config.getBoolean(ITERATION_SOLUTION_SET_UPDATE_SKIP_REPROBE, false);
    }

    public void setWaitForSolutionSetUpdate() {
        this.config.setBoolean(ITERATION_SOLUTION_SET_UPDATE_WAIT, true);
    }

    public boolean getWaitForSolutionSetUpdate() {
        return this.config.getBoolean(ITERATION_SOLUTION_SET_UPDATE_WAIT, false);
    }

    public void setIsWorksetUpdate() {
        this.config.setBoolean(ITERATION_WORKSET_UPDATE, true);
    }

    public boolean getIsWorksetUpdate() {
        return this.config.getBoolean(ITERATION_WORKSET_UPDATE, false);
    }

    // --------------------------------------------------------------------------------------------
    //                                    Miscellaneous
    // --------------------------------------------------------------------------------------------

    private void setTypeSerializerFactory(
            TypeSerializerFactory<?> factory, String classNameKey, String parametersPrefix) {
        // sanity check the factory type
        InstantiationUtil.checkForInstantiation(factory.getClass());

        // store the type
        this.config.setString(classNameKey, factory.getClass().getName());
        // store the parameters
        final DelegatingConfiguration parameters =
                new DelegatingConfiguration(this.config, parametersPrefix);
        factory.writeParametersToConfig(parameters);
    }

    private <T> TypeSerializerFactory<T> getTypeSerializerFactory(
            String classNameKey, String parametersPrefix, ClassLoader cl) {
        // check the class name
        final String className = this.config.getString(classNameKey, null);
        if (className == null) {
            return null;
        }

        // instantiate the class
        @SuppressWarnings("unchecked")
        final Class<TypeSerializerFactory<T>> superClass =
                (Class<TypeSerializerFactory<T>>) (Class<?>) TypeSerializerFactory.class;
        final TypeSerializerFactory<T> factory;
        try {
            Class<? extends TypeSerializerFactory<T>> clazz =
                    Class.forName(className, true, cl).asSubclass(superClass);
            factory = InstantiationUtil.instantiate(clazz, superClass);
        } catch (ClassNotFoundException cnfex) {
            throw new RuntimeException(
                    "The class '"
                            + className
                            + "', noted in the configuration as "
                            + "serializer factory, could not be found. It is not part of the user code's class loader resources.");
        } catch (ClassCastException ccex) {
            throw new CorruptConfigurationException(
                    "The class noted in the configuration as the serializer factory "
                            + "is no subclass of TypeSerializerFactory.");
        }

        // parameterize the comparator factory
        final Configuration parameters = new DelegatingConfiguration(this.config, parametersPrefix);
        try {
            factory.readParametersFromConfig(parameters, cl);
        } catch (ClassNotFoundException cnfex) {
            throw new RuntimeException(
                    "The type serializer factory could not load its parameters from the "
                            + "configuration due to missing classes.",
                    cnfex);
        }

        return factory;
    }

    private void setTypeComparatorFactory(
            TypeComparatorFactory<?> factory, String classNameKey, String parametersPrefix) {
        // sanity check the factory type
        InstantiationUtil.checkForInstantiation(factory.getClass());

        // store the type
        this.config.setString(classNameKey, factory.getClass().getName());
        // store the parameters
        final DelegatingConfiguration parameters =
                new DelegatingConfiguration(this.config, parametersPrefix);
        factory.writeParametersToConfig(parameters);
    }

    private <T> TypeComparatorFactory<T> getTypeComparatorFactory(
            String classNameKey, String parametersPrefix, ClassLoader cl) {
        // check the class name
        final String className = this.config.getString(classNameKey, null);
        if (className == null) {
            return null;
        }

        // instantiate the class
        @SuppressWarnings("unchecked")
        final Class<TypeComparatorFactory<T>> superClass =
                (Class<TypeComparatorFactory<T>>) (Class<?>) TypeComparatorFactory.class;
        final TypeComparatorFactory<T> factory;
        try {
            Class<? extends TypeComparatorFactory<T>> clazz =
                    Class.forName(className, true, cl).asSubclass(superClass);
            factory = InstantiationUtil.instantiate(clazz, superClass);
        } catch (ClassNotFoundException cnfex) {
            throw new RuntimeException(
                    "The class '"
                            + className
                            + "', noted in the configuration as "
                            + "comparator factory, could not be found. It is not part of the user code's class loader resources.");
        } catch (ClassCastException ccex) {
            throw new CorruptConfigurationException(
                    "The class noted in the configuration as the comparator factory "
                            + "is no subclass of TypeComparatorFactory.");
        }

        // parameterize the comparator factory
        final Configuration parameters = new DelegatingConfiguration(this.config, parametersPrefix);
        try {
            factory.readParametersFromConfig(parameters, cl);
        } catch (ClassNotFoundException cnfex) {
            throw new RuntimeException(
                    "The type serializer factory could not load its parameters from the "
                            + "configuration due to missing classes.",
                    cnfex);
        }

        return factory;
    }

    public void setSolutionSetUnmanaged(boolean unmanaged) {
        config.setBoolean(SOLUTION_SET_OBJECTS, unmanaged);
    }

    public boolean isSolutionSetUnmanaged() {
        return config.getBoolean(SOLUTION_SET_OBJECTS, false);
    }
}
