Package de.janthomae.leiningenplugin.module

Source Code of de.janthomae.leiningenplugin.module.ModuleCreationUtils$LibraryInfo

package de.janthomae.leiningenplugin.module;

import clojure.lang.LazySeq;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.StdModuleTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import de.janthomae.leiningenplugin.leiningen.LeiningenAPI;
import de.janthomae.leiningenplugin.utils.ClassPathUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Created with IntelliJ IDEA.
* User: Chris Shellenbarger
* Date: 12/6/12
* Time: 5:53 AM
* <p/>
* Class used to assist in creation an IDEA module.  Extracted out of the project creation for reusability and testing purposes.
*/
public class ModuleCreationUtils {

    public final static String LEIN_COMPILE_PATH = "compile-path";
    public final static String LEIN_RESOURCE_PATHS = "resource-paths";
    public final static String LEIN_SOURCE_PATHS = "source-paths";
    public final static String LEIN_JAVA_SOURCE_PATHS = "java-source-paths";
    public final static String LEIN_TEST_PATHS = "test-paths";
    public final static String LEIN_PROJECT_NAME = "name";
    public final static String LEIN_PROJECT_VERSION = "version";
    public final static String LEIN_PROJECT_GROUP = "group";
    /**
     * Default Constructor - intentionally side-effect free.
     */
    public ModuleCreationUtils() {

    }

    /**
     * This function returns a list of virtual files pointing to the paths supporting a particular type (as defined in a leiningen project file: "resource-paths", "test-paths", and "source-paths" are examples).
     * It extracts them based off the corresponding values in leinProjectMap.
     *
     * @param type           The type of paths to extract from the project.  Corresponds with the names of the respective keys in a leiningen project file.  Examples: "resource-paths", "test-paths", or "source-paths"
     * @param leinProjectMap The map to extract values from.
     * @return A list of paths to folders of type.
     */
    public List<String> getPaths(String type, Map leinProjectMap) {
        LazySeq pathStrings = ((LazySeq) leinProjectMap.get(type));
        List<String> results = new ArrayList<String>();
        if (pathStrings != null) {
            for (Object obj : pathStrings) {
                String path = (String) obj;
                results.add(path);
            }
        }
        return results;
    }

    /**
     * Internal method used to add absolute paths to a content entry.
     * <p/>
     * This is done as part of support for multiple source entries.
     * <p/>
     * SIDE-EFFECT: Will modify contentEntry
     *
     * Note: This function will only add values to the content entry if they exist on the file system.  If the path doesn't
     * exist on the file system, then it will be ignored.
     *
     * @param contentEntry The contentEntry to be updated
     * @param paths        The list of paths to add.  These need to be absolute paths.
     * @param isTestSource Indicate if this is a test directory
     */
    protected void addSourceFoldersToContentEntry(final ContentEntry contentEntry, final List<String> paths, final boolean isTestSource) {

        for (final String path : paths) {
            new WriteAction() {
                @Override
                protected void run(Result result) throws Throwable {
                    VirtualFile directory = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
                    if (directory != null) {
                        contentEntry.addSourceFolder(directory, isTestSource);
                    }
                }
            }.execute();
        }
    }

    /**
     * Update the contentEntry with the following values from the leinProjectMap added as source directories.
     * - "resource-paths"
     * - "source-paths"
     * - "test-paths"
     *
     * @param contentEntry   The contentEntry to update.
     * @param leinProjectMap The map to extract values from.
     * @return The contentEntry updated with the sourcePaths added.
     */
    public ContentEntry updateSourceAndResourcesPaths(ContentEntry contentEntry, Map leinProjectMap) {

        List<String> resourcePaths = getPaths(LEIN_RESOURCE_PATHS, leinProjectMap);
        addSourceFoldersToContentEntry(contentEntry, resourcePaths, false);

        List<String> sourcePaths = getPaths(LEIN_SOURCE_PATHS, leinProjectMap);
        addSourceFoldersToContentEntry(contentEntry, sourcePaths, false);

        List<String> javaSourcePaths = getPaths(LEIN_JAVA_SOURCE_PATHS,leinProjectMap);
        addSourceFoldersToContentEntry(contentEntry,javaSourcePaths,false);

        List<String> testPaths = getPaths(LEIN_TEST_PATHS, leinProjectMap);
        addSourceFoldersToContentEntry(contentEntry, testPaths, true);

        return contentEntry;
    }

