<commit_before>package com.github.swissquote.carnotzet.core;

import static java.nio.file.Files.exists;
import static java.nio.file.Files.walk;
import static java.util.stream.Collectors.toList;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang3.SystemUtils;

import com.github.swissquote.carnotzet.core.maven.CarnotzetModuleCoordinates;
import com.github.swissquote.carnotzet.core.maven.MavenDependencyResolver;
import com.github.swissquote.carnotzet.core.maven.ResourcesManager;
import com.google.common.base.Strings;

import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

/**
 * Represents an environment definition as a set of executable applications with their configuration.
 */
@Slf4j
public class Carnotzet {

	@Getter
	private final CarnotzetConfig config;

	@Getter
	private final String topLevelModuleName;

	@Getter
	private final Pattern moduleFilterPattern;

	@Getter
	private final Pattern classifierIncludePattern;

	private List<CarnotzetModule> modules;

	private final MavenDependencyResolver resolver;

	private final ResourcesManager resourceManager;

	private final String defaultContainerRegistry;

	private final List<String> propFileNames;

	private final Boolean failOnDependencyCycle;

	public Carnotzet(CarnotzetConfig config) {
		log.debug("Creating new carnotzet with config [{}]", config);
		this.config = config;

		String filterPattern = CarnotzetConfig.DEFAULT_MODULE_FILTER_PATTERN;
		if (config.getModuleFilterPattern() != null) {
			filterPattern = config.getModuleFilterPattern();
		}
		moduleFilterPattern = Pattern.compile(filterPattern);
		if (moduleFilterPattern.matcher("").groupCount() != 1) {
			throw new CarnotzetDefinitionException("moduleFilterPattern must have exactly 1 capture group");
		}

		if (config.getClassifierIncludePattern() == null) {
			this.classifierIncludePattern = Pattern.compile(CarnotzetConfig.DEFAULT_CLASSIFIER_INCLUDE_PATTERN);
		} else {
			this.classifierIncludePattern = Pattern.compile(config.getClassifierIncludePattern());
		}

		this.topLevelModuleName = getModuleName(config.getTopLevelModuleId());

		Path resourcesPath = config.getResourcesPath();
		if (resourcesPath == null) {
			resourcesPath = Paths.get("/tmp/carnotzet_" + System.nanoTime());
		}
		this.resourceManager = new ResourcesManager(resourcesPath, config.getTopLevelModuleResourcesPath());

		if (config.getDefaultDockerRegistry() != null) {
			this.defaultContainerRegistry = config.getDefaultDockerRegistry();
		} else {
			this.defaultContainerRegistry = CarnotzetConfig.DEFAULT_DOCKER_REGISTRY;
		}

		if (config.getPropFileNames() != null) {
			this.propFileNames = config.getPropFileNames();
		} else {
			this.propFileNames = CarnotzetConfig.DEFAULT_PROP_FILE_NAMES;
		}

		if (config.getFailOnDependencyCycle() != null) {
			this.failOnDependencyCycle = config.getFailOnDependencyCycle();
		} else {
			this.failOnDependencyCycle = true;
		}

		resolver = new MavenDependencyResolver(this::getModuleName, resourcesPath.resolve("maven"));

	}

	public List<CarnotzetModule> getModules() {
		if (modules == null) {
			modules = resolver.resolve(config.getTopLevelModuleId(), failOnDependencyCycle);
			if (SystemUtils.IS_OS_LINUX || !getResourcesFolder().resolve("expanded-jars").toFile().exists()) {
				resourceManager.extractResources(modules);
				modules = computeServiceIds(modules);
				resourceManager.resolveResources(modules);
			}
			log.debug("configuring modules");
			modules = configureModules(modules);

			if (config.getExtensions() != null) {
				for (CarnotzetExtension feature : config.getExtensions()) {
					log.debug("Extension [{}] enabled", feature.getClass().getSimpleName());
					modules = feature.apply(this);
				}
			}
			assertNoDuplicateArtifactId(modules);
			modules = selectModulesForUniqueServiceId(modules);
		}
		return modules;
	}

	private List<CarnotzetModule> computeServiceIds(List<CarnotzetModule> modules) {
		return modules.stream().map(this::computeServiceId).collect(toList());
	}

