All Downloads are FREE. Search and download functionalities are using the official Maven repository.

configuration_file_parser.BRunnerConfigurationFileParser Maven / Gradle / Ivy

package configuration_file_parser;

import java.io.FileNotFoundException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.apache.commons.configuration2.ex.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import api.definition.IBenchmark;
import api.definition.ITask;
import api.definition.config.IExport;
import api.definition.config.IInputData;
import api.definition.config.IMeasures;
import api.definition.config.IPlatformParameters;
import api.definition.config.IToolParameters;
import api.definition.config.common.IPropertySet;
import api.running.IToolKeywordsMenu;
import configuration_file_parser.config.ConfigPreprocessor;
import configuration_file_parser.config.ConfigurationLoader;
import configuration_file_parser.segment.ExportClassesParser;
import configuration_file_parser.segment.MeasuresParser;
import configuration_file_parser.segment.PlatformParser;
import configuration_file_parser.segment.ReasonerClassesParser;
import configuration_file_parser.segment.ScenarioParser;
import configuration_file_parser.segment.ToolParser;
import constants.BRunnerKeywords;
import constants.RunnersConstants;
import constants.ToolsConstants;
import impl.factory.DefaultComponentFactory;
import impl.factory.IComponentFactory;
import radicchio.FileUtils;
import utils.ClassUtils;
import utils.EnumerationUtils;

/**
 * Parses configuration files for the BRunner benchmarking tool. This class
 * supports parsing various segments including input data, tool parameters,
 * platform parameters, measures, and export classes. It assembles these
 * components into a comprehensive {@link IBenchmark} object.
 */

public class BRunnerConfigurationFileParser {

	@FunctionalInterface
	public interface TriFunction {
		R apply(T t, U u, V v);
	}

	//////////////////////
	// Fields
	//////////////////////

	protected static Logger LOG = LoggerFactory.getLogger(BRunnerConfigurationFileParser.class);

	protected org.apache.commons.configuration2.Configuration apacheConfigurationObject;

	protected IBenchmark benchmark;

	List inputDatas;
	List toolsParam;
	List platformsParam;
	Collection measures;

	Collection> exportClasses;
	Class toolKeywordMenuClass;
	Collection taskList;

	IComponentFactory factory = DefaultComponentFactory.getInstance();

	private String userConfigurationFile;

	//////////////////////
	// Public Methods
	//////////////////////

	/**
	 * Loads the configuration from a properties file and initializes the
	 * configuration object.
	 * 
	 * @param configurationFile The path to the properties file to be loaded.
	 * @throws ConfigurationException If an error occurs during the loading or
	 *                                parsing of the configuration file.
	 */
	public final void createConfigurationObject(String configurationFile) throws ConfigurationException {

		this.userConfigurationFile = configurationFile;

		ConfigPreprocessor.preprocessConfigFile(configurationFile, ParserConstants.CONFIGURATION_PREPROCESSED_FILE);

		apacheConfigurationObject = ConfigurationLoader
				.loadConfiguration(ParserConstants.CONFIGURATION_PREPROCESSED_FILE, ParserConstants.ARRAY_DELIMITER);

	}

	/**
	 * Returns the current configuration object.
	 * 
	 * @return The loaded configuration object.
	 */
	private org.apache.commons.configuration2.Configuration getConfigObject() {

		return apacheConfigurationObject;

	}

	/**
	 * sets the benchmark configuration assembled from the parsed configuration
	 * file.
	 * 
	 * @return The assembled benchmark configuration.
	 */

	public final IBenchmark getBenchmark() {

		return benchmark;

	}

	/**
	 * Parses the configuration file and assembles all components necessary for
	 * benchmark execution. This includes input data, tool parameters, platform
	 * parameters, measures, and export classes.
	 * 
	 * @param brunner_config_file
	 * @throws Exception
	 * 
	 */
	public final void parse(String brunner_config_file) throws Exception {

		LOG.debug("Parsing configuration file", 1);

		createConfigurationObject(brunner_config_file);

		parseToolKeywordMenuClass();

		parseExportClasses();

		parseInputData();

		parseToolParam();

		parsePlatformParams();

		parseMeasures();

		parseDefaultTaskList();

		parseDefaultBenchmark();

	}

