cleanup
[idea/community.git] / platform / projectModel-impl / src / com / intellij / openapi / module / impl / ModuleManagerImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.openapi.module.impl;
18
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;
62
63 import java.io.File;
64 import java.io.FileNotFoundException;
65 import java.io.IOException;
66 import java.util.*;
67
68 /**
69  * @author max
70  */
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();
78
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";
88
89   public static ModuleManagerImpl getInstanceImpl(Project project) {
90     return (ModuleManagerImpl)getInstance(project);
91   }
92
93   public ModuleManagerImpl(Project project, MessageBus messageBus) {
94     myProject = project;
95     myMessageBus = messageBus;
96   }
97
98   protected void cleanCachedStuff() {
99     myCachedModuleComparator = null;
100     myCachedSortedModules = null;
101   }
102
103   @Override
104   @NotNull
105   public String getComponentName() {
106     return COMPONENT_NAME;
107   }
108
109   @Override
110   public void initComponent() {
111   }
112
113   @Override
114   public void disposeComponent() {
115     myModuleModel.disposeModel();
116   }
117
118   @Override
119   public Element getState() {
120     final Element e = new Element("state");
121     writeExternal(e);
122     return e;
123   }
124
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[]>() {
128       @Override
129       public int computeHashCode(String[] object) {
130         return Arrays.hashCode(object);
131       }
132
133       @Override
134       public boolean equals(String[] o1, String[] o2) {
135         return Arrays.equals(o1, o2);
136       }
137     });
138
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++) {
144           String g = group[i];
145           cached[i] = groups.intern(g);
146         }
147         paths.put(cached, cached);
148       }
149       model.setModuleGroupPath(module, cached);
150     }
151   }
152
153   @Override
154   public void loadState(Element state) {
155     List<ModulePath> prevPaths = myModulePaths;
156     readExternal(state);
157     if (prevPaths != null) {
158       final ModifiableModuleModel model = getModifiableModel();
159
160       Module[] existingModules = model.getModules();
161
162       ModuleGroupInterner groupInterner = new ModuleGroupInterner();
163       for (Module existingModule : existingModules) {
164         ModulePath correspondingPath = findCorrespondingPath(existingModule);
165         if (correspondingPath == null) {
166           model.disposeModule(existingModule);
167         }
168         else {
169           myModulePaths.remove(correspondingPath);
170
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);
175           }
176         }
177       }
178
179       loadModules((ModuleModelImpl)model);
180
181       ApplicationManager.getApplication().runWriteAction(new Runnable() {
182         @Override
183         public void run() {
184           model.commit();
185         }
186       });
187     }
188   }
189
190   private ModulePath findCorrespondingPath(@NotNull Module existingModule) {
191     for (ModulePath modulePath : myModulePaths) {
192       if (modulePath.getPath().equals(existingModule.getModuleFilePath())) return modulePath;
193     }
194
195     return null;
196   }
197
198   public static final class ModulePath {
199     private final String myPath;
200     private final String myModuleGroup;
201
202     public ModulePath(String path, String moduleGroup) {
203       myPath = path;
204       myModuleGroup = moduleGroup;
205     }
206
207     public String getPath() {
208       return myPath;
209     }
210
211     public String getModuleGroup() {
212       return myModuleGroup;
213     }
214   }
215
216   @NotNull
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);
227         }
228         else {
229           // [dsl] support for older formats
230           filepath = moduleElement.getAttributeValue(ATTRIBUTE_FILEPATH).replace('/', File.separatorChar);
231         }
232         final String group = moduleElement.getAttributeValue(ATTRIBUTE_GROUP);
233         paths.add(new ModulePath(filepath, group));
234       }
235     }
236     return paths.toArray(new ModulePath[paths.size()]);
237   }
238
239   public void readExternal(@NotNull Element element) {
240     myModulePaths = new ArrayList<ModulePath>(Arrays.asList(getPathsToModuleFiles(element)));
241   }
242
243   protected void loadModules(@NotNull ModuleModelImpl moduleModel) {
244     if (myModulePaths == null || myModulePaths.isEmpty()) {
245       return;
246     }
247     ModuleGroupInterner groupInterner = new ModuleGroupInterner();
248
249     final ProgressIndicator progressIndicator = myProject.isDefault() ? null : ProgressIndicatorProvider.getGlobalProgressIndicator();
250     if (progressIndicator != null) {
251       progressIndicator.setText("Loading modules...");
252       progressIndicator.setText2("");
253     }
254     myFailedModulePaths.clear();
255     myFailedModulePaths.addAll(myModulePaths);
256     final List<Module> modulesWithUnknownTypes = new ArrayList<Module>();
257     List<ModuleLoadingErrorDescription> errors = new ArrayList<ModuleLoadingErrorDescription>();
258
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());
263       }
264       try {
265         final Module module = moduleModel.loadModuleInternal(modulePath.getPath());
266         if (isUnknownModuleType(module)) {
267           modulesWithUnknownTypes.add(module);
268         }
269         final String groupPathString = modulePath.getModuleGroup();
270         if (groupPathString != null) {
271           final String[] groupPath = groupPathString.split(MODULE_GROUP_SEPARATOR);
272
273           groupInterner.setModuleGroupPath(moduleModel, module, groupPath); //model should be updated too
274         }
275         myFailedModulePaths.remove(modulePath);
276       }
277       catch (IOException e) {
278         errors.add(ModuleLoadingErrorDescription.create(ProjectBundle.message("module.cannot.load.error", modulePath.getPath(), e.getMessage()),
279                                                      modulePath, this));
280       }
281       catch (ModuleWithNameAlreadyExists moduleWithNameAlreadyExists) {
282         errors.add(ModuleLoadingErrorDescription.create(moduleWithNameAlreadyExists.getMessage(), modulePath, this));
283       }
284     }
285
286     onModuleLoadErrors(errors);
287
288     showUnknownModuleTypeNotification(modulesWithUnknownTypes);
289
290     if (progressIndicator != null) {
291       progressIndicator.setIndeterminate(true);
292     }
293   }
294
295   protected boolean isUnknownModuleType(@NotNull Module module) {
296     return false;
297   }
298
299   protected void showUnknownModuleTypeNotification(@NotNull List<Module> types) {
300   }
301
302   protected void fireModuleAdded(@NotNull Module module) {
303     myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleAdded(myProject, module);
304   }
305
306   protected void fireModuleRemoved(@NotNull Module module) {
307     myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleRemoved(myProject, module);
308   }
309
310   protected void fireBeforeModuleRemoved(@NotNull Module module) {
311     myMessageBus.syncPublisher(ProjectTopics.MODULES).beforeModuleRemoved(myProject, module);
312   }
313
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>() {
317         @Override
318         public String fun(Module module) {
319           return oldNames.get(module);
320         }
321       });
322     }
323   }
324
325   protected void onModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
326     if (errors.isEmpty()) return;
327
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() {
334           @Override
335           public void run() {
336             Disposer.dispose(module);
337           }
338         }, module.getDisposed());
339       }
340     }
341
342     fireModuleLoadErrors(errors);
343   }
344
345   protected void fireModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
346     if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
347       throw new RuntimeException(errors.get(0).getDescription());
348     }
349
350     ProjectLoadingErrorsNotifier.getInstance(myProject).registerErrors(errors);
351   }
352
353   public void removeFailedModulePath(@NotNull ModulePath modulePath) {
354     myFailedModulePaths.remove(modulePath);
355   }
356
357   @Override
358   @NotNull
359   public ModifiableModuleModel getModifiableModel() {
360     ApplicationManager.getApplication().assertReadAccessAllowed();
361     return new ModuleModelImpl(myModuleModel);
362   }
363
364
365   private abstract static class SaveItem {
366     @NotNull
367     protected abstract String getModuleName();
368     protected abstract String getGroupPathString();
369     @NotNull
370     protected abstract String getModuleFilePath();
371
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);
379
380       final String groupPath = getGroupPathString();
381       if (groupPath != null) {
382         moduleElement.setAttribute(ATTRIBUTE_GROUP, groupPath);
383       }
384       parentElement.addContent(moduleElement);
385     }
386   }
387
388   private class ModuleSaveItem extends SaveItem{
389     private final Module myModule;
390
391     public ModuleSaveItem(@NotNull Module module) {
392       myModule = module;
393     }
394
395     @Override
396     @NotNull
397     protected String getModuleName() {
398       return myModule.getName();
399     }
400
401     @Override
402     protected String getGroupPathString() {
403       String[] groupPath = getModuleGroupPath(myModule);
404       return groupPath != null ? StringUtil.join(groupPath, MODULE_GROUP_SEPARATOR) : null;
405     }
406
407     @Override
408     @NotNull
409     protected String getModuleFilePath() {
410       return myModule.getModuleFilePath().replace(File.separatorChar, '/');
411     }
412   }
413
414   private static class ModulePathSaveItem extends SaveItem{
415     private final ModulePath myModulePath;
416     private final String myFilePath;
417     private final String myName;
418
419     private ModulePathSaveItem(@NotNull ModulePath modulePath) {
420       myModulePath = modulePath;
421       myFilePath = modulePath.getPath().replace(File.separatorChar, '/');
422
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);
429     }
430
431     @Override
432     @NotNull
433     protected String getModuleName() {
434       return myName;
435     }
436
437     @Override
438     protected String getGroupPathString() {
439       return myModulePath.getModuleGroup();
440     }
441
442     @Override
443     @NotNull
444     protected String getModuleFilePath() {
445       return myFilePath;
446     }
447   }
448
449   public void writeExternal(@NotNull Element element) {
450     final Module[] collection = getModules();
451
452     List<SaveItem> sorted = new ArrayList<SaveItem>(collection.length + myFailedModulePaths.size());
453     for (Module module : collection) {
454       sorted.add(new ModuleSaveItem(module));
455     }
456     for (ModulePath modulePath : myFailedModulePaths) {
457       sorted.add(new ModulePathSaveItem(modulePath));
458     }
459
460     if (!sorted.isEmpty()) {
461       Collections.sort(sorted, new Comparator<SaveItem>() {
462         @Override
463         public int compare(SaveItem item1, SaveItem item2) {
464           return item1.getModuleName().compareTo(item2.getModuleName());
465         }
466       });
467
468       Element modules = new Element(ELEMENT_MODULES);
469       for (SaveItem saveItem : sorted) {
470         saveItem.writeExternal(modules);
471       }
472       element.addContent(modules);
473     }
474   }
475
476   @Override
477   @NotNull
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();
483     return module;
484   }
485
486   @Override
487   @NotNull
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();
493     return module;
494   }
495
496   @Override
497   public void disposeModule(@NotNull final Module module) {
498     ApplicationManager.getApplication().runWriteAction(new Runnable() {
499       @Override
500       public void run() {
501         final ModifiableModuleModel modifiableModel = getModifiableModel();
502         modifiableModel.disposeModule(module);
503         modifiableModel.commit();
504       }
505     });
506   }
507
508   @Override
509   @NotNull
510   public Module[] getModules() {
511     if (myModuleModel.myIsWritable) {
512       ApplicationManager.getApplication().assertReadAccessAllowed();
513     }
514     return myModuleModel.getModules();
515   }
516
517   private volatile Module[] myCachedSortedModules;
518
519   @Override
520   @NotNull
521   public Module[] getSortedModules() {
522     ApplicationManager.getApplication().assertReadAccessAllowed();
523     deliverPendingEvents();
524     if (myCachedSortedModules == null) {
525       myCachedSortedModules = myModuleModel.getSortedModules();
526     }
527     return myCachedSortedModules;
528   }
529
530   @Override
531   public Module findModuleByName(@NotNull String name) {
532     ApplicationManager.getApplication().assertReadAccessAllowed();
533     return myModuleModel.findModuleByName(name);
534   }
535
536   private volatile Comparator<Module> myCachedModuleComparator;
537
538   @Override
539   @NotNull
540   public Comparator<Module> moduleDependencyComparator() {
541     ApplicationManager.getApplication().assertReadAccessAllowed();
542     deliverPendingEvents();
543     if (myCachedModuleComparator == null) {
544       myCachedModuleComparator = myModuleModel.moduleDependencyComparator();
545     }
546     return myCachedModuleComparator;
547   }
548
549   protected void deliverPendingEvents() {
550   }
551
552   @Override
553   @NotNull
554   public Graph<Module> moduleGraph() {
555     return moduleGraph(true);
556   }
557
558   @NotNull
559   @Override
560   public Graph<Module> moduleGraph(boolean includeTests) {
561     ApplicationManager.getApplication().assertReadAccessAllowed();
562     return myModuleModel.moduleGraph(includeTests);
563   }
564
565   @Override
566   @NotNull
567   public List<Module> getModuleDependentModules(@NotNull Module module) {
568     ApplicationManager.getApplication().assertReadAccessAllowed();
569     return myModuleModel.getModuleDependentModules(module);
570   }
571
572   @Override
573   public boolean isModuleDependent(@NotNull Module module, @NotNull Module onModule) {
574     ApplicationManager.getApplication().assertReadAccessAllowed();
575     return myModuleModel.isModuleDependent(module, onModule);
576   }
577
578   @Override
579   public void projectOpened() {
580     fireModulesAdded();
581
582     myModuleModel.projectOpened();
583   }
584
585   protected void fireModulesAdded() {
586     for (final Module module : myModuleModel.myModules.values()) {
587       fireModuleAddedInWriteAction(module);
588     }
589   }
590
591   protected void fireModuleAddedInWriteAction(@NotNull final Module module) {
592     ApplicationManager.getApplication().runWriteAction(new Runnable() {
593       @Override
594       public void run() {
595         ((ModuleEx)module).moduleAdded();
596         fireModuleAdded(module);
597       }
598     });
599   }
600
601   @Override
602   public void projectClosed() {
603     myModuleModel.projectClosed();
604   }
605
606   public static void commitModelWithRunnable(@NotNull ModifiableModuleModel model, Runnable runnable) {
607     ((ModuleModelImpl)model).commitWithRunnable(runnable);
608   }
609
610   @NotNull
611   protected abstract ModuleEx createModule(@NotNull String filePath);
612
613   @NotNull
614   protected abstract ModuleEx createAndLoadModule(@NotNull String filePath) throws IOException;
615
616   class ModuleModelImpl implements ModifiableModuleModel {
617     final Map<String, Module> myModules = new LinkedHashMap<String, Module>();
618     private volatile Module[] myModulesCache;
619
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;
625
626     private ModuleModelImpl() {
627       myIsWritable = false;
628     }
629
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);
636       }
637       myIsWritable = true;
638     }
639
640     private void assertWritable() {
641       LOG.assertTrue(myIsWritable, "Attempt to modify committed ModifiableModuleModel");
642     }
643
644     @Override
645     @NotNull
646     public Module[] getModules() {
647       if (myModulesCache == null) {
648         Collection<Module> modules = myModules.values();
649         myModulesCache = modules.toArray(new Module[modules.size()]);
650       }
651       return myModulesCache;
652     }
653
654     @NotNull
655     private Module[] getSortedModules() {
656       Module[] allModules = getModules().clone();
657       Arrays.sort(allModules, moduleDependencyComparator());
658       return allModules;
659     }
660
661     @Override
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);
668       } else {
669         myModuleToNewName.put(module, newName);
670         myNewNameToModule.put(newName, module);
671       }
672
673       if (oldModule != null) {
674         throw new ModuleWithNameAlreadyExists(ProjectBundle.message("module.already.exists.error", newName), newName);
675       }
676     }
677
678     @Override
679     public Module getModuleToBeRenamed(@NotNull String newName) {
680       return myNewNameToModule.get(newName);
681     }
682
683     private Module getModuleByNewName(@NotNull String newName) {
684       final Module moduleToBeRenamed = getModuleToBeRenamed(newName);
685       if (moduleToBeRenamed != null) {
686         return moduleToBeRenamed;
687       }
688       final Module moduleWithOldName = findModuleByName(newName);
689       return myModuleToNewName.get(moduleWithOldName) == null ? moduleWithOldName : null;
690     }
691
692     @Override
693     public String getNewName(@NotNull Module module) {
694       return myModuleToNewName.get(module);
695     }
696
697     @Override
698     @NotNull
699     public Module newModule(@NotNull String filePath, final String moduleTypeId) {
700       return newModule(filePath, moduleTypeId, null);
701     }
702
703     @Override
704     @NotNull
705     public Module newModule(@NotNull String filePath, @NotNull final String moduleTypeId, @Nullable final Map<String, String> options) {
706       assertWritable();
707       filePath = FileUtil.toSystemIndependentName(resolveShortWindowsName(filePath));
708
709       ModuleEx module = getModuleByFilePath(filePath);
710       if (module == null) {
711         module = createModule(filePath);
712         final ModuleEx newModule = module;
713         initModule(module, filePath, new Runnable() {
714           @Override
715           public void run() {
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());
720               }
721             }
722           }
723         });
724       }
725       return module;
726     }
727
728     @NotNull
729     private String resolveShortWindowsName(@NotNull String filePath) {
730       try {
731         return FileUtil.resolveShortWindowsName(filePath);
732       }
733       catch (IOException ignored) {
734         return filePath;
735       }
736     }
737
738     @Nullable
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;
743         }
744       }
745       return null;
746     }
747
748     @Override
749     @NotNull
750     public Module loadModule(@NotNull String filePath) throws IOException, ModuleWithNameAlreadyExists {
751       assertWritable();
752       try {
753         return loadModuleInternal(filePath);
754       }
755       catch (FileNotFoundException e) {
756         throw e;
757       }
758       catch (IOException e) {
759         throw new IOException(ProjectBundle.message("module.corrupted.file.error", FileUtil.toSystemDependentName(filePath), e.getMessage()), e);
760       }
761     }
762
763     @NotNull
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));
769       }
770
771       String path = moduleFile.getPath();
772       ModuleEx module = getModuleByFilePath(path);
773       if (module == null) {
774         ApplicationManager.getApplication().invokeAndWait(new Runnable() {
775           @Override
776           public void run() {
777             moduleFile.refresh(false, false);
778           }
779         }, ModalityState.any());
780         module = createAndLoadModule(path);
781         initModule(module, path, null);
782       }
783       return module;
784     }
785
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);
790     }
791
792     @Override
793     public void disposeModule(@NotNull Module module) {
794       assertWritable();
795       myModulesCache = null;
796       if (myModules.remove(module.getName()) != null) {
797         myModulesToDispose.add(module);
798       }
799       if (myModuleGroupPath != null){
800         myModuleGroupPath.remove(module);
801       }
802     }
803
804     @Override
805     public Module findModuleByName(@NotNull String name) {
806       Module module = myModules.get(name);
807       if (module != null && !module.isDisposed()) {
808         return module;
809       }
810       return null;
811     }
812
813     private Comparator<Module> moduleDependencyComparator() {
814       DFSTBuilder<Module> builder = new DFSTBuilder<Module>(moduleGraph(true));
815       return builder.comparator();
816     }
817
818     private Graph<Module> moduleGraph(final boolean includeTests) {
819       return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<Module>() {
820         @Override
821         public Collection<Module> getNodes() {
822           return myModules.values();
823         }
824
825         @Override
826         public Iterator<Module> getIn(Module m) {
827           Module[] dependentModules = ModuleRootManager.getInstance(m).getDependencies(includeTests);
828           return Arrays.asList(dependentModules).iterator();
829         }
830       }));
831     }
832
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)) {
837           result.add(aModule);
838         }
839       }
840       return result;
841     }
842
843     private boolean isModuleDependent(Module module, Module onModule) {
844       return ModuleRootManager.getInstance(module).isDependsOn(onModule);
845     }
846
847     @Override
848     public void commit() {
849       ModifiableRootModel[] rootModels = new ModifiableRootModel[0];
850       ModifiableModelCommitter.multiCommit(rootModels, this);
851     }
852
853     private void commitWithRunnable(Runnable runnable) {
854       commitModel(this, runnable);
855       clearRenamingStuff();
856     }
857
858     private void clearRenamingStuff() {
859       myModuleToNewName.clear();
860       myNewNameToModule.clear();
861     }
862
863     @Override
864     public void dispose() {
865       assertWritable();
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);
872         }
873       }
874       for (Module moduleToDispose : myModulesToDispose) {
875         if (!set.contains(moduleToDispose)) {
876           Disposer.dispose(moduleToDispose);
877         }
878       }
879       clearRenamingStuff();
880     }
881
882     @Override
883     public boolean isChanged() {
884       if (!myIsWritable) {
885         return false;
886       }
887       return !myModules.equals(myModuleModel.myModules) || !Comparing.equal(myModuleModel.myModuleGroupPath, myModuleGroupPath);
888     }
889
890     private void disposeModel() {
891       myModulesCache = null;
892       for (final Module module : myModules.values()) {
893         Disposer.dispose(module);
894       }
895       myModules.clear();
896       myModuleGroupPath = null;
897     }
898
899     public void projectOpened() {
900       for (final Module aCollection : myModules.values()) {
901         ModuleEx module = (ModuleEx)aCollection;
902         module.projectOpened();
903       }
904     }
905
906     public void projectClosed() {
907       for (Module aCollection : myModules.values()) {
908         ModuleEx module = (ModuleEx)aCollection;
909         module.projectClosed();
910       }
911     }
912
913     @Override
914     public String[] getModuleGroupPath(Module module) {
915       return myModuleGroupPath == null ? null : myModuleGroupPath.get(module);
916     }
917
918     @Override
919     public boolean hasModuleGroups() {
920       return myModuleGroupPath != null && !myModuleGroupPath.isEmpty();
921     }
922
923     @Override
924     public void setModuleGroupPath(@NotNull Module module, @Nullable("null means remove") String[] groupPath) {
925       if (myModuleGroupPath == null) {
926         myModuleGroupPath = new THashMap<Module, String[]>();
927       }
928       if (groupPath == null) {
929         myModuleGroupPath.remove(module);
930       }
931       else {
932         myModuleGroupPath.put(module, groupPath);
933       }
934     }
935   }
936
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);
947
948     ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
949       @Override
950       public void run() {
951         for (Module removedModule : removedModules) {
952           fireBeforeModuleRemoved(removedModule);
953           cleanCachedStuff();
954         }
955
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);
961         }
962
963         if (runnable != null) {
964           runnable.run();
965         }
966
967         final Map<Module, String> modulesToNewNamesMap = moduleModel.myModuleToNewName;
968         final Set<Module> modulesToBeRenamed = modulesToNewNamesMap.keySet();
969         modulesToBeRenamed.removeAll(moduleModel.myModulesToDispose);
970
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());
976           modules.add(module);
977           ((ModuleEx)module).rename(modulesToNewNamesMap.get(module));
978           moduleModel.myModules.put(module.getName(), module);
979         }
980
981         moduleModel.myIsWritable = false;
982         myModuleModel = moduleModel;
983
984         for (Module module : removedModules) {
985           fireModuleRemoved(module);
986           cleanCachedStuff();
987           Disposer.dispose(module);
988           cleanCachedStuff();
989         }
990
991         for (Module addedModule : addedModules) {
992           ((ModuleEx)addedModule).moduleAdded();
993           cleanCachedStuff();
994           fireModuleAdded(addedModule);
995           cleanCachedStuff();
996         }
997         cleanCachedStuff();
998         fireModulesRenamed(modules, oldNames);
999         cleanCachedStuff();
1000       }
1001     }, false, true);
1002   }
1003
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);
1008
1009     ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
1010       @Override
1011       public void run() {
1012         fireModulesRenamed(Collections.singletonList(module), Collections.singletonMap(module, oldName));
1013       }
1014     }, false, true);
1015   }
1016
1017   @Override
1018   public String[] getModuleGroupPath(@NotNull Module module) {
1019     return myModuleModel.getModuleGroupPath(module);
1020   }
1021
1022   public void setModuleGroupPath(Module module, String[] groupPath) {
1023     myModuleModel.setModuleGroupPath(module, groupPath);
1024   }
1025 }
1026