org.continuousassurance.swamp.eclipse.ImprovedClasspathHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.continuousassurance.swamp.eclipse.ImprovedClasspathHandler.java

Source

/*
 * Copyright 2016-2017 Malcolm Reid Jr.
 * 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 org.continuousassurance.swamp.eclipse;

import static org.continuousassurance.swamp.eclipse.Activator.PLUGIN_ID;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.SubMonitor;
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;

public class ImprovedClasspathHandler {
    /**
     * Eclipse project option for getting source compiler version 
     */
    private static final String SOURCE_VERSION_OPTION = "org.eclipse.jdt.core.compiler.source";
    /**
     * Eclipse project option for getting target compiler version
     */
    private static final String TARGET_VERSION_OPTION = "org.eclipse.jdt.core.compiler.codegen.targetPlatform";
    /**
     * The name used for storing SWAMP dependencies
     */
    public static final String SWAMPBIN_DIR = "swampbin";
    /**
     * Dash char
     */
    private static final char DASH = '-';
    /**
     * Path to directory that holds all of the binaries that the project within
     * this ImprovedClasspathHandler object is dependent on
     */
    private IPath SWAMPBIN_PATH = null;

    // TODO Consider making these sets of entries instead of Lists
    /**
     * List of source code classpath entries
     */
    private List<IClasspathEntry> sources = null;
    /**
     * List of library classpath entries
     */
    private List<IClasspathEntry> libs = null;
    /**
     * List of system library classpath entries
     */
    private List<IClasspathEntry> systemLibs = null;
    /**
     * List of exported classpath entries
     */
    private List<IClasspathEntry> exportedEntries = null;
    /**
     * List of ImprovedClasspathHandler objects for projects dependent upon
     * this one
     */
    private List<ImprovedClasspathHandler> dependentProjects = null;
    /**
     * Map of projects that we have already visited in our DFS of dependencies
     */
    private Map<String, ImprovedClasspathHandler> visitedProjects = null;
    /**
     * ImprovedClasspathHandler object for the Eclipse Java project that was
     * selected to be assessed (i.e. the project that all of the dependent
     * projects are dependent on)
     */
    private ImprovedClasspathHandler root = null;
    /**
     * List of file paths to zip when packaging
     */
    private Set<String> filesToArchive = null;
    /**
     * Whether system libraries should be excluded from packaging
     */
    private boolean excludeSysLibs = false;
    /**
     * Whether this project depends on binaries that are moved into Swampbin
     */
    private boolean hasSwampbinDependencies = false;
    /**
     * The Java project underneath this ImprovedClasspathHandler object
     */
    private IJavaProject project;
    /**
     * Source code version
     */
    private String srcVersion;
    /**
     * Compiler target version
     */
    private String targetVersion;
    /**
     * Submonitor reference
     */
    private SubMonitor subMonitor;

    /**
     * Constructor for ImprovedClasspathHandler
     * @param project the Java project that we will generate a build file for
     * @param root the ImprovedClasspathHandler object for the project (this is the root of the recursive tree that we build from projects having dependencies)
     * @param exclSysLibs if true, Java system libraries get copied into the package at submission
     * @param subMonitor submonitor for tracking progress
     */
    public ImprovedClasspathHandler(IJavaProject project, ImprovedClasspathHandler root, boolean exclSysLibs,
            SubMonitor subMonitor) {
        this.excludeSysLibs = exclSysLibs;
        sources = new ArrayList<IClasspathEntry>();
        libs = new ArrayList<IClasspathEntry>();
        systemLibs = new ArrayList<IClasspathEntry>();
        dependentProjects = new ArrayList<ImprovedClasspathHandler>();
        exportedEntries = new ArrayList<IClasspathEntry>();

        this.project = project;
        this.srcVersion = this.project.getOption(SOURCE_VERSION_OPTION, true);
        this.targetVersion = this.project.getOption(TARGET_VERSION_OPTION, true);

        if (root == null) {
            this.root = this;
            this.subMonitor = subMonitor;
            this.subMonitor.setWorkRemaining(100);
            visitedProjects = new HashMap<String, ImprovedClasspathHandler>();
            SWAMPBIN_PATH = setupBinDir(project.getProject());
            filesToArchive = new HashSet<String>();
        } else {
            this.root = root;
            visitedProjects = root.visitedProjects;
            SWAMPBIN_PATH = root.SWAMPBIN_PATH;
            filesToArchive = root.filesToArchive;
        }

        try {
            project.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD, null);
        } catch (CoreException e1) {
            // TODO Auto-generated catch block
            System.err.println("Unable to do a clean build on the project for some reason");
            e1.printStackTrace();
        }

        IClasspathEntry[] entries = null;
        try {
            entries = project.getRawClasspath();
            if (entries == null || entries.length == 0) {
                return;
            }
        } catch (JavaModelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return;
        }
        IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
        try {
            for (IClasspathEntry entry : entries) {
                int kind = entry.getEntryKind();
                if (this.subMonitor != null) {
                    if (this.subMonitor.isCanceled()) {
                        System.out.println("Sub monitor got cancelled!");
                    }
                    this.subMonitor.split(100 / SwampSubmitter.CLASSPATH_ENTRY_TICKS);
                }
                if (kind == IClasspathEntry.CPE_SOURCE) {
                    handleSource(entry, wsRoot);
                } else if (kind == IClasspathEntry.CPE_LIBRARY) {
                    handleLibrary(entry, wsRoot);
                } else if (kind == IClasspathEntry.CPE_PROJECT) {
                    handleProject(entry, wsRoot);
                } else if (kind == IClasspathEntry.CPE_VARIABLE) {
                    handleVariable(entry, wsRoot);
                } else { // kind == IClasspathEntry.CPE_CONTAINER
                    handleContainer(entry, wsRoot);
                }
            }
        } catch (IOException | JavaModelException e) {
            // TODO Report this error! This is very bad
            e.printStackTrace();
        }
        if (hasSwampbinDependencies) {
            filesToArchive.add(SWAMPBIN_PATH.toOSString());
        }
    }

    /**
     * Creates a swampbin directory for holding dependencies that need to be copied
     * @param project the Java project that we will generate a build file for
     * @return path of the swampbin directory
     */
    private IPath setupBinDir(IProject project) {
        // make SWAMPBIN directory
        String path = project.getWorkingLocation(PLUGIN_ID).toOSString() + File.separator + SWAMPBIN_DIR;
        File f = new File(path);
        if (f.exists()) {
            System.out.println("SWAMPBIN already exists but we should be deleting it now!");
            try {
                FileUtils.deleteDirectory(f);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        f.mkdir();
        return new org.eclipse.core.runtime.Path(path);
    }

    /**
     * Handles a source entry in the classpath
     * @param entry the classpath entry
     * @param root the workspace root (necessary for getting the file specified by this entry)
     */
    private void handleSource(IClasspathEntry entry, IWorkspaceRoot root) {
        // Each entry either has an associated Output Location or goes to the project's default output location
        // Associated output locations are also just absolute paths (e.g. /MalcolmsProject/bin) just like sources
        // We'll need to mkdir all of these output locations but that shouldn't be hard
        //System.out.println("Source absolute path: " + entry.getPath());
        //System.out.println("Associated output location: " + entry.getOutputLocation());
        sources.add(entry);
        IFile file = root.getFile(entry.getPath());
        IProject project = file.getProject();
        IPath projectPath = project.getLocation();
        System.out.println("Source location: " + projectPath.toOSString());

        // (1) Copy the files from here to plug-in area
        File src = new File(projectPath.toOSString());
        File dst = new File(getRootProjectPluginLocation() + File.separator + project.getName());
        System.out.println("Src path: " + src.getPath());
        System.out.println("Dst path: " + dst.getPath());
        try {
            FileUtils.copyDirectory(src, dst);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            // TODO Handle this better!
            e.printStackTrace();
        }
        filesToArchive.add(dst.getPath().toString());

        // (2) Name the directory the Eclipse project name
        // (3) Add that path to filesToArchive

        //filesToArchive.add(projectPath.toOSString());
    }

    /**
     * Handles a library entry in the classpath
     * @param entry the classpath entry
     * @param root the workspace root
     * @throws IOException
     */
    private void handleLibrary(IClasspathEntry entry, IWorkspaceRoot root) throws IOException {
        // 3 types of library entries: internal (this project), internal (another project), and external
        // (1) Rooted absolute path
        // (2) Rooted absolute path (but with different project directory) --> Need to copy this to swampbin
        // (3) Actual absolute path to somewhere else on the filesystem --> Need to copy this to swampbin
        System.out.println("\n\n\n\n");
        System.out.println("Library absolute path: " + entry.getPath().makeAbsolute());
        //System.out.println("First segment: " + entry.getPath().segment(0));
        IFile file = root.getFile(entry.getPath());
        System.out.println("File project: " + file.getProject().getName());

        if (file.getProject().equals(this.project.getProject())) {
            System.out.println("Is inside project");
            libs.add(entry);
        } else {
            System.out.println("Is outside project");
            IFile libFile = root.getFile(entry.getPath());
            IProject libProject = libFile.getProject();
            String filename = getLibraryFileName(entry.getPath());
            IPath destPath;
            if (libProject.exists()) {
                if (libProject.isOpen()) {
                    try {
                        System.out.println("Local project");
                        destPath = copyWorkspacePathIntoBinDir(libFile, filename, SWAMPBIN_PATH);
                    } catch (Exception e) {
                        System.out.println("Local project that failed");
                        String srcPath = getProjectLibraryLocation(libProject, entry.getPath());
                        destPath = copyAbsolutePathIntoBinDir(srcPath, filename, SWAMPBIN_PATH);
                    }
                } else {
                    System.out.println("Local project that's closed");
                    String srcPath = getProjectLibraryLocation(libProject, entry.getPath());
                    destPath = copyAbsolutePathIntoBinDir(srcPath, filename, SWAMPBIN_PATH);
                }
            } else {
                System.out.println("Not a project - just an absolute path");
                destPath = copyAbsolutePathIntoBinDir(entry.getPath().toOSString(), filename, SWAMPBIN_PATH);
            }
            hasSwampbinDependencies = true;
            System.out.println("SWAMPBIN path: " + destPath);
            IClasspathEntry newEntry = JavaCore.newLibraryEntry(destPath, entry.getSourceAttachmentPath(),
                    entry.getSourceAttachmentRootPath());
            System.out.println("New entry path: " + newEntry.getPath());
            libs.add(newEntry);
            if (entry.isExported()) {
                exportedEntries.add(newEntry);
            }
        }
    }

    /**
     * Handles a project entry in the classpath. If not already processed, recursively create a new ImprovedClasspathHandler object for the dependent project
     * @param entry the project entry
     * @param root the workspace root
     */
    private void handleProject(IClasspathEntry entry, IWorkspaceRoot root) {
        String path = entry.getPath().toOSString();
        ImprovedClasspathHandler ich;
        if (visitedProjects.containsKey(path)) {
            ich = visitedProjects.get(path);
            dependentProjects.add(ich);
        } else {
            IProject project = root.getProject(entry.getPath().toOSString());
            ich = new ImprovedClasspathHandler(JavaCore.create(project), this.root, this.root.excludeSysLibs, null);
            dependentProjects.add(ich);
            visitedProjects.put(path, ich);
        }
        for (IClasspathEntry e : ich.getExportedEntries()) {
            this.libs.add(e);
            if (entry.isExported()) {
                this.exportedEntries.add(e);
            }
        }
    }

    /**
     * Handles a variable entry in the classpath. A variable entry can be resolved to either a library entry or a project entry
     * @param entry the variable entry
     * @param root the workspace entry
     * @throws IOException
     */
    private void handleVariable(IClasspathEntry entry, IWorkspaceRoot root) throws IOException {
        IClasspathEntry resolvedEntry = JavaCore.getResolvedClasspathEntry(entry);
        if (resolvedEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
            handleLibrary(resolvedEntry, root);
        } else {
            handleProject(resolvedEntry, root);
        }
    }

    /**
     * Handles a container entry in the classpath. A container entry contains 1+ entries each of which is either a library entry or a project entry
     * @param entry the container entry
     * @param root the workspace root
     * @throws IOException
     * @throws JavaModelException
     */
    public void handleContainer(IClasspathEntry entry, IWorkspaceRoot root) throws IOException, JavaModelException {
        IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project);
        System.out.println("Here's a container" + container);
        int kind = container.getKind();
        if ((this.excludeSysLibs)
                && (kind == IClasspathContainer.K_APPLICATION || kind == IClasspathContainer.K_DEFAULT_SYSTEM)) {
            System.out.println("System library container");
            System.out.println(entry.getPath());
            return;
        }
        for (IClasspathEntry subEntry : container.getClasspathEntries()) {
            if (subEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
                handleLibrary(subEntry, root);
            } else {
                handleProject(subEntry, root);
            }
        }
    }

    /**
     * Deletes swampbin directory
     * @throws IOException
     */
    public void deleteSwampBin() throws IOException {
        File f = SWAMPBIN_PATH.toFile();
        if (f.exists()) {
            FileUtils.forceDelete(f);
        }
    }

    // TODO THIS NEEDS SERIOUS, INDUSTRIAL-STRENGTH TESTING - 
    // The assumption that it's making is that a project in our workspace is always a top-level directory in the root
    // (i.e. that entryPath.removeFirstSegments(1) is a valid project name
    /**
     * Utility method for getting project library location
     * @param project the project
     * @param entryPath the path of the entry
     * @return project library location
     */
    private static String getProjectLibraryLocation(IProject project, IPath entryPath) {
        String projectDir = project.getLocation().toOSString();
        String srcFile = projectDir + File.separator + entryPath.removeFirstSegments(1);
        return srcFile;
    }

    /**
     * Given a workspace-relative path (see Eclipse documentation) for a dependency, copies it into the specified directory 
     * @param file the file to be copied
     * @param filename name of the file
     * @param swampBinPath path of swampbin directory
     * @return path of the newly created copy
     * @throws CoreException
     */
    private static IPath copyWorkspacePathIntoBinDir(IFile file, String filename, IPath swampBinPath)
            throws CoreException {
        String filePath = swampBinPath.toOSString() + File.separator + filename;
        IPath destPath = new org.eclipse.core.runtime.Path(filePath);
        file.copy(destPath, true, null);
        //return destPath;
        return new org.eclipse.core.runtime.Path(File.separator + SWAMPBIN_DIR + File.separator + filename);
    }

    /**
     * Given an absolute path for a dependency, copies it into the specified directory
     * @param srcStr path of the file
     * @param filename name of the file
     * @param swampBinPath path of the swampbin directory
     * @return path of the newly created copy
     * @throws IOException
     */
    private static IPath copyAbsolutePathIntoBinDir(String srcStr, String filename, IPath swampBinPath)
            throws IOException {
        String destStr = swampBinPath.toOSString() + File.separator + filename;
        Path destPath = new File(destStr).toPath();
        Path srcPath = new File(srcStr).toPath();
        // This copy needs to be forced to disk
        System.out.println("Path we're reading from: " + srcPath);
        byte[] bytes = Files.readAllBytes(srcPath);
        System.out.println("Length of file in bytes: " + bytes.length);
        OpenOption options[] = { StandardOpenOption.DSYNC, StandardOpenOption.CREATE, StandardOpenOption.WRITE };
        System.out.println("Path we're writing to: " + destPath);
        Files.write(destPath, bytes, options);
        return new org.eclipse.core.runtime.Path(File.separator + SWAMPBIN_DIR + File.separator + filename);
    }

    /**
     * Gets the new name for a file (replacing separator and device separator characters)
     * @param path the path of the file
     * @return sanitized path
     */
    private static String getLibraryFileName(IPath path) {
        String strPath = path.toOSString();

        //if (strPath.charAt(0) == IPath.SEPARATOR) {
        if (strPath.charAt(0) == File.separatorChar) {
            strPath = strPath.substring(1);
        }
        //strPath = strPath.replace(IPath.SEPARATOR, DASH);
        strPath = strPath.replace(File.separatorChar, DASH);
        strPath = strPath.replace(IPath.DEVICE_SEPARATOR, DASH);
        return strPath;
    }

    /**
     * Accessor for set of filepaths for files that need to be in the archive uploaded for the package
     * @return set of filepaths
     */
    public Set<String> getFilesToArchive() {
        System.out.println("FILES TO ARCHIVE");
        for (String s : filesToArchive) {
            System.out.println(s);
        }
        return filesToArchive;
    }

    /**
     * Accessor for this ImprovedClasspathHandler object's project's name
     * @return project name
     */
    public String getProjectName() {
        //return Utils.getProjectDirectory(project.getProject());
        return project.getProject().getName();
    }

    /**
     * Accessor for source code version of this ImprovedClasspathHandler object's project
     * @return version of source code (e.g. 1.7)
     */
    public String getSourceVersion() {
        return srcVersion;
    }

    /**
     * Accessor for target version of this ImprovedClasspathHandler object's project
     * @return version of target
     */
    public String getTargetVersion() {
        return targetVersion;
    }

    /**
     * Accessor for library classpath entries 
     * @return list of library classpath entries
     */
    public List<IClasspathEntry> getLibraryClasspath() {
        return libs;
    }

    /**
     * Accessor for system library classpath entries
     * @return list of system library classpath entries
     */
    public List<IClasspathEntry> getSystemLibraryClasspath() {
        return systemLibs;
    }

    /**
     * Accessor for source classpath entries
     * @return list of source classpath entries
     */
    public List<IClasspathEntry> getSourceClasspath() {
        return sources;
    }

    /**
     * Accessor for exported classpath entries
     * @return list of exported classpath entries
     */
    public List<IClasspathEntry> getExportedEntries() {
        return exportedEntries;
    }

    /**
     * Get default output location for the project (used if the source entry doesn't have a specified output location)
     * @return project's default output location
     */
    public IPath getDefaultOutputLocation() {
        try {
            System.out.println("Absolute default output location: " + project.getOutputLocation().makeAbsolute());
            System.out.println("Relative default output location: " + project.getOutputLocation().makeRelative());
            return project.getOutputLocation().makeAbsolute();
        } catch (JavaModelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Get project plugin location for the root project
     * @return project plugin location
     */
    public String getRootProjectPluginLocation() {
        return root.project.getProject().getWorkingLocation(PLUGIN_ID).toOSString();
    }

    /**
     * Get project plugin location for the project in this 
     * ImprovedClasspathHandler (this is a directory in which we can store
     * project-specific files on behalf of this plug-in)
     * @return
     */
    public String getProjectPluginLocation() {
        return project.getProject().getWorkingLocation(PLUGIN_ID).toOSString();
    }

    public String getEncoding() {
        String encoding = "";
        try {
            encoding = project.getProject().getDefaultCharset(true);
        } catch (CoreException e) {
        }
        return encoding;
    }

    /**
     * Accessor for dependent projects
     * @return list of ImprovedClasspathHandlers for dependent projects
     */
    public List<ImprovedClasspathHandler> getDependentProjects() {
        return dependentProjects;
    }

    /**
     * Gets referenced projects for a given Java project
     * @param jp the Java project
     * @param projectSet the set of referenced projects
     */
    public static void getReferencedProjects(IJavaProject jp, Set<IProject> projectSet) {
        projectSet.add(jp.getProject());
        try {
            IProject[] prjArray = jp.getProject().getReferencedProjects();
            for (IProject p : prjArray) {
                getReferencedProjects(JavaCore.create(p), projectSet);
            }
        } catch (CoreException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}