	private CarnotzetModule computeServiceId(CarnotzetModule module) {
		Map<String, String> ownProperties = readPropertiesFiles(resourceManager.getOwnModuleResourcesPath(module));
		String serviceId = module.getName();
		if (ownProperties.containsKey("service.id") && !ownProperties.get("service.id").isEmpty()) {
			serviceId = ownProperties.get("service.id");
		}
		return module.toBuilder().serviceId(serviceId).build();
	}

	/**
	 * Ensures that serviceIds are unique in the environment, keeping only the first in the list when multiple exist.
	 */
	private List<CarnotzetModule> selectModulesForUniqueServiceId(List<CarnotzetModule> modules) {
		Set<String> serviceIds = new HashSet<>();
		List<CarnotzetModule> result = new ArrayList<>();
		for (CarnotzetModule module : modules) {
			if (!serviceIds.contains(module.getServiceId())) {
				result.add(module);
				serviceIds.add(module.getServiceId());
				log.debug("Module [{}] was selected for serviceId [{}]", module.getName(), module.getServiceId());
			} else {
				log.debug("Module [{}] was NOT selected for serviceId [{}]", module.getName(), module.getServiceId());
			}
		}
		return result;
	}

	private void assertNoDuplicateArtifactId(List<CarnotzetModule> modules) {
		Map<String, CarnotzetModule> seen = new HashMap<>();
		modules.forEach(m -> {
			String artifactId = m.getId().getArtifactId();
			if (seen.containsKey(artifactId)) {
				throw new CarnotzetDefinitionException("Duplicate artifact ID [" + artifactId + "] with groupIds "
						+ "[" + m.getId().getGroupId() + "] and [" + seen.get(artifactId).getId().getGroupId() + "]");
			}
			seen.put(artifactId, m);
		});
	}<commit_msg>Use reverse sorted array to resolve services<commit_after>package com.github.swissquote.carnotzet.core;

import static java.nio.file.Files.exists;
import static java.nio.file.Files.walk;
import static java.util.stream.Collectors.toList;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang3.SystemUtils;

import com.github.swissquote.carnotzet.core.maven.CarnotzetModuleCoordinates;
import com.github.swissquote.carnotzet.core.maven.MavenDependencyResolver;
import com.github.swissquote.carnotzet.core.maven.ResourcesManager;
import com.google.common.base.Strings;

import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

/**
 * Represents an environment definition as a set of executable applications with their configuration.
 */
@Slf4j
public class Carnotzet {

	@Getter
	private final CarnotzetConfig config;

	@Getter
	private final String topLevelModuleName;

	@Getter
	private final Pattern moduleFilterPattern;

	@Getter
	private final Pattern classifierIncludePattern;

	private List<CarnotzetModule> modules;

	private final MavenDependencyResolver resolver;

	private final ResourcesManager resourceManager;

	private final String defaultContainerRegistry;

	private final List<String> propFileNames;

	private final Boolean failOnDependencyCycle;

	public Carnotzet(CarnotzetConfig config) {
		log.debug("Creating new carnotzet with config [{}]", config);
		this.config = config;

		String filterPattern = CarnotzetConfig.DEFAULT_MODULE_FILTER_PATTERN;
		if (config.getModuleFilterPattern() != null) {
			filterPattern = config.getModuleFilterPattern();
		}
		moduleFilterPattern = Pattern.compile(filterPattern);
		if (moduleFilterPattern.matcher("").groupCount() != 1) {
			throw new CarnotzetDefinitionException("moduleFilterPattern must have exactly 1 capture group");
		}

		if (config.getClassifierIncludePattern() == null) {
			this.classifierIncludePattern = Pattern.compile(CarnotzetConfig.DEFAULT_CLASSIFIER_INCLUDE_PATTERN);
		} else {
			this.classifierIncludePattern = Pattern.compile(config.getClassifierIncludePattern());
		}

		this.topLevelModuleName = getModuleName(config.getTopLevelModuleId());

		Path resourcesPath = config.getResourcesPath();
		if (resourcesPath == null) {
			resourcesPath = Paths.get("/tmp/carnotzet_" + System.nanoTime());
		}
		this.resourceManager = new ResourcesManager(resourcesPath, config.getTopLevelModuleResourcesPath());

		if (config.getDefaultDockerRegistry() != null) {
			this.defaultContainerRegistry = config.getDefaultDockerRegistry();
		} else {
			this.defaultContainerRegistry = CarnotzetConfig.DEFAULT_DOCKER_REGISTRY;
		}

		if (config.getPropFileNames() != null) {
			this.propFileNames = config.getPropFileNames();
		} else {
			this.propFileNames = CarnotzetConfig.DEFAULT_PROP_FILE_NAMES;
		}

		if (config.getFailOnDependencyCycle() != null) {
			this.failOnDependencyCycle = config.getFailOnDependencyCycle();
		} else {
			this.failOnDependencyCycle = true;
		}

		resolver = new MavenDependencyResolver(this::getModuleName, resourcesPath.resolve("maven"));

	}

