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