org.eclipse.m2e.jdt.internal.AbstractJavaProjectConfigurator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.m2e.jdt.internal.AbstractJavaProjectConfigurator.java

Source

/*******************************************************************************
 * Copyright (c) 2008-2013 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
 *
 * Contributors:
 *      Sonatype, Inc. - initial API and implementation
 *******************************************************************************/

package org.eclipse.m2e.jdt.internal;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
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.SubMonitor;
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.launching.JavaRuntime;
import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
import org.eclipse.jdt.launching.environments.IExecutionEnvironmentsManager;

import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;

import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.internal.M2EUtils;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IProjectConfigurationManager;
import org.eclipse.m2e.core.project.configurator.AbstractProjectConfigurator;
import org.eclipse.m2e.core.project.configurator.ILifecycleMapping;
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.MavenJdtPlugin;

/**
 * AbstractJavaProjectConfigurator
 * 
 * @author igor
 */
public abstract class AbstractJavaProjectConfigurator extends AbstractProjectConfigurator {
    private static final Logger log = LoggerFactory.getLogger(AbstractJavaProjectConfigurator.class);

    private static final String GOAL_COMPILE = "compile";

    private static final String GOAL_TESTCOMPILE = "testCompile";

    public static final String COMPILER_PLUGIN_ARTIFACT_ID = "maven-compiler-plugin";

    public static final String COMPILER_PLUGIN_GROUP_ID = "org.apache.maven.plugins";

    protected static final List<String> SOURCES = Arrays.asList("1.1,1.2,1.3,1.4,1.5,5,1.6,6,1.7,7".split(",")); //$NON-NLS-1$ //$NON-NLS-2$

    protected static final List<String> TARGETS = Arrays
            .asList("1.1,1.2,1.3,1.4,jsr14,1.5,5,1.6,6,1.7,7".split(",")); //$NON-NLS-1$ //$NON-NLS-2$

    private static final String GOAL_RESOURCES = "resources";

    private static final String GOAL_TESTRESOURCES = "testResources";

    private static final String RESOURCES_PLUGIN_ARTIFACT_ID = "maven-resources-plugin";

    private static final String RESOURCES_PLUGIN_GROUP_ID = "org.apache.maven.plugins";

    protected static final LinkedHashMap<String, String> ENVIRONMENTS = new LinkedHashMap<String, String>();