	public List<CarnotzetModule> getModules() {
		if (modules == null) {
			modules = resolver.resolve(config.getTopLevelModuleId(), failOnDependencyCycle);
			if (SystemUtils.IS_OS_LINUX || !getResourcesFolder().resolve("expanded-jars").toFile().exists()) {
				resourceManager.extractResources(modules);
				modules = computeServiceIds(modules);
				resourceManager.resolveResources(modules);
			}
			log.debug("configuring modules");
			modules = configureModules(modules);

			if (config.getExtensions() != null) {
				for (CarnotzetExtension feature : config.getExtensions()) {
					log.debug("Extension [{}] enabled", feature.getClass().getSimpleName());
					modules = feature.apply(this);
				}
			}
			assertNoDuplicateArtifactId(modules);
			modules = selectModulesForUniqueServiceId(modules);
		}
		return modules;
	}

	private List<CarnotzetModule> computeServiceIds(List<CarnotzetModule> modules) {
		return modules.stream().map(this::computeServiceId).collect(toList());
	}

	private CarnotzetModule computeServiceId(CarnotzetModule module) {
		Map<String, String> ownProperties = readPropertiesFiles(resourceManager.getOwnModuleResourcesPath(module));
		String serviceId = module.getName();
		if (ownProperties.containsKey("service.id") && !ownProperties.get("service.id").isEmpty()) {
			serviceId = ownProperties.get("service.id");
		}
		return module.toBuilder().serviceId(serviceId).build();
	}

	/**
	 * Ensures that serviceIds are unique in the environment, keeping only the first in the list when multiple exist.
	 */
	private List<CarnotzetModule> selectModulesForUniqueServiceId(List<CarnotzetModule> modules) {
		Set<String> serviceIds = new HashSet<>();
		List<CarnotzetModule> result = new ArrayList<>();
		ArrayList<CarnotzetModule> invertedTopologicalOrder = new ArrayList<>(modules);
		Collections.reverse(invertedTopologicalOrder);
		for (CarnotzetModule module : invertedTopologicalOrder) {
			if (!serviceIds.contains(module.getServiceId())) {
				result.add(module);
				serviceIds.add(module.getServiceId());
				log.debug("Module [{}] was selected for serviceId [{}]", module.getName(), module.getServiceId());
			} else {
				log.debug("Module [{}] was NOT selected for serviceId [{}]", module.getName(), module.getServiceId());
			}
		}
		return result;
	}

	private void assertNoDuplicateArtifactId(List<CarnotzetModule> modules) {
		Map<String, CarnotzetModule> seen = new HashMap<>();
		modules.forEach(m -> {
			String artifactId = m.getId().getArtifactId();
			if (seen.containsKey(artifactId)) {
				throw new CarnotzetDefinitionException("Duplicate artifact ID [" + artifactId + "] with groupIds "
						+ "[" + m.getId().getGroupId() + "] and [" + seen.get(artifactId).getId().getGroupId() + "]");
			}
			seen.put(artifactId, m);
		});
	}
{'fraction_non_alphanumeric': 0.11821162920898984, 'fraction_numerical': 0.0009597696552827321, 'mean_word_length': 5.492211838006231, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1220060', 'n_tokens_mistral': 4044, 'n_tokens_neox': 3640, 'n_words': 715}