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.ApplicationManager;
21 import com.intellij.openapi.application.ModalityState;
22 import com.intellij.openapi.components.PersistentStateComponent;
23 import com.intellij.openapi.components.ProjectComponent;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.module.*;
26 import com.intellij.openapi.progress.ProgressIndicator;
27 import com.intellij.openapi.progress.ProgressIndicatorProvider;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.project.ProjectBundle;
30 import com.intellij.openapi.roots.ModifiableRootModel;
31 import com.intellij.openapi.roots.ModuleRootManager;
32 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
33 import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
34 import com.intellij.openapi.util.Comparing;
35 import com.intellij.openapi.util.Disposer;
36 import com.intellij.openapi.util.Key;
37 import com.intellij.openapi.util.SystemInfo;
38 import com.intellij.openapi.util.io.FileUtil;
39 import com.intellij.openapi.util.text.StringUtil;
40 import com.intellij.openapi.vfs.StandardFileSystems;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.openapi.vfs.VirtualFileManager;
43 import com.intellij.util.Function;
44 import com.intellij.util.containers.ContainerUtil;
45 import com.intellij.util.containers.HashMap;
46 import com.intellij.util.containers.StringInterner;
47 import com.intellij.util.containers.hash.HashSet;
48 import com.intellij.util.containers.hash.LinkedHashMap;
49 import com.intellij.util.graph.CachingSemiGraph;
50 import com.intellij.util.graph.DFSTBuilder;
51 import com.intellij.util.graph.Graph;
52 import com.intellij.util.graph.GraphGenerator;
53 import com.intellij.util.io.URLUtil;
54 import com.intellij.util.messages.MessageBus;
55 import gnu.trove.THashMap;
56 import gnu.trove.TObjectHashingStrategy;
57 import org.jdom.Element;
58 import org.jdom.JDOMException;
59 import org.jetbrains.annotations.NonNls;
60 import org.jetbrains.annotations.NotNull;
61 import org.jetbrains.annotations.Nullable;
64 import java.io.FileNotFoundException;
65 import java.io.IOException;
71 public abstract class ModuleManagerImpl extends ModuleManager implements ProjectComponent, PersistentStateComponent<Element> {
72 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.module.impl.ModuleManagerImpl");
73 private static final Key<String> DISPOSED_MODULE_NAME = Key.create("DisposedNeverAddedModuleName");
74 private static final String IML_EXTENSION = ".iml";
75 protected final Project myProject;
76 protected final MessageBus myMessageBus;
77 protected volatile ModuleModelImpl myModuleModel = new ModuleModelImpl();
79 @NonNls public static final String COMPONENT_NAME = "ProjectModuleManager";
80 private static final String MODULE_GROUP_SEPARATOR = "/";
81 private List<ModulePath> myModulePaths;
82 private final List<ModulePath> myFailedModulePaths = new ArrayList<ModulePath>();
83 @NonNls public static final String ELEMENT_MODULES = "modules";
84 @NonNls public static final String ELEMENT_MODULE = "module";
85 @NonNls private static final String ATTRIBUTE_FILEURL = "fileurl";
86 @NonNls public static final String ATTRIBUTE_FILEPATH = "filepath";
87 @NonNls private static final String ATTRIBUTE_GROUP = "group";
89 public static ModuleManagerImpl getInstanceImpl(Project project) {
90 return (ModuleManagerImpl)getInstance(project);
93 public ModuleManagerImpl(Project project, MessageBus messageBus) {
95 myMessageBus = messageBus;
98 protected void cleanCachedStuff() {
99 myCachedModuleComparator = null;
100 myCachedSortedModules = null;
105 public String getComponentName() {
106 return COMPONENT_NAME;
110 public void initComponent() {
114 public void disposeComponent() {
115 myModuleModel.disposeModel();
119 public Element getState() {
120 final Element e = new Element("state");
125 private static class ModuleGroupInterner {
126 private final StringInterner groups = new StringInterner();
127 private final Map<String[], String[]> paths = new THashMap<String[], String[]>(new TObjectHashingStrategy<String[]>() {
129 public int computeHashCode(String[] object) {
130 return Arrays.hashCode(object);
134 public boolean equals(String[] o1, String[] o2) {
135 return Arrays.equals(o1, o2);
139 private void setModuleGroupPath(@NotNull ModifiableModuleModel model, @NotNull Module module, @Nullable String[] group) {
140 String[] cached = group == null ? null : paths.get(group);
141 if (cached == null && group != null) {
142 cached = new String[group.length];
143 for (int i = 0; i < group.length; i++) {
145 cached[i] = groups.intern(g);
147 paths.put(cached, cached);
149 model.setModuleGroupPath(module, cached);
154 public void loadState(Element state) {
155 List<ModulePath> prevPaths = myModulePaths;
157 if (prevPaths != null) {
158 final ModifiableModuleModel model = getModifiableModel();
160 Module[] existingModules = model.getModules();
162 ModuleGroupInterner groupInterner = new ModuleGroupInterner();
163 for (Module existingModule : existingModules) {
164 ModulePath correspondingPath = findCorrespondingPath(existingModule);
165 if (correspondingPath == null) {
166 model.disposeModule(existingModule);
169 myModulePaths.remove(correspondingPath);
171 String groupStr = correspondingPath.getModuleGroup();
172 String[] group = groupStr == null ? null : groupStr.split(MODULE_GROUP_SEPARATOR);
173 if (!Arrays.equals(group, model.getModuleGroupPath(existingModule))) {
174 groupInterner.setModuleGroupPath(model, existingModule, group);
179 loadModules((ModuleModelImpl)model);
181 ApplicationManager.getApplication().runWriteAction(new Runnable() {
190 private ModulePath findCorrespondingPath(@NotNull Module existingModule) {
191 for (ModulePath modulePath : myModulePaths) {
192 if (modulePath.getPath().equals(existingModule.getModuleFilePath())) return modulePath;
198 public static final class ModulePath {
199 private final String myPath;
200 private final String myModuleGroup;
202 public ModulePath(String path, String moduleGroup) {
204 myModuleGroup = moduleGroup;
207 public String getPath() {
211 public String getModuleGroup() {
212 return myModuleGroup;
217 public static ModulePath[] getPathsToModuleFiles(@NotNull Element element) {
218 final List<ModulePath> paths = new ArrayList<ModulePath>();
219 final Element modules = element.getChild(ELEMENT_MODULES);
220 if (modules != null) {
221 for (final Object value : modules.getChildren(ELEMENT_MODULE)) {
222 Element moduleElement = (Element)value;
223 final String fileUrlValue = moduleElement.getAttributeValue(ATTRIBUTE_FILEURL);
224 final String filepath;
225 if (fileUrlValue != null) {
226 filepath = VirtualFileManager.extractPath(fileUrlValue).replace('/', File.separatorChar);
229 // [dsl] support for older formats
230 filepath = moduleElement.getAttributeValue(ATTRIBUTE_FILEPATH).replace('/', File.separatorChar);
232 final String group = moduleElement.getAttributeValue(ATTRIBUTE_GROUP);
233 paths.add(new ModulePath(filepath, group));
236 return paths.toArray(new ModulePath[paths.size()]);
239 public void readExternal(@NotNull Element element) {
240 myModulePaths = new ArrayList<ModulePath>(Arrays.asList(getPathsToModuleFiles(element)));
243 protected void loadModules(@NotNull ModuleModelImpl moduleModel) {
244 if (myModulePaths == null || myModulePaths.isEmpty()) {
247 ModuleGroupInterner groupInterner = new ModuleGroupInterner();
249 final ProgressIndicator progressIndicator = myProject.isDefault() ? null : ProgressIndicatorProvider.getGlobalProgressIndicator();
250 if (progressIndicator != null) {
251 progressIndicator.setText("Loading modules...");
252 progressIndicator.setText2("");
254 myFailedModulePaths.clear();
255 myFailedModulePaths.addAll(myModulePaths);
256 final List<Module> modulesWithUnknownTypes = new ArrayList<Module>();
257 List<ModuleLoadingErrorDescription> errors = new ArrayList<ModuleLoadingErrorDescription>();
259 for (int i = 0; i < myModulePaths.size(); i++) {
260 ModulePath modulePath = myModulePaths.get(i);
261 if (progressIndicator != null) {
262 progressIndicator.setFraction((double) i / myModulePaths.size());
265 final Module module = moduleModel.loadModuleInternal(modulePath.getPath());
266 if (isUnknownModuleType(module)) {
267 modulesWithUnknownTypes.add(module);
269 final String groupPathString = modulePath.getModuleGroup();
270 if (groupPathString != null) {
271 final String[] groupPath = groupPathString.split(MODULE_GROUP_SEPARATOR);
273 groupInterner.setModuleGroupPath(moduleModel, module, groupPath); //model should be updated too
275 myFailedModulePaths.remove(modulePath);
277 catch (IOException e) {
278 errors.add(ModuleLoadingErrorDescription.create(ProjectBundle.message("module.cannot.load.error", modulePath.getPath(), e.getMessage()),
281 catch (ModuleWithNameAlreadyExists moduleWithNameAlreadyExists) {
282 errors.add(ModuleLoadingErrorDescription.create(moduleWithNameAlreadyExists.getMessage(), modulePath, this));
286 onModuleLoadErrors(errors);
288 showUnknownModuleTypeNotification(modulesWithUnknownTypes);
290 if (progressIndicator != null) {
291 progressIndicator.setIndeterminate(true);
295 protected boolean isUnknownModuleType(@NotNull Module module) {
299 protected void showUnknownModuleTypeNotification(@NotNull List<Module> types) {
302 protected void fireModuleAdded(@NotNull Module module) {
303 myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleAdded(myProject, module);
306 protected void fireModuleRemoved(@NotNull Module module) {
307 myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleRemoved(myProject, module);
310 protected void fireBeforeModuleRemoved(@NotNull Module module) {
311 myMessageBus.syncPublisher(ProjectTopics.MODULES).beforeModuleRemoved(myProject, module);
314 protected void fireModulesRenamed(@NotNull List<Module> modules, @NotNull final Map<Module, String> oldNames) {
315 if (!modules.isEmpty()) {
316 myMessageBus.syncPublisher(ProjectTopics.MODULES).modulesRenamed(myProject, modules, new Function<Module, String>() {
318 public String fun(Module module) {
319 return oldNames.get(module);
325 protected void onModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
326 if (errors.isEmpty()) return;
328 myModuleModel.myModulesCache = null;
329 for (ModuleLoadingErrorDescription error : errors) {
330 final Module module = myModuleModel.getModuleByFilePath(FileUtil.toSystemIndependentName(error.getModulePath().getPath()));
331 if (module != null) {
332 myModuleModel.myModules.remove(module.getName());
333 ApplicationManager.getApplication().invokeLater(new Runnable() {
336 Disposer.dispose(module);
338 }, module.getDisposed());
342 fireModuleLoadErrors(errors);
345 protected void fireModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
346 if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
347 throw new RuntimeException(errors.get(0).getDescription());
350 ProjectLoadingErrorsNotifier.getInstance(myProject).registerErrors(errors);
353 public void removeFailedModulePath(@NotNull ModulePath modulePath) {
354 myFailedModulePaths.remove(modulePath);
359 public ModifiableModuleModel getModifiableModel() {
360 ApplicationManager.getApplication().assertReadAccessAllowed();
361 return new ModuleModelImpl(myModuleModel);
365 private abstract static class SaveItem {
367 protected abstract String getModuleName();
368 protected abstract String getGroupPathString();
370 protected abstract String getModuleFilePath();
372 public final void writeExternal(@NotNull Element parentElement) {
373 Element moduleElement = new Element(ELEMENT_MODULE);
374 final String moduleFilePath = getModuleFilePath();
375 final String url = VirtualFileManager.constructUrl(URLUtil.FILE_PROTOCOL, moduleFilePath);
376 moduleElement.setAttribute(ATTRIBUTE_FILEURL, url);
377 // [dsl] support for older builds
378 moduleElement.setAttribute(ATTRIBUTE_FILEPATH, moduleFilePath);
380 final String groupPath = getGroupPathString();
381 if (groupPath != null) {
382 moduleElement.setAttribute(ATTRIBUTE_GROUP, groupPath);
384 parentElement.addContent(moduleElement);
388 private class ModuleSaveItem extends SaveItem{
389 private final Module myModule;
391 public ModuleSaveItem(@NotNull Module module) {
397 protected String getModuleName() {
398 return myModule.getName();
402 protected String getGroupPathString() {
403 String[] groupPath = getModuleGroupPath(myModule);
404 return groupPath != null ? StringUtil.join(groupPath, MODULE_GROUP_SEPARATOR) : null;
409 protected String getModuleFilePath() {
410 return myModule.getModuleFilePath().replace(File.separatorChar, '/');
414 private static class ModulePathSaveItem extends SaveItem{
415 private final ModulePath myModulePath;
416 private final String myFilePath;
417 private final String myName;
419 private ModulePathSaveItem(@NotNull ModulePath modulePath) {
420 myModulePath = modulePath;
421 myFilePath = modulePath.getPath().replace(File.separatorChar, '/');
423 final int slashIndex = myFilePath.lastIndexOf('/');
424 final int startIndex = slashIndex >= 0 && slashIndex + 1 < myFilePath.length() ? slashIndex + 1 : 0;
425 final int endIndex = myFilePath.endsWith(IML_EXTENSION)
426 ? myFilePath.length() - IML_EXTENSION.length()
427 : myFilePath.length();
428 myName = myFilePath.substring(startIndex, endIndex);
433 protected String getModuleName() {
438 protected String getGroupPathString() {
439 return myModulePath.getModuleGroup();
444 protected String getModuleFilePath() {
449 public void writeExternal(@NotNull Element element) {
450 final Module[] collection = getModules();
452 List<SaveItem> sorted = new ArrayList<SaveItem>(collection.length + myFailedModulePaths.size());
453 for (Module module : collection) {
454 sorted.add(new ModuleSaveItem(module));
456 for (ModulePath modulePath : myFailedModulePaths) {
457 sorted.add(new ModulePathSaveItem(modulePath));
460 if (!sorted.isEmpty()) {
461 Collections.sort(sorted, new Comparator<SaveItem>() {
463 public int compare(SaveItem item1, SaveItem item2) {
464 return item1.getModuleName().compareTo(item2.getModuleName());
468 Element modules = new Element(ELEMENT_MODULES);
469 for (SaveItem saveItem : sorted) {
470 saveItem.writeExternal(modules);
472 element.addContent(modules);
478 public Module newModule(@NotNull String filePath, final String moduleTypeId) {
479 incModificationCount();
480 final ModifiableModuleModel modifiableModel = getModifiableModel();
481 final Module module = modifiableModel.newModule(filePath, moduleTypeId);
482 modifiableModel.commit();
488 public Module loadModule(@NotNull String filePath) throws IOException, JDOMException, ModuleWithNameAlreadyExists {
489 incModificationCount();
490 final ModifiableModuleModel modifiableModel = getModifiableModel();
491 final Module module = modifiableModel.loadModule(filePath);
492 modifiableModel.commit();
497 public void disposeModule(@NotNull final Module module) {
498 ApplicationManager.getApplication().runWriteAction(new Runnable() {
501 final ModifiableModuleModel modifiableModel = getModifiableModel();
502 modifiableModel.disposeModule(module);
503 modifiableModel.commit();
510 public Module[] getModules() {
511 if (myModuleModel.myIsWritable) {
512 ApplicationManager.getApplication().assertReadAccessAllowed();
514 return myModuleModel.getModules();
517 private volatile Module[] myCachedSortedModules;
521 public Module[] getSortedModules() {
522 ApplicationManager.getApplication().assertReadAccessAllowed();
523 deliverPendingEvents();
524 if (myCachedSortedModules == null) {
525 myCachedSortedModules = myModuleModel.getSortedModules();
527 return myCachedSortedModules;
531 public Module findModuleByName(@NotNull String name) {
532 ApplicationManager.getApplication().assertReadAccessAllowed();
533 return myModuleModel.findModuleByName(name);
536 private volatile Comparator<Module> myCachedModuleComparator;
540 public Comparator<Module> moduleDependencyComparator() {
541 ApplicationManager.getApplication().assertReadAccessAllowed();
542 deliverPendingEvents();
543 if (myCachedModuleComparator == null) {
544 myCachedModuleComparator = myModuleModel.moduleDependencyComparator();
546 return myCachedModuleComparator;
549 protected void deliverPendingEvents() {
554 public Graph<Module> moduleGraph() {
555 return moduleGraph(true);
560 public Graph<Module> moduleGraph(boolean includeTests) {
561 ApplicationManager.getApplication().assertReadAccessAllowed();
562 return myModuleModel.moduleGraph(includeTests);
567 public List<Module> getModuleDependentModules(@NotNull Module module) {
568 ApplicationManager.getApplication().assertReadAccessAllowed();
569 return myModuleModel.getModuleDependentModules(module);
573 public boolean isModuleDependent(@NotNull Module module, @NotNull Module onModule) {
574 ApplicationManager.getApplication().assertReadAccessAllowed();
575 return myModuleModel.isModuleDependent(module, onModule);
579 public void projectOpened() {
582 myModuleModel.projectOpened();
585 protected void fireModulesAdded() {
586 for (final Module module : myModuleModel.myModules.values()) {
587 fireModuleAddedInWriteAction(module);
591 protected void fireModuleAddedInWriteAction(@NotNull final Module module) {
592 ApplicationManager.getApplication().runWriteAction(new Runnable() {
595 ((ModuleEx)module).moduleAdded();
596 fireModuleAdded(module);
602 public void projectClosed() {
603 myModuleModel.projectClosed();
606 public static void commitModelWithRunnable(@NotNull ModifiableModuleModel model, Runnable runnable) {
607 ((ModuleModelImpl)model).commitWithRunnable(runnable);
611 protected abstract ModuleEx createModule(@NotNull String filePath);
614 protected abstract ModuleEx createAndLoadModule(@NotNull String filePath) throws IOException;
616 class ModuleModelImpl implements ModifiableModuleModel {
617 final Map<String, Module> myModules = new LinkedHashMap<String, Module>();
618 private volatile Module[] myModulesCache;
620 private final List<Module> myModulesToDispose = new ArrayList<Module>();
621 private final Map<Module, String> myModuleToNewName = new HashMap<Module, String>();
622 private final Map<String, Module> myNewNameToModule = new HashMap<String, Module>();
623 private boolean myIsWritable;
624 private Map<Module, String[]> myModuleGroupPath;
626 private ModuleModelImpl() {
627 myIsWritable = false;
630 private ModuleModelImpl(@NotNull ModuleModelImpl that) {
631 myModules.putAll(that.myModules);
632 final Map<Module, String[]> groupPath = that.myModuleGroupPath;
633 if (groupPath != null){
634 myModuleGroupPath = new THashMap<Module, String[]>();
635 myModuleGroupPath.putAll(that.myModuleGroupPath);
640 private void assertWritable() {
641 LOG.assertTrue(myIsWritable, "Attempt to modify committed ModifiableModuleModel");
646 public Module[] getModules() {
647 if (myModulesCache == null) {
648 Collection<Module> modules = myModules.values();
649 myModulesCache = modules.toArray(new Module[modules.size()]);
651 return myModulesCache;
655 private Module[] getSortedModules() {
656 Module[] allModules = getModules().clone();
657 Arrays.sort(allModules, moduleDependencyComparator());
662 public void renameModule(@NotNull Module module, @NotNull String newName) throws ModuleWithNameAlreadyExists {
663 final Module oldModule = getModuleByNewName(newName);
664 myNewNameToModule.remove(myModuleToNewName.get(module));
665 if(module.getName().equals(newName)){ // if renaming to itself, forget it altogether
666 myModuleToNewName.remove(module);
667 myNewNameToModule.remove(newName);
669 myModuleToNewName.put(module, newName);
670 myNewNameToModule.put(newName, module);
673 if (oldModule != null) {
674 throw new ModuleWithNameAlreadyExists(ProjectBundle.message("module.already.exists.error", newName), newName);
679 public Module getModuleToBeRenamed(@NotNull String newName) {
680 return myNewNameToModule.get(newName);
683 private Module getModuleByNewName(@NotNull String newName) {
684 final Module moduleToBeRenamed = getModuleToBeRenamed(newName);
685 if (moduleToBeRenamed != null) {
686 return moduleToBeRenamed;
688 final Module moduleWithOldName = findModuleByName(newName);
689 return myModuleToNewName.get(moduleWithOldName) == null ? moduleWithOldName : null;
693 public String getNewName(@NotNull Module module) {
694 return myModuleToNewName.get(module);
699 public Module newModule(@NotNull String filePath, final String moduleTypeId) {
700 return newModule(filePath, moduleTypeId, null);
705 public Module newModule(@NotNull String filePath, @NotNull final String moduleTypeId, @Nullable final Map<String, String> options) {
707 filePath = FileUtil.toSystemIndependentName(resolveShortWindowsName(filePath));
709 ModuleEx module = getModuleByFilePath(filePath);
710 if (module == null) {
711 module = createModule(filePath);
712 final ModuleEx newModule = module;
713 initModule(module, filePath, new Runnable() {
716 newModule.setOption(Module.ELEMENT_TYPE, moduleTypeId);
717 if (options != null) {
718 for (Map.Entry<String, String> option : options.entrySet()) {
719 newModule.setOption(option.getKey(), option.getValue());
729 private String resolveShortWindowsName(@NotNull String filePath) {
731 return FileUtil.resolveShortWindowsName(filePath);
733 catch (IOException ignored) {
739 private ModuleEx getModuleByFilePath(@NotNull String filePath) {
740 for (Module module : myModules.values()) {
741 if (SystemInfo.isFileSystemCaseSensitive ? module.getModuleFilePath().equals(filePath) : module.getModuleFilePath().equalsIgnoreCase(filePath)) {
742 return (ModuleEx)module;
750 public Module loadModule(@NotNull String filePath) throws IOException, ModuleWithNameAlreadyExists {
753 return loadModuleInternal(filePath);
755 catch (FileNotFoundException e) {
758 catch (IOException e) {
759 throw new IOException(ProjectBundle.message("module.corrupted.file.error", FileUtil.toSystemDependentName(filePath), e.getMessage()), e);
764 private Module loadModuleInternal(@NotNull String filePath) throws ModuleWithNameAlreadyExists, IOException {
765 filePath = resolveShortWindowsName(filePath);
766 final VirtualFile moduleFile = StandardFileSystems.local().findFileByPath(filePath);
767 if (moduleFile == null || !moduleFile.exists()) {
768 throw new FileNotFoundException(ProjectBundle.message("module.file.does.not.exist.error", filePath));
771 String path = moduleFile.getPath();
772 ModuleEx module = getModuleByFilePath(path);
773 if (module == null) {
774 ApplicationManager.getApplication().invokeAndWait(new Runnable() {
777 moduleFile.refresh(false, false);
779 }, ModalityState.any());
780 module = createAndLoadModule(path);
781 initModule(module, path, null);
786 private void initModule(@NotNull ModuleEx module, @NotNull String path, @Nullable Runnable beforeComponentCreation) {
787 module.init(path, beforeComponentCreation);
788 myModulesCache = null;
789 myModules.put(module.getName(), module);
793 public void disposeModule(@NotNull Module module) {
795 myModulesCache = null;
796 if (myModules.remove(module.getName()) != null) {
797 myModulesToDispose.add(module);
799 if (myModuleGroupPath != null){
800 myModuleGroupPath.remove(module);
805 public Module findModuleByName(@NotNull String name) {
806 Module module = myModules.get(name);
807 if (module != null && !module.isDisposed()) {
813 private Comparator<Module> moduleDependencyComparator() {
814 DFSTBuilder<Module> builder = new DFSTBuilder<Module>(moduleGraph(true));
815 return builder.comparator();
818 private Graph<Module> moduleGraph(final boolean includeTests) {
819 return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<Module>() {
821 public Collection<Module> getNodes() {
822 return myModules.values();
826 public Iterator<Module> getIn(Module m) {
827 Module[] dependentModules = ModuleRootManager.getInstance(m).getDependencies(includeTests);
828 return Arrays.asList(dependentModules).iterator();
833 @NotNull private List<Module> getModuleDependentModules(Module module) {
834 List<Module> result = new ArrayList<Module>();
835 for (Module aModule : myModules.values()) {
836 if (isModuleDependent(aModule, module)) {
843 private boolean isModuleDependent(Module module, Module onModule) {
844 return ModuleRootManager.getInstance(module).isDependsOn(onModule);
848 public void commit() {
849 ModifiableRootModel[] rootModels = new ModifiableRootModel[0];
850 ModifiableModelCommitter.multiCommit(rootModels, this);
853 private void commitWithRunnable(Runnable runnable) {
854 commitModel(this, runnable);
855 clearRenamingStuff();
858 private void clearRenamingStuff() {
859 myModuleToNewName.clear();
860 myNewNameToModule.clear();
864 public void dispose() {
866 ApplicationManager.getApplication().assertWriteAccessAllowed();
867 final Set<Module> set = new HashSet<Module>();
868 set.addAll(myModuleModel.myModules.values());
869 for (Module thisModule : myModules.values()) {
870 if (!set.contains(thisModule)) {
871 Disposer.dispose(thisModule);
874 for (Module moduleToDispose : myModulesToDispose) {
875 if (!set.contains(moduleToDispose)) {
876 Disposer.dispose(moduleToDispose);
879 clearRenamingStuff();
883 public boolean isChanged() {
887 return !myModules.equals(myModuleModel.myModules) || !Comparing.equal(myModuleModel.myModuleGroupPath, myModuleGroupPath);
890 private void disposeModel() {
891 myModulesCache = null;
892 for (final Module module : myModules.values()) {
893 Disposer.dispose(module);
896 myModuleGroupPath = null;
899 public void projectOpened() {
900 for (final Module aCollection : myModules.values()) {
901 ModuleEx module = (ModuleEx)aCollection;
902 module.projectOpened();
906 public void projectClosed() {
907 for (Module aCollection : myModules.values()) {
908 ModuleEx module = (ModuleEx)aCollection;
909 module.projectClosed();
914 public String[] getModuleGroupPath(Module module) {
915 return myModuleGroupPath == null ? null : myModuleGroupPath.get(module);
919 public boolean hasModuleGroups() {
920 return myModuleGroupPath != null && !myModuleGroupPath.isEmpty();
924 public void setModuleGroupPath(@NotNull Module module, @Nullable("null means remove") String[] groupPath) {
925 if (myModuleGroupPath == null) {
926 myModuleGroupPath = new THashMap<Module, String[]>();
928 if (groupPath == null) {
929 myModuleGroupPath.remove(module);
932 myModuleGroupPath.put(module, groupPath);
937 private void commitModel(final ModuleModelImpl moduleModel, final Runnable runnable) {
938 myModuleModel.myModulesCache = null;
939 incModificationCount();
940 ApplicationManager.getApplication().assertWriteAccessAllowed();
941 final Collection<Module> oldModules = myModuleModel.myModules.values();
942 final Collection<Module> newModules = moduleModel.myModules.values();
943 final List<Module> removedModules = new ArrayList<Module>(oldModules);
944 removedModules.removeAll(newModules);
945 final List<Module> addedModules = new ArrayList<Module>(newModules);
946 addedModules.removeAll(oldModules);
948 ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
951 for (Module removedModule : removedModules) {
952 fireBeforeModuleRemoved(removedModule);
956 List<Module> neverAddedModules = new ArrayList<Module>(moduleModel.myModulesToDispose);
957 neverAddedModules.removeAll(myModuleModel.myModules.values());
958 for (final Module neverAddedModule : neverAddedModules) {
959 neverAddedModule.putUserData(DISPOSED_MODULE_NAME, neverAddedModule.getName());
960 Disposer.dispose(neverAddedModule);
963 if (runnable != null) {
967 final Map<Module, String> modulesToNewNamesMap = moduleModel.myModuleToNewName;
968 final Set<Module> modulesToBeRenamed = modulesToNewNamesMap.keySet();
969 modulesToBeRenamed.removeAll(moduleModel.myModulesToDispose);
971 List<Module> modules = new ArrayList<Module>();
972 Map<Module, String> oldNames = ContainerUtil.newHashMap();
973 for (final Module module : modulesToBeRenamed) {
974 oldNames.put(module, module.getName());
975 moduleModel.myModules.remove(module.getName());
977 ((ModuleEx)module).rename(modulesToNewNamesMap.get(module));
978 moduleModel.myModules.put(module.getName(), module);
981 moduleModel.myIsWritable = false;
982 myModuleModel = moduleModel;
984 for (Module module : removedModules) {
985 fireModuleRemoved(module);
987 Disposer.dispose(module);
991 for (Module addedModule : addedModules) {
992 ((ModuleEx)addedModule).moduleAdded();
994 fireModuleAdded(addedModule);
998 fireModulesRenamed(modules, oldNames);
1004 public void fireModuleRenamedByVfsEvent(@NotNull final Module module, @NotNull final String oldName) {
1005 Module moduleInMap = myModuleModel.myModules.remove(oldName);
1006 LOG.assertTrue(moduleInMap == null || moduleInMap == module);
1007 myModuleModel.myModules.put(module.getName(), module);
1009 ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
1012 fireModulesRenamed(Collections.singletonList(module), Collections.singletonMap(module, oldName));
1018 public String[] getModuleGroupPath(@NotNull Module module) {
1019 return myModuleModel.getModuleGroupPath(module);
1022 public void setModuleGroupPath(Module module, String[] groupPath) {
1023 myModuleModel.setModuleGroupPath(module, groupPath);