	private void parseInputData() throws FileNotFoundException {

		var raw = ScenarioParser.parse(getConfigObject(), this.userConfigurationFile);

		inputDatas = getDefaultRepresentation(raw, factory::createInputData, this::extractId);

	}

	private void parseToolParam() throws InstantiationException, IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException {

		var raw = ToolParser.parse(getConfigObject());

		toolsParam = getDefaultRepresentationForTyped(raw, factory::createToolParameters,
				ToolsConstants.getConstantByClass(toolKeywordMenuClass), this::extractId);

		checkInnerLevelToolParams();

	}

	/**
	 * 
	 * Verifies that the keys and values used in the configuration file are allowed by the reasoner.
	 * 
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 * @throws SecurityException
	 */
	private void checkInnerLevelToolParams() throws InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {

		for (IToolParameters param : toolsParam) {
			for (String propertyName : param.getTrimmedProperties()) {

				String value = param.getValueForTrimmedProperty(propertyName);

				if (value == null) {
					LOG.error("No value found for propertyName {} in param {}", propertyName, param);
					continue;
				}

				EnumerationUtils.findEnumConstantInClass(toolKeywordMenuClass, propertyName, value);

			}
		}
	}

	private void parsePlatformParams() {

		var raw = PlatformParser.parse(getConfigObject());

		platformsParam = getDefaultRepresentationForTyped(raw, factory::createPlatformParameters, RunnersConstants.JMH,
				this::extractId);

	}

	private void parseMeasures() {

		var raw = MeasuresParser.parse(getConfigObject());

		measures = getDefaultRepresentation(raw, factory::createMeasures);

		if (measures.isEmpty()) {

			measures.add(DefaultComponentFactory.getInstance().createMeasures("time"));

		}
	}

	private Integer extractId(Map map) {

		for (var k : map.keySet()) {
			if (k.endsWith(BRunnerKeywords.InnerLevel.PARAMETER_ID.kw)) {
				return Integer.parseInt(map.get(k));
			}
		}

		return null;

	}

	private void parseExportClasses() throws ClassNotFoundException, ConfigurationException {

		exportClasses = ExportClassesParser.parse(getConfigObject());

	}

	private void parseToolKeywordMenuClass()
			throws ClassNotFoundException, ConfigurationException, InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {

		toolKeywordMenuClass = ReasonerClassesParser.parse(getConfigObject());

		/*
		 * check that the keyword menu which is loaded is consistent
		 */
		IToolKeywordsMenu instance = ClassUtils.getInstance(toolKeywordMenuClass);

		instance.sanityCheck();

	}

	/**
	 * Prints the parsed benchmark and any unused properties from the configuration
	 * file.
	 */
	public final void print() {
		benchmark.toString();
		System.out.println("\nUnused .properties properties");
		System.out.println(ParserUtils.getUnused(getConfigObject()));
	}

	//////////////////////

	//////////////////////

	//////////////////////
	// Private Methods
	//////////////////////

	//////////////////////

	//////////////////////

	/**
	 * Instantiates and populates a collection of objects that implement the
	 * {@link IPropertySet} interface, based on a provided map of configuration
	 * data. This method uses a constructor reference to instantiate objects of the
	 * specified class type, enhancing type safety and performance by avoiding
	 * reflection.
	 *
	 * @param            The type parameter that extends {@link IPropertySet},
	 *                      indicating the type of objects to be created and
	 *                      populated.
	 * @param map           A {@link Map} where each entry consists of a String key
	 *                      and another {@link Map} as its value. The key is used to
	 *                      instantiate the object (usually as an identifier or
	 *                      configuration name), and the value map contains
	 *                      properties to populate the object with.
	 * @param objectCreator A {@link Function} that accepts a String argument (the
	 *                      key) and returns a new instance of type T. This replaces
	 *                      the need for the clazz parameter and direct use of
	 *                      reflection to instantiate objects.
	 * @return A {@link Collection} of instantiated and populated objects of type T.
	 *         Each object's state is initialized based on the corresponding entry's
	 *         value map from the provided configuration data map.
	 */

