org.eclipse.buildship.core.launch.internal.GradleClasspathProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.buildship.core.launch.internal.GradleClasspathProvider.java

Source

/*
 * Copyright (c) 2017 the original author or authors.
 * 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.eclipse.buildship.core.launch.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

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.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunchConfiguration;
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.jdt.internal.launching.DefaultProjectClasspathEntry;
import org.eclipse.jdt.internal.launching.RuntimeClasspathEntry;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry2;
import org.eclipse.jdt.launching.IRuntimeClasspathProvider;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.StandardClasspathProvider;

import org.eclipse.buildship.core.CorePlugin;

/**
 * Classpath provider for Gradle projects filtering the project output folders based on the Gradle
 * dependency scope information.
 *
 * @author Donat Csikos
 */
@SuppressWarnings("restriction")
public final class GradleClasspathProvider extends StandardClasspathProvider implements IRuntimeClasspathProvider {

    public static final String ID = "org.eclipse.buildship.core.classpathprovider";

    private static final IRuntimeClasspathEntry[] EMPTY_RESULT = new IRuntimeClasspathEntry[0];

    public GradleClasspathProvider() {
        super();
    }

    @Override
    public IRuntimeClasspathEntry[] computeUnresolvedClasspath(ILaunchConfiguration configuration)
            throws CoreException {
        return filterUnusedDependencies(configuration, super.computeUnresolvedClasspath(configuration));
    }

    private IRuntimeClasspathEntry[] filterUnusedDependencies(ILaunchConfiguration configuration,
            IRuntimeClasspathEntry[] entriesToFilter) throws CoreException {
        // if the run configuration uses Java 9 then the library dependencies are already present in the
        // unresolved classpath. That is because the Java 9 support calculates the class/module path from
        // the result of IJavaProject.getResolvedClasspath(true). Unfortunately, the runtime entries don't
        // have the source set attribute, so we have to filter them base on entry paths.
        IJavaProject project = JavaRuntime.getJavaProject(configuration);
        IClasspathEntry[] classpath = project.getResolvedClasspath(true);
        LaunchConfigurationScope configurationScopes = LaunchConfigurationScope.from(configuration);
        Set<IPath> excludedPaths = Sets.newHashSet();
        for (IClasspathEntry entry : classpath) {
            if (!configurationScopes.isEntryIncluded(entry)) {
                excludedPaths.add(entry.getPath());
            }
        }

        List<IRuntimeClasspathEntry> result = new ArrayList<IRuntimeClasspathEntry>(entriesToFilter.length);
        for (IRuntimeClasspathEntry entry : entriesToFilter) {
            if (!excludedPaths.contains(entry.getPath())) {
                result.add(entry);
            }
        }

        return result.toArray(new IRuntimeClasspathEntry[0]);
    }

    @Override
    public IRuntimeClasspathEntry[] resolveClasspath(IRuntimeClasspathEntry[] entries,
            ILaunchConfiguration configuration) throws CoreException {
        Set<IRuntimeClasspathEntry> result = new LinkedHashSet<>(entries.length);
        for (IRuntimeClasspathEntry entry : entries) {
            switch (entry.getType()) {
            case IRuntimeClasspathEntry.OTHER:
                Collections.addAll(result, resolveOther(entry, configuration));
                break;
            case IRuntimeClasspathEntry.PROJECT:
                Collections.addAll(result, resolveProject(entry, configuration));
                break;
            default:
                Collections.addAll(result, JavaRuntime.resolveRuntimeClasspathEntry(entry, configuration));
                break;
            }
        }

        return result.toArray(new IRuntimeClasspathEntry[result.size()]);
    }

