/*
* $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.m2e;
import java.io.File;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import com.google.common.base.Strings;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.internal.M2EUtils;
import org.eclipse.m2e.core.lifecyclemapping.model.IPluginExecutionMetadata;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.MavenProjectUtils;
import org.eclipse.m2e.core.project.configurator.AbstractBuildParticipant;
import org.eclipse.m2e.core.project.configurator.AbstractBuildParticipant2;
import org.eclipse.m2e.core.project.configurator.AbstractProjectConfigurator;
import org.eclipse.m2e.core.project.configurator.ProjectConfigurationRequest;
import org.eclipse.m2e.jdt.IClasspathDescriptor;
import org.eclipse.m2e.jdt.IClasspathEntryDescriptor;
import org.eclipse.m2e.jdt.IJavaProjectConfigurator;
import org.eclipse.m2e.jdt.internal.ClasspathDescriptor;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
import org.sonatype.plexus.build.incremental.BuildContext;
import io.sarl.eclipse.SARLEclipseConfig;
import io.sarl.eclipse.buildpath.SARLClasspathContainerInitializer;
import io.sarl.eclipse.util.Utilities;
import io.sarl.lang.SARLConfig;
import io.sarl.lang.SARLVersion;
import io.sarl.lang.ui.preferences.SARLPreferences;
/** Project configuration for the M2E.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public class SARLProjectConfigurator extends AbstractProjectConfigurator implements IJavaProjectConfigurator {
private static final String SARL_LANG_BUNDLE_NAME = "io.sarl.lang.core"; //$NON-NLS-1$
private static final String SARL_GROUP_ID = "io.sarl.lang"; //$NON-NLS-1$
private static final String SARL_ARTIFACT_ID = "io.sarl.lang.core"; //$NON-NLS-1$
private static final String SARL_MAVENLIB_GROUP_ID = "io.sarl.maven"; //$NON-NLS-1$
private static final String SARL_MAVENLIB_ARTIFACT_ID = "io.sarl.maven.sdk"; //$NON-NLS-1$
private static final String SARL_PLUGIN_GROUP_ID = "io.sarl.maven"; //$NON-NLS-1$
private static final String SARL_PLUGIN_ARTIFACT_ID = "sarl-maven-plugin"; //$NON-NLS-1$
/** Invoked to add the preferences dedicated to SARL, JRE, etc.
*
* @param facade - the Maven face.
* @param config - the configuration.
* @param monitor - the monitor.
* @throws CoreException if cannot add the source folders.
*/
@SuppressWarnings("static-method")
protected void addPreferences(
IMavenProjectFacade facade, SARLConfiguration config,
IProgressMonitor monitor) throws CoreException {
final IPath outputPath = makeProjectRelativePath(facade, config.getOutput());
// Set the SARL preferences
SARLPreferences.setSpecificSARLConfigurationFor(
facade.getProject(), outputPath);
}
private static IPath makeFullPath(IMavenProjectFacade facade, File file) {
assert file != null;
final IProject project = facade.getProject();
final IPath path;
if (!file.isAbsolute()) {
path = Path.fromOSString(file.getPath());
} else {
path = MavenProjectUtils.getProjectRelativePath(project, file.getAbsolutePath());
}
return project.getFullPath().append(path);
}
private static IPath makeProjectRelativePath(IMavenProjectFacade facade, File file) {
assert file != null;
final IProject project = facade.getProject();
if (!file.isAbsolute()) {
return Path.fromOSString(file.getPath());
}
return MavenProjectUtils.getProjectRelativePath(project, file.getAbsolutePath());
}
private static IFolder ensureFolderExists(IMavenProjectFacade facade, IPath path, boolean derived,
IProgressMonitor monitor) throws CoreException {
final IFolder folder = facade.getProject().getFolder(path.makeRelativeTo(facade.getProject().getFullPath()));
assert folder != null;
if (!folder.exists()) {
M2EUtils.createFolder(folder, derived || folder.isDerived(), monitor);
}
return folder;
}
/** Invoked to add the source folders.
*
* @param facade - the facade of the Maven project.
* @param config - the configuration.
* @param classpath - the project classpath.
* @param monitor - the monitor.
* @throws CoreException if cannot add the source folders.
*/
@SuppressWarnings("checkstyle:magicnumber")
protected void addSourceFolders(
IMavenProjectFacade facade, SARLConfiguration config,
IClasspathDescriptor classpath, IProgressMonitor monitor)
throws CoreException {
assertHasNature(facade.getProject(), SARLEclipseConfig.NATURE_ID);
assertHasNature(facade.getProject(), SARLEclipseConfig.XTEXT_NATURE_ID);
assertHasNature(facade.getProject(), JavaCore.NATURE_ID);
final String encoding = config.getEncoding();
final SubMonitor subMonitor = SubMonitor.convert(monitor, 4);
// Add the source folders
final IPath inputPath = makeFullPath(facade, config.getInput());
final IFolder inputFolder = ensureFolderExists(facade, inputPath, false, subMonitor);
if (encoding != null && inputFolder != null && inputFolder.exists()) {
inputFolder.setDefaultCharset(encoding, monitor);
}
classpath.addSourceEntry(
inputPath,
facade.getOutputLocation(),
true);
subMonitor.worked(1);
final IPath outputPath = makeFullPath(facade, config.getOutput());
final IFolder outputFolder = ensureFolderExists(facade, outputPath, true, subMonitor);
if (encoding != null && outputFolder != null && outputFolder.exists()) {
outputFolder.setDefaultCharset(encoding, monitor);
}
IClasspathEntryDescriptor descriptor = classpath.addSourceEntry(
outputPath,
facade.getOutputLocation(),
true);
descriptor.setClasspathAttribute(IClasspathAttribute.IGNORE_OPTIONAL_PROBLEMS, Boolean.TRUE.toString());
subMonitor.worked(1);
// Add the test folders
final IPath testInputPath = makeFullPath(facade, config.getTestInput());
final IFolder testInputFolder = ensureFolderExists(facade, testInputPath, false, subMonitor);
if (encoding != null && testInputFolder != null && testInputFolder.exists()) {
testInputFolder.setDefaultCharset(encoding, monitor);
}
classpath.addSourceEntry(
testInputPath,
facade.getOutputLocation(),
true);
subMonitor.worked(1);
final IPath testOutputPath = makeFullPath(facade, config.getTestOutput());
final IFolder testOutputFolder = ensureFolderExists(facade, testOutputPath, true, subMonitor);
if (encoding != null && testOutputFolder != null && testOutputFolder.exists()) {
testOutputFolder.setDefaultCharset(encoding, monitor);
}
descriptor = classpath.addSourceEntry(
testOutputPath,
facade.getOutputLocation(),
true);
descriptor.setClasspathAttribute(IClasspathAttribute.IGNORE_OPTIONAL_PROBLEMS, Boolean.TRUE.toString());
subMonitor.done();
}
/** Replies the configuration value.
*
* @param <T> - the expected type.
* @param project - the project.
* @param parameter - the parameter name.
* @param asType - the expected type.
* @param mojoExecution - the mojo execution.
* @param monitor - the monitor.
* @param defaultValue - the default value.
* @return the value of the parameter.
* @throws CoreException if cannot read the value.
*/
protected <T> T getParameterValue(MavenProject project, String parameter, Class<T> asType,
MojoExecution mojoExecution, IProgressMonitor monitor, T defaultValue) throws CoreException {
T value = getParameterValue(project, parameter, asType, mojoExecution, monitor);
if (value == null) {
value = defaultValue;
}
return value;
}
/** Read the SARL configuration.
*
* @param request - the configuration request.
* @param monitor - the monitor.
* @return the SARL configuration.
* @throws CoreException if something wrong appends.
*/
protected SARLConfiguration readConfiguration(ProjectConfigurationRequest request,
IProgressMonitor monitor) throws CoreException {
SARLConfiguration initConfig = null;
SARLConfiguration compileConfig = null;
final List<MojoExecution> mojos = getMojoExecutions(request, monitor);
for (final MojoExecution mojo : mojos) {
final String goal = mojo.getGoal();
switch (goal) {
case "initialize": //$NON-NLS-1$
initConfig = readInitializeConfiguration(request, mojo, monitor);
break;
case "compile": //$NON-NLS-1$
compileConfig = readCompileConfiguration(request, mojo, monitor);
break;
default:
}
}
if (compileConfig != null && initConfig != null) {
compileConfig.setFrom(initConfig);
}
return compileConfig;
}
/** Read the configuration for the Initialize mojo.
*
* @param request - the request.
* @param mojo - the mojo execution.
* @param monitor - the monitor.
* @return the configuration.
* @throws CoreException error in the eCore configuration.
*/
private SARLConfiguration readInitializeConfiguration(
ProjectConfigurationRequest request, MojoExecution mojo, IProgressMonitor monitor)
throws CoreException {
final SARLConfiguration config = new SARLConfiguration();
final MavenProject project = request.getMavenProject();
final File input = getParameterValue(project, "input", File.class, mojo, monitor, //$NON-NLS-1$
new File(SARLConfig.FOLDER_SOURCE_SARL));
final File output = getParameterValue(project, "output", File.class, mojo, monitor, //$NON-NLS-1$
new File(SARLConfig.FOLDER_SOURCE_GENERATED));
final File testInput = getParameterValue(project, "testInput", File.class, mojo, monitor, //$NON-NLS-1$
new File(SARLConfig.FOLDER_TEST_SOURCE_SARL));
final File testOutput = getParameterValue(project, "testOutput", File.class, mojo, monitor, //$NON-NLS-1$
new File(SARLConfig.FOLDER_TEST_SOURCE_GENERATED));
config.setInput(input);
config.setOutput(output);
config.setTestInput(testInput);
config.setTestOutput(testOutput);
return config;
}
/** Read the configuration for the Compilation mojo.
*
* @param request - the request.
* @param mojo - the mojo execution.
* @param monitor - the monitor.
* @return the configuration.
* @throws CoreException error in the eCore configuration.
*/
private SARLConfiguration readCompileConfiguration(
ProjectConfigurationRequest request, MojoExecution mojo, IProgressMonitor monitor)
throws CoreException {
final SARLConfiguration config = new SARLConfiguration();
final MavenProject project = request.getMavenProject();
final File input = getParameterValue(project, "input", File.class, mojo, monitor); //$NON-NLS-1$
final File output = getParameterValue(project, "output", File.class, mojo, monitor); //$NON-NLS-1$
final File testInput = getParameterValue(project, "testInput", File.class, mojo, monitor); //$NON-NLS-1$
final File testOutput = getParameterValue(project, "testOutput", File.class, mojo, monitor); //$NON-NLS-1$
config.setInput(input);
config.setOutput(output);
config.setTestInput(testInput);
config.setTestOutput(testOutput);
final String inputCompliance = getParameterValue(project, "source", String.class, mojo, monitor); //$NON-NLS-1$
final String outputCompliance = getParameterValue(project, "target", String.class, mojo, monitor); //$NON-NLS-1$
config.setInputCompliance(inputCompliance);
config.setOutputCompliance(outputCompliance);
final String encoding = getParameterValue(project, "encoding", String.class, mojo, monitor); //$NON-NLS-1$
config.setEncoding(encoding);
return config;
}
/** Remove any reference to the SARL libraries from the given classpath.
*
* @param classpath - the classpath to update.
*/
@SuppressWarnings("static-method")
protected void removeSarlLibraries(IClasspathDescriptor classpath) {
classpath.removeEntry(SARLClasspathContainerInitializer.CONTAINER_ID);
}
/** Add the SARL libraries into the given classpath.
*
* @param classpath - the classpath to update.
*/
@SuppressWarnings("static-method")
protected void addSarlLibraries(IClasspathDescriptor classpath) {
final IClasspathEntry entry = JavaCore.newContainerEntry(SARLClasspathContainerInitializer.CONTAINER_ID);
classpath.addEntry(entry);
}
private static void setVersion(Properties props, String propName, String value, String minValue) {
final String currentVersion = props.getProperty(propName);
String newVersion = value;
if (M2EUtilities.compareOsgiVersions(currentVersion, newVersion) > 0) {
newVersion = currentVersion;
}
if (M2EUtilities.compareOsgiVersions(newVersion, minValue) < 0) {
props.setProperty(propName, minValue);
} else {
props.setProperty(propName, newVersion);
}
}
private static void forceMavenCompilerConfiguration(IMavenProjectFacade facade, SARLConfiguration config) {
final Properties props = facade.getMavenProject().getProperties();
setVersion(props, "maven.compiler.source", config.getInputCompliance(), //$NON-NLS-1$
SARLVersion.MINIMAL_JDK_VERSION);
setVersion(props, "maven.compiler.target", config.getOutputCompliance(), //$NON-NLS-1$
SARLVersion.MINIMAL_JDK_VERSION);
final String encoding = config.getEncoding();
if (encoding != null && !encoding.isEmpty()) {
props.setProperty("maven.compiler.encoding", encoding); //$NON-NLS-1$
}
}
@Override
public void configure(ProjectConfigurationRequest request,
IProgressMonitor monitor) throws CoreException {
final SubMonitor subMonitor = SubMonitor.convert(monitor, 3);
final SARLConfiguration config = readConfiguration(request, subMonitor.newChild(1));
forceMavenCompilerConfiguration(request.getMavenProjectFacade(), config);
subMonitor.worked(1);
io.sarl.eclipse.natures.SARLProjectConfigurator.addSarlNatures(
request.getMavenProjectFacade().getProject(),
subMonitor.newChild(1));
}
@Override
public void unconfigure(ProjectConfigurationRequest request,
IProgressMonitor monitor) throws CoreException {
final IJavaProject javaProject = JavaCore.create(request.getProject());
final IClasspathDescriptor classpath = new ClasspathDescriptor(javaProject);
addSarlLibraries(classpath);
}
@Override
public void configureClasspath(IMavenProjectFacade facade,
IClasspathDescriptor classpath, IProgressMonitor monitor)
throws CoreException {
//
}
@Override
public void configureRawClasspath(ProjectConfigurationRequest request,
IClasspathDescriptor classpath, IProgressMonitor monitor)
throws CoreException {
final SubMonitor subm = SubMonitor.convert(monitor, 4);
final IMavenProjectFacade facade = request.getMavenProjectFacade();
final SARLConfiguration config = readConfiguration(request, subm.newChild(1));
subm.worked(1);
removeSarlLibraries(classpath);
subm.worked(2);
addSourceFolders(facade, config, classpath, subm.newChild(1));
subm.worked(1);
addPreferences(facade, config, subm.newChild(1));
subm.worked(1);
}
@Override
public AbstractBuildParticipant getBuildParticipant(
IMavenProjectFacade projectFacade, MojoExecution execution,
IPluginExecutionMetadata executionMetadata) {
return new BuildParticipant();
}
/** Build participant for detecting invalid versions of SARL components.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
protected static class BuildParticipant extends AbstractBuildParticipant2 {
private static final int NSTEPS = 4;
/** Construct a build participant.
*/
public BuildParticipant() {
//
}
@Override
public Set<IProject> build(int kind, IProgressMonitor monitor) throws Exception {
if (kind == AbstractBuildParticipant.AUTO_BUILD || kind == AbstractBuildParticipant.FULL_BUILD) {
final SubMonitor subm = SubMonitor.convert(monitor, Messages.SARLProjectConfigurator_7, NSTEPS);
getBuildContext().removeMessages(getMavenProjectFacade().getPomFile());
subm.worked(1);
validateSARLCompilerPlugin();
subm.worked(2);
validateSARLLibraryVersion();
subm.worked(3);
validateSARLDependenciesVersions(subm.newChild(1));
subm.worked(NSTEPS);
}
return null;
}
private Bundle validateSARLVersion(String groupId, String artifactId, String artifactVersion) {
final Bundle bundle = Platform.getBundle(SARL_LANG_BUNDLE_NAME);
if (bundle == null) {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
MessageFormat.format(Messages.SARLProjectConfigurator_0, SARL_LANG_BUNDLE_NAME),
BuildContext.SEVERITY_ERROR,
null);
return bundle;
}
final Version bundleVersion = bundle.getVersion();
if (bundleVersion == null) {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
MessageFormat.format(Messages.SARLProjectConfigurator_1, SARL_LANG_BUNDLE_NAME),
BuildContext.SEVERITY_ERROR,
null);
return bundle;
}
final Version minVersion = new Version(bundleVersion.getMajor(), bundleVersion.getMinor(), 0);
final Version maxVersion = new Version(bundleVersion.getMajor(), bundleVersion.getMinor() + 1, 0);
assert minVersion != null && maxVersion != null;
final Version mvnVersion = M2EUtilities.parseMavenVersion(artifactVersion);
final int compare = Utilities.compareVersionToRange(mvnVersion, minVersion, maxVersion);
if (compare < 0) {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
MessageFormat.format(Messages.SARLProjectConfigurator_2,
groupId, artifactId, artifactVersion, minVersion.toString()),
BuildContext.SEVERITY_ERROR,
null);
} else if (compare > 0) {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
MessageFormat.format(Messages.SARLProjectConfigurator_3,
groupId, artifactId, artifactVersion, maxVersion.toString()),
BuildContext.SEVERITY_ERROR,
null);
}
return bundle;
}
/** Validate the version of the SARL library in the dependencies.
*
* @throws CoreException if internal error occurs.
*/
protected void validateSARLLibraryVersion() throws CoreException {
final Map<String, Artifact> artifacts = getMavenProjectFacade().getMavenProject().getArtifactMap();
final Artifact artifact = artifacts.get(ArtifactUtils.versionlessKey(SARL_GROUP_ID, SARL_ARTIFACT_ID));
if (artifact != null) {
validateSARLVersion(SARL_GROUP_ID, SARL_ARTIFACT_ID, artifact.getVersion());
} else {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
Messages.SARLProjectConfigurator_6,
BuildContext.SEVERITY_ERROR,
null);
}
}
/** Validate the version of the SARL compiler in the Maven configuration.
*
* @return the SARL bundle.
* @throws CoreException if internal error occurs.
*/
protected Bundle validateSARLCompilerPlugin() throws CoreException {
final Map<String, Artifact> plugins = getMavenProjectFacade().getMavenProject().getPluginArtifactMap();
final Artifact pluginArtifact = plugins.get(ArtifactUtils.versionlessKey(SARL_PLUGIN_GROUP_ID,
SARL_PLUGIN_ARTIFACT_ID));
if (pluginArtifact == null) {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
Messages.SARLProjectConfigurator_5,
BuildContext.SEVERITY_ERROR,
null);
} else {
final String version = pluginArtifact.getVersion();
if (Strings.isNullOrEmpty(version)) {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
Messages.SARLProjectConfigurator_5,
BuildContext.SEVERITY_ERROR,
null);
} else {
return validateSARLVersion(
SARL_PLUGIN_GROUP_ID, SARL_PLUGIN_ARTIFACT_ID,
version);
}
}
return null;
}
/** Validate the versions of the libraries that are in the project dependencies have compatible versions
* with the specific dependencies of the SARL library.
*
* <p>The nearest-win strategy of the dependency resolver may select invalid version selection for artifacts
* that are used by the SARL libraries.
*
* @param monitor the progress monitor.
* @throws CoreException if internal error occurs.
*/
protected void validateSARLDependenciesVersions(IProgressMonitor monitor) throws CoreException {
final SubMonitor subm = SubMonitor.convert(monitor, 3);
final Map<String, String> neededArtifactVersions = new TreeMap<>();
final DependencyNode root = MavenPlugin.getMavenModelManager().readDependencyTree(
getMavenProjectFacade(), getMavenProjectFacade().getMavenProject(),
Artifact.SCOPE_COMPILE,
subm.newChild(1));
final DependencyNode[] sarlNode = new DependencyNode[] {null};
root.accept(new DependencyVisitor() {
@Override
public boolean visitLeave(DependencyNode node) {
if (sarlNode[0] == null
&& node.getDependency() != null
&& Objects.equals(node.getDependency().getArtifact().getGroupId(), SARL_MAVENLIB_GROUP_ID)
&& Objects.equals(node.getDependency().getArtifact().getArtifactId(), SARL_MAVENLIB_ARTIFACT_ID)) {
sarlNode[0] = node;
return false;
}
return true;
}
@Override
public boolean visitEnter(DependencyNode node) {
return sarlNode[0] == null;
}
});
subm.worked(1);
if (sarlNode[0] != null) {
sarlNode[0].accept(new DependencyVisitor() {
@Override
public boolean visitLeave(DependencyNode node) {
if (node.getDependency() != null) {
final String grId = node.getDependency().getArtifact().getGroupId();
final String arId = node.getDependency().getArtifact().getArtifactId();
final String key = ArtifactUtils.versionlessKey(grId, arId);
final String vers = neededArtifactVersions.get(key);
if (vers == null
|| M2EUtilities.compareMavenVersions(vers, node.getVersion().toString()) < 0) {
neededArtifactVersions.put(key, node.getVersion().toString());
}
}
return true;
}
@Override
public boolean visitEnter(DependencyNode node) {
return true;
}
});
}
subm.worked(2);
final SubMonitor subm2 = SubMonitor.convert(subm, neededArtifactVersions.size());
int i = 0;
final Map<String, Artifact> artifacts = getMavenProjectFacade().getMavenProject().getArtifactMap();
for (final Entry<String, String> neededDependency : neededArtifactVersions.entrySet()) {
final Artifact artifact = artifacts.get(neededDependency.getKey());
if (artifact != null) {
final int cmp = M2EUtilities.compareMavenVersions(neededDependency.getValue(), artifact.getVersion());
if (cmp > 1) {
getBuildContext().addMessage(
getMavenProjectFacade().getPomFile(),
-1, -1,
MessageFormat.format(
Messages.SARLProjectConfigurator_8,
artifact.getGroupId(),
artifact.getArtifactId(),
artifact.getVersion(),
neededDependency.getValue()),
BuildContext.SEVERITY_ERROR,
null);
}
}
subm2.worked(i);
++i;
}
subm.worked(3);
}
}
}