/*
 * $Id$
 *
 * SARL is an general-purpose agent programming language.
 * More details on http://www.sarl.io
 *
 * Copyright (C) 2014-2026 SARL.io, the original authors and main authors.
 *
 * Licensed 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 io.sarl.lang.compiler.batch;

import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.LogManager;
import org.apache.log4j.spi.HierarchyEventListener;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.log4j.spi.LoggerRepository;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtend.core.macro.ProcessorInstanceForJvmTypeProvider;
import org.eclipse.xtext.Constants;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider;
import org.eclipse.xtext.common.types.access.impl.IndexedJvmTypeAccess;
import org.eclipse.xtext.common.types.descriptions.IStubGenerator;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.generator.GeneratorContext;
import org.eclipse.xtext.generator.GeneratorDelegate;
import org.eclipse.xtext.generator.IFileSystemAccess;
import org.eclipse.xtext.generator.IOutputConfigurationProvider;
import org.eclipse.xtext.generator.JavaIoFileSystemAccess;
import org.eclipse.xtext.generator.OutputConfiguration;
import org.eclipse.xtext.generator.OutputConfigurationAdapter;
import org.eclipse.xtext.mwe.NameBasedFilter;
import org.eclipse.xtext.mwe.PathTraverser;
import org.eclipse.xtext.parser.IEncodingProvider;
import org.eclipse.xtext.resource.CompilerPhases;
import org.eclipse.xtext.resource.FileExtensionProvider;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.resource.persistence.StorageAwareResource;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.Files;
import org.eclipse.xtext.util.JavaVersion;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.util.UriUtil;
import org.eclipse.xtext.validation.CheckMode;
import org.eclipse.xtext.validation.Issue;
import org.eclipse.xtext.workspace.FileProjectConfig;
import org.eclipse.xtext.workspace.ProjectConfigAdapter;
import org.eclipse.xtext.xbase.compiler.GeneratorConfig;
import org.eclipse.xtext.xbase.compiler.GeneratorConfigProvider;
import org.eclipse.xtext.xbase.compiler.IGeneratorConfigProvider;
import org.eclipse.xtext.xbase.lib.Inline;
import org.eclipse.xtext.xbase.lib.Pure;
import org.eclipse.xtext.xbase.resource.BatchLinkableResource;

import io.sarl.lang.SARLConfig;
import io.sarl.lang.compiler.GeneratorConfig2;
import io.sarl.lang.compiler.GeneratorConfigProvider2;
import io.sarl.lang.compiler.IGeneratorConfigProvider2;
import io.sarl.lang.compiler.batch.InternalXtextLogger.InternalXtextLoggerFactory;
import io.sarl.lang.extralanguage.IExtraLanguageContribution;
import io.sarl.lang.extralanguage.IExtraLanguageContributions;
import io.sarl.lang.util.Utils;
import io.sarl.lang.validation.IConfigurableIssueSeveritiesProvider;

/** The compiler from SARL that could be used for batch tasks (Maven, CLI).
 *
 * <p>This compiler is inspired by the Xtend batch compiler.
 *
 * @author $Author: sgalland$
 * @version $FullVersion$
 * @mavengroupid $GroupId$
 * @mavenartifactid $ArtifactId$
 * @since 0.5
 */
public class SarlBatchCompiler {

	private static final String BINCLASS_FOLDER_PREFIX = "classes"; //$NON-NLS-1$

	private static final String STUB_FOLDER_PREFIX = "stubs"; //$NON-NLS-1$

	private static final String INTERNAL_ERROR_CODE = SarlBatchCompiler.class.getName() + ".internal_error"; //$NON-NLS-1$

	private static final Predicate<IExtraLanguageContribution> DISABLER = it -> false;

	/** The provider of resource sets.
	 */
	protected Provider<ResourceSet> resourceSetProvider;

	private File outputPath;

	private File classOutputPath;

	private File tempPath;

	private CleaningPolicy cleaningPolicy = CleaningPolicy.getDefault();

	private List<File> classpath;

	private List<File> modulepath;

	private String encoding;

	private boolean writeTraceFiles = true;