    /**
     * Update the compiler extension to have the appropriate paths as configured in the project map.
     * <p/>
     * SIDE-EFFECT: Changes state of extension
     *
     * @param extension      The compiler extension.
     * @param leinProjectMap The map to extract values from.
     * @return The compiler extension updated with the given settings.
     */
    public CompilerModuleExtension updateCompilePath(final CompilerModuleExtension extension, Map leinProjectMap) {

        final String outputPathString = (String) leinProjectMap.get(LEIN_COMPILE_PATH);
        new WriteAction() {
            @Override
            protected void run(Result result) throws Throwable {
                try {
                    VirtualFile outputPath = VfsUtil.createDirectoryIfMissing(outputPathString);
                    extension.inheritCompilerOutputPath(false);
                    extension.setCompilerOutputPath(outputPath);
                    extension.setCompilerOutputPathForTests(outputPath);
                } catch (IOException e) {
                    throw new RuntimeException("Could not create output directory" + outputPathString);
                }
            }
        }.execute();
        return extension;
    }

    /**
     * Utility method to obtain the root model of the module.
     * <p/>
     * This performs a read of the iml file so we can reconstruct the state.
     *
     * @param module The module to obtain the root model for.
     * @return The root model of the module.
     */
    public ModifiableRootModel getRootModel(final Module module) {
        return new ReadAction<ModifiableRootModel>() {
            protected void run(Result<ModifiableRootModel> result) throws Throwable {
                result.setResult(ModuleRootManager.getInstance(module).getModifiableModel());
            }
        }.execute().getResultObject();
    }

    /**
     * Private utility to create the module manager, which manages the list of modules that this project knows about.
     *
     * @param ideaProject The idea project.
     * @return The Model for the module manager.
     */
    private ModifiableModuleModel createModuleManager(final Project ideaProject) {
        return new ReadAction<ModifiableModuleModel>() {
            protected void run(Result<ModifiableModuleModel> result) throws Throwable {
                result.setResult(ModuleManager.getInstance(ideaProject).getModifiableModel());
            }
        }.execute().getResultObject();
    }

    /**
     * Create or find the modules root model.
     * <p/>
     * If there exists a module with a matching name, that will be returned.
     * <p/>
     * Otherwise, we'll create a new module and add it to the moduleManager.
     *
     * @param moduleManager The module manager
     * @param name          The name of the module
     * @return The module's modifiable model
     */
    public ModifiableRootModel createModule(ModifiableModuleModel moduleManager, String workingDir, String name) {
        Module module = moduleManager.findModuleByName(name);

        if (module == null) {
            // oh-kay we don't have a module yet.
            String filePath = workingDir + File.separator + FileUtil.sanitizeFileName(name) + ModuleFileType.DOT_DEFAULT_EXTENSION;
            module = moduleManager.newModule(filePath, StdModuleTypes.JAVA.getId());
        }

        return getRootModel(module);
    }

