notnull
[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.components.StateStorageException;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.module.*;
27 import com.intellij.openapi.progress.ProgressIndicator;
28 import com.intellij.openapi.progress.ProgressIndicatorProvider;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.project.ProjectBundle;
31 import com.intellij.openapi.roots.ModifiableRootModel;
32 import com.intellij.openapi.roots.ModuleRootManager;
33 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
34 import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
35 import com.intellij.openapi.util.Comparing;
36 import com.intellij.openapi.util.Disposer;
37 import com.intellij.openapi.util.InvalidDataException;
38 import com.intellij.openapi.util.Key;
39 import com.intellij.openapi.util.io.FileUtil;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.openapi.vfs.StandardFileSystems;
42 import com.intellij.openapi.vfs.VirtualFile;
43 import com.intellij.openapi.vfs.VirtualFileManager;
44 import com.intellij.util.Function;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.containers.HashMap;
47 import com.intellij.util.containers.HashSet;
48 import com.intellij.util.containers.StringInterner;
49 import com.intellij.util.containers.hash.EqualityPolicy;
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 com.intellij.util.text.FilePathHashingStrategy;
58 import gnu.trove.THashMap;
59 import gnu.trove.TObjectHashingStrategy;
60 import org.jdom.Element;
61 import org.jdom.JDOMException;
62 import org.jetbrains.annotations.NonNls;
63 import org.jetbrains.annotations.NotNull;
64 import org.jetbrains.annotations.Nullable;
65
66 import java.io.File;
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
106   @Override
107   @NotNull
108   public String getComponentName() {
109     return COMPONENT_NAME;
110   }
111
112   @Override
113   public void initComponent() {
114   }
115
116   @Override
117   public void disposeComponent() {
118     myModuleModel.disposeModel();
119   }
120
121   @Override
122   public Element getState() {
123     final Element e = new Element("state");
124     writeExternal(e);
125     return e;
126   }
127
128   private static class ModuleGroupInterner {
129     private final StringInterner groups = new StringInterner();
130     private final Map<String[], String[]> paths = new THashMap<String[], String[]>(new TObjectHashingStrategy<String[]>() {
131       @Override
132       public int computeHashCode(String[] object) {
133         return Arrays.hashCode(object);
134       }
135
136       @Override
137       public boolean equals(String[] o1, String[] o2) {
138         return Arrays.equals(o1, o2);
139       }
140     });
141
142     private void setModuleGroupPath(@NotNull ModifiableModuleModel model, @NotNull Module module, @Nullable String[] group) {
143       String[] cached = group == null ? null : paths.get(group);
144       if (cached == null && group != null) {
145         cached = new String[group.length];
146         for (int i = 0; i < group.length; i++) {
147           String g = group[i];
148           cached[i] = groups.intern(g);
149         }
150         paths.put(cached, cached);
151       }
152       model.setModuleGroupPath(module, cached);
153     }
154   }
155
156   @Override
157   public void loadState(Element state) {
158     List<ModulePath> prevPaths = myModulePaths;
159     readExternal(state);
160     if (prevPaths != null) {
161       ModifiableModuleModel model = getModifiableModel();
162
163       Module[] existingModules = model.getModules();
164
165       ModuleGroupInterner groupInterner = new ModuleGroupInterner();
166       for (Module existingModule : existingModules) {
167         ModulePath correspondingPath = findCorrespondingPath(existingModule);
168         if (correspondingPath == null) {
169           model.disposeModule(existingModule);
170         }
171         else {
172           myModulePaths.remove(correspondingPath);
173
174           String groupStr = correspondingPath.getModuleGroup();
175           String[] group = groupStr == null ? null : groupStr.split(MODULE_GROUP_SEPARATOR);
176           if (!Arrays.equals(group, model.getModuleGroupPath(existingModule))) {
177             groupInterner.setModuleGroupPath(model, existingModule, group);
178           }
179         }
180       }
181
182       loadModules((ModuleModelImpl)model);
183
184       model.commit();
185     }
186   }
187
188   private ModulePath findCorrespondingPath(@NotNull Module existingModule) {
189     for (ModulePath modulePath : myModulePaths) {
190       if (modulePath.getPath().equals(existingModule.getModuleFilePath())) return modulePath;
191     }
192
193     return null;
194   }
195
196   public static final class ModulePath {
197     private final String myPath;
198     private final String myModuleGroup;
199
200     public ModulePath(String path, String moduleGroup) {
201       myPath = path;
202       myModuleGroup = moduleGroup;
203     }
204
205     public String getPath() {
206       return myPath;
207     }
208
209     public String getModuleGroup() {
210       return myModuleGroup;
211     }
212   }
213
214   @NotNull
215   public static ModulePath[] getPathsToModuleFiles(@NotNull Element element) {
216     final List<ModulePath> paths = new ArrayList<ModulePath>();
217     final Element modules = element.getChild(ELEMENT_MODULES);
218     if (modules != null) {
219       for (final Object value : modules.getChildren(ELEMENT_MODULE)) {
220         Element moduleElement = (Element)value;
221         final String fileUrlValue = moduleElement.getAttributeValue(ATTRIBUTE_FILEURL);
222         final String filepath;
223         if (fileUrlValue != null) {
224           filepath = VirtualFileManager.extractPath(fileUrlValue).replace('/', File.separatorChar);
225         }
226         else {
227           // [dsl] support for older formats
228           filepath = moduleElement.getAttributeValue(ATTRIBUTE_FILEPATH).replace('/', File.separatorChar);
229         }
230         final String group = moduleElement.getAttributeValue(ATTRIBUTE_GROUP);
231         paths.add(new ModulePath(filepath, group));
232       }
233     }
234     return paths.toArray(new ModulePath[paths.size()]);
235   }
236
237   public void readExternal(@NotNull Element element) {
238     myModulePaths = new ArrayList<ModulePath>(Arrays.asList(getPathsToModuleFiles(element)));
239   }
240
241   protected void loadModules(@NotNull ModuleModelImpl moduleModel) {
242     if (myModulePaths == null || myModulePaths.isEmpty()) {
243       return;
244     }
245     ModuleGroupInterner groupInterner = new ModuleGroupInterner();
246
247     final ProgressIndicator progressIndicator = myProject.isDefault() ? null : ProgressIndicatorProvider.getGlobalProgressIndicator();
248     if (progressIndicator != null) {
249       progressIndicator.setText("Loading modules...");
250       progressIndicator.setText2("");
251     }
252     myFailedModulePaths.clear();
253     myFailedModulePaths.addAll(myModulePaths);
254     final List<Module> modulesWithUnknownTypes = new ArrayList<Module>();
255     List<ModuleLoadingErrorDescription> errors = new ArrayList<ModuleLoadingErrorDescription>();
256
257     for (int i = 0; i < myModulePaths.size(); i++) {
258       ModulePath modulePath = myModulePaths.get(i);
259       if (progressIndicator != null) {
260         progressIndicator.setFraction((double) i / myModulePaths.size());
261       }
262       try {
263         final Module module = moduleModel.loadModuleInternal(modulePath.getPath());
264         if (isUnknownModuleType(module)) {
265           modulesWithUnknownTypes.add(module);
266         }
267         final String groupPathString = modulePath.getModuleGroup();
268         if (groupPathString != null) {
269           final String[] groupPath = groupPathString.split(MODULE_GROUP_SEPARATOR);
270
271           groupInterner.setModuleGroupPath(moduleModel, module, groupPath); //model should be updated too
272         }
273         myFailedModulePaths.remove(modulePath);
274       }
275       catch (final IOException e) {
276         errors.add(ModuleLoadingErrorDescription.create(ProjectBundle.message("module.cannot.load.error", modulePath.getPath(), e.getMessage()),
277                                                      modulePath, this));
278       }
279       catch (final ModuleWithNameAlreadyExists moduleWithNameAlreadyExists) {
280         errors.add(ModuleLoadingErrorDescription.create(moduleWithNameAlreadyExists.getMessage(), modulePath, this));
281       }
282       catch (StateStorageException e) {
283         errors.add(ModuleLoadingErrorDescription.create(ProjectBundle.message("module.cannot.load.error", modulePath.getPath(), e.getMessage()),
284                                                      modulePath, this));
285       }
286     }
287
288     onModuleLoadErrors(errors);
289
290     showUnknownModuleTypeNotification(modulesWithUnknownTypes);
291
292     if (progressIndicator != null) {
293       progressIndicator.setIndeterminate(true);
294     }
295   }
296
297   protected boolean isUnknownModuleType(@NotNull Module module) {
298     return false;
299   }
300
301   protected void showUnknownModuleTypeNotification(@NotNull List<Module> types) {
302   }
303
304   protected void fireModuleAdded(@NotNull Module module) {
305     myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleAdded(myProject, module);
306   }
307
308   protected void fireModuleRemoved(@NotNull Module module) {
309     myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleRemoved(myProject, module);
310   }
311
312   protected void fireBeforeModuleRemoved(@NotNull Module module) {
313     myMessageBus.syncPublisher(ProjectTopics.MODULES).beforeModuleRemoved(myProject, module);
314   }
315
316   protected void fireModulesRenamed(@NotNull List<Module> modules, @NotNull final Map<Module, String> oldNames) {
317     if (!modules.isEmpty()) {
318       myMessageBus.syncPublisher(ProjectTopics.MODULES).modulesRenamed(myProject, modules, new Function<Module, String>() {
319         @Override
320         public String fun(Module module) {
321           return oldNames.get(module);
322         }
323       });
324     }
325   }
326
327   protected void onModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
328     if (errors.isEmpty()) return;
329
330     myModuleModel.myModulesCache = null;
331     for (ModuleLoadingErrorDescription error : errors) {
332       final Module module = myModuleModel.myPathToModule.remove(FileUtil.toSystemIndependentName(error.getModulePath().getPath()));
333       if (module != null) {
334         ApplicationManager.getApplication().invokeLater(new Runnable() {
335           @Override
336           public void run() {
337             Disposer.dispose(module);
338           }
339         });
340       }
341     }
342
343     fireModuleLoadErrors(errors);
344   }
345
346   protected void fireModuleLoadErrors(@NotNull List<ModuleLoadingErrorDescription> errors) {
347     if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
348       throw new RuntimeException(errors.get(0).getDescription());
349     }
350
351     ProjectLoadingErrorsNotifier.getInstance(myProject).registerErrors(errors);
352   }
353
354   public void removeFailedModulePath(@NotNull ModulePath modulePath) {
355     myFailedModulePaths.remove(modulePath);
356   }
357
358   @Override
359   @NotNull
360   public ModifiableModuleModel getModifiableModel() {
361     ApplicationManager.getApplication().assertReadAccessAllowed();
362     return new ModuleModelImpl(myModuleModel);
363   }
364
365
366   private abstract static class SaveItem {
367     @NotNull
368     protected abstract String getModuleName();
369     protected abstract String getGroupPathString();
370     @NotNull
371     protected abstract String getModuleFilePath();
372
373     public final void writeExternal(@NotNull Element parentElement) {
374       Element moduleElement = new Element(ELEMENT_MODULE);
375       final String moduleFilePath = getModuleFilePath();
376       final String url = VirtualFileManager.constructUrl(URLUtil.FILE_PROTOCOL, moduleFilePath);
377       moduleElement.setAttribute(ATTRIBUTE_FILEURL, url);
378       // [dsl] support for older builds
379       moduleElement.setAttribute(ATTRIBUTE_FILEPATH, moduleFilePath);
380
381       final String groupPath = getGroupPathString();
382       if (groupPath != null) {
383         moduleElement.setAttribute(ATTRIBUTE_GROUP, groupPath);
384       }
385       parentElement.addContent(moduleElement);
386     }
387   }
388
389   private class ModuleSaveItem extends SaveItem{
390     private final Module myModule;
391
392     public ModuleSaveItem(@NotNull Module module) {
393       myModule = module;
394     }
395
396     @Override
397     @NotNull
398     protected String getModuleName() {
399       return myModule.getName();
400     }
401
402     @Override
403     protected String getGroupPathString() {
404       String[] groupPath = getModuleGroupPath(myModule);
405       return groupPath != null ? StringUtil.join(groupPath, MODULE_GROUP_SEPARATOR) : null;
406     }
407
408     @Override
409     @NotNull
410     protected String getModuleFilePath() {
411       return myModule.getModuleFilePath().replace(File.separatorChar, '/');
412     }
413   }
414
415   private static class ModulePathSaveItem extends SaveItem{
416     private final ModulePath myModulePath;
417     private final String myFilePath;
418     private final String myName;
419
420     private ModulePathSaveItem(@NotNull ModulePath modulePath) {
421       myModulePath = modulePath;
422       myFilePath = modulePath.getPath().replace(File.separatorChar, '/');
423
424       final int slashIndex = myFilePath.lastIndexOf('/');
425       final int startIndex = slashIndex >= 0 && slashIndex + 1 < myFilePath.length() ? slashIndex + 1 : 0;
426       final int endIndex = myFilePath.endsWith(IML_EXTENSION)
427                            ? myFilePath.length() - IML_EXTENSION.length()
428                            : myFilePath.length();
429       myName = myFilePath.substring(startIndex, endIndex);
430     }
431
432     @Override
433     @NotNull
434     protected String getModuleName() {
435       return myName;
436     }
437
438     @Override
439     protected String getGroupPathString() {
440       return myModulePath.getModuleGroup();
441     }
442
443     @Override
444     @NotNull
445     protected String getModuleFilePath() {
446       return myFilePath;
447     }
448   }
449
450   public void writeExternal(@NotNull Element element) {
451     final Element modules = new Element(ELEMENT_MODULES);
452     final Module[] collection = getModules();
453
454     List<SaveItem> sorted = new ArrayList<SaveItem>(collection.length + myFailedModulePaths.size());
455     for (Module module : collection) {
456       sorted.add(new ModuleSaveItem(module));
457     }
458     for (ModulePath modulePath : myFailedModulePaths) {
459       sorted.add(new ModulePathSaveItem(modulePath));
460     }
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     for (SaveItem saveItem : sorted) {
468       saveItem.writeExternal(modules);
469     }
470
471     element.addContent(modules);
472   }
473
474   @Override
475   @NotNull
476   public Module newModule(@NotNull String filePath, final String moduleTypeId) {
477     incModificationCount();
478     final ModifiableModuleModel modifiableModel = getModifiableModel();
479     final Module module = modifiableModel.newModule(filePath, moduleTypeId);
480     modifiableModel.commit();
481     return module;
482   }
483
484   @Override
485   @NotNull
486   public Module loadModule(@NotNull String filePath) throws InvalidDataException, IOException, JDOMException, ModuleWithNameAlreadyExists {
487     incModificationCount();
488     final ModifiableModuleModel modifiableModel = getModifiableModel();
489     final Module module = modifiableModel.loadModule(filePath);
490     modifiableModel.commit();
491     return module;
492   }
493
494   @Override
495   public void disposeModule(@NotNull final Module module) {
496     ApplicationManager.getApplication().runWriteAction(new Runnable() {
497       @Override
498       public void run() {
499         final ModifiableModuleModel modifiableModel = getModifiableModel();
500         modifiableModel.disposeModule(module);
501         modifiableModel.commit();
502       }
503     });
504   }
505
506   @Override
507   @NotNull
508   public Module[] getModules() {
509     if (myModuleModel.myIsWritable) {
510       ApplicationManager.getApplication().assertReadAccessAllowed();
511     }
512     return myModuleModel.getModules();
513   }
514
515   private volatile Module[] myCachedSortedModules;
516
517   @Override
518   @NotNull
519   public Module[] getSortedModules() {
520     ApplicationManager.getApplication().assertReadAccessAllowed();
521     deliverPendingEvents();
522     if (myCachedSortedModules == null) {
523       myCachedSortedModules = myModuleModel.getSortedModules();
524     }
525     return myCachedSortedModules;
526   }
527
528   @Override
529   public Module findModuleByName(@NotNull String name) {
530     ApplicationManager.getApplication().assertReadAccessAllowed();
531     return myModuleModel.findModuleByName(name);
532   }
533
534   private volatile Comparator<Module> myCachedModuleComparator;
535
536   @Override
537   @NotNull
538   public Comparator<Module> moduleDependencyComparator() {
539     ApplicationManager.getApplication().assertReadAccessAllowed();
540     deliverPendingEvents();
541     if (myCachedModuleComparator == null) {
542       myCachedModuleComparator = myModuleModel.moduleDependencyComparator();
543     }
544     return myCachedModuleComparator;
545   }
546
547   protected void deliverPendingEvents() {
548   }
549
550   @Override
551   @NotNull
552   public Graph<Module> moduleGraph() {
553     return moduleGraph(true);
554   }
555
556   @NotNull
557   @Override
558   public Graph<Module> moduleGraph(boolean includeTests) {
559     ApplicationManager.getApplication().assertReadAccessAllowed();
560     return myModuleModel.moduleGraph(includeTests);
561   }
562
563   @Override
564   @NotNull
565   public List<Module> getModuleDependentModules(@NotNull Module module) {
566     ApplicationManager.getApplication().assertReadAccessAllowed();
567     return myModuleModel.getModuleDependentModules(module);
568   }
569
570   @Override
571   public boolean isModuleDependent(@NotNull Module module, @NotNull Module onModule) {
572     ApplicationManager.getApplication().assertReadAccessAllowed();
573     return myModuleModel.isModuleDependent(module, onModule);
574   }
575
576   @Override
577   public void projectOpened() {
578     fireModulesAdded();
579
580     myModuleModel.projectOpened();
581   }
582
583   protected void fireModulesAdded() {
584     for (final Module module : myModuleModel.myPathToModule.values()) {
585       fireModuleAddedInWriteAction(module);
586     }
587   }
588
589   protected void fireModuleAddedInWriteAction(@NotNull final Module module) {
590     ApplicationManager.getApplication().runWriteAction(new Runnable() {
591       @Override
592       public void run() {
593         ((ModuleEx)module).moduleAdded();
594         fireModuleAdded(module);
595       }
596     });
597   }
598
599   @Override
600   public void projectClosed() {
601     myModuleModel.projectClosed();
602   }
603
604   public static void commitModelWithRunnable(@NotNull ModifiableModuleModel model, Runnable runnable) {
605     ((ModuleModelImpl)model).commitWithRunnable(runnable);
606   }
607
608   @NotNull
609   protected abstract ModuleEx createModule(@NotNull String filePath);
610
611   @NotNull
612   protected abstract ModuleEx createAndLoadModule(@NotNull String filePath) throws IOException;
613
614   class ModuleModelImpl implements ModifiableModuleModel {
615     final Map<String, Module> myPathToModule = new LinkedHashMap<String, Module>(new EqualityPolicy.ByHashingStrategy<String>(FilePathHashingStrategy.create()));
616     private volatile Module[] myModulesCache;
617
618     private final List<Module> myModulesToDispose = new ArrayList<Module>();
619     private final Map<Module, String> myModuleToNewName = new HashMap<Module, String>();
620     private final Map<String, Module> myNewNameToModule = new HashMap<String, Module>();
621     private boolean myIsWritable;
622     private Map<Module, String[]> myModuleGroupPath;
623
624     private ModuleModelImpl() {
625       myIsWritable = false;
626     }
627
628     private ModuleModelImpl(@NotNull ModuleModelImpl that) {
629       myPathToModule.putAll(that.myPathToModule);
630       final Map<Module, String[]> groupPath = that.myModuleGroupPath;
631       if (groupPath != null){
632         myModuleGroupPath = new THashMap<Module, String[]>();
633         myModuleGroupPath.putAll(that.myModuleGroupPath);
634       }
635       myIsWritable = true;
636     }
637
638     private void assertWritable() {
639       LOG.assertTrue(myIsWritable, "Attempt to modify committed ModifiableModuleModel");
640     }
641
642     @Override
643     @NotNull
644     public Module[] getModules() {
645       if (myModulesCache == null) {
646         Collection<Module> modules = myPathToModule.values();
647         myModulesCache = modules.toArray(new Module[modules.size()]);
648       }
649       return myModulesCache;
650     }
651
652     @NotNull
653     private Module[] getSortedModules() {
654       Module[] allModules = getModules().clone();
655       Arrays.sort(allModules, moduleDependencyComparator());
656       return allModules;
657     }
658
659     @Override
660     public void renameModule(@NotNull Module module, @NotNull String newName) throws ModuleWithNameAlreadyExists {
661       final Module oldModule = getModuleByNewName(newName);
662       myNewNameToModule.remove(myModuleToNewName.get(module));
663       if(module.getName().equals(newName)){ // if renaming to itself, forget it altogether
664         myModuleToNewName.remove(module);
665         myNewNameToModule.remove(newName);
666       } else {
667         myModuleToNewName.put(module, newName);
668         myNewNameToModule.put(newName, module);
669       }
670
671       if (oldModule != null) {
672         throw new ModuleWithNameAlreadyExists(ProjectBundle.message("module.already.exists.error", newName), newName);
673       }
674     }
675
676     @Override
677     public Module getModuleToBeRenamed(@NotNull String newName) {
678       return myNewNameToModule.get(newName);
679     }
680
681     private Module getModuleByNewName(@NotNull String newName) {
682       final Module moduleToBeRenamed = getModuleToBeRenamed(newName);
683       if (moduleToBeRenamed != null) {
684         return moduleToBeRenamed;
685       }
686       final Module moduleWithOldName = findModuleByName(newName);
687       return myModuleToNewName.get(moduleWithOldName) == null ? moduleWithOldName : null;
688     }
689
690     @Override
691     public String getNewName(@NotNull Module module) {
692       return myModuleToNewName.get(module);
693     }
694
695     @Override
696     @NotNull
697     public Module newModule(@NotNull String filePath, final String moduleTypeId) {
698       return newModule(filePath, moduleTypeId, null);
699     }
700
701     @Override
702     @NotNull
703     public Module newModule(@NotNull String filePath,
704                             final String moduleTypeId,
705                             @Nullable Map<String, String> options) {
706       assertWritable();
707       filePath = resolveShortWindowsName(filePath);
708
709       ModuleEx module = getModuleByFilePath(filePath);
710       if (module == null) {
711         module = createModule(filePath);
712         module.setOption(Module.ELEMENT_TYPE, moduleTypeId);
713         if (options != null) {
714           for ( Map.Entry<String,String> option : options.entrySet()) {
715             module.setOption(option.getKey(),option.getValue());
716           }
717         }
718         initModule(module);
719       }
720       return module;
721     }
722
723     @NotNull
724     private String resolveShortWindowsName(@NotNull String filePath) {
725       try {
726         return FileUtil.resolveShortWindowsName(filePath);
727       }
728       catch (IOException ignored) {
729         return filePath;
730       }
731     }
732
733     @Nullable
734     private ModuleEx getModuleByFilePath(@NotNull String filePath) {
735       return (ModuleEx)myPathToModule.get(filePath);
736     }
737
738     @Override
739     @NotNull
740     public Module loadModule(@NotNull String filePath) throws InvalidDataException, IOException, ModuleWithNameAlreadyExists {
741       assertWritable();
742       try {
743         return loadModuleInternal(filePath);
744       }
745       catch (StateStorageException e) {
746         throw new IOException(ProjectBundle.message("module.corrupted.file.error", FileUtil.toSystemDependentName(filePath), e.getMessage()));
747       }
748     }
749
750     @NotNull
751     private Module loadModuleInternal(@NotNull String filePath) throws ModuleWithNameAlreadyExists, IOException {
752       filePath = resolveShortWindowsName(filePath);
753       final VirtualFile moduleFile = StandardFileSystems.local().findFileByPath(filePath);
754       if (moduleFile == null || !moduleFile.exists()) {
755         throw new IOException(ProjectBundle.message("module.file.does.not.exist.error", filePath));
756       }
757
758       final String name = moduleFile.getName();
759
760       if (name.endsWith(IML_EXTENSION)) {
761         final String moduleName = name.substring(0, name.length() - 4);
762         for (Module module : myPathToModule.values()) {
763           if (module.getName().equals(moduleName)) {
764             throw new ModuleWithNameAlreadyExists(ProjectBundle.message("module.already.exists.error", moduleName), moduleName);
765           }
766         }
767       }
768
769       ModuleEx module = getModuleByFilePath(moduleFile.getPath());
770       if (module == null) {
771         ApplicationManager.getApplication().invokeAndWait(new Runnable() {
772           @Override
773           public void run() {
774             moduleFile.refresh(false, false);
775           }
776         }, ModalityState.any());
777         module = createAndLoadModule(moduleFile.getPath());
778         initModule(module);
779       }
780       return module;
781     }
782
783     private void initModule(@NotNull ModuleEx module) {
784       String path = module.getModuleFilePath();
785       myModulesCache = null;
786       myPathToModule.put(path, module);
787       module.loadModuleComponents();
788       module.init();
789     }
790
791     @Override
792     public void disposeModule(@NotNull Module module) {
793       assertWritable();
794       myModulesCache = null;
795       if (myPathToModule.values().contains(module)) {
796         myPathToModule.remove(module.getModuleFilePath());
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       for (Module module : myPathToModule.values()) {
807         if (!module.isDisposed() && module.getName().equals(name)) {
808           return module;
809         }
810       }
811       return null;
812     }
813
814     private Comparator<Module> moduleDependencyComparator() {
815       DFSTBuilder<Module> builder = new DFSTBuilder<Module>(moduleGraph(true));
816       return builder.comparator();
817     }
818
819     private Graph<Module> moduleGraph(final boolean includeTests) {
820       return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<Module>() {
821         @Override
822         public Collection<Module> getNodes() {
823           return myPathToModule.values();
824         }
825
826         @Override
827         public Iterator<Module> getIn(Module m) {
828           Module[] dependentModules = ModuleRootManager.getInstance(m).getDependencies(includeTests);
829           return Arrays.asList(dependentModules).iterator();
830         }
831       }));
832     }
833
834     @NotNull private List<Module> getModuleDependentModules(Module module) {
835       List<Module> result = new ArrayList<Module>();
836       for (Module aModule : myPathToModule.values()) {
837         if (isModuleDependent(aModule, module)) {
838           result.add(aModule);
839         }
840       }
841       return result;
842     }
843
844     private boolean isModuleDependent(Module module, Module onModule) {
845       return ModuleRootManager.getInstance(module).isDependsOn(onModule);
846     }
847
848     @Override
849     public void commit() {
850       ModifiableRootModel[] rootModels = new ModifiableRootModel[0];
851       ModifiableModelCommitter.multiCommit(rootModels, this);
852     }
853
854     private void commitWithRunnable(Runnable runnable) {
855       commitModel(this, runnable);
856       clearRenamingStuff();
857     }
858
859     private void clearRenamingStuff() {
860       myModuleToNewName.clear();
861       myNewNameToModule.clear();
862     }
863
864     @Override
865     public void dispose() {
866       assertWritable();
867       ApplicationManager.getApplication().assertWriteAccessAllowed();
868       final Collection<Module> list = myModuleModel.myPathToModule.values();
869       final Collection<Module> thisModules = myPathToModule.values();
870       for (Module thisModule : thisModules) {
871         if (!list.contains(thisModule)) {
872           Disposer.dispose(thisModule);
873         }
874       }
875       for (Module moduleToDispose : myModulesToDispose) {
876         if (!list.contains(moduleToDispose)) {
877           Disposer.dispose(moduleToDispose);
878         }
879       }
880       clearRenamingStuff();
881     }
882
883     @Override
884     public boolean isChanged() {
885       if (!myIsWritable) {
886         return false;
887       }
888       Set<Module> thisModules = new HashSet<Module>(myPathToModule.values());
889       Set<Module> thatModules = new HashSet<Module>(myModuleModel.myPathToModule.values());
890       return !thisModules.equals(thatModules) || !Comparing.equal(myModuleModel.myModuleGroupPath, myModuleGroupPath);
891     }
892
893     private void disposeModel() {
894       myModulesCache = null;
895       for (final Module module : myPathToModule.values()) {
896         Disposer.dispose(module);
897       }
898       myPathToModule.clear();
899       myModuleGroupPath = null;
900     }
901
902     public void projectOpened() {
903       final Collection<Module> collection = myPathToModule.values();
904       for (final Module aCollection : collection) {
905         ModuleEx module = (ModuleEx)aCollection;
906         module.projectOpened();
907       }
908     }
909
910     public void projectClosed() {
911       final Collection<Module> collection = myPathToModule.values();
912       for (final Module aCollection : collection) {
913         ModuleEx module = (ModuleEx)aCollection;
914         module.projectClosed();
915       }
916     }
917
918     @Override
919     public String[] getModuleGroupPath(Module module) {
920       return myModuleGroupPath == null ? null : myModuleGroupPath.get(module);
921     }
922
923     @Override
924     public boolean hasModuleGroups() {
925       return myModuleGroupPath != null && !myModuleGroupPath.isEmpty();
926     }
927
928     @Override
929     public void setModuleGroupPath(@NotNull Module module, @Nullable("null means remove") String[] groupPath) {
930       if (myModuleGroupPath == null) {
931         myModuleGroupPath = new THashMap<Module, String[]>();
932       }
933       if (groupPath == null) {
934         myModuleGroupPath.remove(module);
935       }
936       else {
937         myModuleGroupPath.put(module, groupPath);
938       }
939     }
940
941     @Override
942     public void setModuleFilePath(@NotNull Module module, String oldPath, String newFilePath) {
943       myPathToModule.remove(oldPath);
944       myPathToModule.put(newFilePath, module);
945     }
946   }
947
948   private void commitModel(final ModuleModelImpl moduleModel, final Runnable runnable) {
949     myModuleModel.myModulesCache = null;
950     incModificationCount();
951     ApplicationManager.getApplication().assertWriteAccessAllowed();
952     final Collection<Module> oldModules = myModuleModel.myPathToModule.values();
953     final Collection<Module> newModules = moduleModel.myPathToModule.values();
954     final List<Module> removedModules = new ArrayList<Module>(oldModules);
955     removedModules.removeAll(newModules);
956     final List<Module> addedModules = new ArrayList<Module>(newModules);
957     addedModules.removeAll(oldModules);
958
959     ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
960       @Override
961       public void run() {
962         for (Module removedModule : removedModules) {
963           fireBeforeModuleRemoved(removedModule);
964           cleanCachedStuff();
965         }
966
967         List<Module> neverAddedModules = new ArrayList<Module>(moduleModel.myModulesToDispose);
968         neverAddedModules.removeAll(myModuleModel.myPathToModule.values());
969         for (final Module neverAddedModule : neverAddedModules) {
970           neverAddedModule.putUserData(DISPOSED_MODULE_NAME, neverAddedModule.getName());
971           Disposer.dispose(neverAddedModule);
972         }
973
974         if (runnable != null) {
975           runnable.run();
976         }
977
978         final Map<Module, String> modulesToNewNamesMap = moduleModel.myModuleToNewName;
979         final Set<Module> modulesToBeRenamed = modulesToNewNamesMap.keySet();
980         modulesToBeRenamed.removeAll(moduleModel.myModulesToDispose);
981
982         List<Module> modules = new ArrayList<Module>();
983         Map<Module, String> oldNames = ContainerUtil.newHashMap();
984         for (final Module module : modulesToBeRenamed) {
985           oldNames.put(module, module.getName());
986           moduleModel.myPathToModule.remove(module.getModuleFilePath());
987           modules.add(module);
988           ((ModuleEx)module).rename(modulesToNewNamesMap.get(module));
989           moduleModel.myPathToModule.put(module.getModuleFilePath(), module);
990         }
991
992         moduleModel.myIsWritable = false;
993         myModuleModel = moduleModel;
994
995         for (Module module : removedModules) {
996           fireModuleRemoved(module);
997           cleanCachedStuff();
998           Disposer.dispose(module);
999           cleanCachedStuff();
1000         }
1001
1002         for (Module addedModule : addedModules) {
1003           ((ModuleEx)addedModule).moduleAdded();
1004           cleanCachedStuff();
1005           fireModuleAdded(addedModule);
1006           cleanCachedStuff();
1007         }
1008         cleanCachedStuff();
1009         fireModulesRenamed(modules, oldNames);
1010         cleanCachedStuff();
1011       }
1012     }, false, true);
1013   }
1014
1015   void fireModuleRenamedByVfsEvent(@NotNull final Module module, @NotNull final String oldName) {
1016     ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
1017       @Override
1018       public void run() {
1019         fireModulesRenamed(Collections.singletonList(module), Collections.singletonMap(module, oldName));
1020       }
1021     }, false, true);
1022   }
1023
1024   @Override
1025   public String[] getModuleGroupPath(@NotNull Module module) {
1026     return myModuleModel.getModuleGroupPath(module);
1027   }
1028
1029   public void setModuleGroupPath(Module module, String[] groupPath) {
1030     myModuleModel.setModuleGroupPath(module, groupPath);
1031   }
1032 }
1033