	private boolean writeStorageFiles = true;

	private boolean verbose;

	private boolean enableSarlCompilation = true;

	private boolean enableJavaPostCompilation;

	private List<File> sourcePath;

	private boolean useCurrentClassLoaderAsParent;

	private org.eclipse.emf.common.util.URI baseUri;

	private FileProjectConfig projectConfig;

	private Map<String, OutputConfiguration> outputConfigurations;

	private ClassLoader currentClassLoader;

	private ClassLoader jvmTypesClassLoader;

	private ClassLoader annotationProcessingClassLoader;

	@Inject
	private IGeneratorConfigProvider generatorConfigProvider;

	@Inject
	private IGeneratorConfigProvider2 generatorConfigProvider2;

	@Inject
	private IOutputConfigurationProvider outputConfigurationProvider;

	@Inject
	private CompilerPhases compilerPhases;

	@Inject
	private Provider<JavaIoFileSystemAccess> javaIoFileSystemAccessProvider;

	@Inject
	private IndexedJvmTypeAccess indexedJvmTypeAccess;

	@Inject
	private IEncodingProvider.Runtime encodingProvider;

	@Inject
	private FileExtensionProvider fileExtensionProvider;

	@Inject
	private IResourceDescription.Manager resourceDescriptionManager;

	@Inject
	private IStubGenerator stubGenerator;

	@Inject
	private GeneratorDelegate generator;

	@Inject
	private IConfigurableIssueSeveritiesProvider issueSeverityProvider;

	@Inject
	private IExtraLanguageContributions extraLanguageContributions;

	@Inject
	@Named(Constants.LANGUAGE_NAME)
	private String languageName;

	private IJavaBatchCompiler javaCompiler;

	private Logger logger;

	private IssueMessageFormatter messageFormatter;

	private Collection<IssueMessageListener> messageListeners = new LinkedList<>();

	private Collection<ICompilatedResourceReceiver> resourceReceivers = new LinkedList<>();

	private final List<File> tempFolders = new ArrayList<>();

	private Comparator<Issue> issueComparator = new DefaultIssueComparator();

	private GeneratorConfig currentGeneratorConfiguration;

	private GeneratorConfig2 currentGeneratorConfiguration2;

	private String enabledExtraLanguageContributions;

	private boolean reportInternalProblemsAsIssues;

	private boolean reportWarningsAsErrors;

	private OptimizationLevel optimizationLevel;

	/** Constructor the batch compiler.
	 */
	public SarlBatchCompiler() {
		//
	}

	/** Change the Java compiler.
	 *
	 * @param compiler the Java compiler
	 * @since 0.8
	 */
	@Inject
	public void setJavaCompiler(IJavaBatchCompiler compiler) {
		assert compiler != null;
		this.javaCompiler = compiler;
	}

	/** Replies the Java compiler.
	 *
	 * @return the Java compiler
	 * @since 0.8
	 */
	public IJavaBatchCompiler getJavaCompiler() {
		if (this.javaCompiler == null) {
			this.javaCompiler = SarlBatchCompilerUtils.newDefaultJavaBatchCompiler();
		}
		return this.javaCompiler;
	}

	/** Change the optimization level that should be applied to the generated Java byte code.
	 *
	 * @param level the optimization level.
	 * @since 0.8
	 */
	public void setOptimizationLevel(OptimizationLevel level) {
		this.optimizationLevel = level;
	}

	/** Replies the optimization level that should be applied to the generated Java byte code.
	 *
	 * @return the optimization level.
	 * @since 0.8
	 */
	public OptimizationLevel getOptimizationLevel() {
		if (this.optimizationLevel == null) {
			this.optimizationLevel = OptimizationLevel.getDefault();
		}
		return this.optimizationLevel;
	}

	/** Change the flag that permits to report the compiler's internal problems as issues.
	 *
	 * @param reportAsIssues {@code true} if the internal errors are reported as issues.
	 * @since 0.8
	 * @see #addIssueMessageListener(IssueMessageListener)
	 */
	public void setReportInternalProblemsAsIssues(boolean reportAsIssues) {
		this.reportInternalProblemsAsIssues = reportAsIssues;
	}