    /**
     * Initialize the source, resources, test, and compile paths on module.
     *
     * @param projectMap  The leiningen project map.
     * @param module      The module to update
     * @param contentRoot The virtual file pointing to the leiningen project root directory. (Usually where the project.clj file is)
     */
    public void initializeModulePaths(Map projectMap, ModifiableRootModel module, VirtualFile contentRoot) {
        //Set up the paths
        module.inheritSdk();
        final ContentEntry contentEntry = module.addContentEntry(contentRoot);

        //Maven doesn't let you have source files that aren't configured in the pom.xml for consistency reasons.
        //We'll apply the same laws to leiningen projects.
        contentEntry.clearSourceFolders();

        //Add the source and resource paths to the module
        updateSourceAndResourcesPaths(contentEntry, projectMap);

        //Handle the compile path (output)
        CompilerModuleExtension compilerExtension = module.getModuleExtension(CompilerModuleExtension.class);
        updateCompilePath(compilerExtension, projectMap);
    }

    /**
     * Initialize the dependencies for the module.  This will add any dependencies to the list of project libraries and
     * then add those libraries to the module via Order Entries.
     *
     *
     * @param module           The module to update.
     * @param projectLibraries The list of project libraries.
     * @param dependencyMaps The list of maps containing the dependency information.
     * @return The set of libraries that were created.
     */
    private List<LibraryInfo> initializeDependencies(ModifiableRootModel module, LibraryTable.ModifiableModel projectLibraries, List dependencyMaps) {

        //Reset them module's library order entries here - this actually happens in org.jetbrains.idea.maven.importing.MavenRootModelAdapter.initOrderEntries()
        for (OrderEntry orderEntry : module.getOrderEntries()) {
            if (orderEntry instanceof LibraryOrderEntry) {
                //Remove any library from the project list so it can be refreshed.
              Library library = ((LibraryOrderEntry) orderEntry).getLibrary();
              if (library != null) {
                projectLibraries.removeLibrary(library);
              }
                module.removeOrderEntry(orderEntry);
            }
        }

        //Add the dependencies to the projects's library table - this is how maven does it - but we could put the libraries directly on the module - but maybe it's better if we share a lot of libraries between modules.
        List<LibraryInfo> libraries = createLibraries(projectLibraries, dependencyMaps);

        //Now add the libraries to the modules.
        for (LibraryInfo entry : libraries) {
            module.addLibraryEntry(entry.library).setScope(entry.dependencyScope);
        }
        return libraries;
    }


    /**
     * This method imports a leiningen module from a leiningen project file and imports it into the idea project.
     * <p/>
     * Notes:
     * <p/>
     * Each of the IDEA components has a getModifiableModel on it.  This method returns a new instance each time you
     * invoke it.  Once you have a modifiable model of the component you wish to update, you mutate it to the state
     * you wish.  Once you're done, you call commit() on the modifiable model and it updates the component it came from.
     * <p/>
     * Since a lot of the components are persisted in files, commit() updates these files as well.  Therefore you need
     * to make any calls to commit() from within a WriteAction.
     *
     * @param ideaProject The IDEA project to add the leiningen module to.
     * @param leinProjectFile  The leiningen project file
     * @return The leiningen project map.
     */
    public Map importModule(Project ideaProject, VirtualFile leinProjectFile) {

        ClassPathUtils.getInstance().switchToPluginClassLoader();
        Map projectMap = LeiningenAPI.loadProject(leinProjectFile.getPath());
        String name = (String) projectMap.get(LEIN_PROJECT_NAME);

        final ModifiableModuleModel moduleManager = createModuleManager(ideaProject);
        final ModifiableRootModel module = createModule(moduleManager, leinProjectFile.getParent().getPath(), name);
        initializeModulePaths(projectMap, module, leinProjectFile.getParent());

        ProjectRootManagerEx rootManager = ProjectRootManagerEx.getInstanceEx(ideaProject);
        module.setSdk(rootManager.getProjectSdk());

        //Setup the dependencies
        // Based loosely on org.jetbrains.idea.maven.importing.MavenRootModelAdapter#addLibraryDependency

        //We could use the module table here, but then the libraries wouldn't be shared across modules.
        final LibraryTable.ModifiableModel projectLibraries = ProjectLibraryTable.getInstance(ideaProject).getModifiableModel();

        //Load all the dependencies from the project file
        List dependencyMaps = LeiningenAPI.loadDependencies(leinProjectFile.getCanonicalPath());
        final List<LibraryInfo> dependencies = initializeDependencies(module, projectLibraries,dependencyMaps);

        new WriteAction() {
            @Override
            protected void run(Result result) throws Throwable {

                for (LibraryInfo library : dependencies) {
                    library.modifiableModel.commit();
                }

                //Save the project libraries
                projectLibraries.commit();

                //Save the module itself to the module file.
                module.commit();

                //Save the list of modules that are in this project to the IDEA project file
                moduleManager.commit();
            }
        }.execute();

        return projectMap;
    }

