/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.module.impl;
import com.intellij.ProjectTopics;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.*;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
import com.intellij.openapi.roots.impl.ModuleRootManagerImpl;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.StringInterner;
import com.intellij.util.graph.*;
import com.intellij.util.messages.MessageBus;
import consulo.annotations.RequiredDispatchThread;
import consulo.annotations.RequiredReadAction;
import consulo.annotations.RequiredWriteAction;
import consulo.module.ModuleDirIsNotExistsException;
import gnu.trove.THashMap;
import gnu.trove.TObjectHashingStrategy;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author max
*/
public abstract class ModuleManagerImpl extends ModuleManager implements ProjectComponent, PersistentStateComponent<Element>, ModificationTracker {
public static final Logger LOGGER = Logger.getInstance(ModuleManagerImpl.class);
public static class ModuleLoadItem {
private final String myDirUrl;
private final String myName;
private final String[] myGroups;
private final Element myElement;
public ModuleLoadItem(@NotNull String name, @Nullable String dirUrl, @NotNull Element element) {
myDirUrl = dirUrl;
myElement = element;
if (name.contains(MODULE_GROUP_SEPARATOR)) {
final String[] split = name.split(MODULE_GROUP_SEPARATOR);
myName = split[split.length - 1];
myGroups = new String[split.length - 1];
System.arraycopy(split, 0, myGroups, 0, myGroups.length);
}
else {
myName = name;
myGroups = null;
}
}
@Nullable
public String[] getGroups() {
return myGroups;
}
@Nullable
public String getDirUrl() {
return myDirUrl;
}
@NotNull
public String getName() {
return myName;
}
@NotNull
public Element getElement() {
return myElement;
}
}
private static class ModuleGroupInterner {
private final StringInterner groups = new StringInterner();
private final Map<String[], String[]> paths = new THashMap<>(new TObjectHashingStrategy<String[]>() {
@Override
public int computeHashCode(String[] object) {
return Arrays.hashCode(object);
}
@Override
public boolean equals(String[] o1, String[] o2) {
return Arrays.equals(o1, o2);
}
});
private void setModuleGroupPath(ModifiableModuleModel model, Module module, String[] group) {
String[] cached = paths.get(group);
if (cached == null) {
cached = new String[group.length];
for (int i = 0; i < group.length; i++) {
String g = group[i];
cached[i] = groups.intern(g);
}
paths.put(cached, cached);
}
model.setModuleGroupPath(module, cached);
}
}
public static final Key<String> DISPOSED_MODULE_NAME = Key.create("DisposedNeverAddedModuleName");
protected final Project myProject;
protected final MessageBus myMessageBus;
protected volatile ModuleModelImpl myModuleModel = new ModuleModelImpl();
@NonNls
public static final String COMPONENT_NAME = "ModuleManager";
private static final String MODULE_GROUP_SEPARATOR = "/";
private final List<ModuleLoadItem> myFailedModulePaths = new ArrayList<>();
private List<ModuleLoadItem> myModuleLoadItems = Collections.emptyList();
private boolean myFirstLoad = true;
@NonNls
public static final String ELEMENT_MODULES = "modules";
@NonNls
public static final String ELEMENT_MODULE = "module";
@NonNls
private static final String ATTRIBUTE_DIRURL = "dirurl";
@NonNls
private static final String ATTRIBUTE_NAME = "name";
private long myModificationCount;
public static ModuleManagerImpl getInstanceImpl(Project project) {
return (ModuleManagerImpl)getInstance(project);
}
protected void cleanCachedStuff() {
myCachedModuleComparator = null;
myCachedSortedModules = null;
}
public ModuleManagerImpl(Project project, MessageBus messageBus) {
myProject = project;
myMessageBus = messageBus;
}
@Override
@NotNull
public String getComponentName() {
return COMPONENT_NAME;
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
myModuleModel.disposeModel();
}
@Override
public long getModificationCount() {
return myModificationCount;
}
@Override
@RequiredReadAction
public Element getState() {
final Element e = new Element("state");
getState0(e);
return e;
}
@Override
@RequiredWriteAction
public void loadState(Element state) {
boolean firstLoad = myFirstLoad;
if (firstLoad) {
myFirstLoad = false;
}
final Element modules = state.getChild(ELEMENT_MODULES);
if (modules != null) {
myModuleLoadItems = new ArrayList<>();
for (final Element moduleElement : modules.getChildren(ELEMENT_MODULE)) {
final String name = moduleElement.getAttributeValue(ATTRIBUTE_NAME);
if (name == null) {
continue;
}
final String dirUrl = moduleElement.getAttributeValue(ATTRIBUTE_DIRURL);
myModuleLoadItems.add(new ModuleLoadItem(name, dirUrl, moduleElement));
}
}
else {
myModuleLoadItems = Collections.emptyList();
}
// if file changed, load changes
if (!firstLoad) {
ModuleModelImpl model = new ModuleModelImpl(myModuleModel);
// dispose not exists module
for (Module module : model.getModules()) {
ModuleLoadItem item = findModuleByUrl(module.getName(), module.getModuleDirUrl());
if (item == null) {
WriteAction.run(() -> model.disposeModule(module));
}
}
loadModules(model, false);
WriteAction.run(model::commit);
}
}
@Nullable
private ModuleLoadItem findModuleByUrl(@NotNull String name, @Nullable String url) {
if (url == null) {
for (ModuleLoadItem item : myModuleLoadItems) {
if (item.getName().equals(name) && item.getDirUrl() == null) {
return item;
}
}
}
else {
for (ModuleLoadItem item : myModuleLoadItems) {
if (url.equals(item.getDirUrl())) {
return item;
}
}
}
return null;
}
protected void loadModules(final ModuleModelImpl moduleModel, boolean firstLoad) {
if (myModuleLoadItems.isEmpty()) {
return;
}
ModuleGroupInterner groupInterner = new ModuleGroupInterner();
final ProgressIndicator progressIndicator = myProject.isDefault() ? null : ProgressIndicatorProvider.getGlobalProgressIndicator();
if (progressIndicator != null) {
progressIndicator.setText("Loading modules...");
progressIndicator.setText2("");
}
myFailedModulePaths.clear();
myFailedModulePaths.addAll(myModuleLoadItems);
List<ModuleLoadingErrorDescription> errors = new ArrayList<>();
for (ModuleLoadItem moduleLoadItem : myModuleLoadItems) {
if (progressIndicator != null) {
progressIndicator.checkCanceled();
}
try {
final Module module = moduleModel.loadModuleInternal(moduleLoadItem, firstLoad, progressIndicator);
final String[] groups = moduleLoadItem.getGroups();
if (groups != null) {
groupInterner.setModuleGroupPath(moduleModel, module, groups); //model should be updated too
}
myFailedModulePaths.remove(moduleLoadItem);
}
catch (ProcessCanceledException e) {
throw e;
}
catch (ModuleWithNameAlreadyExistsException | ModuleDirIsNotExistsException e) {
errors.add(ModuleLoadingErrorDescription.create(e.getMessage(), moduleLoadItem, this));
}
catch (Exception e) {
errors.add(ModuleLoadingErrorDescription
.create(ProjectBundle.message("module.cannot.load.error", moduleLoadItem.getName(), ExceptionUtil.getThrowableText(e)),
moduleLoadItem, this));
}
}
fireErrors(errors);
}
protected void fireModuleAdded(Module module) {
myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleAdded(myProject, module);
}
protected void fireModuleRemoved(Module module) {
myMessageBus.syncPublisher(ProjectTopics.MODULES).moduleRemoved(myProject, module);
}
protected void fireBeforeModuleRemoved(Module module) {
myMessageBus.syncPublisher(ProjectTopics.MODULES).beforeModuleRemoved(myProject, module);
}
protected void fireModulesRenamed(List<Module> modules) {
if (!modules.isEmpty()) {
myMessageBus.syncPublisher(ProjectTopics.MODULES).modulesRenamed(myProject, modules);
}
}
private void fireErrors(final List<ModuleLoadingErrorDescription> errors) {
if (errors.isEmpty()) return;
myModuleModel.myModulesCache = null;
for (ModuleLoadingErrorDescription error : errors) {
String dirUrl = error.getModuleLoadItem().getDirUrl();
if (dirUrl == null) {
continue;
}
final Module module = myModuleModel.removeModuleByDirUrl(dirUrl);
if (module != null) {
ApplicationManager.getApplication().invokeLater(() -> Disposer.dispose(module));
}
}
if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
throw new RuntimeException(errors.get(0).getDescription());
}
ProjectLoadingErrorsNotifier.getInstance(myProject).registerErrors(errors);
}
public void removeFailedModulePath(@NotNull ModuleManagerImpl.ModuleLoadItem modulePath) {
myFailedModulePaths.remove(modulePath);
}
@RequiredReadAction
@Override
@NotNull
public ModifiableModuleModel getModifiableModel() {
ApplicationManager.getApplication().assertReadAccessAllowed();
return new ModuleModelImpl(myModuleModel);
}
@RequiredReadAction
public void getState0(Element element) {
final Element modulesElement = new Element(ELEMENT_MODULES);
final Module[] modules = getModules();
for (Module module : modules) {
Element moduleElement = new Element(ELEMENT_MODULE);
String name = module.getName();
final String[] moduleGroupPath = getModuleGroupPath(module);
if (moduleGroupPath != null) {
name = StringUtil.join(moduleGroupPath, MODULE_GROUP_SEPARATOR) + MODULE_GROUP_SEPARATOR + name;
}
moduleElement.setAttribute(ATTRIBUTE_NAME, name);
String moduleDirUrl = module.getModuleDirUrl();
if (moduleDirUrl != null) {
moduleElement.setAttribute(ATTRIBUTE_DIRURL, moduleDirUrl);
}
final ModuleRootManagerImpl moduleRootManager = (ModuleRootManagerImpl)ModuleRootManager.getInstance(module);
moduleRootManager.saveState(moduleElement);
collapseOrExpandMacros(module, moduleElement, true);
modulesElement.addContent(moduleElement);
}
for (ModuleLoadItem failedModulePath : myFailedModulePaths) {
final Element clone = failedModulePath.getElement().clone();
modulesElement.addContent(clone);
}
element.addContent(modulesElement);
}
/**
* Method expand or collapse element children. This is need because PathMacroManager affected to attributes to.
* If dirurl equals file://$PROJECT_DIR$ it ill replace to file://$MODULE_DIR$, and after restart it ill throw error directory not found
*/
private static void collapseOrExpandMacros(Module module, Element element, boolean collapse) {
final PathMacroManager pathMacroManager = PathMacroManager.getInstance(module);
for (Element child : element.getChildren()) {
if (collapse) {
pathMacroManager.collapsePaths(child);
}
else {
pathMacroManager.expandPaths(child);
}
}
}
@Override
@NotNull
@RequiredWriteAction
public Module newModule(@NotNull @NonNls String name, @NotNull @NonNls String dirPath) {
myModificationCount++;
final ModifiableModuleModel modifiableModel = getModifiableModel();
final Module module = modifiableModel.newModule(name, dirPath);
modifiableModel.commit();
return module;
}
@Override
@RequiredWriteAction
public void disposeModule(@NotNull final Module module) {
final ModifiableModuleModel modifiableModel = getModifiableModel();
modifiableModel.disposeModule(module);
modifiableModel.commit();
}
@RequiredReadAction
@Override
@NotNull
public Module[] getModules() {
if (myModuleModel.myIsWritable) {
ApplicationManager.getApplication().assertReadAccessAllowed();
}
return myModuleModel.getModules();
}
private Module[] myCachedSortedModules = null;
@RequiredReadAction
@Override
@NotNull
public Module[] getSortedModules() {
ApplicationManager.getApplication().assertReadAccessAllowed();
deliverPendingEvents();
if (myCachedSortedModules == null) {
myCachedSortedModules = myModuleModel.getSortedModules();
}
return myCachedSortedModules;
}
@RequiredReadAction
@Override
public Module findModuleByName(@NotNull String name) {
ApplicationManager.getApplication().assertReadAccessAllowed();
return myModuleModel.findModuleByName(name);
}
private Comparator<Module> myCachedModuleComparator = null;
@RequiredReadAction
@Override
@NotNull
public Comparator<Module> moduleDependencyComparator() {
ApplicationManager.getApplication().assertReadAccessAllowed();
deliverPendingEvents();
if (myCachedModuleComparator == null) {
myCachedModuleComparator = myModuleModel.moduleDependencyComparator();
}
return myCachedModuleComparator;
}
protected void deliverPendingEvents() {
}
@RequiredReadAction
@Override
@NotNull
public Graph<Module> moduleGraph() {
return moduleGraph(true);
}
@RequiredReadAction
@NotNull
@Override
public Graph<Module> moduleGraph(boolean includeTests) {
ApplicationManager.getApplication().assertReadAccessAllowed();
return myModuleModel.moduleGraph(includeTests);
}
@RequiredReadAction
@Override
@NotNull
public List<Module> getModuleDependentModules(@NotNull Module module) {
ApplicationManager.getApplication().assertReadAccessAllowed();
return myModuleModel.getModuleDependentModules(module);
}
@RequiredReadAction
@Override
public boolean isModuleDependent(@NotNull Module module, @NotNull Module onModule) {
ApplicationManager.getApplication().assertReadAccessAllowed();
return myModuleModel.isModuleDependent(module, onModule);
}
@Override
public void projectOpened() {
fireModulesAdded();
myModuleModel.projectOpened();
}
protected void fireModulesAdded() {
for (final Module module : myModuleModel.myModules) {
TransactionGuard.getInstance().submitTransactionAndWait(() -> fireModuleAddedInWriteAction(module));
}
}
@RequiredDispatchThread
protected void fireModuleAddedInWriteAction(final Module module) {
ApplicationManager.getApplication().runWriteAction(() -> {
((ModuleEx)module).moduleAdded();
fireModuleAdded(module);
});
}
@Override
public void projectClosed() {
myModuleModel.projectClosed();
}
@RequiredWriteAction
public static void commitModelWithRunnable(ModifiableModuleModel model, Runnable runnable) {
((ModuleModelImpl)model).commitWithRunnable(runnable);
}
@NotNull
protected abstract ModuleEx createModule(@NotNull String name, @Nullable String dirUrl, ProgressIndicator progressIndicator);
@NotNull
protected ModuleEx createAndLoadModule(@NotNull final ModuleLoadItem moduleLoadItem,
@NotNull ModuleModelImpl moduleModel,
@Nullable final ProgressIndicator progressIndicator) {
final ModuleEx module = createModule(moduleLoadItem.getName(), moduleLoadItem.getDirUrl(), progressIndicator);
moduleModel.initModule(module);
collapseOrExpandMacros(module, moduleLoadItem.getElement(), false);
final ModuleRootManagerImpl moduleRootManager = (ModuleRootManagerImpl)ModuleRootManager.getInstance(module);
ApplicationManager.getApplication().runReadAction(() -> moduleRootManager.loadState(moduleLoadItem.getElement(), progressIndicator));
return module;
}
class ModuleModelImpl implements ModifiableModuleModel {
private Set<Module> myModules = new LinkedHashSet<>();
private Module[] myModulesCache;
private final List<Module> myModulesToDispose = new ArrayList<>();
private final Map<Module, String> myModuleToNewName = new HashMap<>();
private final Map<String, Module> myNewNameToModule = new HashMap<>();
private boolean myIsWritable;
private Map<Module, String[]> myModuleGroupPath;
ModuleModelImpl() {
myIsWritable = false;
}
ModuleModelImpl(@NotNull ModuleModelImpl that) {
myModules.addAll(that.myModules);
final Map<Module, String[]> groupPath = that.myModuleGroupPath;
if (groupPath != null) {
myModuleGroupPath = new THashMap<>();
myModuleGroupPath.putAll(that.myModuleGroupPath);
}
myIsWritable = true;
}
private void assertWritable() {
LOGGER.assertTrue(myIsWritable, "Attempt to modify committed ModifiableModuleModel");
}
@Override
@NotNull
public Module[] getModules() {
if (myModulesCache == null) {
myModulesCache = ContainerUtil.toArray(myModules, Module.ARRAY_FACTORY);
}
return myModulesCache;
}
private Module[] getSortedModules() {
Module[] allModules = getModules().clone();
Arrays.sort(allModules, moduleDependencyComparator());
return allModules;
}
@Override
public void renameModule(@NotNull Module module, @NotNull String newName) throws ModuleWithNameAlreadyExistsException {
final Module oldModule = getModuleByNewName(newName);
myNewNameToModule.remove(myModuleToNewName.get(module));
if (module.getName().equals(newName)) { // if renaming to itself, forget it altogether
myModuleToNewName.remove(module);
myNewNameToModule.remove(newName);
}
else {
myModuleToNewName.put(module, newName);
myNewNameToModule.put(newName, module);
}
if (oldModule != null) {
throw new ModuleWithNameAlreadyExistsException(ProjectBundle.message("module.already.exists.error", newName), newName);
}
}
@Override
public Module getModuleToBeRenamed(@NotNull String newName) {
return myNewNameToModule.get(newName);
}
@Nullable
public Module getModuleByNewName(String newName) {
final Module moduleToBeRenamed = getModuleToBeRenamed(newName);
if (moduleToBeRenamed != null) {
return moduleToBeRenamed;
}
final Module moduleWithOldName = findModuleByName(newName);
if (myModuleToNewName.get(moduleWithOldName) == null) {
return moduleWithOldName;
}
else {
return null;
}
}
@Override
public String getNewName(@NotNull Module module) {
return myModuleToNewName.get(module);
}
@Override
@NotNull
public Module newModule(@NotNull @NonNls String name, @Nullable @NonNls String dirPath) {
return newModule(name, dirPath, null);
}
@Override
@NotNull
public Module newModule(@NotNull @NonNls String name, @Nullable @NonNls String dirPath, @Nullable Map<String, String> options) {
assertWritable();
final String dirUrl = dirPath == null ? null : VirtualFileManager.constructUrl(StandardFileSystems.FILE_PROTOCOL, dirPath);
ModuleEx moduleEx = null;
if (dirUrl != null) {
moduleEx = getModuleByDirUrl(dirUrl);
}
if (moduleEx == null) {
moduleEx = createModule(name, dirUrl, null);
if (options != null) {
for (Map.Entry<String, String> option : options.entrySet()) {
moduleEx.setOption(option.getKey(), option.getValue());
}
}
initModule(moduleEx);
}
return moduleEx;
}
@Nullable
private ModuleEx getModuleByDirUrl(@NotNull String dirUrl) {
for (Module module : myModules) {
if (FileUtil.pathsEqual(dirUrl, module.getModuleDirUrl())) {
return (ModuleEx)module;
}
}
return null;
}
@Nullable
private Module removeModuleByDirUrl(@NotNull String dirUrl) {
Module toRemove = null;
for (Module module : myModules) {
if (FileUtil.pathsEqual(dirUrl, module.getModuleDirUrl())) {
toRemove = module;
}
}
myModules.remove(toRemove);
return toRemove;
}
@NotNull
private Module loadModuleInternal(@NotNull ModuleLoadItem item, boolean firstLoad, @Nullable ProgressIndicator progressIndicator)
throws ModuleWithNameAlreadyExistsException, ModuleDirIsNotExistsException, StateStorageException {
final String moduleName = item.getName();
if (progressIndicator != null) {
progressIndicator.setText2(moduleName);
}
if(firstLoad) {
for (Module module : myModules) {
if (module.getName().equals(moduleName)) {
throw new ModuleWithNameAlreadyExistsException(ProjectBundle.message("module.already.exists.error", moduleName), moduleName);
}
}
}
ModuleEx oldModule = null;
String dirUrl = item.getDirUrl();
if (dirUrl != null) {
Ref<VirtualFile> ref = Ref.create();
ApplicationManager.getApplication().invokeAndWait(() -> ref.set(VirtualFileManager.getInstance().refreshAndFindFileByUrl(dirUrl)));
VirtualFile moduleDir = ref.get();
if (moduleDir == null || !moduleDir.exists() || !moduleDir.isDirectory()) {
throw new ModuleDirIsNotExistsException(
ProjectBundle.message("module.dir.does.not.exist.error", FileUtil.toSystemDependentName(VirtualFileManager.extractPath(dirUrl))));
}
oldModule = getModuleByDirUrl(moduleDir.getUrl());
}
if (oldModule == null) {
oldModule = createAndLoadModule(item, this, progressIndicator);
}
else {
collapseOrExpandMacros(oldModule, item.getElement(), false);
final ModuleRootManagerImpl moduleRootManager = (ModuleRootManagerImpl)ModuleRootManager.getInstance(oldModule);
ApplicationManager.getApplication().runReadAction(() -> moduleRootManager.loadState(item.getElement(), progressIndicator));
}
return oldModule;
}
private void initModule(ModuleEx module) {
myModulesCache = null;
myModules.add(module);
module.loadModuleComponents();
module.init();
}
@Override
public void disposeModule(@NotNull Module module) {
assertWritable();
myModulesCache = null;
if (myModules.contains(module)) {
myModules.remove(module);
myModulesToDispose.add(module);
}
if (myModuleGroupPath != null) {
myModuleGroupPath.remove(module);
}
}
@Override
public Module findModuleByName(@NotNull String name) {
for (Module module : myModules) {
if (!module.isDisposed() && module.getName().equals(name)) {
return module;
}
}
return null;
}
private Comparator<Module> moduleDependencyComparator() {
DFSTBuilder<Module> builder = new DFSTBuilder<>(moduleGraph(true));
return builder.comparator();
}
private Graph<Module> moduleGraph(final boolean includeTests) {
return GraphGenerator.generate(CachingSemiGraph.cache(new InboundSemiGraph<Module>() {
@Override
public Collection<Module> getNodes() {
return myModules;
}
@Override
public Iterator<Module> getIn(Module m) {
Module[] dependentModules = ModuleRootManager.getInstance(m).getDependencies(includeTests);
return Arrays.asList(dependentModules).iterator();
}
}));
}
@NotNull
private List<Module> getModuleDependentModules(@NotNull Module module) {
List<Module> result = new ArrayList<>();
for (Module aModule : myModules) {
if (isModuleDependent(aModule, module)) {
result.add(aModule);
}
}
return result;
}
private boolean isModuleDependent(Module module, Module onModule) {
return ModuleRootManager.getInstance(module).isDependsOn(onModule);
}
@Override
@RequiredWriteAction
public void commit() {
ModifiableRootModel[] rootModels = new ModifiableRootModel[0];
ModifiableModelCommitter.multiCommit(rootModels, this);
}
@RequiredWriteAction
public void commitWithRunnable(Runnable runnable) {
commitModel(this, runnable);
clearRenamingStuff();
}
private void clearRenamingStuff() {
myModuleToNewName.clear();
myNewNameToModule.clear();
}
@RequiredWriteAction
@Override
public void dispose() {
assertWritable();
ApplicationManager.getApplication().assertWriteAccessAllowed();
final Collection<Module> list = myModuleModel.myModules;
final Collection<Module> thisModules = myModules;
for (Module thisModule : thisModules) {
if (!list.contains(thisModule)) {
Disposer.dispose(thisModule);
}
}
for (Module moduleToDispose : myModulesToDispose) {
if (!list.contains(moduleToDispose)) {
Disposer.dispose(moduleToDispose);
}
}
clearRenamingStuff();
}
@Override
public boolean isChanged() {
if (!myIsWritable) {
return false;
}
Set<Module> thisModules = new HashSet<>(myModules);
Set<Module> thatModules = new HashSet<>(myModuleModel.myModules);
return !thisModules.equals(thatModules) || !Comparing.equal(myModuleModel.myModuleGroupPath, myModuleGroupPath);
}
private void disposeModel() {
myModulesCache = null;
for (final Module module : myModules) {
Disposer.dispose(module);
}
myModules.clear();
myModuleGroupPath = null;
}
public void projectOpened() {
for (final Module aCollection : myModules) {
ModuleEx module = (ModuleEx)aCollection;
module.projectOpened();
}
}
public void projectClosed() {
for (final Module aCollection : myModules) {
ModuleEx module = (ModuleEx)aCollection;
module.projectClosed();
}
}
@Override
@Nullable
public String[] getModuleGroupPath(Module module) {
return myModuleGroupPath == null ? null : myModuleGroupPath.get(module);
}
@Override
public boolean hasModuleGroups() {
return myModuleGroupPath != null && !myModuleGroupPath.isEmpty();
}
@Override
public void setModuleGroupPath(Module module, String[] groupPath) {
if (myModuleGroupPath == null) {
myModuleGroupPath = new THashMap<>();
}
if (groupPath == null) {
myModuleGroupPath.remove(module);
}
else {
myModuleGroupPath.put(module, groupPath);
}
}
}
@RequiredWriteAction
private void commitModel(final ModuleModelImpl moduleModel, final Runnable runnable) {
myModuleModel.myModulesCache = null;
myModificationCount++;
ApplicationManager.getApplication().assertWriteAccessAllowed();
final Collection<Module> oldModules = myModuleModel.myModules;
final Collection<Module> newModules = moduleModel.myModules;
final List<Module> removedModules = new ArrayList<>(oldModules);
removedModules.removeAll(newModules);
final List<Module> addedModules = new ArrayList<>(newModules);
addedModules.removeAll(oldModules);
ProjectRootManagerEx.getInstanceEx(myProject).makeRootsChange(() -> {
for (Module removedModule : removedModules) {
fireBeforeModuleRemoved(removedModule);
cleanCachedStuff();
}
List<Module> neverAddedModules = new ArrayList<>(moduleModel.myModulesToDispose);
neverAddedModules.removeAll(myModuleModel.myModules);
for (final Module neverAddedModule : neverAddedModules) {
neverAddedModule.putUserData(DISPOSED_MODULE_NAME, neverAddedModule.getName());
Disposer.dispose(neverAddedModule);
}
if (runnable != null) {
runnable.run();
}
final Map<Module, String> modulesToNewNamesMap = moduleModel.myModuleToNewName;
final Set<Module> modulesToBeRenamed = modulesToNewNamesMap.keySet();
modulesToBeRenamed.removeAll(moduleModel.myModulesToDispose);
final List<Module> modules = new ArrayList<>();
for (final Module moduleToBeRenamed : modulesToBeRenamed) {
ModuleEx module = (ModuleEx)moduleToBeRenamed;
moduleModel.myModules.remove(moduleToBeRenamed);
modules.add(moduleToBeRenamed);
module.rename(modulesToNewNamesMap.get(moduleToBeRenamed));
moduleModel.myModules.add(module);
}
moduleModel.myIsWritable = false;
myModuleModel = moduleModel;
for (Module module : removedModules) {
fireModuleRemoved(module);
cleanCachedStuff();
Disposer.dispose(module);
cleanCachedStuff();
}
for (Module addedModule : addedModules) {
((ModuleEx)addedModule).moduleAdded();
cleanCachedStuff();
fireModuleAdded(addedModule);
cleanCachedStuff();
}
cleanCachedStuff();
fireModulesRenamed(modules);
cleanCachedStuff();
}, false, true);
}
@RequiredReadAction
@Override
public String[] getModuleGroupPath(@NotNull Module module) {
return myModuleModel.getModuleGroupPath(module);
}
public void setModuleGroupPath(Module module, String[] groupPath) {
myModuleModel.setModuleGroupPath(module, groupPath);
}
}