	/** Replies the flag that indicates to report the compiler's internal problems as issues.
	 *
	 * @return {@code true} if the internal errors are reported as issues.
	 * @since 0.8
	 * @see #addIssueMessageListener(IssueMessageListener)
	 */
	public boolean getReportInternalProblemsAsIssues() {
		return this.reportInternalProblemsAsIssues;
	}

	/** Change the flag that permits to report the warning issues detected by the SARL compiler as errors to the user
	 * of the batch compiler.
	 *
	 * @param reportAsErrors {@code true} if the warnings are reported as errors.
	 * @since 0.13
	 * @see #addIssueMessageListener(IssueMessageListener)
	 */
	public void setReportWarningsAsErrors(boolean reportAsErrors) {
		this.reportWarningsAsErrors = reportAsErrors;
	}

	/** Replies the flag that permits to report the warning issues detected by the SARL compiler as errors to the user
	 * of the batch compiler.
	 *
	 * @return {@code true} if the warnings are reported as errors.
	 * @since 0.13
	 * @see #addIssueMessageListener(IssueMessageListener)
	 */
	public boolean getReportWarningsAsErrors() {
		return this.reportWarningsAsErrors;
	}

	/** Change the extra languages' generators that should be enabled.
	 *
	 * @param identifiers the identifier, the identifiers (separated by {@link File#pathSeparator} of the
	 *     extra languages' generator(s) to be enabled. If this parameter is {@code null}, all the extra
	 *     languages' generator are disabled.
	 * @since 0.8
	 */
	public void setExtraLanguageGenerators(String identifiers) {
		this.enabledExtraLanguageContributions = Strings.emptyIfNull(identifiers);
	}

	/** Replies the extra languages' generators that should be enabled.
	 *
	 * @return the identifier, the identifiers (separated by {@link File#pathSeparator} of the
	 *     extra languages' generator(s) to be enabled. If this parameter is {@code null}, all the extra
	 *     languages' generator are disabled.
	 * @since 0.8
	 */
	public String getExtraLanguageGenerators() {
		return this.enabledExtraLanguageContributions;
	}

	/** Set the comparator of issues that is used for sorting the issues before they are logged.
	 *
	 * @param comparator the comparator; never {@code null}.
	 */
	public void setIssueComparator(Comparator<Issue> comparator) {
		if (comparator != null) {
			this.issueComparator = comparator;
		}
	}

	/** Replies the comparator of issues that is used for sorting the issues before they are logged.
	 *
	 * @return the comparator; never {@code null}.
	 */
	public Comparator<Issue> getIssueComparator() {
		return this.issueComparator;
	}

	/** Replies if the Java compiler should be invoked after the SARL compiler is invoked.
	 *
	 * @return {@code true} if the Java compiler is invoked after the SARL compiler.
	 */
	public boolean isJavaPostCompilationEnable() {
		return this.enableJavaPostCompilation;
	}

	/** Set if the Java compiler should be invoked after the SARL compiler is invoked.
	 *
	 * @param enable {@code true} if the Java compiler is invoked after the SARL compiler.
	 */
	public void setJavaPostCompilationEnable(boolean enable) {
		this.enableJavaPostCompilation = enable;
	}

	/** Replies if the SARL compiler should be invoked before the Java compiler is invoked.
	 *
	 * @return {@code true} if the SARL compiler is invoked before the Java compiler.
	 * @since 0.12
	 */
	public boolean isSarlCompilationEnable() {
		return this.enableSarlCompilation;
	}

	/** Set if the SARL compiler should be invoked before the Java compiler is invoked.
	 *
	 * @param enable is {@code true} if the SARL compiler is invoked before the Java compiler.
	 * @since 0.12
	 */
	public void setSarlCompilationEnable(boolean enable) {
		this.enableSarlCompilation = enable;
	}

	/** Replies the formatter of the issue messages.
	 *
	 * @return the formatter.
	 */
	public IssueMessageFormatter getIssueMessageFormatter() {
		return this.messageFormatter;
	}