    /**
     * Create the libraries for the list of dependency maps.
     * <p/>
     * This will add the libraries to the libraryTable (if they don't already exist) and return a map of libraries mapped to their scopes as used in the module.
     *
     * @param libraryTable   The library table to add the libraries to.
     * @param dependencyMaps The list of dependency maps definining the libraries needed.
     * @return A Map of the Libraries which were described in dependencyMaps along with their scope for the module
     */
    private List<LibraryInfo> createLibraries(LibraryTable.ModifiableModel libraryTable, List dependencyMaps) {

        List<LibraryInfo> result = new ArrayList<LibraryInfo>();
        final String LEIN_LIB_PREFIX = "Leiningen";
        for (Object obj : dependencyMaps) {
            Map dependency = (Map) obj;
            //Check if the library already exists
            String libraryName = LEIN_LIB_PREFIX + ": " + dependency.get("groupid") + ":" +
                    dependency.get("artifactid") + ":" + dependency.get("version");
            Library library = libraryTable.getLibraryByName(libraryName);
            if (library == null) {
                library = libraryTable.createLibrary(libraryName);
            }

            // Add the library to a library model, which represents the data for a single library.
            Library.ModifiableModel libraryModel = library.getModifiableModel();

            //Right now only deal with classes - a lot of clojure libraries have the .clj in them and not in a separate file
            //Remove existing classes as this is what maven does - you need to declare the dependencies in the project file
            for (String url : libraryModel.getUrls(OrderRootType.CLASSES)) {
                libraryModel.removeRoot(url, OrderRootType.CLASSES);
            }

            File file = ((File) dependency.get("file"));
            String path = file.getAbsolutePath();
            String url = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, path) + JarFileSystem.JAR_SEPARATOR;
            libraryModel.addRoot(url, OrderRootType.CLASSES);

            DependencyScope scope = determineScope((String) dependency.get("scope"));
            LibraryInfo libraryInfo = new LibraryInfo();
            libraryInfo.library = library;
            libraryInfo.modifiableModel = libraryModel;
            libraryInfo.dependencyScope = scope;
            result.add(libraryInfo);
        }
        return result;
    }

    /**
     * Do a check to determine the proper scope.
     *
     * @param s The string scope.
     * @return A DependencyScope object
     */
    private DependencyScope determineScope(String s) {

        //Issue 35: If the scope that is on the dependency doesn't match one of the DependencyScope types, then default to compile scope.
        DependencyScope scope = DependencyScope.COMPILE;

        if (s.equalsIgnoreCase("compile")) {
            scope = DependencyScope.COMPILE;
        }

        if (s.equalsIgnoreCase("test")) {
            scope = DependencyScope.TEST;
        }

        if (s.equalsIgnoreCase("runtime")) {
            scope = DependencyScope.RUNTIME;
        }

        if (s.equalsIgnoreCase("provided")) {
            scope = DependencyScope.PROVIDED;
        }
        return scope;
    }

    private static class LibraryInfo {
        public Library library;
        public Library.ModifiableModel modifiableModel;
        public DependencyScope dependencyScope;
    }

}
TOP

Related Classes of de.janthomae.leiningenplugin.module.ModuleCreationUtils$LibraryInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.