/*******************************************************************************
* Copyright (c) 2008 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.maven.ide.eclipse.scala;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoExecution;
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.jdt.core.ClasspathContainerInitializer;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.ClasspathAttribute;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.configurator.ProjectConfigurationRequest;
import org.eclipse.m2e.jdt.AbstractJavaProjectConfigurator;
import org.eclipse.m2e.jdt.IClasspathDescriptor;
import org.eclipse.m2e.jdt.IClasspathEntryDescriptor;
//TODO check the jre/java version compliance (>= 1.5)
//TODO check JDT Weaving is enabled (if not enabled, icon of scala file is [J] same as java (and property of the file display "Type :... Java Source File" )
//TODO check that pom.xml and ScalaLib Container declare the same scala version
//TODO keep sync scala-compiler configuration between pom.xml and scala-plugin ? (sync bi-direction) ?
//TODO ask sonatype/mailing-list about how to retreive maven plugin configuration, like additional sourceDirectory
/**
* @author Sonatype Inc (http://github.com/sonatype/m2eclipse-scala)
* @author davidB (http://github.com/davidB)
* @author germanklf (http://github.com/germanklf)
* @author Fred Bricon
*/
public class ScalaProjectConfigurator extends AbstractJavaProjectConfigurator {
private static final String SCALA_CONTAINER_PATH = "org.scala-ide.sdt.launching.SCALA_CONTAINER";
private static final String MAVEN_CONTAINER_PATH = "org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER";
private static String DEPLOYABLE_KEY = "org.eclipse.jst.component.dependency";
private static String NON_DEPLOYABLE_KEY = "org.eclipse.jst.component.nondependency";
private Map<String, Integer> mapSourceTypeWeight;
private Map<String, Integer> mapResourceWeight;
private Comparator<IClasspathEntry> comparator;
public ScalaProjectConfigurator() {
super();
mapSourceTypeWeight = new HashMap<String, Integer>();
mapSourceTypeWeight.put("src/main/", 9000);
mapSourceTypeWeight.put("src/test/", 1000);
mapResourceWeight = new HashMap<String, Integer>();
mapResourceWeight.put("java", 100);
mapResourceWeight.put("resources", -10);
comparator = new Comparator<IClasspathEntry>() {
public int compare(IClasspathEntry o1, IClasspathEntry o2) {
Integer w1 = getWeight(o1);
Integer w2 = getWeight(o2);
if(w1 > 0 || w2 > 0) {
if(w1.equals(w2))
return o1.getPath().toString().compareTo(o2.getPath().toString());
else
return w1.compareTo(w2) * -1;
}
return 0;
}
private Integer getWeight(IClasspathEntry ce) {
int value = 0;
if(ce.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
for(Entry<String, Integer> e : mapSourceTypeWeight.entrySet()) {
if(ce.getPath().toString().contains(e.getKey())) {
value += e.getValue();
}
}
for(Entry<String, Integer> e : mapResourceWeight.entrySet()) {
if(ce.getPath().toString().endsWith(e.getKey())) {
value += e.getValue();
}
}
}
return value;
}
};
}
private String scalaNatureId() {
ScalaPluginIds ids = Activator.getInstance().scalaPluginIds();
return (ids == null)?null : ids.natureId;
}
// private boolean shouldManageClasspath() {
// Version v4 = new Version(4,0,0);
// return v4.compareTo(Activator.getInstance().scalaPluginIds().version) > 0;
// }
@Override
public void configure(ProjectConfigurationRequest request, IProgressMonitor monitor) throws CoreException {
super.configure(request, monitor);
String scalaNature = scalaNatureId();
if (scalaNature == null) {
//TODO show an alert to user that he should to install scala-ide plugin;
return;
}
try {
if (request != null) {
IProject project = request.getProject();
if(!project.hasNature(scalaNature) && isScalaProject(request.getMavenProjectFacade(), monitor)) {
if(!project.hasNature("org.eclipse.jdt.core.javanature")) {
addNature(project, "org.eclipse.jdt.core.javanature", monitor);
}
addNature(project, scalaNature, monitor);
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* configure Classpath : contain of "Maven Dependencies" Librairies Container.
*/
@Override
public void configureClasspath(IMavenProjectFacade facade, IClasspathDescriptor classpath, IProgressMonitor monitor) throws CoreException {
String scalaNature = scalaNatureId();
if (scalaNature == null) {
//TODO show an alert to user that he should to install scala-ide plugin;
return;
}
if (!isLaunchConfigurationCtx()) {
IProject project = facade.getProject();
if(isScalaProject(project)) {
// if(!project.hasNature(ID_NATURE)) {
// addNature(project, ID_NATURE, monitor);
// }
removeScalaFromMavenContainer(classpath);
sortContainerScalaJre(project, monitor); //
makeScalaLibDeployable(facade, classpath, monitor);
}
}
}
// workaround to avoid NPE
@Override
protected IPath getFullPath( IMavenProjectFacade facade, File file ) {
if (file == null) return null;
return super.getFullPath(facade, file);
}
private void removeScalaFromMavenContainer(final IClasspathDescriptor classpath) {
classpath.removeEntry(new IClasspathDescriptor.EntryFilter() {
public boolean accept(IClasspathEntryDescriptor descriptor) {
boolean back = "org.scala-lang".equals(descriptor.getGroupId());
//TODO, use content of Scala Library Container instead of hardcoded value
back = back && ("scala-library".equals(descriptor.getArtifactId())
//|| "scala-compiler".equals(descriptor.getArtifactId())
|| "scala-dbc".equals(descriptor.getArtifactId()) || "scala-swing".equals(descriptor.getArtifactId()));
return back;
}
});
}
/**
* Check and reorder Containers : "Scala Lib" should be before "JRE Sys",
* else 'Run Scala Application' set Boot Entries JRE before Scala and failed with scala.* NotFound Exception.
* Should already be done when adding nature
* @see scala.tools.eclipse.Nature#configure()
*/
private void sortContainerScalaJre(final IProject project, IProgressMonitor monitor) throws CoreException {
IJavaProject javaProject = JavaCore.create(project);
IClasspathEntry[] classpathEntries = javaProject.getRawClasspath();
Boolean sorted = true;
for (int i = 0; i < classpathEntries.length - 1; i++) {
if (comparator.compare(classpathEntries[i], classpathEntries[i+1]) > 0){sorted = false; break;}
}
if(!sorted) {
List<IClasspathEntry> entries = new ArrayList<IClasspathEntry>(classpathEntries.length);
Collections.addAll(entries, classpathEntries);
Collections.sort(entries, comparator);
javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[entries.size()]), monitor);
}
}
private boolean isScalaProject(IProject project) {
try {
return project != null && project.isAccessible() && project.hasNature(scalaNatureId());
} catch(CoreException e) {
return false;
}
}
private boolean isScalaProject(IMavenProjectFacade facade, IProgressMonitor monitor) throws CoreException {
List<Plugin> plugins = facade.getMavenProject(monitor).getBuildPlugins();
if(plugins != null) {
for(Plugin plugin : plugins) {
if(isMavenBundlePluginMojo(plugin) && !plugin.getExecutions().isEmpty()) {
return true;
}
}
}
return false;
}
protected boolean isLaunchConfigurationCtx() {
for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
if ("launch".equals(e.getMethodName())) {
return true;
}
}
return false;
}
protected boolean isMavenBundlePluginMojo(MojoExecution execution) {
return isMavenBundlePluginMojo(execution.getGroupId(), execution.getArtifactId());
}
private boolean isMavenBundlePluginMojo(Plugin plugin) {
return isMavenBundlePluginMojo(plugin.getGroupId(), plugin.getArtifactId());
}
private boolean isMavenBundlePluginMojo(String groupId, String artifactId) {
return ("org.scala-tools".equals(groupId) && "maven-scala-plugin".equals(artifactId))
|| ("net.alchim31.maven".equals(groupId) && "scala-maven-plugin".equals(artifactId));
}
@SuppressWarnings("restriction")
private void makeScalaLibDeployable(IMavenProjectFacade facade, IClasspathDescriptor classpath, IProgressMonitor monitor) throws CoreException {
IJavaProject javaProject = JavaCore.create(facade.getProject());
if (javaProject == null) {
return;
}
IClasspathEntry scalaLibrary = getContainer(javaProject, SCALA_CONTAINER_PATH);
if (scalaLibrary == null) {
//Really nothing to do here
return;
}
IClasspathEntry mavenLibrary = getContainer(javaProject, MAVEN_CONTAINER_PATH);
IClasspathAttribute deployableAttribute = getDeployableAttribute(mavenLibrary);
//If the Maven Classpath Container is set to be deployed in WTP, then do the same for the Scala one
if (deployableAttribute != null) {
//Add the deployable attribute only if it's not set already.
if (getDeployableAttribute(scalaLibrary) == null) {
addDeployableAttribute(javaProject, deployableAttribute, monitor);
}
}
}
private static void addDeployableAttribute(IJavaProject javaProject, IClasspathAttribute deployableAttribute, IProgressMonitor monitor) throws JavaModelException, CoreException {
if (javaProject == null) return;
ClasspathContainerInitializer scalaInitializer = JavaCore.getClasspathContainerInitializer(SCALA_CONTAINER_PATH);
if (scalaInitializer == null) return;
IPath scalaContainerPath = Path.fromPortableString(SCALA_CONTAINER_PATH);
Boolean updateAble = scalaInitializer.canUpdateClasspathContainer(scalaContainerPath, javaProject);
final IClasspathContainer scalaLibrary = JavaCore.getClasspathContainer(scalaContainerPath, javaProject);
final IClasspathEntry[] cpEntries = scalaLibrary.getClasspathEntries();
for(int i = 0; i < cpEntries.length; i++ ) {
IClasspathEntry cpe = cpEntries[i];
LinkedHashMap<String, IClasspathAttribute> attrs = new LinkedHashMap<String, IClasspathAttribute>();
for(IClasspathAttribute attr : cpe.getExtraAttributes()) {
//Keep all existing attributes except the non_deployable key
if(!attr.getName().equals(NON_DEPLOYABLE_KEY)) {
attrs.put(attr.getName(), attr);
}
}
attrs.put(deployableAttribute.getName(), deployableAttribute);
IClasspathAttribute[] newAttrs = attrs.values().toArray(new IClasspathAttribute[attrs.size()]);
cpEntries[i] = JavaCore.newLibraryEntry(cpe.getPath(), cpe.getSourceAttachmentPath(),
cpe.getSourceAttachmentRootPath(), cpe.getAccessRules(), newAttrs, cpe.isExported());
}
IClasspathContainer candidateScalaContainer = new IClasspathContainer() {
public IPath getPath() { return scalaLibrary.getPath(); }
public IClasspathEntry[] getClasspathEntries() { return cpEntries; }
public String getDescription() { return scalaLibrary.getDescription(); }
public int getKind() { return scalaLibrary.getKind(); }
};
if (updateAble){
scalaInitializer.requestClasspathContainerUpdate(scalaContainerPath, javaProject, candidateScalaContainer);
} else {
IJavaProject [] jPArray = { javaProject };
IClasspathContainer[] cpArray = { candidateScalaContainer };
JavaCore.setClasspathContainer(scalaContainerPath, jPArray, cpArray, null);
}
}
private static IClasspathEntry getContainer(IJavaProject javaProject, String containerPath) throws JavaModelException {
IClasspathEntry[] cp = javaProject.getRawClasspath();
for(int i = 0; i < cp.length; i++ ) {
if(IClasspathEntry.CPE_CONTAINER == cp[i].getEntryKind()
&& containerPath.equals(cp[i].getPath().lastSegment())) {
return cp[i];
}
}
return null;
}
private static IClasspathAttribute getDeployableAttribute(IClasspathEntry library) {
if (library == null) {
return null;
}
IClasspathAttribute[] attributes = library.getExtraAttributes();
if (attributes == null || attributes.length == 0) {
return null;
}
for(IClasspathAttribute attr : attributes) {
if (DEPLOYABLE_KEY.equals(attr.getName())) {
return new ClasspathAttribute(attr.getName(), attr.getValue());
}
}
return null;
}
}