	private static final > Collection getDefaultRepresentation(
			Map> map, Function objectCreator) {

		Collection result = new ArrayList<>();

		for (Entry> entry : map.entrySet()) {

			// Create new object using the creator function
			T instance = objectCreator.apply(entry.getKey());

			// Fill the object
			instance.fillWith(entry.getValue());

			// Add object to the result
			result.add(instance);
		}
		return result;
	}

	/**
	 * Variant of getDefaultRepresentation for bifunctions
	 * 
	 * @param 
	 * @param 
	 * @param map
	 * @param objectCreator
	 * @param computeValueFunction
	 * @return
	 */
	private static final , U> List getDefaultRepresentation(
			Map> map, BiFunction objectCreator,
			Function, U> computeValueFunction) {

		List result = new ArrayList<>();

		for (Entry> entry : map.entrySet()) {

			// Create new object using the creator function
			T instance = objectCreator.apply(entry.getKey(), computeValueFunction.apply(entry.getValue()));

			// Fill the object
			instance.fillWith(entry.getValue());

			// Add object to the result
			result.add(instance);
		}
		return result;
	}

	/**
	 * Variant of getDefaultRepresentation for trifunctions
	 * 
	 * @param 
	 * @param 
	 * @param 
	 * @param map
	 * @param objectCreator
	 * @param value
	 * @param computeValueFunction
	 * @return
	 */
	private static final , U, S> List getDefaultRepresentationForTyped(
			Map> map, TriFunction objectCreator, U value,
			Function, S> computeValueFunction) {

		List result = new ArrayList<>();

		for (Entry> entry : map.entrySet()) {

			T instance = objectCreator.apply(entry.getKey(), value, computeValueFunction.apply(entry.getValue()));

			instance.fillWith(entry.getValue());

			result.add(instance);
		}
		return result;
	}

	/**
	 * Creates a default benchmark representation by assembling all the necessary
	 * components collected during the parsing process. This includes input data
	 * scenarios, tool parameters, platform parameters, measures, export classes,
	 * and task list. It automatically generates a unique benchmark name based on an
	 * incremented counter to ensure each benchmark is distinctly identified.
	 * 
	 */
	private void parseDefaultBenchmark() {

		incrementBenchmarkCounter();

		resetTaskCounter();

		String benchName = "BENCH_" + "_" + getBenchmarkCounter()
				+ FileUtils.extractFileNameWithoutExtension(userConfigurationFile);

		benchmark = factory.createBenchmark(benchName, inputDatas, toolsParam, platformsParam, measures, exportClasses,
				toolKeywordMenuClass, taskList);

	}

	/**
	 * Generates a default collection of task representations based on the parsed
	 * configuration. This method prepares a list of execution tasks by combining
	 * different configurations (input data, tool parameters, platform parameters,
	 * measures) specified in the configuration file. It supports executing specific
	 * configurations by filtering them according to the execution only parameters
	 * if provided.
	 * 
	 * @param defaultTaskCreator
	 * 
	 */
	private void parseDefaultTaskList() {

		resetTaskCounter();

		Queue> executionsAll = prepareTaskList();

		Collection result = new ArrayList<>();

		Collections.sort(inputDatas);
		Collections.sort(toolsParam);
		Collections.sort(platformsParam);

		for (Map singleExec : executionsAll) {

			final Object[] resultHolder = new Object[4]; // needed to collect values from the lambdas

			inputDatas.forEach(scenario -> {

				if (scenario.getName().equals(singleExec.get(BRunnerKeywords.OuterLevel.INPUTDATA.kw))) {

					resultHolder[0] = scenario;

				}
			});

			toolsParam.forEach(reasoner -> {

				if (reasoner.getName().equals(singleExec.get(BRunnerKeywords.OuterLevel.TOOL_PARAMETERS.kw))) {

					resultHolder[1] = reasoner;

				}
			});

			platformsParam.forEach(platform -> {

				if (platform.getName()
						.equals(singleExec.get(BRunnerKeywords.OuterLevel.EXECUTION_PLATFORM_PARAMETERS.kw))) {

					resultHolder[2] = platform;

				}
			});

			this.measures.forEach(measure -> {

				if (measure.getName().equals(singleExec.get(BRunnerKeywords.OuterLevel.MEASURE.kw))) {

					resultHolder[3] = measure;

				}
			});

			// the task is now completely specified and ready to go into the benchmark

			var scenario = (IInputData) resultHolder[0];
			var toolparameters = (IToolParameters) resultHolder[1];
			var platformparameters = (IPlatformParameters) resultHolder[2];
			var measures = (IMeasures) resultHolder[3];

			incrementTaskCounter();

			String taskName = "BENCH_" + getBenchmarkCounter() + "_"
					+ FileUtils.extractFileNameWithoutExtension(userConfigurationFile) + "--TASK_" + getTaskCounter()
					+ "_" + toolparameters.getName() + "_" + scenario.getName();

			var newTask = factory.createTask(taskName, scenario, toolparameters, platformparameters, measures,
					exportClasses, toolKeywordMenuClass);

			result.add(newTask);

		}

		taskList = result;

	}

