de.tobject.findbugs.builder.PDEClassPathGenerator.java Source code

Java tutorial

Introduction

Here is the source code for de.tobject.findbugs.builder.PDEClassPathGenerator.java

Source

/*
 * Contributions to FindBugs
 * Copyright (C) 2008, Andrei Loskutov
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package de.tobject.findbugs.builder;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.CheckForNull;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
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.internal.launching.JREContainer;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.PluginRegistry;
import org.eclipse.pde.internal.build.site.PDEState;
import org.eclipse.pde.internal.core.ClasspathUtilCore;

import de.tobject.findbugs.FindbugsPlugin;

/**
 * Helper class to resolve full classpath for Eclipse plugin projects. Plugin
 * projects are very special for Eclipse and they differ from "usual" java
 * projects...
 *
 * @author Andrei
 */
public class PDEClassPathGenerator {

    /**
     * @param javaProject non null
     * @return never null (may be empty array)
     */
    public static String[] computeClassPath(IJavaProject javaProject) {
        Collection<String> classPath = Collections.EMPTY_SET;
        Set<IProject> projectOnCp = new HashSet<>();
        projectOnCp.add(javaProject.getProject());
        try {
            // first try to check and resolve plugin project. It can fail if
            // there is no
            // PDE plugins installed in the current Eclipse instance (PDE is
            // optional)
            classPath = createPluginClassPath(javaProject, projectOnCp);
        } catch (NoClassDefFoundError ce) {
            // ok, we do not have PDE installed, now try to get default java
            // classpath
            classPath = createJavaClasspath(javaProject, projectOnCp);
        } catch (CoreException e) {
            FindbugsPlugin.getDefault().logException(e,
                    "Could not compute aux. classpath for project " + javaProject);
            return new String[0];
        }
        return classPath.toArray(new String[classPath.size()]);
    }

