fd3dd9d460e7bf68d0890c67d2350042853eafd2
[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   public 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   protected void cleanCachedStuff() {
96     myCachedModuleComparator = null;
97     myCachedSortedModules = null;
98   }
99
100   public ModuleManagerImpl(Project project, MessageBus messageBus) {
101     myProject = project;
102     myMessageBus = messageBus;
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, 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(final 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(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(final Element element) {
238     myModulePaths = new ArrayList<ModulePath>(Arrays.asList(getPathsToModuleFiles(element)));
239   }
240
241   protected void loadModules(final 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(Module module) {
298     return false;
299   }
300
301   protected void showUnknownModuleTypeNotification(List<Module> types) {
302   }
303
304   protected void fireModuleAdded(Module module) {
305     myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleAdded(myProject, module);
306   }
307
308   protected void fireModuleRemoved(Module module) {
309     myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleRemoved(myProject, module);
310   }
311
312   protected void fireBeforeModuleRemoved(Module module) {
313     myMessageBus.syncPublisher(ProjectTopics.MODULES).beforeModuleRemoved(myProject, module);
314   }
315
316   protected void fireModulesRenamed(List<Module> modules, 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(final 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(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
368     protected abstract String getModuleName();
369     protected abstract String getGroupPathString();
370     protected abstract String getModuleFilePath();
371
372     public final void writeExternal(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(Module module) {
392       myModule = module;
393     }
394
395     @Override
396     protected String getModuleName() {
397       return myModule.getName();
398     }
399
400     @Override
401     protected String getGroupPathString() {
402       String[] groupPath = getModuleGroupPath(myModule);
403       return groupPath != null ? StringUtil.join(groupPath, MODULE_GROUP_SEPARATOR) : null;
404     }
405
406     @Override
407     protected String getModuleFilePath() {
408       return myModule.getModuleFilePath().replace(File.separatorChar, '/');
409     }
410   }
411
412   private static class ModulePathSaveItem extends SaveItem{
413     private final ModulePath myModulePath;
414     private final String myFilePath;
415     private final String myName;
416
417     public ModulePathSaveItem(ModulePath modulePath) {
418       myModulePath = modulePath;
419       myFilePath = modulePath.getPath().replace(File.separatorChar, '/');
420
421       final int slashIndex = myFilePath.lastIndexOf('/');
422       final int startIndex = slashIndex >= 0 && slashIndex + 1 < myFilePath.length() ? slashIndex + 1 : 0;
423       final int endIndex = myFilePath.endsWith(IML_EXTENSION)
424                            ? myFilePath.length() - IML_EXTENSION.length()
425                            : myFilePath.length();
426       myName = myFilePath.substring(startIndex, endIndex);
427     }
428
429     @Override
430     protected String getModuleName() {
431       return myName;
432     }
433
434     @Override
435     protected String getGroupPathString() {
436       return myModulePath.getModuleGroup();
437     }
438
439     @Override
440     protected String getModuleFilePath() {
441       return myFilePath;
442     }
443   }
444
445   public void writeExternal(Element element) {
446     final Element modules = new Element(ELEMENT_MODULES);
447     final Module[] collection = getModules();
448
449     ArrayList<SaveItem> sorted = new ArrayList<SaveItem>(collection.length + myFailedModulePaths.size());
450     for (Module module : collection) {
451       sorted.add(new ModuleSaveItem(module));
452     }
453     for (ModulePath modulePath : myFailedModulePaths) {
454       sorted.add(new ModulePathSaveItem(modulePath));
455     }
456     Collections.sort(sorted, new Comparator<SaveItem>() {
457       @Override
458       public int compare(SaveItem item1, SaveItem item2) {
459         return item1.getModuleName().compareTo(item2.getModuleName());
460       }
461     });
462     for (SaveItem saveItem : sorted) {
463       saveItem.writeExternal(modules);
464     }
465
466     element.addContent(modules);
467   }
468
469   @Override
470   @NotNull
471   public Module newModule(@NotNull String filePath, final String moduleTypeId) {
472     incModificationCount();
473     final ModifiableModuleModel modifiableModel = getModifiableModel();
474     final Module module = modifiableModel.newModule(filePath, moduleTypeId);
475     modifiableModel.commit();
476     return module;
477   }
478
479   @Override
480   @NotNull
481   public Module loadModule(@NotNull String filePath) throws InvalidDataException, IOException, JDOMException, ModuleWithNameAlreadyExists {
482     incModificationCount();
483     final ModifiableModuleModel modifiableModel = getModifiableModel();
484     final Module module = modifiableModel.loadModule(filePath);
485     modifiableModel.commit();
486     return module;
487   }
488
489   @Override
490   public void disposeModule(@NotNull final Module module) {
491     ApplicationManager.getApplication().runWriteAction(new Runnable() {
492       @Override
493       public void run() {
494         final ModifiableModuleModel modifiableModel = getModifiableModel();
495         modifiableModel.disposeModule(module);
496         modifiableModel.commit();
497       }
498     });
499   }
500
501   @Override
502   @NotNull
503   public Module[] getModules() {
504     if (myModuleModel.myIsWritable) {
505       ApplicationManager.getApplication().assertReadAccessAllowed();
506     }
507     return myModuleModel.getModules();
508   }
509
510   private volatile Module[] myCachedSortedModules = null;
511
512   @Override
513   @NotNull
514   public Module[] getSortedModules() {
515     ApplicationManager.getApplication().assertReadAccessAllowed();
516     deliverPendingEvents();
517     if (myCachedSortedModules == null) {
518       myCachedSortedModules = myModuleModel.getSortedModules();
519     }
520     return myCachedSortedModules;
521   }
522
523   @Override
524   public Module findModuleByName(@NotNull String name) {
525     ApplicationManager.getApplication().assertReadAccessAllowed();
526     return myModuleModel.findModuleByName(name);
527   }
528
529   private volatile Comparator<Module> myCachedModuleComparator = null;
530
531   @Override
532   @NotNull
533   public Comparator<Module> moduleDependencyComparator() {
534     ApplicationManager.getApplication().assertReadAccessAllowed();
535     deliverPendingEvents();
536     if (myCachedModuleComparator == null) {
537       myCachedModuleComparator = myModuleModel.moduleDependencyComparator();
538     }
539     return myCachedModuleComparator;
540   }
541
542   protected void deliverPendingEvents() {
543   }
544
545   @Override
546   @NotNull
547   public Graph<Module> moduleGraph() {
548     return moduleGraph(true);
549   }
550
551   @NotNull
552   @Override
553   public Graph<Module> moduleGraph(boolean includeTests) {
554     ApplicationManager.getApplication().assertReadAccessAllowed();
555     return myModuleModel.moduleGraph(includeTests);
556   }
557
558   @Override
559   @NotNull public List<Module> getModuleDependentModules(@NotNull Module module) {
560     ApplicationManager.getApplication().assertReadAccessAllowed();
561     return myModuleModel.getModuleDependentModules(module);
562   }
563
564   @Override
565   public boolean isModuleDependent(@NotNull Module module, @NotNull Module onModule) {
566     ApplicationManager.getApplication().assertReadAccessAllowed();
567     return myModuleModel.isModuleDependent(module, onModule);
568   }
569
570   @Override
571   public void projectOpened() {
572     fireModulesAdded();
573
574     myModuleModel.projectOpened();
575   }
576
577   protected void fireModulesAdded() {
578     for (final Module module : myModuleModel.myPathToModule.values()) {
579       fireModuleAddedInWriteAction(module);
580     }
581   }
582
583   protected void fireModuleAddedInWriteAction(final Module module) {
584     ApplicationManager.getApplication().runWriteAction(new Runnable() {
585       @Override
586       public void run() {
587         ((ModuleEx)module).moduleAdded();
588         fireModuleAdded(module);
589       }
590     });
591   }
592
593   @Override
594   public void projectClosed() {
595     myModuleModel.projectClosed();
596   }
597
598   public static void commitModelWithRunnable(ModifiableModuleModel model, Runnable runnable) {
599     ((ModuleModelImpl)model).commitWithRunnable(runnable);
600   }
601
602   protected abstract ModuleEx createModule(String filePath);
603
604   protected abstract ModuleEx createAndLoadModule(String filePath) throws IOException;
605
606   class ModuleModelImpl implements ModifiableModuleModel {
607     final Map<String, Module> myPathToModule = new LinkedHashMap<String, Module>(new EqualityPolicy.ByHashingStrategy<String>(FilePathHashingStrategy.create()));
608     private volatile Module[] myModulesCache;
609
610     private final List<Module> myModulesToDispose = new ArrayList<Module>();
611     private final Map<Module, String> myModuleToNewName = new HashMap<Module, String>();
612     private final Map<String, Module> myNewNameToModule = new HashMap<String, Module>();
613     private boolean myIsWritable;
614     private Map<Module, String[]> myModuleGroupPath;
615
616     ModuleModelImpl() {
617       myIsWritable = false;
618     }
619
620     ModuleModelImpl(ModuleModelImpl that) {
621       myPathToModule.putAll(that.myPathToModule);
622       final Map<Module, String[]> groupPath = that.myModuleGroupPath;
623       if (groupPath != null){
624         myModuleGroupPath = new THashMap<Module, String[]>();
625         myModuleGroupPath.putAll(that.myModuleGroupPath);
626       }
627       myIsWritable = true;
628     }
629
630     private void assertWritable() {
631       LOG.assertTrue(myIsWritable, "Attempt to modify committed ModifiableModuleModel");
632     }
633
634     @Override
635     @NotNull
636     public Module[] getModules() {
637       if (myModulesCache == null) {
638         Collection<Module> modules = myPathToModule.values();
639         myModulesCache = modules.toArray(new Module[modules.size()]);
640       }
641       return myModulesCache;
642     }
643
644     private Module[] getSortedModules() {
645       Module[] allModules = getModules().clone();
646       Arrays.sort(allModules, moduleDependencyComparator());
647       return allModules;
648     }
649
650     @Override
651     public void renameModule(@NotNull Module module, @NotNull String newName) throws ModuleWithNameAlreadyExists {
652       final Module oldModule = getModuleByNewName(newName);
653       myNewNameToModule.remove(myModuleToNewName.get(module));
654       if(module.getName().equals(newName)){ // if renaming to itself, forget it altogether
655         myModuleToNewName.remove(module);
656         myNewNameToModule.remove(newName);
657       } else {
658         myModuleToNewName.put(module, newName);
659         myNewNameToModule.put(newName, module);
660       }
661
662       if (oldModule != null) {
663         throw new ModuleWithNameAlreadyExists(ProjectBundle.message("module.already.exists.error", newName), newName);
664       }
665     }
666
667     @Override
668     public Module getModuleToBeRenamed(@NotNull String newName) {
669       return myNewNameToModule.get(newName);
670     }
671
672     public Module getModuleByNewName(String newName) {
673       final Module moduleToBeRenamed = getModuleToBeRenamed(newName);
674       if (moduleToBeRenamed != null) {
675         return moduleToBeRenamed;
676       }
677       final Module moduleWithOldName = findModuleByName(newName);
678       if (myModuleToNewName.get(moduleWithOldName) == null) {
679         return moduleWithOldName;
680       }
681       else {
682         return null;
683       }
684     }
685
686     @Override
687     public String getNewName(@NotNull Module module) {
688       return myModuleToNewName.get(module);
689     }
690
691     @Override
692     @NotNull
693     public Module newModule(@NotNull String filePath, final String moduleTypeId) {
694       return newModule(filePath, moduleTypeId, null);
695     }
696
697     @Override
698     @NotNull
699     public Module newModule(@NotNull String filePath,
700                             final String moduleTypeId,
701                             @Nullable Map<String, String> options) {
702       assertWritable();
703       filePath = resolveShortWindowsName(filePath);
704
705       ModuleEx module = getModuleByFilePath(filePath);
706       if (module == null) {
707         module = createModule(filePath);
708         module.setOption(Module.ELEMENT_TYPE, moduleTypeId);
709         if (options != null) {
710           for ( Map.Entry<String,String> option : options.entrySet()) {
711             module.setOption(option.getKey(),option.getValue());
712           }
713         }
714         module.loadModuleComponents();
715         initModule(module);
716       }
717       return module;
718     }
719
720     private String resolveShortWindowsName(String filePath) {
721       try {
722         return FileUtil.resolveShortWindowsName(filePath);
723       }
724       catch (IOException ignored) {
725         return filePath;
726       }
727     }
728
729     @Nullable
730     private ModuleEx getModuleByFilePath(String filePath) {
731       return (ModuleEx)myPathToModule.get(filePath);
732     }
733
734     @Override
735     @NotNull
736     public Module loadModule(@NotNull String filePath) throws InvalidDataException, IOException, ModuleWithNameAlreadyExists {
737       assertWritable();
738       try {
739         return loadModuleInternal(filePath);
740       }
741       catch (StateStorageException e) {
742         throw new IOException(ProjectBundle.message("module.corrupted.file.error", FileUtil.toSystemDependentName(filePath), e.getMessage()));
743       }
744     }
745
746     private Module loadModuleInternal(String filePath) throws ModuleWithNameAlreadyExists, IOException {
747       filePath = resolveShortWindowsName(filePath);
748       final VirtualFile moduleFile = StandardFileSystems.local().findFileByPath(filePath);
749       if (moduleFile == null || !moduleFile.exists()) {
750         throw new IOException(ProjectBundle.message("module.file.does.not.exist.error", filePath));
751       }
752
753       final String name = moduleFile.getName();
754
755       if (name.endsWith(IML_EXTENSION)) {
756         final String moduleName = name.substring(0, name.length() - 4);
757         for (Module module : myPathToModule.values()) {
758           if (module.getName().equals(moduleName)) {
759             throw new ModuleWithNameAlreadyExists(ProjectBundle.message("module.already.exists.error", moduleName), moduleName);
760           }
761         }
762       }
763
764       ModuleEx module = getModuleByFilePath(moduleFile.getPath());
765       if (module == null) {
766         ApplicationManager.getApplication().invokeAndWait(new Runnable() {
767           @Override
768           public void run() {
769             moduleFile.refresh(false, false);
770           }
771         }, ModalityState.any());
772         module = createAndLoadModule(moduleFile.getPath());
773         module.loadModuleComponents();
774         initModule(module);
775       }
776       return module;
777     }
778
779     private void initModule(ModuleEx module) {
780       String path = module.getModuleFilePath();
781       myModulesCache = null;
782       myPathToModule.put(path, module);
783       module.init();
784     }
785
786     @Override
787     public void disposeModule(@NotNull Module module) {
788       assertWritable();
789       myModulesCache = null;
790       if (myPathToModule.values().contains(module)) {
791         myPathToModule.remove(module.getModuleFilePath());
792         myModulesToDispose.add(module);
793       }
794       if (myModuleGroupPath != null){
795         myModuleGroupPath.remove(module);
796       }
797     }
798
799     @Override
800     public Module findModuleByName(@NotNull String name) {
801       for (Module module : myPathToModule.values()) {
802         if (!module.isDisposed() && module.getName().equals(name)) {
803           return module;
804         }
805       }
806       return null;
807     }
808
809     private Comparator<Module> moduleDependencyComparator() {
810       DFSTBuilder<Module> builder = new DFSTBuilder<Module>(moduleGraph(true));
811       return builder.comparator();
812     }
813
814     private Graph<Module> moduleGraph(final boolean includeTests) {
815       return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<Module>() {
816         @Override
817         public Collection<Module> getNodes() {
818           return myPathToModule.values();
819         }
820
821         @Override
822         public Iterator<Module> getIn(Module m) {
823           Module[] dependentModules = ModuleRootManager.getInstance(m).getDependencies(includeTests);
824           return Arrays.asList(dependentModules).iterator();
825         }
826       }));
827     }
828
829     @NotNull private List<Module> getModuleDependentModules(Module module) {
830       List<Module> result = new ArrayList<Module>();
831       for (Module aModule : myPathToModule.values()) {
832         if (isModuleDependent(aModule, module)) {
833           result.add(aModule);
834         }
835       }
836       return result;
837     }
838
839     private boolean isModuleDependent(Module module, Module onModule) {
840       return ModuleRootManager.getInstance(module).isDependsOn(onModule);
841     }
842
843     @Override
844     public void commit() {
845       ModifiableRootModel[] rootModels = new ModifiableRootModel[0];
846       ModifiableModelCommitter.multiCommit(rootModels, this);
847     }
848
849     public void commitWithRunnable(Runnable runnable) {
850       commitModel(this, runnable);
851       clearRenamingStuff();
852     }
853
854     private void clearRenamingStuff() {
855       myModuleToNewName.clear();
856       myNewNameToModule.clear();
857     }
858
859     @Override
860     public void dispose() {
861       assertWritable();
862       ApplicationManager.getApplication().assertWriteAccessAllowed();
863       final Collection<Module> list = myModuleModel.myPathToModule.values();
864       final Collection<Module> thisModules = myPathToModule.values();
865       for (Module thisModule : thisModules) {
866         if (!list.contains(thisModule)) {
867           Disposer.dispose(thisModule);
868         }
869       }
870       for (Module moduleToDispose : myModulesToDispose) {
871         if (!list.contains(moduleToDispose)) {
872           Disposer.dispose(moduleToDispose);
873         }
874       }
875       clearRenamingStuff();
876     }
877
878     @Override
879     public boolean isChanged() {
880       if (!myIsWritable) {
881         return false;
882       }
883       Set<Module> thisModules = new HashSet<Module>(myPathToModule.values());
884       Set<Module> thatModules = new HashSet<Module>(myModuleModel.myPathToModule.values());
885       return !thisModules.equals(thatModules) || !Comparing.equal(myModuleModel.myModuleGroupPath, myModuleGroupPath);
886     }
887
888     private void disposeModel() {
889       myModulesCache = null;
890       for (final Module module : myPathToModule.values()) {
891         Disposer.dispose(module);
892       }
893       myPathToModule.clear();
894       myModuleGroupPath = null;
895     }
896
897     public void projectOpened() {
898       final Collection<Module> collection = myPathToModule.values();
899       for (final Module aCollection : collection) {
900         ModuleEx module = (ModuleEx)aCollection;
901         module.projectOpened();
902       }
903     }
904
905     public void projectClosed() {
906       final Collection<Module> collection = myPathToModule.values();
907       for (final Module aCollection : collection) {
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     @Override
937     public void setModuleFilePath(@NotNull Module module, String oldPath, String newFilePath) {
938       myPathToModule.remove(oldPath);
939       myPathToModule.put(newFilePath, module);
940     }
941   }
942
943   private void commitModel(final ModuleModelImpl moduleModel, final Runnable runnable) {
944     myModuleModel.myModulesCache = null;
945     incModificationCount();
946     ApplicationManager.getApplication().assertWriteAccessAllowed();
947     final Collection<Module> oldModules = myModuleModel.myPathToModule.values();
948     final Collection<Module> newModules = moduleModel.myPathToModule.values();
949     final List<Module> removedModules = new ArrayList<Module>(oldModules);
950     removedModules.removeAll(newModules);
951     final List<Module> addedModules = new ArrayList<Module>(newModules);
952     addedModules.removeAll(oldModules);
953
954     ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
955       @Override
956       public void run() {
957         for (Module removedModule : removedModules) {
958           fireBeforeModuleRemoved(removedModule);
959           cleanCachedStuff();
960         }
961
962         List<Module> neverAddedModules = new ArrayList<Module>(moduleModel.myModulesToDispose);
963         neverAddedModules.removeAll(myModuleModel.myPathToModule.values());
964         for (final Module neverAddedModule : neverAddedModules) {
965           neverAddedModule.putUserData(DISPOSED_MODULE_NAME, neverAddedModule.getName());
966           Disposer.dispose(neverAddedModule);
967         }
968
969         if (runnable != null) {
970           runnable.run();
971         }
972
973         final Map<Module, String> modulesToNewNamesMap = moduleModel.myModuleToNewName;
974         final Set<Module> modulesToBeRenamed = modulesToNewNamesMap.keySet();
975         modulesToBeRenamed.removeAll(moduleModel.myModulesToDispose);
976
977         List<Module> modules = new ArrayList<Module>();
978         Map<Module, String> oldNames = ContainerUtil.newHashMap();
979         for (final Module module : modulesToBeRenamed) {
980           oldNames.put(module, module.getName());
981           moduleModel.myPathToModule.remove(module.getModuleFilePath());
982           modules.add(module);
983           ((ModuleEx)module).rename(modulesToNewNamesMap.get(module));
984           moduleModel.myPathToModule.put(module.getModuleFilePath(), module);
985         }
986
987         moduleModel.myIsWritable = false;
988         myModuleModel = moduleModel;
989
990         for (Module module : removedModules) {
991           fireModuleRemoved(module);
992           cleanCachedStuff();
993           Disposer.dispose(module);
994           cleanCachedStuff();
995         }
996
997         for (Module addedModule : addedModules) {
998           ((ModuleEx)addedModule).moduleAdded();
999           cleanCachedStuff();
1000           fireModuleAdded(addedModule);
1001           cleanCachedStuff();
1002         }
1003         cleanCachedStuff();
1004         fireModulesRenamed(modules, oldNames);
1005         cleanCachedStuff();
1006       }
1007     }, false, true);
1008   }
1009
1010   void fireModuleRenamedByVfsEvent(@NotNull final Module module, @NotNull final String oldName) {
1011     ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(new Runnable() {
1012       @Override
1013       public void run() {
1014         fireModulesRenamed(Collections.singletonList(module), Collections.singletonMap(module, oldName));
1015       }
1016     }, false, true);
1017   }
1018
1019   @Override
1020   public String[] getModuleGroupPath(@NotNull Module module) {
1021     return myModuleModel.getModuleGroupPath(module);
1022   }
1023
1024   public void setModuleGroupPath(Module module, String[] groupPath) {
1025     myModuleModel.setModuleGroupPath(module, groupPath);
1026   }
1027 }
1028