	/** Set the formatter of the issue messages.
	 *
	 * @param formatter the formatter.
	 */
	public void setIssueMessageFormatter(IssueMessageFormatter formatter) {
		this.messageFormatter = formatter;
	}

	/** Add a listener on the issue messages.
	 *
	 * @param listener the listener.
	 * @since 0.6
	 */
	public void addIssueMessageListener(IssueMessageListener listener) {
		this.messageListeners.add(listener);
	}

	/** Add a listener on the issue messages.
	 *
	 * @param listener the listener.
	 * @since 0.6
	 */
	public void removeIssueMessageListener(IssueMessageListener listener) {
		this.messageListeners.remove(listener);
	}

	/** Replies the message for the given issue.
	 *
	 * @param concreteSeverity the severity that was considered by the batch compiler. It may be stronger alert level that those in the {@code issue}.
	 * @param issue the issue.
	 * @param uri URI to the problem.
	 * @param message the formatted message.
	 * @since 0.6
	 */
	private void notifiesIssueMessageListeners(Severity concreteSeverity, Issue issue, org.eclipse.emf.common.util.URI uri, String message) {
		for (final var listener : this.messageListeners) {
			listener.onIssue(concreteSeverity, issue, uri, message);
		}
	}

	/** Add a receiver on the successfully compiled resources.
	 *
	 * @param receiver the receiver.
	 * @since 0.6
	 */
	public void addCompiledResourceReceiver(ICompilatedResourceReceiver receiver) {
		this.resourceReceivers.add(receiver);
	}

	/** Remove a receiver on the successfully compiled resources.
	 *
	 * @param receiver the receiver.
	 * @since 0.6
	 */
	public void removeCompiledResourceReceiver(ICompilatedResourceReceiver receiver) {
		this.resourceReceivers.remove(receiver);
	}

	/** Replies the message for the given issue.
	 *
	 * @param resource the compiled resource.
	 * @since 0.6
	 */
	private void notifiesCompiledResourceReceiver(Resource resource) {
		for (final var receiver : this.resourceReceivers) {
			receiver.receiveCompiledResource(resource);
		}
	}

	/** Replies the logger.
	 *
	 * @return the logger.
	 */
	public Logger getLogger() {
		if (this.logger == null) {
			this.logger = Logger.getLogger(getClass().getName());
		}
		return this.logger;
	}

	/** Set the logger.
	 *
	 * @param logger the logger.
	 */
	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	/** Set the provider of resource sets.
	 *
	 * @param resourceSetProvider the provider.
	 */
	@Inject
	public void setResourceSetProvider(Provider<ResourceSet> resourceSetProvider) {
		this.resourceSetProvider = resourceSetProvider;
	}

	private static File normalizeFile(String file) {
		return new File(new File(file).getAbsoluteFile().toURI().normalize());
	}

	/** Replies if the trace files must be generated.
	 *
	 * <p>A trace file contains the links between the class, java and SARL files.
	 * They are mandatory for retrieving and displaying the SARL source code from
	 * a JVM element.
	 *
	 * <p>The usual filename for the trace files follows the pattern
	 * {@code .Type.java._trace}, where {@code Type} is the name of the SARL type declaration.
	 *
	 * @return {@code true} for generation.
	 */
	public boolean isWriteTraceFiles() {
		return this.writeTraceFiles;
	}

	/** Set if the trace files must be generated.
	 *
	 * <p>A trace file contains the links between the class, java and SARL files.
	 * They are mandatory for retreiving and displaying the SARL source code from
	 * a JVM element.
	 *
	 * <p>The usual filename for the trace files follows the pattern
	 * {@code .Type.java._trace}, where {@code Type} is the name of the SARL type declaration.
	 *
	 * @param writeTraceFiles {@code true} for generation.
	 */
	public void setWriteTraceFiles(boolean writeTraceFiles) {
		this.writeTraceFiles = writeTraceFiles;
	}