    static {
        ENVIRONMENTS.put("1.1", "JRE-1.1"); //$NON-NLS-1$ //$NON-NLS-2$
        ENVIRONMENTS.put("1.2", "J2SE-1.2"); //$NON-NLS-1$ //$NON-NLS-2$
        ENVIRONMENTS.put("1.3", "J2SE-1.3"); //$NON-NLS-1$ //$NON-NLS-2$
        ENVIRONMENTS.put("1.4", "J2SE-1.4"); //$NON-NLS-1$ //$NON-NLS-2$
        ENVIRONMENTS.put("1.5", "J2SE-1.5"); //$NON-NLS-1$ //$NON-NLS-2$
        ENVIRONMENTS.put("jsr14", "J2SE-1.5"); //$NON-NLS-1$ //$NON-NLS-2$
        ENVIRONMENTS.put("1.6", "JavaSE-1.6"); //$NON-NLS-1$ //$NON-NLS-2$
        ENVIRONMENTS.put("1.7", "JavaSE-1.7"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected static final String DEFAULT_COMPILER_LEVEL = "1.4"; //$NON-NLS-1$

    public void configure(ProjectConfigurationRequest request, IProgressMonitor monitor) throws CoreException {
        IProject project = request.getProject();

        monitor.setTaskName(Messages.AbstractJavaProjectConfigurator_task_name + project.getName());

        addJavaNature(project, monitor);

        IJavaProject javaProject = JavaCore.create(project);

        Map<String, String> options = new HashMap<String, String>();

        addJavaProjectOptions(options, request, monitor);

        IClasspathDescriptor classpath = new ClasspathDescriptor(javaProject);

        addProjectSourceFolders(classpath, request, monitor);

        String environmentId = getExecutionEnvironmentId(options);

        addJREClasspathContainer(classpath, environmentId);

        addMavenClasspathContainer(classpath);

        addCustomClasspathEntries(javaProject, classpath);

        invokeJavaProjectConfigurators(classpath, request, monitor);

        // now apply new configuration

        // A single setOptions call erases everything else from an existing settings file.
        // Must invoke setOption individually to preserve previous options. 
        for (Map.Entry<String, String> option : options.entrySet()) {
            javaProject.setOption(option.getKey(), option.getValue());
        }

        IContainer classesFolder = getOutputLocation(request, project);

        javaProject.setRawClasspath(classpath.getEntries(), classesFolder.getFullPath(), monitor);

        MavenJdtPlugin.getDefault().getBuildpathManager().updateClasspath(project, monitor);
    }

    protected IContainer getOutputLocation(ProjectConfigurationRequest request, IProject project) {
        MavenProject mavenProject = request.getMavenProject();
        return getFolder(project, mavenProject.getBuild().getOutputDirectory());
    }

    protected String getExecutionEnvironmentId(Map<String, String> options) {
        return ENVIRONMENTS.get(options.get(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM));
    }

    protected void addJavaNature(IProject project, IProgressMonitor monitor) throws CoreException {
        addNature(project, JavaCore.NATURE_ID, monitor);
    }

    protected void addCustomClasspathEntries(IJavaProject javaProject, IClasspathDescriptor classpath)
            throws JavaModelException {
    }

    protected void invokeJavaProjectConfigurators(IClasspathDescriptor classpath,
            ProjectConfigurationRequest request, final IProgressMonitor monitor) throws CoreException {
        IMavenProjectFacade facade = request.getMavenProjectFacade();
        IProjectConfigurationManager configurationManager = MavenPlugin.getProjectConfigurationManager();
        ILifecycleMapping lifecycleMapping = configurationManager.getLifecycleMapping(facade);
        if (lifecycleMapping == null) {
            return;
        }
        for (AbstractProjectConfigurator configurator : lifecycleMapping.getProjectConfigurators(facade, monitor)) {
            if (configurator instanceof IJavaProjectConfigurator) {
                ((IJavaProjectConfigurator) configurator).configureRawClasspath(request, classpath, monitor);
            }
        }
    }

    protected void addJREClasspathContainer(IClasspathDescriptor classpath, String environmentId) {

        IClasspathEntry cpe;
        IExecutionEnvironment executionEnvironment = getExecutionEnvironment(environmentId);
        if (executionEnvironment == null) {
            cpe = JavaRuntime.getDefaultJREContainerEntry();
        } else {
            IPath containerPath = JavaRuntime.newJREContainerPath(executionEnvironment);
            cpe = JavaCore.newContainerEntry(containerPath);
        }

        final IPath pathToKeep = cpe.getPath();
        // remove existing JRE entry, only if the path is different from the entry we are going to add. See bug398121
        classpath.removeEntry(new ClasspathDescriptor.EntryFilter() {
            public boolean accept(IClasspathEntryDescriptor descriptor) {
                return JavaRuntime.JRE_CONTAINER.equals(descriptor.getPath().segment(0))
                        && !descriptor.getPath().equals(pathToKeep);
            }
        });

        classpath.addEntry(cpe);
    }

    private IExecutionEnvironment getExecutionEnvironment(String environmentId) {
        IExecutionEnvironmentsManager manager = JavaRuntime.getExecutionEnvironmentsManager();
        for (IExecutionEnvironment environment : manager.getExecutionEnvironments()) {
            if (environment.getId().equals(environmentId)) {
                return environment;
            }
        }
        return null;
    }

    protected void addMavenClasspathContainer(IClasspathDescriptor classpath) {

        IClasspathEntry cpe = MavenClasspathHelpers.getDefaultContainerEntry();
        // add new entry without removing existing entries first, see bug398121
        classpath.addEntry(cpe);
    }

    protected void addProjectSourceFolders(IClasspathDescriptor classpath, ProjectConfigurationRequest request,
            IProgressMonitor monitor) throws CoreException {
        SubMonitor mon = SubMonitor.convert(monitor, 6);
        try {
            IProject project = request.getProject();
            MavenProject mavenProject = request.getMavenProject();
            IMavenProjectFacade projectFacade = request.getMavenProjectFacade();

            IFolder classes = getFolder(project, mavenProject.getBuild().getOutputDirectory());
            IFolder testClasses = getFolder(project, mavenProject.getBuild().getTestOutputDirectory());

            M2EUtils.createFolder(classes, true, mon.newChild(1));
            M2EUtils.createFolder(testClasses, true, mon.newChild(1));

            IPath[] inclusion = new IPath[0];
            IPath[] exclusion = new IPath[0];

            IPath[] inclusionTest = new IPath[0];
            IPath[] exclusionTest = new IPath[0];

            String mainSourceEncoding = null;
            String testSourceEncoding = null;

            String mainResourcesEncoding = null;
            String testResourcesEncoding = null;

            List<MojoExecution> executions = getCompilerMojoExecutions(request, mon.newChild(1));

            for (MojoExecution compile : executions) {
                if (isCompileExecution(compile)) {
                    mainSourceEncoding = maven.getMojoParameterValue(mavenProject, compile, "encoding", //$NON-NLS-1$
                            String.class, monitor);
                    try {
                        inclusion = toPaths(maven.getMojoParameterValue(mavenProject, compile, "includes", //$NON-NLS-1$
                                String[].class, monitor));
                    } catch (CoreException ex) {
                        log.error("Failed to determine compiler inclusions, assuming defaults", ex);
                    }
                    try {
                        exclusion = toPaths(maven.getMojoParameterValue(mavenProject, compile, "excludes", //$NON-NLS-1$
                                String[].class, monitor));
                    } catch (CoreException ex) {
                        log.error("Failed to determine compiler exclusions, assuming defaults", ex);
                    }
                }
            }

            for (MojoExecution compile : executions) {
                if (isTestCompileExecution(compile)) {
                    testSourceEncoding = maven.getMojoParameterValue(mavenProject, compile, "encoding", //$NON-NLS-1$
                            String.class, monitor);
                    try {
                        inclusionTest = toPaths(maven.getMojoParameterValue(mavenProject, compile, "testIncludes", //$NON-NLS-1$
                                String[].class, monitor));
                    } catch (CoreException ex) {
                        log.error("Failed to determine compiler test inclusions, assuming defaults", ex);
                    }
                    try {
                        exclusionTest = toPaths(maven.getMojoParameterValue(mavenProject, compile, "testExcludes", //$NON-NLS-1$
                                String[].class, monitor));
                    } catch (CoreException ex) {
                        log.error("Failed to determine compiler test exclusions, assuming defaults", ex);
                    }
                }
            }

            for (MojoExecution resources : projectFacade.getMojoExecutions(RESOURCES_PLUGIN_GROUP_ID,
                    RESOURCES_PLUGIN_ARTIFACT_ID, mon.newChild(1), GOAL_RESOURCES)) {
                mainResourcesEncoding = maven.getMojoParameterValue(mavenProject, resources, "encoding", //$NON-NLS-1$
                        String.class, monitor);
            }

            for (MojoExecution resources : projectFacade.getMojoExecutions(RESOURCES_PLUGIN_GROUP_ID,
                    RESOURCES_PLUGIN_ARTIFACT_ID, mon.newChild(1), GOAL_TESTRESOURCES)) {
                testResourcesEncoding = maven.getMojoParameterValue(mavenProject, resources, "encoding", //$NON-NLS-1$
                        String.class, monitor);
            }

            addSourceDirs(classpath, project, mavenProject.getCompileSourceRoots(), classes.getFullPath(),
                    inclusion, exclusion, mainSourceEncoding, mon.newChild(1));
            addResourceDirs(classpath, project, mavenProject.getBuild().getResources(), classes.getFullPath(),
                    mainResourcesEncoding, mon.newChild(1));

            addSourceDirs(classpath, project, mavenProject.getTestCompileSourceRoots(), testClasses.getFullPath(),
                    inclusionTest, exclusionTest, testSourceEncoding, mon.newChild(1));
            addResourceDirs(classpath, project, mavenProject.getBuild().getTestResources(),
                    testClasses.getFullPath(), testResourcesEncoding, mon.newChild(1));
        } finally {
            mon.done();
        }
    }

    protected boolean isTestCompileExecution(MojoExecution execution) {
        return GOAL_TESTCOMPILE.equals(execution.getGoal());
    }

    protected boolean isCompileExecution(MojoExecution execution) {
        return GOAL_COMPILE.equals(execution.getGoal());
    }

    private IPath[] toPaths(String[] values) {
        if (values == null) {
            return new IPath[0];
        }
        IPath[] paths = new IPath[values.length];
        for (int i = 0; i < values.length; i++) {
            if (values[i] != null && !"".equals(values[i].trim())) {
                paths[i] = new Path(values[i]);
            }
        }
        return paths;
    }

    private void addSourceDirs(IClasspathDescriptor classpath, IProject project, List<String> sourceRoots,
            IPath outputPath, IPath[] inclusion, IPath[] exclusion, String sourceEncoding, IProgressMonitor monitor)
            throws CoreException {

        for (int i = 0; i < sourceRoots.size(); i++) {
            IFolder sourceFolder = getFolder(project, sourceRoots.get(i));

            if (sourceFolder == null) {
                // this cannot actually happen, unless I misunderstand how project.getFolder works
                continue;
            }

            // be extra nice to less perfectly written maven plugins, which contribute compile source root to the model 
            // but do not use BuildContext to tell as about the actual resources 
            sourceFolder.refreshLocal(IResource.DEPTH_ZERO, monitor);

            if (sourceFolder.exists() && !sourceFolder.getProject().equals(project)) {
                // source folders outside of ${project.basedir} are not supported
                continue;
            }

            // Set folder encoding (null = platform/container default)
            if (sourceFolder.exists()) {
                sourceFolder.setDefaultCharset(sourceEncoding, monitor);
            }

            IClasspathEntryDescriptor enclosing = getEnclosingEntryDescriptor(classpath,
                    sourceFolder.getFullPath());
            if (enclosing == null || getEntryDescriptor(classpath, sourceFolder.getFullPath()) != null) {
                log.info("Adding source folder " + sourceFolder.getFullPath());

                // source folder entries are created even when corresponding resources do not actually exist in workspace
                // to keep JDT from complaining too loudly about non-existing folders, 
                // all source entries are marked as generated (a.k.a. optional)
                classpath.addSourceEntry(sourceFolder.getFullPath(), outputPath, inclusion, exclusion,
                        true /*generated*/);
            } else {
                log.info("Not adding source folder " + sourceFolder.getFullPath() + " because it overlaps with "
                        + enclosing.getPath());
            }
        }
    }

    private IClasspathEntryDescriptor getEnclosingEntryDescriptor(IClasspathDescriptor classpath, IPath fullPath) {
        for (IClasspathEntryDescriptor cped : classpath.getEntryDescriptors()) {
            if (cped.getPath().isPrefixOf(fullPath)) {
                return cped;
            }
        }
        return null;
    }

    private IClasspathEntryDescriptor getEntryDescriptor(IClasspathDescriptor classpath, IPath fullPath) {
        for (IClasspathEntryDescriptor cped : classpath.getEntryDescriptors()) {
            if (cped.getPath().equals(fullPath)) {
                return cped;
            }
        }
        return null;
    }

    private void addResourceDirs(IClasspathDescriptor classpath, IProject project, List<Resource> resources,
            IPath outputPath, String resourceEncoding, IProgressMonitor monitor) throws CoreException {

        for (Resource resource : resources) {
            String directory = resource.getDirectory();
            if (directory == null) {
                continue;
            }
            File resourceDirectory = new File(directory);
            if (resourceDirectory.exists() && resourceDirectory.isDirectory()) {
                IPath relativePath = getProjectRelativePath(project, directory);
                IResource r = project.findMember(relativePath);
                if (r == project) {
                    /* 
                     * Workaround for the Java Model Exception: 
                     *   Cannot nest output folder 'xxx/src/main/resources' inside output folder 'xxx'
                     * when pom.xml have something like this:
                     * 
                     * <build>
                     *   <resources>
                     *     <resource>
                     *       <directory>${basedir}</directory>
                     *       <targetPath>META-INF</targetPath>
                     *       <includes>
                     *         <include>LICENSE</include>
                     *       </includes>
                     *     </resource>
                     */
                    log.error("Skipping resource folder " + r.getFullPath());
                } else if (r != null && project.equals(r.getProject())) {
                    IClasspathEntryDescriptor enclosing = getEnclosingEntryDescriptor(classpath, r.getFullPath());
                    if (enclosing == null || isResourceDescriptor(getEntryDescriptor(classpath, r.getFullPath()))) {
                        log.info("Adding resource folder " + r.getFullPath());
                        classpath.addSourceEntry(r.getFullPath(), outputPath, new IPath[0] /*inclusions*/,
                                new IPath[] { new Path("**") } /*exclusion*/, false /*optional*/);
                    } else {
                        // resources and sources folders overlap. make sure JDT only processes java sources.
                        log.info("Resources folder " + r.getFullPath() + " overlaps with sources folder "
                                + enclosing.getPath());
                        enclosing.addInclusionPattern(new Path("**/*.java"));
                    }

                    // Set folder encoding (null = platform default)
                    IFolder resourceFolder = project.getFolder(relativePath);
                    resourceFolder.setDefaultCharset(resourceEncoding, monitor);
                } else {
                    log.info("Not adding resources folder " + resourceDirectory.getAbsolutePath());
                }
            }
        }
    }

    private boolean isResourceDescriptor(IClasspathEntryDescriptor cped) {
        //How can we know for sure this is a resource folder?
        if (cped != null) {
            IPath[] exclusionPatterns = cped.getExclusionPatterns();
            if (exclusionPatterns != null && exclusionPatterns.length == 1) {
                IPath excludeAllPattern = new Path("**");
                return excludeAllPattern.equals(exclusionPatterns[0]);
            }
        }
        return false;
    }

    protected void addJavaProjectOptions(Map<String, String> options, ProjectConfigurationRequest request,
            IProgressMonitor monitor) throws CoreException {
        String source = null, target = null;

        for (MojoExecution execution : getCompilerMojoExecutions(request, monitor)) {
            source = getCompilerLevel(request.getMavenProject(), execution, "source", source, SOURCES, monitor); //$NON-NLS-1$
            target = getCompilerLevel(request.getMavenProject(), execution, "target", target, TARGETS, monitor); //$NON-NLS-1$
        }

        if (source == null) {
            source = DEFAULT_COMPILER_LEVEL;
            log.warn("Could not determine source level, using default " + source);
        }

        if (target == null) {
            target = DEFAULT_COMPILER_LEVEL;
            log.warn("Could not determine target level, using default " + target);
        }

        // While "5" and "6" are valid synonyms for Java 5 and Java 6 source,
        // Eclipse expects the values 1.5 and 1.6.
        if (source.equals("5")) {
            source = "1.5";
        } else if (source.equals("6")) {
            source = "1.6";
        } else if (source.equals("7")) {
            source = "1.7";
        }

        // While "5" and "6" are valid synonyms for Java 5 and Java 6 target,
        // Eclipse expects the values 1.5 and 1.6.
        if (target.equals("5")) {
            target = "1.5";
        } else if (target.equals("6")) {
            target = "1.6";
        } else if (target.equals("7")) {
            target = "1.7";
        }

        options.put(JavaCore.COMPILER_SOURCE, source);
        options.put(JavaCore.COMPILER_COMPLIANCE, source);
        options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, target);

        // 360962 keep forbidden_reference severity set by the user
        IJavaProject jp = JavaCore.create(request.getProject());
        if (jp != null && jp.getOption(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, false) == null) {
            options.put(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, "warning"); //$NON-NLS-1$
        }
    }

    protected List<MojoExecution> getCompilerMojoExecutions(ProjectConfigurationRequest request,
            IProgressMonitor monitor) throws CoreException {
        return request.getMavenProjectFacade().getMojoExecutions(COMPILER_PLUGIN_GROUP_ID,
                COMPILER_PLUGIN_ARTIFACT_ID, monitor, GOAL_COMPILE, GOAL_TESTCOMPILE);
    }

    private String getCompilerLevel(MavenProject mavenProject, MojoExecution execution, String parameter,
            String source, List<String> levels, IProgressMonitor monitor) {
        int levelIdx = getLevelIndex(source, levels);

        try {
            source = maven.getMojoParameterValue(mavenProject, execution, parameter, String.class, monitor);
        } catch (CoreException ex) {
            log.error("Failed to determine compiler " + parameter + " setting, assuming default", ex);
        }

        int newLevelIdx = getLevelIndex(source, levels);

        if (newLevelIdx > levelIdx) {
            levelIdx = newLevelIdx;
        }

        if (levelIdx < 0) {
            return DEFAULT_COMPILER_LEVEL;
        }

        return levels.get(levelIdx);
    }

    private int getLevelIndex(String level, List<String> levels) {
        return level != null ? levels.indexOf(level) : -1;
    }

    public void unconfigure(ProjectConfigurationRequest request, IProgressMonitor monitor) throws CoreException {
        super.unconfigure(request, monitor);
        removeMavenClasspathContainer(request.getProject());
    }

    private void removeMavenClasspathContainer(IProject project) throws JavaModelException {
        IJavaProject javaProject = JavaCore.create(project);
        if (javaProject != null) {
            // remove classpatch container from JavaProject
            ArrayList<IClasspathEntry> newEntries = new ArrayList<IClasspathEntry>();
            for (IClasspathEntry entry : javaProject.getRawClasspath()) {
                if (!MavenClasspathHelpers.isMaven2ClasspathContainer(entry.getPath())) {
                    newEntries.add(entry);
                }
            }
            javaProject.setRawClasspath(newEntries.toArray(new IClasspathEntry[newEntries.size()]), null);
        }
    }

    protected IFolder getFolder(IProject project, String absolutePath) {
        return project.getFolder(getProjectRelativePath(project, absolutePath));
    }

    protected IPath getProjectRelativePath(IProject project, String absolutePath) {
        File basedir = project.getLocation().toFile();
        String relative;
        if (absolutePath.equals(basedir.getAbsolutePath())) {
            relative = "."; //$NON-NLS-1$
        } else if (absolutePath.startsWith(basedir.getAbsolutePath())) {
            relative = absolutePath.substring(basedir.getAbsolutePath().length() + 1);
        } else {
            relative = absolutePath;
        }
        return new Path(relative.replace('\\', '/')); //$NON-NLS-1$ //$NON-NLS-2$
    }
}