/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or 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.URI; import java.net.URL; import java.net.URLClassLoader; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Pattern; import javax.inject.Provider; import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.inject.Inject; import com.google.inject.name.Named; import org.apache.log4j.Logger; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.jdt.core.compiler.CompilationProgress; import org.eclipse.jdt.core.compiler.batch.BatchCompiler; 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.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.util.internal.AlternateJdkLoader; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; 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.generator.GeneratorConfig2; import io.sarl.lang.generator.GeneratorConfigProvider2; import io.sarl.lang.generator.IGeneratorConfigProvider2; 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 */ @SuppressWarnings({"checkstyle:classfanoutcomplexity", "checkstyle:methodcount", "checkstyle:classdataabstractioncoupling"}) 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 FileFilter ACCEPT_ALL_FILTER = new FileFilter() { @Override public boolean accept(File pathname) { return true; } }; /** The provider of resource sets. */ protected Provider<ResourceSet> resourceSetProvider; private File outputPath; private File classOutputPath; private File tempPath; private boolean deleteTempPath = true; private List<File> bootClasspath; private List<File> classpath; private String encoding; private boolean writeTraceFiles = true; private boolean writeStorageFiles = true; private boolean verbose; private boolean enableJavaPostCompilation; private List<File> sourcePath; private boolean useCurrentClassLoaderAsParent; private org.eclipse.emf.common.util.URI baseUri; private FileProjectConfig projectConfig; private OutputConfiguration outputConfiguration; 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 @Named(Constants.LANGUAGE_NAME) private String languageName; 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; /** Constructor the batch compiler. */ public SarlBatchCompiler() { this.logger = Logger.getLogger(getClass().getName()); } /** Set the comparator of issues that is used for sorting the issues before they are logged. * * @param comparator the comparator; never <code>null</code>. */ 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</code>. */ public Comparator<Issue> getIssueComparator() { return this.issueComparator; } /** Replies if the Java compiler should be invoked after the SARL compiler is invoked. * * @return <code>true</code> 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</code> if the Java compiler is invoked after the SARL compiler. */ public void setJavaPostCompilationEnable(boolean enable) { this.enableJavaPostCompilation = 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 issue the issue. * @param uri URI to the problem. * @param message the formatted message. * @since 0.6 */ private void notifiesIssueMessageListeners(Issue issue, org.eclipse.emf.common.util.URI uri, String message) { for (final IssueMessageListener listener : this.messageListeners) { listener.onIssue(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 ICompilatedResourceReceiver receiver : this.resourceReceivers) { receiver.receiveCompiledResource(resource); } } /** Replies the logger. * * @return the logger. */ public Logger getLogger() { return this.logger; } /** Set the logger. * * @param logger the logger. */ public void setLogger(Logger logger) { this.logger = logger == null ? Logger.getLogger(getClass().getName()) : 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 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. * * @return <code>true</code> 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</code> 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</code> 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</code> for generation. */ public void setWriteStorageFiles(boolean writeStorageFiles) { this.writeStorageFiles = writeStorageFiles; } /** Replies if the compiler is verbose. * * @return <code>true</code> if the compiler is verbose. */ @Pure public boolean isJavaCompilerVerbose() { return this.verbose; } /** Set the underlying Java compiler verbosity. * * @param verbose <code>true</code> 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</code> 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</code> 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</code> 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</code> 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</code> 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</code> for ignoring the class generation. */ @Pure public void setClassOutputPath(File path) { this.classOutputPath = path; } /** Change the boot classpath. * * <p>The boot classpath is a list the names of folders or jar files that are separated by {@link File#pathSeparator}. * * @param bootClasspath the new boot classpath. */ public void setBootClassPath(String bootClasspath) { this.bootClasspath = new ArrayList<>(); for (final String path : Strings.split(bootClasspath, Pattern.quote(File.pathSeparator))) { this.bootClasspath.add(normalizeFile(path)); } } /** Change the boot classpath. * * @param bootClasspath the new boot classpath. */ public void setBootClassPath(Collection<File> bootClasspath) { this.bootClasspath = new ArrayList<>(bootClasspath); } /** Replies the boot classpath. *@Inject private IResourceDescription.Manager resourceDescriptionManager; * @return the boot classpath. */ @Pure public List<File> getBootClassPath() { if (this.bootClasspath == null) { return Collections.emptyList(); } return Collections.unmodifiableList(this.bootClasspath); } /** 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 String path : Strings.split(classpath, Pattern.quote(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 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</code> 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 File tmpPath = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ int i = 0; File 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 if the temp folder must be deleted at the end of the compilation. * * @return <code>true</code> if the temp folder is deleted. */ @Pure public boolean isDeleteTempDirectory() { return this.deleteTempPath; } /** Set if the temp folder must be deleted at the end of the compilation. * * @param delete <code>true</code> if the temp folder is deleted. */ public void setDeleteTempDirectory(boolean delete) { this.deleteTempPath = delete; } /** Change the file encoding. * * @param encoding the encoding, usually <code>UTF-8</code>. */ public void setFileEncoding(String encoding) { this.encoding = encoding; } /** Change the file encoding. * * @return the file encoding, or <code>null</code> 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 JavaVersion javaVersion = JavaVersion.fromQualifier(version); if (javaVersion == null) { final List<String> qualifiers = new ArrayList<>(); for (final JavaVersion vers : JavaVersion.values()) { qualifiers.add(vers.getQualifier()); } 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 the compiler generate the Xbase expressions. * * @return <code>true</code> 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</code> if the compiler generates the expressions */ public void setGenerateExpressions(boolean generateExpressions) { getGeneratorConfig().setGenerateExpressions(generateExpressions); } /** Replies the <code>@SuppressWarnings</code> is generated. * * @return <code>true</code> if the compiler generates the warning supression annotations. */ @Pure public boolean isGenerateSyntheticSuppressWarnings() { return getGeneratorConfig().isGenerateSyntheticSuppressWarnings(); } /** Set if the <code>@SuppressWarnings</code> is generated. * * @param generateAnnotations <code>true</code> if the compiler generates the warning supression annotations. */ public void setGenerateSyntheticSuppressWarnings(boolean generateAnnotations) { getGeneratorConfig().setGenerateSyntheticSuppressWarnings(generateAnnotations); } /** Replies the <code>@Generated</code> is generated. * * @return <code>true</code> if the compiler generates the generated annotations. */ @Pure public boolean isGenerateGeneratedAnnotation() { return getGeneratorConfig().isGenerateGeneratedAnnotation(); } /** Set if the <code>@Generated</code> is generated. * * @param generateAnnotations <code>true</code> 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</code> annotations. * * @return <code>true</code> if the generation date is added. */ @Pure public boolean isIncludeDateInGeneratedAnnotation() { return getGeneratorConfig().isIncludeDateInGeneratedAnnotation(); } /** Set if the generation date is included in the <code>@Generated</code> annotations. * * @param includeDateInGeneratedAnnotation <code>true</code> if the generation date is added. */ public void setIncludeDateInGeneratedAnnotation(boolean includeDateInGeneratedAnnotation) { getGeneratorConfig().setIncludeDateInGeneratedAnnotation(includeDateInGeneratedAnnotation); } /** Replies the comment in the <code>@Generated</code> annnotations. * * @return the comment. */ @Pure public String getGeneratedAnnotationComment() { return getGeneratorConfig().getGeneratedAnnotationComment(); } /** Set the comment in the <code>@Generated</code> annnotations. * * @param comment the comment. */ public void setGeneratedAnnotationComment(String comment) { getGeneratorConfig().setGeneratedAnnotationComment(comment); } /** Replies if the <code>@Inline</code> shall be generated. * * @return <code>true</code> if annotation shall be generated. */ @Pure public boolean isGenerateInlineAnnotation() { return getGeneratorConfig2().isGenerateInlineAnnotation(); } /** Set if the <code>@Inline</code> shall be generated. * * @param generateInlineAnnotation <code>true</code> 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</code>. * * @return <code>true</code> 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</code>. * * @param generateInlineAnnotation <code>true</code> if annotation shall be generated. */ public void setUseExpressionInterpreterForInlineAnnotation(final boolean generateInlineAnnotation) { getGeneratorConfig2().setUseExpressionInterpreterForInlineAnnotation(generateInlineAnnotation); } /** Change the source path. * * <p>The source path is a list the names of folders that are separated by {@link File#pathSeparator}. * * @param sourcePath the new source path. */ public void setSourcePath(String sourcePath) { this.sourcePath = new ArrayList<>(); for (final String path : Strings.split(sourcePath, Pattern.quote(File.pathSeparator))) { this.sourcePath.add(normalizeFile(path)); } } /** Change the source path. * * @param sourcePath the new source path. */ public void setSourcePath(Collection<File> sourcePath) { this.sourcePath = new ArrayList<>(sourcePath); } /** Add a folder to the source path. * * @param sourcePath the new source path. */ public void addSourcePath(String sourcePath) { if (!Strings.isEmpty(sourcePath)) { addSourcePath(normalizeFile(sourcePath)); } } /** Add a folder to the source path. * * @param sourcePath the new source path. */ public void addSourcePath(File sourcePath) { if (this.sourcePath == null) { this.sourcePath = new ArrayList<>(); } this.sourcePath.add(sourcePath); } /** Replies the source path. * * @return the source path. */ @Pure public List<File> getSourcePaths() { if (this.sourcePath == null) { return Collections.emptyList(); } return Collections.unmodifiableList(this.sourcePath); } private List<String> getSourcePathStrings() { if (this.sourcePath == null) { return Collections.emptyList(); } final List<String> list = new ArrayList<>(this.sourcePath.size()); for (final File input : this.sourcePath) { list.add(input.getAbsolutePath()); } return list; } /** Run the compilation. * * @return success status. */ @SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"}) @Inline("compile(null)") public boolean compile() { return compile(null); } /** Run the compilation. * * @param cancelIndicator monitor for cancelling the compilation. * @return success status. */ @SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity", "checkstyle:returncount"}) public boolean compile(CancelIndicator cancelIndicator) { final CancelIndicator cancel = cancelIndicator == null ? CancelIndicator.NullImpl : cancelIndicator; try { if (!checkConfiguration(cancel)) { return false; } final ResourceSet resourceSet = this.resourceSetProvider.get(); if (!configureWorkspace(resourceSet, cancel)) { return false; } if (this.logger.isDebugEnabled()) { this.logger.debug(Utils.dump(getGeneratorConfig(), false)); } if (this.generatorConfigProvider instanceof GeneratorConfigProvider) { ((GeneratorConfigProvider) this.generatorConfigProvider).install(resourceSet, getGeneratorConfig()); } if (cancel.isCanceled()) { return false; } if (this.generatorConfigProvider2 instanceof GeneratorConfigProvider2) { ((GeneratorConfigProvider2) this.generatorConfigProvider2).install(resourceSet, getGeneratorConfig2()); } if (this.logger.isDebugEnabled()) { this.logger.debug(Utils.dump(getGeneratorConfig2(), false)); } if (cancel.isCanceled()) { return false; } final File stubClassDirectory = createTempDir(BINCLASS_FOLDER_PREFIX); if (cancel.isCanceled()) { return false; } try { this.compilerPhases.setIndexing(resourceSet, true); if (cancel.isCanceled()) { return false; } // install a type provider without index lookup for the first phase installJvmTypeProvider(resourceSet, stubClassDirectory, true, cancel); if (cancel.isCanceled()) { return false; } loadSARLFiles(resourceSet, cancel); if (cancel.isCanceled()) { return false; } final File stubSourceDirectory = createStubs(resourceSet, cancel); if (cancel.isCanceled()) { return false; } if (!preCompileStubs(stubSourceDirectory, stubClassDirectory, cancel)) { if (cancel.isCanceled()) { return false; } this.logger.warn(Messages.SarlBatchCompiler_2); } if (!preCompileJava(stubSourceDirectory, stubClassDirectory, cancel)) { if (cancel.isCanceled()) { return false; } this.logger.debug(Messages.SarlBatchCompiler_3); } } finally { this.compilerPhases.setIndexing(resourceSet, false); if (cancel.isCanceled()) { return false; } } // install a fresh type provider for the second phase, so we clear all previously cached classes and misses. installJvmTypeProvider(resourceSet, stubClassDirectory, false, cancel); if (cancel.isCanceled()) { return false; } generateJvmElements(resourceSet, cancel); if (cancel.isCanceled()) { return false; } final List<Resource> validatedResources = new ArrayList<>(); final boolean hasError = validate(resourceSet, validatedResources, cancel); if (hasError || cancel.isCanceled()) { return false; } generateJavaFiles(validatedResources, cancel); if (cancel.isCanceled()) { return false; } if (isJavaPostCompilationEnable()) { postCompileJava(cancel); if (cancel.isCanceled()) { return false; } } } finally { destroyClassLoader(this.jvmTypesClassLoader); destroyClassLoader(this.annotationProcessingClassLoader); if (isDeleteTempDirectory()) { for (final File file : this.tempFolders) { cleanFolder(file, ACCEPT_ALL_FILTER, true, true); } } } return true; } /** Create a message for the issue. * * @param issue the issue. * @return the message. */ protected String createIssueMessage(Issue issue) { final IssueMessageFormatter formatter = getIssueMessageFormatter(); final org.eclipse.emf.common.util.URI uriToProblem = issue.getUriToProblem(); if (formatter != null) { return formatter.format(issue, uriToProblem); } if (uriToProblem != null) { final org.eclipse.emf.common.util.URI resourceUri = uriToProblem.trimFragment(); return MessageFormat.format(Messages.SarlBatchCompiler_4, issue.getSeverity(), resourceUri.lastSegment(), resourceUri.isFile() ? resourceUri.toFileString() : "", //$NON-NLS-1$ issue.getLineNumber(), issue.getMessage()); } return MessageFormat.format(Messages.SarlBatchCompiler_5, issue.getSeverity(), issue.getLineNumber(), issue.getMessage()); } /** Output the given issues. * * @param issues the issues to report. */ protected void reportIssues(Iterable<Issue> issues) { for (final Issue issue : issues) { final String issueMessage = createIssueMessage(issue); switch (issue.getSeverity()) { case ERROR: this.logger.error(issueMessage); break; case WARNING: this.logger.warn(issueMessage); break; case INFO: this.logger.info(issueMessage); break; case IGNORE: default: break; } notifiesIssueMessageListeners(issue, issue.getUriToProblem(), issueMessage); } } /** Generate the Java files from the SARL scripts. * * @param validatedResources the validatedResources for which the Java files could be generated. * @param cancelIndicator monitor for cancelling the compilation. */ protected void generateJavaFiles(Iterable<Resource> validatedResources, CancelIndicator cancelIndicator) { assert cancelIndicator != null; this.logger.info(MessageFormat.format(Messages.SarlBatchCompiler_28, getOutputPath())); final JavaIoFileSystemAccess javaIoFileSystemAccess = this.javaIoFileSystemAccessProvider.get(); javaIoFileSystemAccess.setOutputPath(getOutputPath().getAbsolutePath()); javaIoFileSystemAccess.setWriteTrace(isWriteTraceFiles()); if (cancelIndicator.isCanceled()) { return; } final GeneratorContext context = new GeneratorContext(); context.setCancelIndicator(cancelIndicator); for (final Resource resource : validatedResources) { if (cancelIndicator.isCanceled()) { return; } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_23, resource.getURI().lastSegment())); if (isWriteStorageFiles() && resource instanceof StorageAwareResource) { final StorageAwareResource storageAwareResource = (StorageAwareResource) resource; storageAwareResource.getResourceStorageFacade().saveResource(storageAwareResource, javaIoFileSystemAccess); } if (cancelIndicator.isCanceled()) { return; } this.generator.generate(resource, javaIoFileSystemAccess, context); notifiesCompiledResourceReceiver(resource); } } /** Generate the JVM model elements. * * @param cancelIndicator monitor for cancelling the compilation. * @param resourceSet the container of the scripts. */ protected void generateJvmElements(ResourceSet resourceSet, CancelIndicator cancelIndicator) { assert cancelIndicator != null; this.logger.info(Messages.SarlBatchCompiler_21); final List<Resource> resources = new LinkedList<>(resourceSet.getResources()); for (final Resource resource : resources) { if (cancelIndicator.isCanceled()) { return; } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_26, resource.getURI().lastSegment())); resource.getContents(); } for (final Resource resource : resources) { if (cancelIndicator.isCanceled()) { return; } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_27, resource.getURI().lastSegment())); EcoreUtil2.resolveLazyCrossReferences(resource, CancelIndicator.NullImpl); } } /** Generate the JVM model elements, and validate generated elements. * * @param resourceSet the container of the scripts. * @param validResources will be filled by this function with the collection of resources that was successfully validated. * @param cancelIndicator monitor for cancelling the compilation. * @return <code>true</code> if an error exists in the issues. Replies <code>false</code> if the activity is cancelled. */ @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"}) protected boolean validate(ResourceSet resourceSet, Collection<Resource> validResources, CancelIndicator cancelIndicator) { assert cancelIndicator != null; boolean hasError = false; final List<Resource> resources = new LinkedList<>(resourceSet.getResources()); for (final Resource resource : resources) { if (cancelIndicator.isCanceled()) { return false; } if (isSourceFile(resource)) { this.logger.info(MessageFormat.format(Messages.SarlBatchCompiler_22, resource.getURI().lastSegment())); final IResourceServiceProvider resourceServiceProvider = IResourceServiceProvider.Registry.INSTANCE .getResourceServiceProvider(resource.getURI()); if (resourceServiceProvider != null) { final IResourceValidator resourceValidator = resourceServiceProvider.getResourceValidator(); final List<Issue> result = resourceValidator.validate(resource, CheckMode.ALL, null); if (cancelIndicator.isCanceled()) { return false; } final SortedSet<Issue> issues = new TreeSet<>(getIssueComparator()); boolean hasValidationError = false; for (final Issue issue : result) { if (cancelIndicator.isCanceled()) { return false; } if (issue.isSyntaxError() || issue.getSeverity() == Severity.ERROR) { hasValidationError = true; } issues.add(issue); } hasError |= hasValidationError; if (!hasValidationError) { if (!issues.isEmpty()) { reportIssues(issues); } validResources.add(resource); } else { reportIssues(issues); } } } } return hasError; } /** Replies if the given resource is a script. * * @param resource the resource to test. * @return <code>true</code> if the given resource is a script. */ @SuppressWarnings("static-method") protected boolean isSourceFile(Resource resource) { if (resource instanceof BatchLinkableResource) { return !((BatchLinkableResource) resource).isLoadedFromStorage(); } return false; } /** Compile the stub files before the compilation of the project's files. * * @param sourceDirectory the source directory where stubs are stored. * @param classDirectory the output directory, where stub binary files should be generated. * @param cancelIndicator monitor for cancelling the compilation. * @return the success status. Replies <code>false</code> if the activity is cancelled. */ protected boolean preCompileStubs(File sourceDirectory, File classDirectory, CancelIndicator cancelIndicator) { assert cancelIndicator != null; return runJavaCompiler(classDirectory, Collections.singletonList(sourceDirectory), getClassPath(), false, cancelIndicator); } /** Compile the java files before the compilation of the project's files. * * @param sourceDirectory the source directory where java files are stored. * @param classDirectory the output directory, where binary files should be generated. * @param cancelIndicator monitor for cancelling the compilation. * @return the success status. Replies <code>false</code> if the activity is cancelled. */ protected boolean preCompileJava(File sourceDirectory, File classDirectory, CancelIndicator cancelIndicator) { assert cancelIndicator != null; return runJavaCompiler(classDirectory, getSourcePaths(), Iterables.concat(Collections.singleton(sourceDirectory), getClassPath()), false, cancelIndicator); } /** Compile the java files after the compilation of the project's files. * * @param cancelIndicator monitor for cancelling the compilation. * @return the success status. Replies <code>false</code> if the activity is cancelled. */ protected boolean postCompileJava(CancelIndicator cancelIndicator) { assert cancelIndicator != null; final File classOutputPath = getClassOutputPath(); if (classOutputPath == null) { this.logger.info(Messages.SarlBatchCompiler_24); return true; } this.logger.info(Messages.SarlBatchCompiler_25); final Iterable<File> sources = Iterables.concat(getSourcePaths(), Collections.singleton(getOutputPath())); if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_29, toPathString(sources))); } final List<File> classpath = getClassPath(); if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_30, toPathString(classpath))); } return runJavaCompiler(classOutputPath, sources, classpath, true, cancelIndicator); } private static String toPathString(Iterable<File> files) { final StringBuilder result = new StringBuilder(); for (final File file : files) { if (result.length() > 0) { result.append(File.pathSeparator); } result.append(file.toString()); } return result.toString(); } /** Run the Java compiler. * * @param classDirectory the output directory. * @param sourcePathDirectories the source directories. * @param classPathEntries classpath entries. * @param enableCompilerOutput indicates if the Java compiler output is displayed. * @param cancelIndicator monitor for cancelling the compilation. * @return the success status. Replies <code>false</code> if the activity is cancelled. */ @SuppressWarnings({ "checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity", "resource" }) protected boolean runJavaCompiler(File classDirectory, Iterable<File> sourcePathDirectories, Iterable<File> classPathEntries, boolean enableCompilerOutput, CancelIndicator cancelIndicator) { assert cancelIndicator != null; final List<String> commandLine = Lists.newArrayList(); commandLine.add("-nowarn"); //$NON-NLS-1$ if (isJavaCompilerVerbose()) { commandLine.add("-verbose"); //$NON-NLS-1$ } if (cancelIndicator.isCanceled()) { return false; } final List<File> bootClassPathEntries = getBootClassPath(); if (cancelIndicator.isCanceled()) { return false; } if (!bootClassPathEntries.isEmpty()) { final StringBuilder cmd = new StringBuilder("-bootclasspath \""); //$NON-NLS-1$ boolean first = true; for (final File entry : bootClassPathEntries) { if (cancelIndicator.isCanceled()) { return false; } if (first) { first = false; } else { cmd.append(File.pathSeparator); } cmd.append(entry.getAbsolutePath()); } cmd.append("\""); //$NON-NLS-1$ commandLine.add(cmd.toString()); } final Iterator<File> classPathIterator = classPathEntries.iterator(); if (classPathIterator.hasNext()) { final StringBuilder cmd = new StringBuilder("-cp \""); //$NON-NLS-1$ boolean first = true; while (classPathIterator.hasNext()) { if (cancelIndicator.isCanceled()) { return false; } if (first) { first = false; } else { cmd.append(File.pathSeparator); } cmd.append(classPathIterator.next().getAbsolutePath()); } cmd.append("\""); //$NON-NLS-1$ commandLine.add(cmd.toString()); } if (cancelIndicator.isCanceled()) { return false; } commandLine.add("-d \"" + classDirectory.getAbsolutePath() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ commandLine.add("-" + getJavaSourceVersion()); //$NON-NLS-1$ commandLine.add("-proceedOnError"); //$NON-NLS-1$ if (this.encodingProvider.getDefaultEncoding() != null) { commandLine.add("-encoding \"" + this.encodingProvider.getDefaultEncoding() + "\""); //$NON-NLS-1$ //$NON-NLS-2$ } if (cancelIndicator.isCanceled()) { return false; } final StringBuilder cmd = new StringBuilder(); boolean first = true; for (final File sourceFolder : sourcePathDirectories) { if (cancelIndicator.isCanceled()) { return false; } if (first) { first = false; } else { cmd.append(" "); //$NON-NLS-1$ } cmd.append("\""); //$NON-NLS-1$ cmd.append(sourceFolder.getAbsolutePath().replaceAll(Pattern.quote("\""), "\\\"")); //$NON-NLS-1$//$NON-NLS-2$ cmd.append("\""); //$NON-NLS-1$ } commandLine.add(cmd.toString()); if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_6, Strings.concat(" ", commandLine))); //$NON-NLS-1$ } if (cancelIndicator.isCanceled()) { return false; } final PrintWriter outWriter = getStubCompilerOutputWriter(); final PrintWriter errWriter; if (enableCompilerOutput) { errWriter = getErrorCompilerOutputWriter(); } else { errWriter = getStubCompilerOutputWriter(); } if (cancelIndicator.isCanceled()) { return false; } return BatchCompiler.compile(Strings.concat(" ", commandLine), outWriter, errWriter, //$NON-NLS-1$ new CancelIndicatorWrapper(cancelIndicator)); } private PrintWriter getStubCompilerOutputWriter() { final Writer debugWriter = new Writer() { @SuppressWarnings("synthetic-access") @Override public void write(char[] data, int offset, int count) throws IOException { final String message = String.copyValueOf(data, offset, count); SarlBatchCompiler.this.logger.debug(message); } @Override public void flush() throws IOException { // } @Override public void close() throws IOException { // } }; return new PrintWriter(debugWriter); } private PrintWriter getErrorCompilerOutputWriter() { final Writer debugWriter = new Writer() { @SuppressWarnings("synthetic-access") @Override public void write(char[] data, int offset, int count) throws IOException { final String message = String.copyValueOf(data, offset, count); SarlBatchCompiler.this.logger.error(message); } @Override public void flush() throws IOException { // } @Override public void close() throws IOException { // } }; return new PrintWriter(debugWriter); } /** Create the stubs. * * @param resourceSet the input resource set. * @param cancelIndicator monitor for cancelling the compilation. * @return the folder in which the stubs are located. Replies <code>null</code> if the activity is cancelled. */ protected File createStubs(ResourceSet resourceSet, CancelIndicator cancelIndicator) { final File outputDirectory = createTempDir(STUB_FOLDER_PREFIX); if (cancelIndicator.isCanceled()) { return null; } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_19, outputDirectory)); final JavaIoFileSystemAccess fileSystemAccess = this.javaIoFileSystemAccessProvider.get(); if (cancelIndicator.isCanceled()) { return null; } fileSystemAccess.setOutputPath(outputDirectory.toString()); final List<Resource> resources = new ArrayList<>(resourceSet.getResources()); for (final Resource resource : resources) { if (cancelIndicator.isCanceled()) { return null; } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_20, resource.getURI())); final IResourceDescription description = this.resourceDescriptionManager.getResourceDescription(resource); this.stubGenerator.doGenerateStubs(fileSystemAccess, description); } return outputDirectory; } /** Load the SARL files in the given resource set. * * @param cancelIndicator monitor for cancelling the compilation. * @param resourceSet the resource set to load from. */ protected void loadSARLFiles(ResourceSet resourceSet, CancelIndicator cancelIndicator) { assert cancelIndicator != null; this.encodingProvider.setDefaultEncoding(getFileEncoding()); final NameBasedFilter nameBasedFilter = new NameBasedFilter(); nameBasedFilter.setExtension(this.fileExtensionProvider.getPrimaryFileExtension()); final PathTraverser pathTraverser = new PathTraverser(); final List<String> sourcePathDirectories = getSourcePathStrings(); if (cancelIndicator.isCanceled()) { return; } final Multimap<String, org.eclipse.emf.common.util.URI> pathes = pathTraverser.resolvePathes(sourcePathDirectories, (input) -> nameBasedFilter.matches(input)); if (cancelIndicator.isCanceled()) { return; } for (final String source : pathes.keySet()) { for (final org.eclipse.emf.common.util.URI uri : pathes.get(source)) { if (cancelIndicator.isCanceled()) { return; } if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_7, uri)); } resourceSet.getResource(uri, true); } } } /** Create a temporary subdirectory inside the root temp directory. * * @param namePrefix the prefix for the folder name. * @return the temp directory. * @see #getTempDirectory() */ protected File createTempDir(String namePrefix) { final File tempDir = new File(getTempDirectory(), namePrefix); cleanFolder(tempDir, ACCEPT_ALL_FILTER, true, true); if (!tempDir.mkdirs()) { throw new RuntimeException(MessageFormat.format(Messages.SarlBatchCompiler_8, tempDir.getAbsolutePath())); } this.tempFolders.add(tempDir); return tempDir; } /** Clean the folders. * * @param parentFolder the parent folder. * @param filter the file filter for the file to remove.. * @param continueOnError indicates if the cleaning should continue on error. * @param deleteParentFolder indicates if the parent folder should be removed. * @return the success status. */ protected boolean cleanFolder(File parentFolder, FileFilter filter, boolean continueOnError, boolean deleteParentFolder) { try { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_9, parentFolder.toString())); return Files.cleanFolder(parentFolder, null, continueOnError, deleteParentFolder); } catch (FileNotFoundException e) { return true; } } /** Check the compiler configuration; and logs errors. * * @param cancelIndicator monitor for cancelling the compilation. * @return success status. Replies <code>false</code> if the operation is cancelled. */ protected boolean checkConfiguration(CancelIndicator cancelIndicator) { assert cancelIndicator != null; final File output = getOutputPath(); this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_35, output)); if (output == null) { this.logger.error(Messages.SarlBatchCompiler_36); return false; } for (final File sourcePath : getSourcePaths()) { if (cancelIndicator.isCanceled()) { return false; } try { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_37, sourcePath)); if (isContainedIn(output.getCanonicalFile(), sourcePath.getCanonicalFile())) { this.logger.error(MessageFormat.format( Messages.SarlBatchCompiler_10, output, sourcePath)); return false; } } catch (IOException e) { this.logger.error(Messages.SarlBatchCompiler_11, e); } } return true; } private static boolean isContainedIn(File child, File possibleParent) { File parent = child; while (parent != null) { if (parent.equals(possibleParent)) { return true; } parent = parent.getParentFile(); } return false; } private static LinkedList<String> splitFile(File file, CancelIndicator cancelIndicator) { assert cancelIndicator != null; final LinkedList<String> elements = new LinkedList<>(); File current = file; do { if (cancelIndicator.isCanceled()) { return null; } elements.addFirst(current.getName()); current = current.getParentFile(); } while (current != null); return elements; } @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"}) private File determineCommonRoot(File outputFile, List<File> sourceFileList, CancelIndicator cancelIndicator) { assert cancelIndicator != null; if (this.baseUri != null) { if (this.baseUri.isFile()) { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_32, this.baseUri)); return new File(this.baseUri.toFileString()); } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_33, this.baseUri)); } LinkedList<String> longuestPrefix = null; for (final File file : Iterables.concat(sourceFileList, Collections.singleton(outputFile))) { if (cancelIndicator.isCanceled()) { return null; } final LinkedList<String> components = splitFile(file, cancelIndicator); if (longuestPrefix == null) { longuestPrefix = components; } else { int i = 0; while (i < longuestPrefix.size() && i < components.size() && Strings.equal(longuestPrefix.get(i), components.get(i))) { if (cancelIndicator.isCanceled()) { return null; } ++i; } while (i < longuestPrefix.size()) { if (cancelIndicator.isCanceled()) { return null; } longuestPrefix.removeLast(); } if (longuestPrefix.isEmpty()) { return null; } } } if (longuestPrefix == null || cancelIndicator.isCanceled()) { return null; } File prefix = null; for (final String component : longuestPrefix) { if (cancelIndicator.isCanceled()) { return null; } if (prefix == null) { prefix = new File(component); } else { prefix = new File(prefix, component); } } return prefix; } @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"}) private boolean configureWorkspace(ResourceSet resourceSet, CancelIndicator cancelIndicator) { assert cancelIndicator != null; final List<File> sourceFolders = getSourcePaths(); final File outputFile = getOutputPath(); if (sourceFolders == null || sourceFolders.isEmpty() || outputFile == null || cancelIndicator.isCanceled()) { return false; } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_31, this.baseUri)); final File commonRoot = determineCommonRoot(outputFile, sourceFolders, cancelIndicator); if (cancelIndicator.isCanceled()) { return false; } this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_34, commonRoot)); if (commonRoot == null) { this.logger.error(Messages.SarlBatchCompiler_12); for (final File sourceFile : sourceFolders) { this.logger.error(MessageFormat.format(Messages.SarlBatchCompiler_13, sourceFile)); } this.logger.error(MessageFormat.format(Messages.SarlBatchCompiler_14, outputFile)); return false; } this.projectConfig = new FileProjectConfig(commonRoot, commonRoot.getName()); if (cancelIndicator.isCanceled()) { return false; } final URI commonURI = commonRoot.toURI(); final URI relativizedTarget = commonURI.relativize(outputFile.toURI()); if (cancelIndicator.isCanceled()) { return false; } if (relativizedTarget.isAbsolute()) { this.logger.error(MessageFormat.format(Messages.SarlBatchCompiler_15, outputFile, commonRoot)); return false; } final CharMatcher slash = CharMatcher.is('/'); final String relativeTargetFolder = slash.trimTrailingFrom(relativizedTarget.getPath()); this.outputConfiguration = this.outputConfigurationProvider.getOutputConfigurations().iterator().next(); this.outputConfiguration.setOutputDirectory(relativeTargetFolder); for (final File source : sourceFolders) { if (cancelIndicator.isCanceled()) { return false; } final URI relSource = commonURI.relativize(source.toURI()); if (relSource.isAbsolute()) { this.logger.error(MessageFormat.format(Messages.SarlBatchCompiler_16, source, commonRoot)); return false; } this.projectConfig.addSourceFolder(slash.trimTrailingFrom(relSource.getPath())); } final Map<String, Set<OutputConfiguration>> outputConfigurations = new HashMap<>(); outputConfigurations.put(this.languageName, Collections.singleton(this.outputConfiguration)); ProjectConfigAdapter.install(resourceSet, this.projectConfig); resourceSet.eAdapters().add(new OutputConfigurationAdapter(outputConfigurations)); if (cancelIndicator.isCanceled()) { return false; } return true; } /** * Installs the JvmTypeProvider optionally including index access into the {@link ResourceSet}. The lookup classpath * is enhanced with the given tmp directory. * * @param resourceSet the resource set that will be compiled. * @param temporaryClassDirectory the directory where the class files of the stubs are generated. * @param skipIndexLookup indicates if the index should be used for looking up types. * @param cancelIndicator monitor for cancelling the compilation. */ @SuppressWarnings({ "resource", "unused", "checkstyle:npathcomplexity" }) private void installJvmTypeProvider(ResourceSet resourceSet, File temporaryClassDirectory, boolean skipIndexLookup, CancelIndicator cancelIndicator) { assert cancelIndicator != null; final Iterable<File> classpath; if (temporaryClassDirectory != null) { classpath = Iterables.concat(getClassPath(), getSourcePaths(), Collections.singletonList(temporaryClassDirectory)); } else { classpath = Iterables.concat(getClassPath(), getSourcePaths()); } if (this.logger.isDebugEnabled()) { this.logger.debug(MessageFormat.format(Messages.SarlBatchCompiler_17, classpath)); } if (cancelIndicator.isCanceled()) { return; } final ClassLoader parentClassLoader; if (isUseCurrentClassLoaderAsParent()) { parentClassLoader = getClass().getClassLoader(); } else if (getBootClassPath().isEmpty()) { parentClassLoader = getCurrentClassLoader(); } else { parentClassLoader = new AlternateJdkLoader(getBootClassPath()); } if (cancelIndicator.isCanceled()) { return; } this.jvmTypesClassLoader = createClassLoader(classpath, parentClassLoader); if (cancelIndicator.isCanceled()) { return; } new ClasspathTypeProvider(this.jvmTypesClassLoader, resourceSet, skipIndexLookup ? null : this.indexedJvmTypeAccess, null); if (cancelIndicator.isCanceled()) { return; } ((XtextResourceSet) resourceSet).setClasspathURIContext(this.jvmTypesClassLoader); if (cancelIndicator.isCanceled()) { return; } // for annotation processing we need to have the compiler's classpath as a parent. this.annotationProcessingClassLoader = createClassLoader(classpath, getCurrentClassLoader()); if (cancelIndicator.isCanceled()) { return; } resourceSet.eAdapters().add(new ProcessorInstanceForJvmTypeProvider.ProcessorClassloaderAdapter(this.annotationProcessingClassLoader)); } /** Create the project class loader. * * @param jarsAndFolders the project class path. * @param parentClassLoader the parent class loader. * @return the class loader for the project. */ @SuppressWarnings("static-method") protected ClassLoader createClassLoader(Iterable<File> jarsAndFolders, ClassLoader parentClassLoader) { return new URLClassLoader(Iterables.toArray(Iterables.transform(jarsAndFolders, (from) -> { try { final URL url = from.toURI().toURL(); assert url != null; return url; } catch (Exception e) { throw new RuntimeException(e); } }), URL.class), parentClassLoader); } /** Null-safe destruction of the given class loaders. * * @param classLoader the class loader to destroy. */ protected void destroyClassLoader(ClassLoader classLoader) { if (classLoader instanceof Closeable) { try { ((Closeable) classLoader).close(); } catch (Exception e) { this.logger.warn(Messages.SarlBatchCompiler_18, e); } } } /** Change the severity level of a warning. * * @param warningId the identifier of the warning. If {@code null} or empty, this function does nothing. * @param severity the new severity. If {@code null} this function does nothing. * @since 0.5 */ public void setWarningSeverity(String warningId, Severity severity) { if (!Strings.isEmpty(warningId) && severity != null) { this.issueSeverityProvider.setSeverity(warningId, severity); } } /** Change the severity level of for all the warnings. * * @param severity the new severity. If {@code null} this function does nothing. * @since 0.5 */ public void setAllWarningSeverities(Severity severity) { if (severity != null) { this.issueSeverityProvider.setAllSeverities(severity); } } /** Formatter for the issue messages. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ @FunctionalInterface public interface IssueMessageFormatter { /** Format the message for the given issue. * * @param issue the issue. * @param uri URI to the problem. * @return the message. */ String format(Issue issue, org.eclipse.emf.common.util.URI uri); } /** Listener for the issue messages. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ @FunctionalInterface public interface IssueMessageListener { /** Replies the message for the given issue. * * @param issue the issue. * @param uri URI to the problem. * @param message the formatted message. */ void onIssue(Issue issue, org.eclipse.emf.common.util.URI uri, String message); } /** Comparator of issues. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public static class DefaultIssueComparator implements Comparator<Issue> { private static int compareSafe(Integer n1, Integer n2) { if (n1 == null) { return n2 == null ? 0 : -1; } if (n2 == null) { return 1; } return Integer.compare(n1.intValue(), n2.intValue()); } private static int compareSafe(Severity s1, Severity s2) { if (s1 == null) { return s2 == null ? 0 : -1; } if (s2 == null) { return 1; } return s1.compareTo(s2); } private static int compareSafe(String s1, String s2) { if (s1 == null) { return s2 == null ? 0 : -1; } if (s2 == null) { return 1; } return s1.compareTo(s2); } @Override @SuppressWarnings("checkstyle:npathcomplexity") public int compare(Issue issue1, Issue issue2) { if (issue1 == issue2) { return 0; } if (issue1 == null) { return -1; } if (issue2 == null) { return 1; } final org.eclipse.emf.common.util.URI u1 = issue1.getUriToProblem(); final org.eclipse.emf.common.util.URI u2 = issue2.getUriToProblem(); int cmp = 0; if (u1 != u2 && u1 != null && u2 != null) { cmp = u1.toFileString().compareTo(u2.toFileString()); } if (cmp != 0) { return cmp; } cmp = compareSafe(issue1.getLineNumber(), issue2.getLineNumber()); if (cmp != 0) { return cmp; } cmp = compareSafe(issue1.getColumn(), issue2.getColumn()); if (cmp != 0) { return cmp; } cmp = compareSafe(issue1.getSeverity(), issue2.getSeverity()); if (cmp != 0) { return cmp; } cmp = compareSafe(issue1.getMessage(), issue2.getMessage()); if (cmp != 0) { return cmp; } return Integer.compare(System.identityHashCode(issue1), System.identityHashCode(issue2)); } } /** Wrap a cancel indicator into a compilation progress. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class CancelIndicatorWrapper extends CompilationProgress { private final CancelIndicator cancelIndicator; /** * @param cancelIndicator the wrapped cancel indicator. */ CancelIndicatorWrapper(CancelIndicator cancelIndicator) { assert cancelIndicator != null; this.cancelIndicator = cancelIndicator; } @Override public void begin(int remainingWork) { // } @Override public void done() { // } @Override public boolean isCanceled() { return this.cancelIndicator.isCanceled(); } @Override public void setTaskName(String name) { // } @Override public void worked(int workIncrement, int remainingWork) { // } } }