	/** Replies if the storage files must be generated.
	 *
	 * <p>The storage files are binary versions of the resources in order
	 * to have faster reading/accessing.
	 *
	 * <p>The usual filename for the storage files follows the pattern
	 * {@code .Type.sarlbin}, where {@code Type} is the name of the SARL type declaration.
	 *
	 * @return {@code true} for generation.
	 */
	@Pure
	public boolean isWriteStorageFiles() {
		return this.writeStorageFiles;
	}

	/** Set if the storage files must be generated.
	 *
	 * <p>The storage files are binary versions of the resources in order
	 * to have faster reading/accessing.
	 *
	 * <p>The usual filename for the storage files follows the pattern
	 * {@code .Type.sarlbin}, where {@code Type} is the name of the SARL type declaration.
	 *
	 * @param writeStorageFiles {@code true} for generation.
	 */
	public void setWriteStorageFiles(boolean writeStorageFiles) {
		this.writeStorageFiles = writeStorageFiles;
	}

	/** Replies if the compiler is verbose.
	 *
	 * @return {@code true} if the compiler is verbose.
	 */
	@Pure
	public boolean isJavaCompilerVerbose() {
		return this.verbose;
	}

	/** Set the underlying Java compiler verbosity.
	 *
	 * @param verbose {@code true} if the Java compiler is verbose.
	 */
	public void setJavaCompilerVerbose(boolean verbose) {
		this.verbose = verbose;
	}

	/** Replies the current class loader.
	 *
	 * @return the class loader.
	 */
	@Pure
	public ClassLoader getCurrentClassLoader() {
		if (this.currentClassLoader == null) {
			this.currentClassLoader = getClass().getClassLoader();
		}
		return this.currentClassLoader;
	}

	/** Set the current class loader.
	 *
	 * @param loader the new current class loader.
	 */
	public void setCurrentClassLoader(ClassLoader loader) {
		this.currentClassLoader = null;
	}

	/** Set if the class loaderr of this batch compiler must be used as sthe parent class loader.
	 *
	 * @param useCurrentClassLoaderAsParent {@code true} for using the class loader of this batch compiler.
	 */
	public void setUseCurrentClassLoaderAsParent(boolean useCurrentClassLoaderAsParent) {
		this.useCurrentClassLoaderAsParent = useCurrentClassLoaderAsParent;
	}

	/** Replies if the class loaderr of this batch compiler must be used as sthe parent class loader.
	 *
	 * @return {@code true} for using the class loader of this batch compiler.
	 */
	@Pure
	public boolean isUseCurrentClassLoaderAsParent() {
		return this.useCurrentClassLoaderAsParent;
	}

	/** Change the base path.
	 *
	 * @param basePath the base path.
	 */
	public void setBasePath(String basePath) {
		setBaseURI(UriUtil.createFolderURI(normalizeFile(basePath)));
	}

	/** Change the base URI.
	 *
	 * @param basePath the base path.
	 */
	public void setBaseURI(org.eclipse.emf.common.util.URI basePath) {
		this.baseUri = basePath;
	}

	/** Change the path where the Java files are generated.
	 *
	 * @param path the path, or {@code null} for using the default path in {@link SARLConfig#FOLDER_SOURCE_GENERATED}..
	 */
	public void setOutputPath(File path) {
		this.outputPath = path;
	}

	/** Change the path where the Java files are generated.
	 *
	 * @param path the path.
	 */
	public void setOutputPath(String path) {
		setOutputPath(normalizeFile(path));
	}

	/** Replies the path where the Java files are generated.
	 *
	 * @return the path; or {@code null} for using the default path in {@link SARLConfig#FOLDER_SOURCE_GENERATED}.
	 */
	@Pure
	public File getOutputPath() {
		return this.outputPath;
	}

	/** Replies the path where the class files are generated.
	 *
	 * @return the path; or {@code null} for ignoring the class generation.
	 */
	@Pure
	public File getClassOutputPath() {
		return this.classOutputPath;
	}

	/** Set the path where the class files are generated.
	 *
	 * @param path the path; or {@code null} for ignoring the class generation.
	 */
	@Pure
	public void setClassOutputPath(File path) {
		this.classOutputPath = path;
	}

