2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.openapi.module.impl;
19 import com.intellij.ProjectTopics;
20 import com.intellij.openapi.application.AccessToken;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ModalityState;
23 import com.intellij.openapi.application.WriteAction;
24 import com.intellij.openapi.components.PersistentStateComponent;
25 import com.intellij.openapi.components.ProjectComponent;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.module.*;
28 import com.intellij.openapi.progress.ProgressIndicator;
29 import com.intellij.openapi.progress.ProgressIndicatorProvider;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.project.ProjectBundle;
32 import com.intellij.openapi.roots.ModifiableRootModel;
33 import com.intellij.openapi.roots.ModuleRootManager;
34 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
35 import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
36 import com.intellij.openapi.util.Comparing;
37 import com.intellij.openapi.util.Disposer;
38 import com.intellij.openapi.util.Key;
39 import com.intellij.openapi.util.SystemInfo;
40 import com.intellij.openapi.util.io.FileUtil;
41 import com.intellij.openapi.util.text.StringUtil;
42 import com.intellij.openapi.vfs.StandardFileSystems;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.openapi.vfs.VirtualFileManager;
45 import com.intellij.util.Function;
46 import com.intellij.util.containers.ContainerUtil;
47 import com.intellij.util.containers.HashMap;
48 import com.intellij.util.containers.StringInterner;
49 import com.intellij.util.containers.hash.HashSet;
50 import com.intellij.util.containers.hash.LinkedHashMap;
51 import com.intellij.util.graph.CachingSemiGraph;
52 import com.intellij.util.graph.DFSTBuilder;
53 import com.intellij.util.graph.Graph;
54 import com.intellij.util.graph.GraphGenerator;
55 import com.intellij.util.io.URLUtil;
56 import com.intellij.util.messages.MessageBus;
57 import gnu.trove.THashMap;
58 import gnu.trove.TObjectHashingStrategy;
59 import org.jdom.Element;
60 import org.jdom.JDOMException;
61 import org.jetbrains.annotations.NonNls;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
66 import java.io.FileNotFoundException;
67 import java.io.IOException;
73 public abstract class ModuleManagerImpl extends ModuleManager implements ProjectComponent, PersistentStateComponent<Element> {
74 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.module.impl.ModuleManagerImpl");
75 static final Key<String> DISPOSED_MODULE_NAME = Key.create("DisposedNeverAddedModuleName");
76 private static final String IML_EXTENSION = ".iml";
77 protected final Project myProject;
78 protected final MessageBus myMessageBus;
79 protected volatile ModuleModelImpl myModuleModel = new ModuleModelImpl();
81 @NonNls public static final String COMPONENT_NAME = "ProjectModuleManager";
82 private static final String MODULE_GROUP_SEPARATOR = "/";
83 private List<ModulePath> myModulePaths;
84 private final List<ModulePath> myFailedModulePaths = new ArrayList<ModulePath>();
85 @NonNls public static final String ELEMENT_MODULES = "modules";
86 @NonNls public static final String ELEMENT_MODULE = "module";
87 @NonNls private static final String ATTRIBUTE_FILEURL = "fileurl";
88 @NonNls public static final String ATTRIBUTE_FILEPATH = "filepath";
89 @NonNls private static final String ATTRIBUTE_GROUP = "group";
91 public static ModuleManagerImpl getInstanceImpl(Project project) {
92 return (ModuleManagerImpl)getInstance(project);
95 public ModuleManagerImpl(Project project, MessageBus messageBus) {
97 myMessageBus = messageBus;
100 protected void cleanCachedStuff() {
101 myCachedModuleComparator = null;
102 myCachedSortedModules = null;
107 public String getComponentName() {
108 return COMPONENT_NAME;
112 public void initComponent() {
116 public void disposeComponent() {
117 myModuleModel.disposeModel();
121 public Element getState() {
122 final Element e = new Element("state");
127 private static class ModuleGroupInterner {
128 private final StringInterner groups = new StringInterner();
129 private final Map<String[], String[]> paths = new THashMap<String[], String[]>(new TObjectHashingStrategy<String[]>() {
131 public int computeHashCode(String[] object) {
132 return Arrays.hashCode(object);
136 public boolean equals(String[] o1, String[] o2) {
137 return Arrays.equals(o1, o2);
141 private void setModuleGroupPath(@NotNull ModifiableModuleModel model, @NotNull Module module, @Nullable String[] group) {
142 String[] cached = group == null ? null : paths.get(group);
143 if (cached == null && group != null) {
144 cached = new String[group.length];
145 for (int i = 0; i < group.length; i++) {
147 cached[i] = groups.intern(g);
149 paths.put(cached, cached);
151 model.setModuleGroupPath(module, cached);
156 public void loadState(Element state) {
157 List<ModulePath> prevPaths = myModulePaths;
159 if (prevPaths != null) {
160 ModifiableModuleModel model = getModifiableModel();
162 Module[] existingModules = model.getModules();
164 ModuleGroupInterner groupInterner = new ModuleGroupInterner();
165 for (Module existingModule : existingModules) {
166 ModulePath correspondingPath = findCorrespondingPath(existingModule);
167 if (correspondingPath == null) {
168 model.disposeModule(existingModule);
171 myModulePaths.remove(correspondingPath);
173 String groupStr = correspondingPath.getModuleGroup();
174 String[] group = groupStr == null ? null : groupStr.split(MODULE_GROUP_SEPARATOR);
175 if (!Arrays.equals(group, model.getModuleGroupPath(existingModule))) {
176 groupInterner.setModuleGroupPath(model, existingModule, group);
181 loadModules((ModuleModelImpl)model);
183 AccessToken token = WriteAction.start();
193 private ModulePath findCorrespondingPath(@NotNull Module existingModule) {
194 for (ModulePath modulePath : myModulePaths) {
195 if (modulePath.getPath().equals(existingModule.getModuleFilePath())) return modulePath;
201 public static final class ModulePath {
202 private final String myPath;
203 private final String myModuleGroup;
205 public ModulePath(String path, String moduleGroup) {
207 myModuleGroup = moduleGroup;
210 public String getPath() {
214 public String getModuleGroup() {
215 return myModuleGroup;
220 public static ModulePath[] getPathsToModuleFiles(@NotNull Element element) {
221 final List<ModulePath> paths = new ArrayList<ModulePath>();
222 final Element modules = element.getChild(ELEMENT_MODULES);
223 if (modules != null) {
224 for (final Object value : modules.getChildren(ELEMENT_MODULE)) {
225 Element moduleElement = (Element)value;
226 final String fileUrlValue = moduleElement.getAttributeValue(ATTRIBUTE_FILEURL);
227 final String filepath;
228 if (fileUrlValue != null) {
229 filepath = VirtualFileManager.extractPath(fileUrlValue).replace('/', File.separatorChar);
232 // [dsl] support for older formats
233 filepath = moduleElement.getAttributeValue(ATTRIBUTE_FILEPATH).replace('/', File.separatorChar);
235 final String group = moduleElement.getAttributeValue(ATTRIBUTE_GROUP);
236 paths.add(new ModulePath(filepath, group));
239 return paths.toArray(new ModulePath[paths.size()]);
242 public void readExternal(@NotNull Element element) {
243 myModulePaths = new ArrayList<ModulePath>(Arrays.asList(getPathsToModuleFiles(element)));
246 protected void loadModules(@NotNull ModuleModelImpl moduleModel) {
247 if (myModulePaths == null || myModulePaths.isEmpty()) {
250 ModuleGroupInterner groupInterner = new ModuleGroupInterner();
252 final ProgressIndicator progressIndicator = myProject.isDefault() ? null : ProgressIndicatorProvider.getGlobalProgressIndicator();
253 if (progressIndicator != null) {
254 progressIndicator.setText("Loading modules...");
255 progressIndicator.setText2("");
257 myFailedModulePaths.clear();
258 myFailedModulePaths.addAll(myModulePaths);
259 final List<Module> modulesWithUnknownTypes = new ArrayList<Module>();
260 List<ModuleLoadingErrorDescription> errors = new ArrayList<ModuleLoadingErrorDescription>();
262 for (int i = 0; i < myModulePaths.size(); i++) {
263 ModulePath modulePath = myModulePaths.get(i);
264 if (progressIndicator != null) {
265 progressIndicator.setFraction((double) i / myModulePaths.size());
268 final Module module = moduleModel.loadModuleInternal(modulePath.getPath());
269 if (isUnknownModuleType(module)) {
270 modulesWithUnknownTypes.add(module);
272 final String groupPathString = modulePath.getModuleGroup();
273 if (groupPathString != null) {
274 final String[] groupPath = groupPathString.split(MODULE_GROUP_SEPARATOR);
276 groupInterner.setModuleGroupPath(moduleModel, module, groupPath); //model should be updated too
278 myFailedModulePaths.remove(modulePath);
280 catch (IOException e) {
281 errors.add(ModuleLoadingErrorDescription.create(ProjectBundle.message("module.cannot.load.error", modulePath.getPath(), e.getMessage()),
284 catch (ModuleWithNameAlreadyExists moduleWithNameAlreadyExists) {
285 errors.add(ModuleLoadingErrorDescription.create(moduleWithNameAlreadyExists.getMessage(), modulePath, this));
289 onModuleLoadErrors(errors);
291 showUnknownModuleTypeNotification(modulesWithUnknownTypes);
293 if (progressIndicator != null) {
294 progressIndicator.setIndeterminate(true);
298 protected boolean isUnknownModuleType(@NotNull Module module) {
302 protected void showUnknownModuleTypeNotification(@NotNull List<Module> types) {
305 protected void fireModuleAdded(@NotNull Module module) {
306 myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleAdded(myProject, module);
309 protected void fireModuleRemoved(@NotNull Module module) {
310 myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleRemoved(myProject, module);
313 protected void fireBeforeModuleRemoved(@NotNull Module module) {
314 myMessageBus.syncPublisher(ProjectTopics.MODULES).beforeModuleRemoved(myProject, module);
317 protected void fireModulesRenamed(@NotNull List<Module> modules, @NotNull final Map<Module, String> oldNames) {
318 if (!modules.isEmpty()) {
319 myMessageBus.syncPublisher(ProjectTopics.MODULES).modulesRenamed(myProject, modules, new Function<Module, String>() {
321 public String fun(Module module) {
322 return oldNames.get(module);
328 protected void onModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
329 if (errors.isEmpty()) return;
331 myModuleModel.myModulesCache = null;
332 for (ModuleLoadingErrorDescription error : errors) {
333 final Module module = myModuleModel.getModuleByFilePath(FileUtil.toSystemIndependentName(error.getModulePath().getPath()));
334 if (module != null) {
335 myModuleModel.myModules.remove(module.getName());
336 ApplicationManager.getApplication().invokeLater(new Runnable() {
339 Disposer.dispose(module);
341 }, module.getDisposed());
345 fireModuleLoadErrors(errors);
348 protected void fireModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
349 if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
350 throw new RuntimeException(errors.get(0).getDescription());
353 ProjectLoadingErrorsNotifier.getInstance(myProject).registerErrors(errors);
356 public void removeFailedModulePath(@NotNull ModulePath modulePath) {
357 myFailedModulePaths.remove(modulePath);
362 public ModifiableModuleModel getModifiableModel() {
363 ApplicationManager.getApplication().assertReadAccessAllowed();
364 return new ModuleModelImpl(myModuleModel);
368 private abstract static class SaveItem {
370 protected abstract String getModuleName();
371 protected abstract String getGroupPathString();
373 protected abstract String getModuleFilePath();
375 public final void writeExternal(@NotNull Element parentElement) {
376 Element moduleElement = new Element(ELEMENT_MODULE);
377 final String moduleFilePath = getModuleFilePath();
378 final String url = VirtualFileManager.constructUrl(URLUtil.FILE_PROTOCOL, moduleFilePath);
379 moduleElement.setAttribute(ATTRIBUTE_FILEURL, url);
380 // [dsl] support for older builds
381 moduleElement.setAttribute(ATTRIBUTE_FILEPATH, moduleFilePath);
383 final String groupPath = getGroupPathString();
384 if (groupPath != null) {
385 moduleElement.setAttribute(ATTRIBUTE_GROUP, groupPath);
387 parentElement.addContent(moduleElement);
391 private class ModuleSaveItem extends SaveItem{
392 private final Module myModule;
394 public ModuleSaveItem(@NotNull Module module) {
400 protected String getModuleName() {
401 return myModule.getName();
405 protected String getGroupPathString() {
406 String[] groupPath = getModuleGroupPath(myModule);
407 return groupPath != null ? StringUtil.join(groupPath, MODULE_GROUP_SEPARATOR) : null;
412 protected String getModuleFilePath() {
413 return myModule.getModuleFilePath().replace(File.separatorChar, '/');
417 private static class ModulePathSaveItem extends SaveItem{
418 private final ModulePath myModulePath;
419 private final String myFilePath;
420 private final String myName;
422 private ModulePathSaveItem(@NotNull ModulePath modulePath) {
423 myModulePath = modulePath;
424 myFilePath = modulePath.getPath().replace(File.separatorChar, '/');
426 final int slashIndex = myFilePath.lastIndexOf('/');
427 final int startIndex = slashIndex >= 0 && slashIndex + 1 < myFilePath.length() ? slashIndex + 1 : 0;
428 final int endIndex = myFilePath.endsWith(IML_EXTENSION)
429 ? myFilePath.length() - IML_EXTENSION.length()
430 : myFilePath.length();
431 myName = myFilePath.substring(startIndex, endIndex);
436 protected String getModuleName() {
441 protected String getGroupPathString() {
442 return myModulePath.getModuleGroup();
447 protected String getModuleFilePath() {
452 public void writeExternal(@NotNull Element element) {
453 final Module[] collection = getModules();
455 List<SaveItem> sorted = new ArrayList<SaveItem>(collection.length + myFailedModulePaths.size());
456 for (Module module : collection) {
457 sorted.add(new ModuleSaveItem(module));
459 for (ModulePath modulePath : myFailedModulePaths) {
460 sorted.add(new ModulePathSaveItem(modulePath));
463 if (!sorted.isEmpty()) {
464 Collections.sort(sorted, new Comparator<SaveItem>() {
466 public int compare(SaveItem item1, SaveItem item2) {
467 return item1.getModuleName().compareTo(item2.getModuleName());
471 Element modules = new Element(ELEMENT_MODULES);
472 for (SaveItem saveItem : sorted) {
473 saveItem.writeExternal(modules);
475 element.addContent(modules);
481 public Module newModule(@NotNull String filePath, final String moduleTypeId) {
482 incModificationCount();
483 final ModifiableModuleModel modifiableModel = getModifiableModel();
484 final Module module = modifiableModel.newModule(filePath, moduleTypeId);
485 modifiableModel.commit();
491 public Module loadModule(@NotNull String filePath) throws IOException, JDOMException, ModuleWithNameAlreadyExists {
492 incModificationCount();
493 final ModifiableModuleModel modifiableModel = getModifiableModel();
494 final Module module = modifiableModel.loadModule(filePath);
495 modifiableModel.commit();
500 public void disposeModule(@NotNull final Module module) {
501 ApplicationManager.getApplication().runWriteAction(new Runnable() {
504 final ModifiableModuleModel modifiableModel = getModifiableModel();
505 modifiableModel.disposeModule(module);
506 modifiableModel.commit();
513 public Module[] getModules() {
514 if (myModuleModel.myIsWritable) {
515 ApplicationManager.getApplication().assertReadAccessAllowed();
517 return myModuleModel.getModules();
520 private volatile Module[] myCachedSortedModules;
524 public Module[] getSortedModules() {
525 ApplicationManager.getApplication().assertReadAccessAllowed();
526 deliverPendingEvents();
527 if (myCachedSortedModules == null) {
528 myCachedSortedModules = myModuleModel.getSortedModules();
530 return myCachedSortedModules;
534 public Module findModuleByName(@NotNull String name) {
535 ApplicationManager.getApplication().assertReadAccessAllowed();
536 return myModuleModel.findModuleByName(name);
539 private volatile Comparator<Module> myCachedModuleComparator;
543 public Comparator<Module> moduleDependencyComparator() {
544 ApplicationManager.getApplication().assertReadAccessAllowed();
545 deliverPendingEvents();
546 if (myCachedModuleComparator == null) {
547 myCachedModuleComparator = myModuleModel.moduleDependencyComparator();
549 return myCachedModuleComparator;
552 protected void deliverPendingEvents() {
557 public Graph<Module> moduleGraph() {
558 return moduleGraph(true);
563 public Graph<Module> moduleGraph(boolean includeTests) {
564 ApplicationManager.getApplication().assertReadAccessAllowed();
565 return myModuleModel.moduleGraph(includeTests);
570 public List<Module> getModuleDependentModules(@NotNull Module module) {
571 ApplicationManager.getApplication().assertReadAccessAllowed();
572 return myModuleModel.getModuleDependentModules(module);
576 public boolean isModuleDependent(@NotNull Module module, @NotNull Module onModule) {
577 ApplicationManager.getApplication().assertReadAccessAllowed();
578 return myModuleModel.isModuleDependent(module, onModule);
582 public void projectOpened() {
585 myModuleModel.projectOpened();
588 protected void fireModulesAdded() {
589 for (final Module module : myModuleModel.myModules.values()) {
590 fireModuleAddedInWriteAction(module);
594 protected void fireModuleAddedInWriteAction(@NotNull final Module module) {
595 ApplicationManager.getApplication().runWriteAction(new Runnable() {
598 ((ModuleEx)module).moduleAdded();
599 fireModuleAdded(module);
605 public void projectClosed() {
606 myModuleModel.projectClosed();
609 public static void commitModelWithRunnable(@NotNull ModifiableModuleModel model, Runnable runnable) {
610 ((ModuleModelImpl)model).commitWithRunnable(runnable);
614 protected abstract ModuleEx createModule(@NotNull String filePath);
617 protected abstract ModuleEx createAndLoadModule(@NotNull String filePath) throws IOException;
619 class ModuleModelImpl implements ModifiableModuleModel {
620 final Map<String, Module> myModules = new LinkedHashMap<String, Module>();
621 private volatile Module[] myModulesCache;
623 private final List<Module> myModulesToDispose = new ArrayList<Module>();
624 private final Map<Module, String> myModuleToNewName = new HashMap<Module, String>();
625 private final Map<String, Module> myNewNameToModule = new HashMap<String, Module>();
626 private boolean myIsWritable;
627 private Map<Module, String[]> myModuleGroupPath;
629 private ModuleModelImpl() {
630 myIsWritable = false;
633 private ModuleModelImpl(@NotNull ModuleModelImpl that) {
634 myModules.putAll(that.myModules);
635 final Map<Module, String[]> groupPath = that.myModuleGroupPath;
636 if (groupPath != null){
637 myModuleGroupPath = new THashMap<Module, String[]>();
638 myModuleGroupPath.putAll(that.myModuleGroupPath);
643 private void assertWritable() {
644 LOG.assertTrue(myIsWritable, "Attempt to modify committed ModifiableModuleModel");
649 public Module[] getModules() {
650 if (myModulesCache == null) {
651 Collection<Module> modules = myModules.values();
652 myModulesCache = modules.toArray(new Module[modules.size()]);
654 return myModulesCache;
658 private Module[] getSortedModules() {
659 Module[] allModules = getModules().clone();
660 Arrays.sort(allModules, moduleDependencyComparator());
665 public void renameModule(@NotNull Module module, @NotNull String newName) throws ModuleWithNameAlreadyExists {
666 final Module oldModule = getModuleByNewName(newName);
667 myNewNameToModule.remove(myModuleToNewName.get(module));
668 if(module.getName().equals(newName)){ // if renaming to itself, forget it altogether
669 myModuleToNewName.remove(module);
670 myNewNameToModule.remove(newName);
672 myModuleToNewName.put(module, newName);
673 myNewNameToModule.put(newName, module);
676 if (oldModule != null) {
677 throw new ModuleWithNameAlreadyExists(ProjectBundle.message("module.already.exists.error", newName), newName);
682 public Module getModuleToBeRenamed(@NotNull String newName) {
683 return myNewNameToModule.get(newName);
686 private Module getModuleByNewName(@NotNull String newName) {
687 final Module moduleToBeRenamed = getModuleToBeRenamed(newName);
688 if (moduleToBeRenamed != null) {
689 return moduleToBeRenamed;
691 final Module moduleWithOldName = findModuleByName(newName);
692 return myModuleToNewName.get(moduleWithOldName) == null ? moduleWithOldName : null;
696 public String getNewName(@NotNull Module module) {
697 return myModuleToNewName.get(module);
702 public Module newModule(@NotNull String filePath, final String moduleTypeId) {
703 return newModule(filePath, moduleTypeId, null);
708 public Module newModule(@NotNull String filePath, @NotNull final String moduleTypeId, @Nullable final Map<String, String> options) {
710 filePath = FileUtil.toSystemIndependentName(resolveShortWindowsName(filePath));
712 ModuleEx module = getModuleByFilePath(filePath);
713 if (module == null) {
714 module = createModule(filePath);
715 final ModuleEx newModule = module;
716 initModule(module, filePath, new Runnable() {
719 newModule.setOption(Module.ELEMENT_TYPE, moduleTypeId);
720 if (options != null) {
721 for (Map.Entry<String, String> option : options.entrySet()) {
722 newModule.setOption(option.getKey(), option.getValue());
732 private String resolveShortWindowsName(@NotNull String filePath) {
734 return FileUtil.resolveShortWindowsName(filePath);
736 catch (IOException ignored) {
742 private ModuleEx getModuleByFilePath(@NotNull String filePath) {
743 for (Module module : myModules.values()) {
744 if (SystemInfo.isFileSystemCaseSensitive ? module.getModuleFilePath().equals(filePath) : module.getModuleFilePath().equalsIgnoreCase(filePath)) {
745 return (ModuleEx)module;
753 public Module loadModule(@NotNull String filePath) throws IOException, ModuleWithNameAlreadyExists {
756 return loadModuleInternal(filePath);
758 catch (FileNotFoundException e) {
761 catch (IOException e) {
762 throw new IOException(ProjectBundle.message("module.corrupted.file.error", FileUtil.toSystemDependentName(filePath), e.getMessage()), e);
767 private Module loadModuleInternal(@NotNull String filePath) throws ModuleWithNameAlreadyExists, IOException {
768 filePath = resolveShortWindowsName(filePath);
769 final VirtualFile moduleFile = StandardFileSystems.local().findFileByPath(filePath);
770 if (moduleFile == null || !moduleFile.exists()) {
771 throw new FileNotFoundException(ProjectBundle.message("module.file.does.not.exist.error", filePath));
774 String path = moduleFile.getPath();
775 ModuleEx module = getModuleByFilePath(path);
776 if (module == null) {
777 ApplicationManager.getApplication().invokeAndWait(new Runnable() {
780 moduleFile.refresh(false, false);
782 }, ModalityState.any());
783 module = createAndLoadModule(path);
784 initModule(module, path, null);
789 private void initModule(@NotNull ModuleEx module, @NotNull String path, @Nullable Runnable beforeComponentCreation) {
790 module.init(path, beforeComponentCreation);
791 myModulesCache = null;
792 myModules.put(module.getName(), module);
796 public void disposeModule(@NotNull Module module) {
798 myModulesCache = null;
799 if (myModules.remove(module.getName()) != null) {
800 myModulesToDispose.add(module);
802 if (myModuleGroupPath != null){
803 myModuleGroupPath.remove(module);
808 public Module findModuleByName(@NotNull String name) {
809 Module module = myModules.get(name);
810 if (module != null && !module.isDisposed()) {
816 private Comparator<Module> moduleDependencyComparator() {
817 DFSTBuilder<Module> builder = new DFSTBuilder<Module>(moduleGraph(true));
818 return builder.comparator();
821 private Graph<Module> moduleGraph(final boolean includeTests) {
822 return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<Module>() {
824 public Collection<Module> getNodes() {
825 return myModules.values();
829 public Iterator<Module> getIn(Module m) {
830 Module[] dependentModules = ModuleRootManager.getInstance(m).getDependencies(includeTests);
831 return Arrays.asList(dependentModules).iterator();
836 @NotNull private List<Module> getModuleDependentModules(Module module) {
837 List<Module> result = new ArrayList<Module>();
838 for (Module aModule : myModules.values()) {
839 if (isModuleDependent(aModule, module)) {
846 private boolean isModuleDependent(Module module, Module onModule) {
847 return ModuleRootManager.getInstance(module).isDependsOn(onModule);
851 public void commit() {
852 ModifiableRootModel[] rootModels = new ModifiableRootModel[0];
853 ModifiableModelCommitter.multiCommit(rootModels, this);
856 private void commitWithRunnable(Runnable runnable) {
857 commitModel(this, runnable);
858 clearRenamingStuff();
861 private void clearRenamingStuff() {
862 myModuleToNewName.clear();
863 myNewNameToModule.clear();
867 public void dispose() {
869 ApplicationManager.getApplication().assertWriteAccessAllowed();
870 final Set<Module> set = new HashSet<Module>();
871 set.addAll(myModuleModel.myModules.values());
872 for (Module thisModule : myModules.values()) {
873 if (!set.contains(thisModule)) {
874 Disposer.dispose(thisModule);
877 for (Module moduleToDispose : myModulesToDispose) {
878 if (!set.contains(moduleToDispose)) {
879 Disposer.dispose(moduleToDispose);
882 clearRenamingStuff();
886 public boolean isChanged() {
890 return !myModules.equals(myModuleModel.myModules) || !Comparing.equal(myModuleModel.myModuleGroupPath, myModuleGroupPath);
893 private void disposeModel() {
894 myModulesCache = null;
895 for (final Module module : myModules.values()) {
896 Disposer.dispose(module);
899 myModuleGroupPath = null;
902 public void projectOpened() {
903 for (final Module aCollection : myModules.values()) {
904 ModuleEx module = (ModuleEx)aCollection;
905 module.projectOpened();
909 public void projectClosed() {
910 for (Module aCollection : myModules.values()) {
911 ModuleEx module = (ModuleEx)aCollection;
912 module.projectClosed();
917 public String[] getModuleGroupPath(Module module) {
918 return myModuleGroupPath == null ? null : myModuleGroupPath.get(module);
922 public boolean hasModuleGroups() {
923 return myModuleGroupPath != null && !myModuleGroupPath.isEmpty();
927 public void setModuleGroupPath(@NotNull Module module, @Nullable("null means remove") String[] groupPath) {
928 if (myModuleGroupPath == null) {
929 myModuleGroupPath = new THashMap<Module, String[]>();
931 if (groupPath == null) {
932 myModuleGroupPath.remove(module);
935 myModuleGroupPath.put(module, groupPath);
940 private void commitModel(final ModuleModelImpl moduleModel, final Runnable runnable) {
941 myModuleModel.myModulesCache = null;
942 incModificationCount();
943 ApplicationManager.getApplication().assertWriteAccessAllowed();
944 final Collection<Module> oldModules = myModuleModel.myModules.values();
945 final Collection<Module> newModules = moduleModel.myModules.values();
946 final List<Module> removedModules = new ArrayList<Module>(oldModules);
947 removedModules.removeAll(newModules);
948 final List<Module> addedModules = new ArrayList<Module>(newModules);
949 addedModules.removeAll(oldModules);
951 ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
954 for (Module removedModule : removedModules) {
955 fireBeforeModuleRemoved(removedModule);
959 List<Module> neverAddedModules = new ArrayList<Module>(moduleModel.myModulesToDispose);
960 neverAddedModules.removeAll(myModuleModel.myModules.values());
961 for (final Module neverAddedModule : neverAddedModules) {
962 neverAddedModule.putUserData(DISPOSED_MODULE_NAME, neverAddedModule.getName());
963 Disposer.dispose(neverAddedModule);
966 if (runnable != null) {
970 final Map<Module, String> modulesToNewNamesMap = moduleModel.myModuleToNewName;
971 final Set<Module> modulesToBeRenamed = modulesToNewNamesMap.keySet();
972 modulesToBeRenamed.removeAll(moduleModel.myModulesToDispose);
974 List<Module> modules = new ArrayList<Module>();
975 Map<Module, String> oldNames = ContainerUtil.newHashMap();
976 for (final Module module : modulesToBeRenamed) {
977 oldNames.put(module, module.getName());
978 moduleModel.myModules.remove(module.getName());
980 ((ModuleEx)module).rename(modulesToNewNamesMap.get(module));
981 moduleModel.myModules.put(module.getName(), module);
984 moduleModel.myIsWritable = false;
985 myModuleModel = moduleModel;
987 for (Module module : removedModules) {
988 fireModuleRemoved(module);
990 Disposer.dispose(module);
994 for (Module addedModule : addedModules) {
995 ((ModuleEx)addedModule).moduleAdded();
997 fireModuleAdded(addedModule);
1001 fireModulesRenamed(modules, oldNames);
1007 public void fireModuleRenamedByVfsEvent(@NotNull final Module module, @NotNull final String oldName) {
1008 Module moduleInMap = myModuleModel.myModules.remove(oldName);
1009 LOG.assertTrue(moduleInMap == null || moduleInMap == module);
1010 myModuleModel.myModules.put(module.getName(), module);
1012 ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
1015 fireModulesRenamed(Collections.singletonList(module), Collections.singletonMap(module, oldName));
1021 public String[] getModuleGroupPath(@NotNull Module module) {
1022 return myModuleModel.getModuleGroupPath(module);
1025 public void setModuleGroupPath(Module module, String[] groupPath) {
1026 myModuleModel.setModuleGroupPath(module, groupPath);