    @SuppressWarnings("restriction")
    private static Set<String> createJavaClasspath(IJavaProject javaProject, Set<IProject> projectOnCp) {
        LinkedHashSet<String> classPath = new LinkedHashSet<String>();
        try {
            // doesn't return jre libraries
            String[] defaultClassPath = JavaRuntime.computeDefaultRuntimeClassPath(javaProject);
            for (String classpathEntry : defaultClassPath) {
                IPath path = new Path(classpathEntry);
                if (isValidPath(path)) {
                    classPath.add(path.toOSString());
                }
            }
            // add CPE_CONTAINER classpathes
            IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
            for (IClasspathEntry entry : rawClasspath) {
                if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
                    IClasspathContainer classpathContainer = JavaCore.getClasspathContainer(entry.getPath(),
                            javaProject);
                    if (classpathContainer != null) {
                        if (classpathContainer instanceof JREContainer) {
                            IClasspathEntry[] classpathEntries = classpathContainer.getClasspathEntries();
                            for (IClasspathEntry iClasspathEntry : classpathEntries) {
                                IPath path = iClasspathEntry.getPath();
                                // smallest possible fix for #1228 Eclipse plugin always uses host VM to resolve JDK classes
                                if (isValidPath(path) && "rt.jar".equals(path.lastSegment())) {
                                    classPath.add(path.toOSString());
                                    break;
                                }
                            }
                        } else {
                            IClasspathEntry[] classpathEntries = classpathContainer.getClasspathEntries();
                            for (IClasspathEntry classpathEntry : classpathEntries) {
                                IPath path = classpathEntry.getPath();
                                // shortcut for real files
                                if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY
                                        && isValidPath(path)) {
                                    classPath.add(path.toOSString());
                                } else {
                                    resolveInWorkspace(classpathEntry, classPath, projectOnCp);
                                }
                            }
                        }
                    }
                }
            }
        } catch (CoreException e) {
            FindbugsPlugin.getDefault().logException(e,
                    "Could not compute aux. classpath for project " + javaProject);
        }
        return classPath;
    }

    private static void resolveInWorkspace(IClasspathEntry classpathEntry, Set<String> classPath,
            Set<IProject> projectOnCp) {
        int entryKind = classpathEntry.getEntryKind();
        switch (entryKind) {
        case IClasspathEntry.CPE_PROJECT:
            Set<String> cp = resolveProjectClassPath(classpathEntry.getPath(), projectOnCp);
            classPath.addAll(cp);
            break;
        case IClasspathEntry.CPE_VARIABLE:
            classpathEntry = JavaCore.getResolvedClasspathEntry(classpathEntry);
            if (classpathEntry == null) {
                return;
            }
            //$FALL-THROUGH$
        case IClasspathEntry.CPE_LIBRARY:
            String lib = resolveLibrary(classpathEntry.getPath());
            if (lib != null) {
                classPath.add(lib);
            }
            break;
        case IClasspathEntry.CPE_SOURCE:
            // ???
            break;
        default:
            break;
        }
    }

    @CheckForNull
    private static String resolveLibrary(IPath path) {
        if (path == null || path.segmentCount() != 1) {
            return null;
        }
        IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
        if (resource == null || !resource.isAccessible()) {
            return null;
        }
        IPath location = resource.getLocation();
        return location == null ? null : location.toOSString();
    }

    private static Set<String> resolveProjectClassPath(IPath path, Set<IProject> projectOnCp) {
        if (path == null || path.segmentCount() != 1) {
            return Collections.emptySet();
        }
        IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment());

        // prevent endless loops because of cyclic project dependencies
        if (project == null || projectOnCp.contains(project)) {
            return Collections.emptySet();
        }
        projectOnCp.add(project);
        IJavaProject javaProject2 = JavaCore.create(project);
        if (javaProject2 != null) {
            return createJavaClasspath(javaProject2, projectOnCp);
        }
        return Collections.emptySet();
    }

    /**
     * @param path may be null
     * @return true if the path is considered as valid for the classpath
     */
    private static boolean isValidPath(IPath path) {
        // segmentCount() > 1 is workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=281189
        // classpath contains unlikely one of root directories like /lib/ "as is"
        return path != null && path.segmentCount() > 1 && path.toFile().exists();
    }

    private static Collection<String> createPluginClassPath(IJavaProject javaProject, Set<IProject> projectOnCp)
            throws CoreException {
        Set<String> javaClassPath = createJavaClasspath(javaProject, projectOnCp);
        IPluginModelBase model = PluginRegistry.findModel(javaProject.getProject());
        if (model == null || model.getPluginBase().getId() == null) {
            return javaClassPath;
        }
        ArrayList<String> pdeClassPath = new ArrayList<>();
        pdeClassPath.addAll(javaClassPath);

        BundleDescription target = model.getBundleDescription();

        Set<BundleDescription> bundles = new HashSet<BundleDescription>();
        // target is null if plugin uses non OSGI format
        if (target != null) {
            addDependentBundles(target, bundles);
        }

        // convert default location (relative to wsp) to absolute path
        IPath defaultOutputLocation = ResourceUtils.relativeToAbsolute(javaProject.getOutputLocation());

        for (BundleDescription bd : bundles) {
            appendBundleToClasspath(bd, pdeClassPath, defaultOutputLocation);
        }

        if (defaultOutputLocation != null) {
            String defaultOutput = defaultOutputLocation.toOSString();
            if (pdeClassPath.indexOf(defaultOutput) > 0) {
                pdeClassPath.remove(defaultOutput);
                pdeClassPath.add(0, defaultOutput);
            }
        }
        return pdeClassPath;
    }

    private static void appendBundleToClasspath(BundleDescription bd, List<String> pdeClassPath,
            IPath defaultOutputLocation) {
        IPluginModelBase model = PluginRegistry.findModel(bd);
        if (model == null) {
            return;
        }
        ArrayList<IClasspathEntry> classpathEntries = new ArrayList<IClasspathEntry>();
        ClasspathUtilCore.addLibraries(model, classpathEntries);

        for (IClasspathEntry cpe : classpathEntries) {
            IPath location;
            if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                location = ResourceUtils.getOutputLocation(cpe, defaultOutputLocation);
            } else {
                location = cpe.getPath();
            }
            if (location == null) {
                continue;
            }
            String locationStr = location.toOSString();
            if (pdeClassPath.contains(locationStr)) {
                continue;
            }
            // extra cleanup for some directories on classpath
            String bundleLocation = bd.getLocation();
            if (bundleLocation != null && !"jar".equals(location.getFileExtension())
                    && new File(bundleLocation).isDirectory()) {
                if (bd.getSymbolicName().equals(location.lastSegment())) {
                    // ignore badly resolved plugin directories inside workspace
                    // ("." as classpath is resolved as plugin root directory)
                    // which is, if under workspace, NOT a part of the classpath
                    continue;
                }
            }
            if (!location.isAbsolute()) {
                location = ResourceUtils.relativeToAbsolute(location);
            }
            if (!isValidPath(location)) {
                continue;
            }
            locationStr = location.toOSString();
            if (!pdeClassPath.contains(locationStr)) {
                pdeClassPath.add(locationStr);
            }
        }
    }

    private static void addDependentBundles(BundleDescription bd, Set<BundleDescription> bundles) {
        // TODO for some reasons, this does not add "native" fragments for the
        // platform. See also: ContributedClasspathEntriesEntry, RequiredPluginsClasspathContainer
        // BundleDescription[] requires = PDEState.getDependentBundles(target);
        BundleDescription[] bundles2 = PDEState.getDependentBundlesWithFragments(bd);
        for (BundleDescription bundleDescription : bundles2) {
            if (bundleDescription == null) {
                continue;
            }
            if (!bundles.contains(bundleDescription)) {
                bundles.add(bundleDescription);
                addDependentBundles(bundleDescription, bundles);
            }
        }
    }

}