	/** Change the classpath.
	 *
	 * <p>The classpath is a list the names of folders or jar files that are separated by {@link File#pathSeparator}.
	 *
	 * @param classpath the new classpath.
	 */
	public void setClassPath(String classpath) {
		this.classpath = new ArrayList<>();
		for (final var path : Strings.split(classpath, File.pathSeparator)) {
			this.classpath.add(normalizeFile(path));
		}
	}

	/** Change the classpath.
	 *
	 * @param classpath the new classpath.
	 */
	public void setClassPath(Collection<File> classpath) {
		this.classpath = new ArrayList<>(classpath);
	}

	/** Replies the classpath.
	 *
	 * @return the classpath.
	 */
	@Pure
	public List<File> getClassPath() {
		if (this.classpath == null) {
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(this.classpath);
	}

	/** Change the module-path.
	 * This function does nothing if the current version of Java is not supporting modules.
	 *
	 * <p>The module-path is a list the names of folders or jar files that are separated by {@link File#pathSeparator}.
	 *
	 * @param modulepath the new module-path.
	 * @since 0.12
	 */
	public void setModulePath(String modulepath) {
		this.modulepath = new ArrayList<>();
		for (final var path : Strings.split(modulepath, File.pathSeparator)) {
			this.modulepath.add(normalizeFile(path));
		}
	}

	/** Change the module-path.
	 * This function does nothing if the current version of Java is not supporting modules.
	 *
	 * @param modulepath the new module-path.
	 * @since 0.12
	 */
	public void setModulePath(Collection<File> modulepath) {
		this.modulepath = new ArrayList<>(modulepath);
	}

	/** Replies the module-path.
	 * This function replies the empty list if the current version of Java is not supporting modules.
	 *
	 * @return the module-path.
	 * @since 0.12
	 */
	@Pure
	public List<File> getModulePath() {
		if (this.modulepath == null) {
			return Collections.emptyList();
		}
		return Collections.unmodifiableList(this.modulepath);
	}

	/** Change the path where the Xtext stubs are generated.
	 *
	 * @param path the path.
	 */
	public void setTempDirectory(File path) {
		this.tempPath = path;
	}

	/** Change the path where the Xtext stubs are generated.
	 *
	 * @param path the path.
	 */
	public void setTempDirectory(String path) {
		setTempDirectory(normalizeFile(path));
	}

	/** Replies the path where the Xtext stubs are generated.
	 *
	 * @return the path; or {@code null} for using the default path.
	 */
	@Pure
	public File getTempDirectory() {
		if (this.tempPath == null) {
			this.tempPath = createTempDirectory();
		}
		return this.tempPath;
	}

	/** Create the temp directory that should be used by the compiler.
	 *
	 * @return the temp directory, never {@code null}.
	 */
	@SuppressWarnings("static-method")
	protected File createTempDirectory() {
		final var tmpPath = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
		var i = 0;
		var tmp = new File(tmpPath, "sarlc" + i); //$NON-NLS-1$
		while (tmp.exists()) {
			++i;
			tmp = new File(tmpPath, "sarlc" + i); //$NON-NLS-1$
		}
		return tmp;
	}

	/** Replies the cleaning policy that is applied by this batch compiler.
	 *
	 * @return the cleaning policy, never {@code null}.
	 * @since 0.10
	 */
	@Pure
	public CleaningPolicy getCleaningPolicy() {
		return this.cleaningPolicy;
	}

	/** Change the cleaning policy that is applied by this batch compiler.
	 *
	 * @param policy the cleaning policy. If it is {@code null}, the
	 *     {@link CleaningPolicy#getDefault() default policy} is used.
	 * @since 0.10
	 */
	public void setCleaningPolicy(CleaningPolicy policy) {
		this.cleaningPolicy = policy == null ? CleaningPolicy.getDefault() : policy;
	}

	/** Replies if the temp folder must be deleted at the end of the compilation.
	 *
	 * @return {@code true} if the temp folder is deleted.
	 * @deprecated since 0.10, see {@link #getCleaningPolicy()}.
	 */
	@Pure
	@Deprecated(forRemoval = true, since = "0.10")
	@Inline(value = "getCleaningPolicy() != $1.NO_CLEANING", imported = {CleaningPolicy.class})
	public boolean isDeleteTempDirectory() {
		return getCleaningPolicy() != CleaningPolicy.NO_CLEANING;
	}

	/** Set if the temp folder must be deleted at the end of the compilation.
	 *
	 * @param delete {@code true} if the temp folder is deleted.
	 * @deprecated since 0.10, see {@link #setCleaningPolicy(CleaningPolicy)}.
	 */
	@Deprecated(forRemoval = true, since = "0.10")
	@Inline(value = "setCleaningPolicy(($1) ? $2.INTERNAL_CLEANING : $2.NO_CLEANING)", imported = {CleaningPolicy.class})
	public void setDeleteTempDirectory(boolean delete) {
		setCleaningPolicy(delete ? CleaningPolicy.INTERNAL_CLEANING : CleaningPolicy.NO_CLEANING);
	}

	/** Change the file encoding.
	 *
	 * @param encoding the encoding, usually {@code UTF-8}.
	 */
	public void setFileEncoding(String encoding) {
		this.encoding = encoding;
	}

	/** Change the file encoding.
	 *
	 * @return the file encoding, or {@code null} if the default encoding must be used.
	 */
	@Pure
	public String getFileEncoding() {
		return this.encoding;
	}

	/** Replies the current generator config.
	 *
	 * @return the generator config.
	 */
	protected GeneratorConfig getGeneratorConfig() {
		if (this.currentGeneratorConfiguration == null) {
			this.currentGeneratorConfiguration = this.generatorConfigProvider.get(null);
		}
		return this.currentGeneratorConfiguration;
	}

	/** Replies the current generator config v2.
	 *
	 * @return the generator config v2.
	 */
	protected GeneratorConfig2 getGeneratorConfig2() {
		if (this.currentGeneratorConfiguration2 == null) {
			this.currentGeneratorConfiguration2 = this.generatorConfigProvider2.get(null);
		}
		return this.currentGeneratorConfiguration2;
	}

	/** Change the version of the Java source to be used for the generated Java files.
	 *
	 * @param version the Java version.
	 */
	public void setJavaSourceVersion(String version) {
		final var javaVersion = JavaVersion.fromQualifier(version);
		if (javaVersion == null) {
			final var qualifiers = new ArrayList<String>();
			for (final var vers : JavaVersion.values()) {
				qualifiers.addAll(vers.getAllQualifiers());
			}

			throw new RuntimeException(MessageFormat.format(
					Messages.SarlBatchCompiler_0, version, Joiner.on(Messages.SarlBatchCompiler_1).join(qualifiers)));
		}
		getGeneratorConfig().setJavaSourceVersion(javaVersion);
	}

	/** Replies the version of the Java source to be used for the generated Java files.
	 *
	 * @return the Java version.
	 */
	@Pure
	public String getJavaSourceVersion() {
		return getGeneratorConfig().getJavaSourceVersion().getQualifier();
	}

	/** Replies if the current project is modular.
	 * A project is module when it defines the "module-info.java" file.
	 *
	 * @return {@code true} if the project is detected as modular.
	 * @since 0.12
	 */
	@Pure
	public boolean isModularProject() {
		for (final var folder : getSourcePaths()) {
			final var infoFile = new File(folder, "module-info.java"); //$NON-NLS-1$
			if (infoFile.isFile()) {
				return true;
			}
		}
		return false;
	}

	/** Replies the compiler generate the Xbase expressions.
	 *
	 * @return {@code true} if the compiler generates the expressions
	 */
	@Pure
	public boolean isGenerateExpressions() {
		return getGeneratorConfig().isGenerateExpressions();
	}

	/** Set if the compiler generate the Xbase expressions.
	 *
	 * @param generateExpressions {@code true} if the compiler generates the expressions
	 */
	public void setGenerateExpressions(boolean generateExpressions) {
		getGeneratorConfig().setGenerateExpressions(generateExpressions);
	}

	/** Replies the {@code @SuppressWarnings} is generated.
	 *
	 * @return {@code true} if the compiler generates the warning supression annotations.
	 */
	@Pure
	public boolean isGenerateSyntheticSuppressWarnings() {
		return getGeneratorConfig().isGenerateSyntheticSuppressWarnings();
	}

	/** Set if the {@code @SuppressWarnings} is generated.
	 *
	 * @param generateAnnotations {@code true} if the compiler generates the warning supression annotations.
	 */
	public void setGenerateSyntheticSuppressWarnings(boolean generateAnnotations) {
		getGeneratorConfig().setGenerateSyntheticSuppressWarnings(generateAnnotations);
	}

	/** Replies the {@code @Generated} is generated.
	 *
	 * @return {@code true} if the compiler generates the generated annotations.
	 */
	@Pure
	public boolean isGenerateGeneratedAnnotation() {
		return getGeneratorConfig().isGenerateGeneratedAnnotation();
	}

	/** Set if the {@code @Generated} is generated.
	 *
	 * @param generateAnnotations {@code true} if the compiler generates the generated annotations.
	 */
	public void setGenerateGeneratedAnnotation(boolean generateAnnotations) {
		getGeneratorConfig().setGenerateGeneratedAnnotation(generateAnnotations);
	}

	/** Replies if the generation date is included in the {@code @Generated} annotations.
	 *
	 * @return {@code true} if the generation date is added.
	 */
	@Pure
	public boolean isIncludeDateInGeneratedAnnotation() {
		return getGeneratorConfig().isIncludeDateInGeneratedAnnotation();
	}

	/** Set if the generation date is included in the {@code @Generated} annotations.
	 *
	 * @param includeDateInGeneratedAnnotation {@code true} if the generation date is added.
	 */
	public void setIncludeDateInGeneratedAnnotation(boolean includeDateInGeneratedAnnotation) {
		getGeneratorConfig().setIncludeDateInGeneratedAnnotation(includeDateInGeneratedAnnotation);
	}

	/** Replies the comment in the {@code @Generated} annnotations.
	 *
	 * @return the comment.
	 */
	@Pure
	public String getGeneratedAnnotationComment() {
		return getGeneratorConfig().getGeneratedAnnotationComment();
	}

	/** Set the comment in the {@code @Generated} annnotations.
	 *
	 * @param comment the comment.
	 */
	public void setGeneratedAnnotationComment(String comment) {
		getGeneratorConfig().setGeneratedAnnotationComment(comment);
	}

	/** Replies if the {@code @Inline} shall be generated.
	 *
	 * @return {@code true} if annotation shall be generated.
	 */
	@Pure
	public boolean isGenerateInlineAnnotation() {
		return getGeneratorConfig2().isGenerateInlineAnnotation();
	}

	/** Set if the {@code @Inline} shall be generated.
	 *
	 * @param generateInlineAnnotation {@code true} if annotation shall be generated.
	 */
	public void setGenerateInlineAnnotation(final boolean generateInlineAnnotation) {
		getGeneratorConfig2().setGenerateInlineAnnotation(generateInlineAnnotation);
	}

	/** Replies if constant expression interpreter shall be called for generated {@code @Inline}.
	 *
	 * @return {@code true} if annotation shall be generated.
	 */
	@Pure
	public boolean isUseExpressionInterpreterForInlineAnnotation() {
		return getGeneratorConfig2().isUseExpressionInterpreterForInlineAnnotation();
	}

	/** Set if the constant expression interpreter shall be called for generated {@code @Inline}.
	 *
	 * @param generateInlineAnnotation {@code true} if annotation shall be generated.
	 */
	public void setUseExpressionInterpreterForInlineAnnotation(final boolean generateInlineAnnotation) {
		getGeneratorConfig2().setUseExpressionInterpreterForInlineAnnotation(generateInlineAnnotation);
	}

	/** Replies if the {@code @Pure} shall be generated.
	 *
	 * @return {@code true} if annotation shall be generated.
	 */
	@Pure
	public boolean isGeneratePureAnnotation() {
		return getGeneratorConfig2().isGeneratePureAnnotation();
	}

	/** Set if the {@code @Pure} shall be generated.
	 *