	/**
	 * Prepares a list of maps, each representing a unique combination of input
	 * data, tool parameters, platform parameters, and measures. This method
	 * accounts for execution-only configurations if specified, allowing for
	 * targeted execution of specific benchmark configurations. It generates all
	 * possible permutations of the configurations or filters them according to the
	 * execution-only specifications.
	 * 
	 * @return A queue of maps, with each map detailing a specific execution
	 *         configuration for a task.
	 */

	private Queue> prepareTaskList() {
		List executeOnlyConfigs = apacheConfigurationObject.getList(String.class,
				BRunnerKeywords.OuterLevel.EXECUTE_ONLY.kw);
		apacheConfigurationObject.clearProperty(BRunnerKeywords.OuterLevel.EXECUTE_ONLY.kw);

		Queue> executionsAll = new ArrayDeque<>();

		Set inputDataScenarioNames = new LinkedHashSet();
		Set toolConfigurationNames = new LinkedHashSet();
		Set platformConfigurationNames = new LinkedHashSet();
		Set measureNames = new LinkedHashSet();

		Collections.sort(inputDatas);
		Collections.sort(toolsParam);
		Collections.sort(platformsParam);

		inputDatas.forEach(input -> inputDataScenarioNames.add(input.getName()));

		toolsParam.forEach(input -> toolConfigurationNames.add(input.getName()));

		platformsParam.forEach(input -> platformConfigurationNames.add(input.getName()));

		measures.forEach(input -> measureNames.add(input.getName()));

		if (executeOnlyConfigs == null) {

			executionsAll = ParserUtils.generateExecutionPermutations(inputDataScenarioNames, toolConfigurationNames,
					platformConfigurationNames, measureNames);

			return executionsAll;
		}

		for (String executionOnlyConfig : executeOnlyConfigs) {
			List executionOnlyParams = Arrays
					.asList(executionOnlyConfig.trim().split(ParserConstants.EXECUTE_ONLY_DELIMITER_REGEX));

			Set s = ParserUtils.getRelevantSubset(executionOnlyParams.get(0), inputDataScenarioNames);

			Set r = ParserUtils.getRelevantSubset(executionOnlyParams.get(1), toolConfigurationNames);

			Set p = ParserUtils.getRelevantSubset(executionOnlyParams.get(2), platformConfigurationNames);

			Set m = ParserUtils.getRelevantSubset(executionOnlyParams.get(3), measureNames);

			executionsAll.addAll(ParserUtils.generateExecutionPermutations(s, r, p, m));
		}

		return executionsAll;
	}

	/**
	 * 
	 * @return the parsed input data
	 */
	public Collection getInputDatas() {
		return inputDatas;
	}

	/**
	 * @return the parsed tool parameters
	 */
	public Collection getToolsParam() {
		return toolsParam;
	}

	/**
	 * @return the parsed platform parameters
	 */
	public Collection getPlatformsParam() {
		return platformsParam;
	}

	//////////////////////////////
	/// Static Fields and Methods
	//////////////////////////////

	private static int taskCounter = 0;

	private static int getTaskCounter() {

		return taskCounter;

	}

	private static void incrementTaskCounter() {

		taskCounter++;

	}

	private static int benchmarkCounter = 0;

	private static int getBenchmarkCounter() {

		return benchmarkCounter;

	}

	private static void incrementBenchmarkCounter() {

		benchmarkCounter++;

	}

	private static void resetTaskCounter() {

		taskCounter = 0;

	}

}