    private IRuntimeClasspathEntry[] resolveOther(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration)
            throws CoreException {
        // The project dependency entries are represented with nonstandard IRuntimeClasspathEntry
        // and resolved by DefaultEntryResolver. The code below is a copy-paste of the
        // DefaultEntryResolver except that the inner resolveRuntimeClasspathEntry() method call is
        // replaced with a resolveClasspath(). This way we can intercept and update the project
        // entry resolution using the resolveProject() method.
        if (entry instanceof DefaultProjectClasspathEntry) {
            List<IRuntimeClasspathEntry> result = new ArrayList<IRuntimeClasspathEntry>();
            for (IRuntimeClasspathEntry e : ((IRuntimeClasspathEntry2) entry)
                    .getRuntimeClasspathEntries(configuration)) {
                Collections.addAll(result, resolveClasspath(new IRuntimeClasspathEntry[] { e }, configuration));
            }
            return result.toArray(new IRuntimeClasspathEntry[result.size()]);
        } else {
            return JavaRuntime.resolveRuntimeClasspathEntry(entry, configuration);
        }
    }

    private IRuntimeClasspathEntry[] resolveProject(IRuntimeClasspathEntry entry,
            ILaunchConfiguration configuration) throws CoreException {
        IResource resource = entry.getResource();
        if (resource instanceof IProject) {
            return resolveProject(entry, (IProject) resource, configuration);
        } else {
            return resolveOptional(entry);
        }
    }

    private IRuntimeClasspathEntry[] resolveProject(IRuntimeClasspathEntry projectEntry, IProject project,
            ILaunchConfiguration configuration) throws CoreException {
        if (!project.isOpen()) {
            return EMPTY_RESULT;
        }

        IJavaProject javaProject = JavaCore.create(project);
        if (javaProject == null || !javaProject.exists()) {
            return EMPTY_RESULT;
        }

        LaunchConfigurationScope configurationScopes = LaunchConfigurationScope.from(configuration);
        return resolveOutputLocations(projectEntry, javaProject, configurationScopes);
    }

    private IRuntimeClasspathEntry[] resolveOptional(IRuntimeClasspathEntry entry) throws CoreException {
        if (isOptional(entry.getClasspathEntry())) {
            return EMPTY_RESULT;
        } else {
            throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID,
                    String.format("The project: %s which is referenced by the classpath, does not exist",
                            entry.getPath().lastSegment())));
        }
    }

    private static boolean isOptional(IClasspathEntry entry) {
        for (IClasspathAttribute attribute : entry.getExtraAttributes()) {
            if (IClasspathAttribute.OPTIONAL.equals(attribute.getName())
                    && Boolean.parseBoolean(attribute.getValue())) {
                return true;
            }
        }
        return false;
    }

    private static IRuntimeClasspathEntry[] resolveOutputLocations(IRuntimeClasspathEntry projectEntry,
            IJavaProject project, LaunchConfigurationScope configurationScopes) throws CoreException {
        List<IPath> outputLocations = Lists.newArrayList();
        boolean hasSourceFolderWithoutCustomOutput = false;

        if (project.exists() && project.getProject().isOpen()) {
            for (IClasspathEntry entry : project.getRawClasspath()) {
                if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {

                    // only add the output location if it's in the same source set
                    if (configurationScopes.isEntryIncluded(entry)) {
                        IPath path = entry.getOutputLocation();
                        if (path != null) {
                            outputLocations.add(path);
                        } else {
                            // only use the default output if there's at least one source folder that doesn't have a custom output location
                            hasSourceFolderWithoutCustomOutput = true;
                        }
                    }
                }
            }
        }

        if (outputLocations.isEmpty()) {
            return new IRuntimeClasspathEntry[] { projectEntry };
        }

        IPath defaultOutputLocation = project.getOutputLocation();
        if (!outputLocations.contains(defaultOutputLocation) && hasSourceFolderWithoutCustomOutput) {
            outputLocations.add(defaultOutputLocation);
        }

        IRuntimeClasspathEntry[] result = new IRuntimeClasspathEntry[outputLocations.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = new RuntimeClasspathEntry(JavaCore.newLibraryEntry(outputLocations.get(i), null, null));
            result[i].setClasspathProperty(projectEntry.getClasspathProperty());
        }
        return result;
    }
}