diff --git a/java/java-tests/testData/moduleRootManager/unloadedModules/.idea/modules.xml b/java/java-tests/testData/moduleRootManager/unloadedModules/.idea/modules.xml new file mode 100644 index 000000000000..f7490c160221 --- /dev/null +++ b/java/java-tests/testData/moduleRootManager/unloadedModules/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/java/java-tests/testData/moduleRootManager/unloadedModules/.idea/workspace.xml b/java/java-tests/testData/moduleRootManager/unloadedModules/.idea/workspace.xml new file mode 100644 index 000000000000..cc4eb0e03b1f --- /dev/null +++ b/java/java-tests/testData/moduleRootManager/unloadedModules/.idea/workspace.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/java-tests/testData/moduleRootManager/unloadedModules/dep.iml b/java/java-tests/testData/moduleRootManager/unloadedModules/dep.iml new file mode 100644 index 000000000000..92baf1cafcc8 --- /dev/null +++ b/java/java-tests/testData/moduleRootManager/unloadedModules/dep.iml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/java/java-tests/testData/moduleRootManager/unloadedModules/main.iml b/java/java-tests/testData/moduleRootManager/unloadedModules/main.iml new file mode 100644 index 000000000000..feb23b47ceff --- /dev/null +++ b/java/java-tests/testData/moduleRootManager/unloadedModules/main.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/java/java-tests/testData/moduleRootManager/unloadedModules/util.iml b/java/java-tests/testData/moduleRootManager/unloadedModules/util.iml new file mode 100644 index 000000000000..fefaba3e3a7b --- /dev/null +++ b/java/java-tests/testData/moduleRootManager/unloadedModules/util.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/java/java-tests/testSrc/com/intellij/roots/UnloadedModulesConfigurationTest.kt b/java/java-tests/testSrc/com/intellij/roots/UnloadedModulesConfigurationTest.kt new file mode 100644 index 000000000000..34ee06d9369f --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/roots/UnloadedModulesConfigurationTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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 com.intellij.roots + +import com.intellij.openapi.application.ex.PathManagerEx +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.ex.ProjectManagerEx +import com.intellij.openapi.project.impl.ProjectManagerImpl +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.testFramework.ModuleTestCase +import java.io.File + +/** + * @author nik + */ +class UnloadedModulesConfigurationTest : ModuleTestCase() { + fun `test load project`() { + val projectPath = File(PathManagerEx.getTestDataPath(), "moduleRootManager/unloadedModules").absolutePath + val project = getProjectManager().loadAndOpenProject(projectPath)!! + try { + val moduleManager = ModuleManager.getInstance(project) + assertEquals(3, moduleManager.allModuleDescriptions.size) + assertEquals(2, moduleManager.unloadedModuleDescriptions.size) + + val util = moduleManager.unloadedModuleDescriptions.find { it.name == "util" }!! + val projectDirUrl = VfsUtilCore.pathToUrl(FileUtil.toSystemIndependentName(projectPath)) + assertEquals("$projectDirUrl/util", assertOneElement(util.contentRoots).url) + assertEmpty(util.dependencyModuleNames) + + val dep = moduleManager.unloadedModuleDescriptions.find { it.name == "dep" }!! + assertEquals("$projectDirUrl/dep", assertOneElement(dep.contentRoots).url) + assertEquals("util", assertOneElement(dep.dependencyModuleNames)) + } + finally { + getProjectManager().forceCloseProject(project, true) + } + } + + fun `test set unloaded modules`() { + val a = createModule("a") + val b = createModule("b") + ModuleRootModificationUtil.addDependency(a, b) + val moduleManager = ModuleManager.getInstance(project) + moduleManager.setUnloadedModules(listOf("a")) + assertEquals("a", assertOneElement(moduleManager.unloadedModuleDescriptions).name) + assertNull(moduleManager.findModuleByName("a")) + assertNotNull(moduleManager.findModuleByName("b")) + + moduleManager.setUnloadedModules(listOf("b")) + assertEquals("b", assertOneElement(moduleManager.unloadedModuleDescriptions).name) + assertNotNull(moduleManager.findModuleByName("a")) + assertNull(moduleManager.findModuleByName("b")) + } + + private fun getProjectManager() = ProjectManagerEx.getInstanceEx() as ProjectManagerImpl +} \ No newline at end of file diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java index 5ab57a25f8b0..94b5e9a96d8c 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsGlobalLoader.java @@ -23,7 +23,9 @@ import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.JpsElementChildRole; +import org.jetbrains.jps.model.JpsElementFactory; import org.jetbrains.jps.model.JpsGlobal; +import org.jetbrains.jps.model.JpsModel; import org.jetbrains.jps.model.ex.JpsElementChildRoleBase; import org.jetbrains.jps.model.serialization.impl.JpsPathVariablesConfigurationImpl; import org.jetbrains.jps.model.serialization.library.JpsLibraryTableSerializer; @@ -55,11 +57,21 @@ public class JpsGlobalLoader extends JpsLoaderBase { public static void loadGlobalSettings(JpsGlobal global, String optionsPath) throws IOException { Path optionsDir = Paths.get(FileUtil.toCanonicalPath(optionsPath)); - new JpsGlobalLoader(global, Collections.emptyMap()).loadGlobalComponents(optionsDir, optionsDir.resolve("other.xml"), new PathVariablesSerializer()); - Map pathVariables = JpsModelSerializationDataService.computeAllPathVariables(global); + Map pathVariables = loadPathVariables(global, optionsDir); new JpsGlobalLoader(global, pathVariables).load(optionsDir); } + private static Map loadPathVariables(JpsGlobal global, Path optionsDir) { + new JpsGlobalLoader(global, Collections.emptyMap()).loadGlobalComponents(optionsDir, optionsDir.resolve("other.xml"), new PathVariablesSerializer()); + return JpsModelSerializationDataService.computeAllPathVariables(global); + } + + public static Map computeAllPathVariables(@NotNull String optionsPath) { + JpsModel model = JpsElementFactory.getInstance().createModel(); + Path optionsDir = Paths.get(FileUtil.toCanonicalPath(optionsPath)); + return loadPathVariables(model.getGlobal(), optionsDir); + } + /** * @deprecated use {@link JpsModelSerializationDataService#getPathVariableValue(org.jetbrains.jps.model.JpsGlobal, String)} instead */ diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java index 63fbcdccc34e..12d52fe9000e 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/JpsProjectLoader.java @@ -124,7 +124,8 @@ public class JpsProjectLoader extends JpsLoaderBase { loadComponents(dir, defaultConfigFile, serializer, myProject); } } - loadModules(loadRootElement(dir.resolve("modules.xml")), projectSdkType); + Path workspaceFile = dir.resolve("workspace.xml"); + loadModules(loadRootElement(dir.resolve("modules.xml")), projectSdkType, workspaceFile); Runnable timingLog = TimingLog.startActivity("loading project libraries"); for (Path libraryFile : listXmlFiles(dir.resolve("libraries"))) { @@ -150,7 +151,7 @@ public class JpsProjectLoader extends JpsLoaderBase { for (Path configurationFile : listXmlFiles(dir.resolve("runConfigurations"))) { JpsRunConfigurationSerializer.loadRunConfigurations(myProject, loadRootElement(configurationFile)); } - JpsRunConfigurationSerializer.loadRunConfigurations(myProject, JDomSerializationUtil.findComponent(loadRootElement(dir.resolve("workspace.xml")), "RunManager")); + JpsRunConfigurationSerializer.loadRunConfigurations(myProject, JDomSerializationUtil.findComponent(loadRootElement(workspaceFile), "RunManager")); runConfTimingLog.run(); } } @@ -197,7 +198,7 @@ public class JpsProjectLoader extends JpsLoaderBase { } } } - loadModules(iprRoot, projectSdkType); + loadModules(iprRoot, projectSdkType, iwsFile); loadProjectLibraries(JDomSerializationUtil.findComponent(iprRoot, "libraryTable")); loadArtifacts(JDomSerializationUtil.findComponent(iprRoot, "ArtifactManager")); if (hasRunConfigurationSerializers()) { @@ -229,18 +230,25 @@ public class JpsProjectLoader extends JpsLoaderBase { JpsLibraryTableSerializer.loadLibraries(libraryTableElement, myProject.getLibraryCollection()); } - private void loadModules(@Nullable Element root, final @Nullable JpsSdkType projectSdkType) { + private void loadModules(@Nullable Element root, final @Nullable JpsSdkType projectSdkType, Path workspaceFile) { Runnable timingLog = TimingLog.startActivity("loading modules"); Element componentRoot = JDomSerializationUtil.findComponent(root, "ProjectModuleManager"); if (componentRoot == null) return; + Set unloadedModules = new HashSet<>(); + if (Files.exists(workspaceFile)) { + Element unloadedModulesList = JDomSerializationUtil.findComponent(loadRootElement(workspaceFile), "UnloadedModulesList"); + for (Element element : JDOMUtil.getChildren(unloadedModulesList, "module")) { + unloadedModules.add(element.getAttributeValue("name")); + } + } final Set foundFiles = new THashSet<>(); final List moduleFiles = new ArrayList<>(); for (Element moduleElement : JDOMUtil.getChildren(componentRoot.getChild("modules"), "module")) { final String path = moduleElement.getAttributeValue("filepath"); final Path file = Paths.get(path); - if (foundFiles.add(file)) { + if (foundFiles.add(file) && !unloadedModules.contains(getModuleName(file))) { moduleFiles.add(file); } } @@ -285,7 +293,7 @@ public class JpsProjectLoader extends JpsLoaderBase { } if (data == null) { - LOG.info("Module '" + FileUtil.getNameWithoutExtension(file.getFileName().toString()) + "' is skipped: " + file.toAbsolutePath() + " doesn't exist"); + LOG.info("Module '" + getModuleName(file) + "' is skipped: " + file.toAbsolutePath() + " doesn't exist"); } return Pair.create(file, data); @@ -323,7 +331,7 @@ public class JpsProjectLoader extends JpsLoaderBase { @NotNull private static JpsModule loadModule(@NotNull Path file, @NotNull Element moduleRoot, List paths, @Nullable JpsSdkType projectSdkType, Map pathVariables) { - String name = FileUtil.getNameWithoutExtension(file.getFileName().toString()); + String name = getModuleName(file); final String typeId = moduleRoot.getAttributeValue("type"); final JpsModulePropertiesSerializer serializer = getModulePropertiesSerializer(typeId); final JpsModule module = createModule(name, moduleRoot, serializer); @@ -354,6 +362,11 @@ public class JpsProjectLoader extends JpsLoaderBase { return module; } + @NotNull + private static String getModuleName(@NotNull Path file) { + return FileUtil.getNameWithoutExtension(file.getFileName().toString()); + } + static JpsMacroExpander createModuleMacroExpander(final Map pathVariables, @NotNull Path moduleFile) { final JpsMacroExpander expander = new JpsMacroExpander(pathVariables); String moduleDirPath = PathMacroUtil.getModuleDir(moduleFile.toAbsolutePath().toString()); diff --git a/jps/model-serialization/testData/unloadedModule/.idea/misc.xml b/jps/model-serialization/testData/unloadedModule/.idea/misc.xml new file mode 100644 index 000000000000..0a1e1b4e681d --- /dev/null +++ b/jps/model-serialization/testData/unloadedModule/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/jps/model-serialization/testData/unloadedModule/.idea/modules.xml b/jps/model-serialization/testData/unloadedModule/.idea/modules.xml new file mode 100644 index 000000000000..a071ba6d9b45 --- /dev/null +++ b/jps/model-serialization/testData/unloadedModule/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jps/model-serialization/testData/unloadedModule/.idea/workspace.xml b/jps/model-serialization/testData/unloadedModule/.idea/workspace.xml new file mode 100644 index 000000000000..3ce6a2c59848 --- /dev/null +++ b/jps/model-serialization/testData/unloadedModule/.idea/workspace.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/jps/model-serialization/testData/unloadedModule/main.iml b/jps/model-serialization/testData/unloadedModule/main.iml new file mode 100644 index 000000000000..feb23b47ceff --- /dev/null +++ b/jps/model-serialization/testData/unloadedModule/main.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jps/model-serialization/testData/unloadedModule/util.iml b/jps/model-serialization/testData/unloadedModule/util.iml new file mode 100644 index 000000000000..fefaba3e3a7b --- /dev/null +++ b/jps/model-serialization/testData/unloadedModule/util.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java index 51aa9efb7f01..f41ea73480a6 100644 --- a/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java +++ b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java @@ -230,6 +230,12 @@ public class JpsProjectSerializationTest extends JpsSerializationTestCase { } } + public void testUnloadedModule() { + String projectPath = "/jps/model-serialization/testData/unloadedModule"; + loadProject(projectPath); + assertEquals("main", assertOneElement(myProject.getModules()).getName()); + } + private void doTestSaveLibrary(@NotNull Path libFile, String libName, JpsLibrary library) { Element actual = new Element("library"); JpsLibraryTableSerializer.saveLibrary(library, actual, libName); diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 02d78afdf412..9796ea2be590 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -917,6 +917,7 @@ + diff --git a/platform/projectModel-api/src/com/intellij/openapi/module/ModuleDescription.kt b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleDescription.kt new file mode 100644 index 000000000000..4329f318c042 --- /dev/null +++ b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleDescription.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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 com.intellij.openapi.module + +import com.intellij.openapi.vfs.pointers.VirtualFilePointer +import org.jetbrains.annotations.ApiStatus + +/** + * Represents a description of a module which may be either loaded into the project or unloaded. Use this class only if you need to process + * all modules including unloaded, in other cases [Module] should be used instead. + * @see [ModuleManager.getUnloadedModuleDescriptions] + * @author nik + */ +@ApiStatus.Experimental +interface ModuleDescription { + val name: String + + /** + * Names of the modules on which the current module depend. + */ + val dependencyModuleNames: List +} + +@ApiStatus.Experimental +interface UnloadedModuleDescription : ModuleDescription { + val contentRoots: List +} \ No newline at end of file diff --git a/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java index 0e25a83c0d61..ee89d6438ed8 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java +++ b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java @@ -20,11 +20,13 @@ import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.SimpleModificationTracker; import com.intellij.util.graph.Graph; import org.jdom.JDOMException; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.util.Collection; import java.util.Comparator; import java.util.List; @@ -170,4 +172,20 @@ public abstract class ModuleManager extends SimpleModificationTracker { public abstract String[] getModuleGroupPath(@NotNull Module module); public abstract boolean hasModuleGroups(); + + /** + * @return description of all modules in the project including unloaded + */ + @ApiStatus.Experimental + public abstract Collection getAllModuleDescriptions(); + + @ApiStatus.Experimental + public abstract Collection getUnloadedModuleDescriptions(); + + /** + * Specify list of modules which will be unloaded from the project. These modules won't be shown in UI, all of their contents will be excluded + * from indexing and compilation. + */ + @ApiStatus.Experimental + public abstract void setUnloadedModules(@NotNull List unloadedModuleNames); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/EmptyModuleManager.kt b/platform/projectModel-impl/src/com/intellij/openapi/module/EmptyModuleManager.kt index 07f679f812ff..45e102b62949 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/module/EmptyModuleManager.kt +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/EmptyModuleManager.kt @@ -48,4 +48,11 @@ class EmptyModuleManager(project: Project, messageBus: MessageBus) : ModuleManag override fun getModifiableModel() = throw UnsupportedOperationException() override fun getModuleGroupPath(module: Module) = emptyArray() + + override fun setUnloadedModules(unloadedModuleNames: MutableList) { + } + + override fun getAllModuleDescriptions() = emptyList() + + override fun getUnloadedModuleDescriptions() = emptyList() } \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/LoadedModuleDescription.kt b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/LoadedModuleDescription.kt new file mode 100644 index 000000000000..25b8f5b1c55b --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/LoadedModuleDescription.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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 com.intellij.openapi.module.impl + +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleDescription +import com.intellij.openapi.roots.ModuleRootManager + +/** + * @author nik + */ +class LoadedModuleDescription(private val module: Module): ModuleDescription { + override val name: String + get() = module.name + + override val dependencyModuleNames: List + get() = ModuleRootManager.getInstance(module).dependencyModuleNames.asList() +} \ No newline at end of file diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java index 8529d94a7dd2..50c375de5954 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java @@ -43,6 +43,8 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.StandardFileSystems; import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.pointers.VirtualFilePointer; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.util.SmartList; import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.containers.ContainerUtil; @@ -62,6 +64,7 @@ import java.io.IOException; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import java.util.stream.Collectors; /** * @author max @@ -86,6 +89,7 @@ public abstract class ModuleManagerImpl extends ModuleManager implements Disposa private LinkedHashSet myModulePathsToLoad; private final Set myFailedModulePaths = new THashSet<>(); + private Map myUnloadedModules = new LinkedHashMap<>(); public static ModuleManagerImpl getInstanceImpl(Project project) { return (ModuleManagerImpl)getInstance(project); @@ -161,6 +165,22 @@ public abstract class ModuleManagerImpl extends ModuleManager implements Disposa public void loadState(Element state) { boolean isFirstLoadState = myModulePathsToLoad == null; myModulePathsToLoad = getPathsToModuleFiles(state); + Set unloadedModuleNames = new HashSet<>(UnloadedModulesListStorage.getInstance(myProject).getUnloadedModuleNames()); + Iterator iterator = myModulePathsToLoad.iterator(); + List unloadedModulePaths = new ArrayList<>(); + while (iterator.hasNext()) { + ModulePath modulePath = iterator.next(); + if (unloadedModuleNames.contains(modulePath.getModuleName())) { + unloadedModulePaths.add(modulePath); + iterator.remove(); + } + } + List descriptions = UnloadedModuleDescriptionImpl.createFromPaths(unloadedModulePaths, this); + myUnloadedModules.clear(); + for (UnloadedModuleDescriptionImpl description : descriptions) { + myUnloadedModules.put(description.getName(), description); + } + if (isFirstLoadState) { // someone else must call loadModules in a appropriate time (e.g. on projectComponentsInitialized) return; @@ -421,13 +441,16 @@ public abstract class ModuleManagerImpl extends ModuleManager implements Disposa public void writeExternal(@NotNull Element element) { final Module[] collection = getModules(); - List sorted = new ArrayList<>(collection.length + myFailedModulePaths.size()); + List sorted = new ArrayList<>(collection.length + myFailedModulePaths.size() + myUnloadedModules.size()); for (Module module : collection) { sorted.add(new ModuleSaveItem(module)); } for (ModulePath modulePath : myFailedModulePaths) { sorted.add(new ModulePathSaveItem(modulePath)); } + for (UnloadedModuleDescriptionImpl description : myUnloadedModules.values()) { + sorted.add(new ModulePathSaveItem(description.getModulePath())); + } if (!sorted.isEmpty()) { Collections.sort(sorted, Comparator.comparing(SaveItem::getModuleName)); @@ -957,6 +980,59 @@ public abstract class ModuleManagerImpl extends ModuleManager implements Disposa return myModuleModel.hasModuleGroups(); } + @Override + public Collection getAllModuleDescriptions() { + Module[] modules = getModules(); + List descriptions = new ArrayList<>(modules.length + myUnloadedModules.size()); + for (Module module : modules) { + descriptions.add(new LoadedModuleDescription(module)); + } + descriptions.addAll(myUnloadedModules.values()); + return descriptions; + } + + @Override + public Collection getUnloadedModuleDescriptions() { + return myUnloadedModules.values(); + } + + @Override + public void setUnloadedModules(@NotNull List unloadedModuleNames) { + if (myUnloadedModules.keySet().equals(unloadedModuleNames)) { + return; + } + + UnloadedModulesListStorage.getInstance(myProject).setUnloadedModuleNames(unloadedModuleNames); + + final ModifiableModuleModel model = getModifiableModel(); + Map toLoad = new LinkedHashMap<>(myUnloadedModules); + myUnloadedModules.clear(); + for (String name : unloadedModuleNames) { + if (toLoad.containsKey(name)) { + myUnloadedModules.put(name, toLoad.remove(name)); + } + else { + Module module = findModuleByName(name); + if (module != null) { + LoadedModuleDescription description = new LoadedModuleDescription(module); + ModuleSaveItem saveItem = new ModuleSaveItem(module); + ModulePath modulePath = new ModulePath(saveItem.getModuleFilePath(), saveItem.getGroupPathString()); + VirtualFilePointerManager pointerManager = VirtualFilePointerManager.getInstance(); + List contentRoots = ContainerUtil.map(ModuleRootManager.getInstance(module).getContentRootUrls(), url -> pointerManager.create(url, this, null)); + UnloadedModuleDescriptionImpl unloadedModuleDescription = new UnloadedModuleDescriptionImpl(modulePath, description.getDependencyModuleNames(), contentRoots); + model.disposeModule(module); + myUnloadedModules.put(name, unloadedModuleDescription); + } + } + } + List oldFailedPaths = new ArrayList<>(myFailedModulePaths); + myModulePathsToLoad = toLoad.values().stream().map(d -> d.getModulePath()).collect(Collectors.toCollection(LinkedHashSet::new)); + loadModules((ModuleModelImpl)model); + ApplicationManager.getApplication().runWriteAction(() -> model.commit()); + myFailedModulePaths.addAll(oldFailedPaths); + myModulePathsToLoad.clear(); + } + public void setModuleGroupPath(Module module, String[] groupPath) { myModuleModel.setModuleGroupPath(module, groupPath); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/UnloadedModuleDescriptionImpl.kt b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/UnloadedModuleDescriptionImpl.kt new file mode 100644 index 000000000000..9fbab89bd3ad --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/UnloadedModuleDescriptionImpl.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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 com.intellij.openapi.module.impl + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.module.UnloadedModuleDescription +import com.intellij.openapi.vfs.pointers.VirtualFilePointer +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager +import org.jetbrains.jps.model.module.JpsModule +import org.jetbrains.jps.model.module.JpsModuleDependency +import org.jetbrains.jps.model.serialization.JpsGlobalLoader +import org.jetbrains.jps.model.serialization.JpsProjectLoader +import java.nio.file.Paths + +/** + * @author nik + */ +class UnloadedModuleDescriptionImpl(val modulePath: ModulePath, + override val dependencyModuleNames: List, + override val contentRoots: List) : UnloadedModuleDescription { + override val name: String + get() = modulePath.moduleName + + companion object { + @JvmStatic + fun createFromPaths(paths: List, parentDisposable: Disposable): List { + val pathVariables = JpsGlobalLoader.computeAllPathVariables(PathManager.getOptionsPath()) + val modules = JpsProjectLoader.loadModules(paths.map { Paths.get(it.path) }, null, pathVariables) + val pathsByName = paths.associateBy { it.moduleName } + return modules.map { create(pathsByName[it.name]!!, it, parentDisposable) } + } + + private fun create(path: ModulePath, module: JpsModule, parentDisposable: Disposable): UnloadedModuleDescriptionImpl { + val dependencyModuleNames = module.dependenciesList.dependencies + .filterIsInstance(JpsModuleDependency::class.java) + .map { it.moduleReference.moduleName } + + val pointerManager = VirtualFilePointerManager.getInstance() + return UnloadedModuleDescriptionImpl(path, dependencyModuleNames, module.contentRootsList.urls.map {pointerManager.create(it, parentDisposable, null)}) + } + } +} diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/UnloadedModulesListStorage.java b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/UnloadedModulesListStorage.java new file mode 100644 index 000000000000..14b532c087b4 --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/UnloadedModulesListStorage.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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 com.intellij.openapi.module.impl; + +import com.intellij.openapi.components.*; +import com.intellij.openapi.project.Project; +import com.intellij.util.xmlb.XmlSerializerUtil; +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.Property; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +@State(name = "UnloadedModulesList", storages = {@Storage(StoragePathMacros.WORKSPACE_FILE)}) +public class UnloadedModulesListStorage implements PersistentStateComponent { + @Property(surroundWithTag = false) + @AbstractCollection(surroundWithTag = false, elementTag = "module", elementValueAttribute = "name") + public List moduleNames = new ArrayList<>(); + + static UnloadedModulesListStorage getInstance(@NotNull Project project) { + return ServiceManager.getService(project, UnloadedModulesListStorage.class); + } + + public List getUnloadedModuleNames() { + return Collections.unmodifiableList(moduleNames); + } + + public void setUnloadedModuleNames(List names) { + moduleNames.clear(); + moduleNames.addAll(names); + } + + @Nullable + @Override + public UnloadedModulesListStorage getState() { + return this; + } + + @Override + public void loadState(UnloadedModulesListStorage state) { + XmlSerializerUtil.copyBean(state, this); + } +}