/*
* Copyright 2000-2012 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.roots.impl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.PsiModificationTrackerImpl;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import consulo.annotations.RequiredWriteAction;
import consulo.roots.ContentFolderScopes;
import consulo.roots.OrderEntryWithTracking;
import consulo.vfs.ArchiveFileSystem;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author max
*/
public class ProjectRootManagerImpl extends ProjectRootManagerEx implements ProjectComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.impl.ProjectRootManagerImpl");
protected final Project myProject;
private final OrderRootsCache myRootsCache;
private final Map<RootProvider, Set<OrderEntry>> myRegisteredRootProviders = new HashMap<RootProvider, Set<OrderEntry>>();
protected final List<OrderEntryWithTracking> myModuleExtensionWithSdkOrderEntries = ContainerUtil.newArrayList();
protected boolean myStartupActivityPerformed = false;
private final RootProviderChangeListener myRootProviderChangeListener = new RootProviderChangeListener();
protected class BatchSession {
private int myBatchLevel = 0;
private boolean myChanged = false;
private final boolean myFileTypes;
private BatchSession(final boolean fileTypes) {
myFileTypes = fileTypes;
}
protected void levelUp() {
if (myBatchLevel == 0) {
myChanged = false;
}
myBatchLevel += 1;
}
@RequiredWriteAction
protected void levelDown() {
myBatchLevel -= 1;
if (myChanged && myBatchLevel == 0) {
try {
fireChange();
}
finally {
myChanged = false;
}
}
}
@RequiredWriteAction
private boolean fireChange() {
return fireRootsChanged(myFileTypes);
}
@RequiredWriteAction
protected void beforeRootsChanged() {
if (myBatchLevel == 0 || !myChanged) {
if (fireBeforeRootsChanged(myFileTypes)) {
myChanged = true;
}
}
}
@RequiredWriteAction
protected void rootsChanged() {
if (myBatchLevel == 0) {
if (fireChange()) {
myChanged = false;
}
}
}
}
private class RootProviderChangeListener implements RootProvider.RootSetChangedListener {
private boolean myInsideRootsChange;
@Override
public void rootSetChanged(final RootProvider wrapper) {
if (myInsideRootsChange) return;
myInsideRootsChange = true;
try {
makeRootsChange(EmptyRunnable.INSTANCE, false, true);
}
finally {
myInsideRootsChange = false;
}
}
}
protected final BatchSession myRootsChanged = new BatchSession(false);
protected final BatchSession myFileTypesChanged = new BatchSession(true);
public static ProjectRootManagerImpl getInstanceImpl(Project project) {
return (ProjectRootManagerImpl)getInstance(project);
}
public ProjectRootManagerImpl(Project project) {
myProject = project;
myRootsCache = new OrderRootsCache(project);
}
@Override
@NotNull
public ProjectFileIndex getFileIndex() {
return ProjectFileIndex.SERVICE.getInstance(myProject);
}
@Override
@NotNull
public List<String> getContentRootUrls() {
final List<String> result = new ArrayList<String>();
for (Module module : getModuleManager().getModules()) {
final String[] urls = ModuleRootManager.getInstance(module).getContentRootUrls();
ContainerUtil.addAll(result, urls);
}
return result;
}
@Override
@NotNull
public VirtualFile[] getContentRoots() {
final List<VirtualFile> result = new ArrayList<VirtualFile>();
for (Module module : getModuleManager().getModules()) {
final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
ContainerUtil.addAll(result, contentRoots);
}
return VfsUtilCore.toVirtualFileArray(result);
}
@Override
public VirtualFile[] getContentSourceRoots() {
final List<VirtualFile> result = new ArrayList<VirtualFile>();
for (Module module : getModuleManager().getModules()) {
final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getContentFolderFiles(ContentFolderScopes.all(false));
ContainerUtil.addAll(result, sourceRoots);
}
return VfsUtilCore.toVirtualFileArray(result);
}
@NotNull
@Override
public OrderEnumerator orderEntries() {
return new ProjectOrderEnumerator(myProject, myRootsCache);
}
@NotNull
@Override
public OrderEnumerator orderEntries(@NotNull Collection<? extends Module> modules) {
return new ModulesOrderEnumerator(myProject, modules);
}
@Override
public VirtualFile[] getContentRootsFromAllModules() {
List<VirtualFile> result = new ArrayList<VirtualFile>();
final Module[] modules = getModuleManager().getSortedModules();
for (Module module : modules) {
final VirtualFile[] files = ModuleRootManager.getInstance(module).getContentRoots();
ContainerUtil.addAll(result, files);
}
result.add(myProject.getBaseDir());
return VfsUtilCore.toVirtualFileArray(result);
}
@Override
public void projectOpened() {
}
@Override
public void projectClosed() {
}
@Override
@NotNull
public String getComponentName() {
return "ProjectRootManager";
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
}
private boolean myMergedCallStarted = false;
private boolean myMergedCallHasRootChange = false;
private int myRootsChangesDepth = 0;
@Override
public void mergeRootsChangesDuring(@NotNull Runnable runnable) {
if (getBatchSession(false).myBatchLevel == 0 && !myMergedCallStarted) {
LOG.assertTrue(myRootsChangesDepth == 0, "Merged rootsChanged not allowed inside rootsChanged, rootsChanged level == " + myRootsChangesDepth);
myMergedCallStarted = true;
myMergedCallHasRootChange = false;
try {
runnable.run();
}
finally {
if (myMergedCallHasRootChange) {
LOG.assertTrue(myRootsChangesDepth == 1, "myMergedCallDepth = " + myRootsChangesDepth);
getBatchSession(false).rootsChanged();
}
myMergedCallStarted = false;
myMergedCallHasRootChange = false;
}
}
else {
runnable.run();
}
}
protected void clearScopesCaches() {
clearScopesCachesForModules();
}
@Override
public void clearScopesCachesForModules() {
myRootsCache.clearCache();
Module[] modules = ModuleManager.getInstance(myProject).getModules();
for (Module module : modules) {
((ModuleRootManagerImpl)ModuleRootManager.getInstance(module)).dropCaches();
}
}
@Override
public void makeRootsChange(@NotNull Runnable runnable, boolean filetypes, boolean fireEvents) {
if (myProject.isDisposed()) return;
BatchSession session = getBatchSession(filetypes);
if (fireEvents) session.beforeRootsChanged();
try {
runnable.run();
}
finally {
if (fireEvents) session.rootsChanged();
}
}
protected BatchSession getBatchSession(final boolean filetypes) {
return filetypes ? myFileTypesChanged : myRootsChanged;
}
protected boolean isFiringEvent = false;
@RequiredWriteAction
private boolean fireBeforeRootsChanged(boolean filetypes) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
LOG.assertTrue(!isFiringEvent, "Do not use API that changes roots from roots events. Try using invoke later or something else.");
if (myMergedCallStarted) {
LOG.assertTrue(!filetypes, "Filetypes change is not supported inside merged call");
}
if (myRootsChangesDepth++ == 0) {
if (myMergedCallStarted) {
myMergedCallHasRootChange = true;
myRootsChangesDepth++; // blocks all firing until finishRootsChangedOnDemand
}
fireBeforeRootsChangeEvent(filetypes);
return true;
}
return false;
}
protected void fireBeforeRootsChangeEvent(boolean filetypes) {
}
@RequiredWriteAction
private boolean fireRootsChanged(boolean filetypes) {
if (myProject.isDisposed()) return false;
ApplicationManager.getApplication().assertWriteAccessAllowed();
LOG.assertTrue(!isFiringEvent, "Do not use API that changes roots from roots events. Try using invoke later or something else.");
if (myMergedCallStarted) {
LOG.assertTrue(!filetypes, "Filetypes change is not supported inside merged call");
}
myRootsChangesDepth--;
if (myRootsChangesDepth > 0) return false;
clearScopesCaches();
incModificationCount();
PsiManager psiManager = PsiManager.getInstance(myProject);
psiManager.dropResolveCaches();
((PsiModificationTrackerImpl)psiManager.getModificationTracker()).incCounter();
fireRootsChangedEvent(filetypes);
doSynchronizeRoots();
addRootsToWatch();
return true;
}
protected void fireRootsChangedEvent(boolean filetypes) {
}
protected void addRootsToWatch() {
}
public Project getProject() {
return myProject;
}
protected void doSynchronizeRoots() {
}
public void markRootsForRefresh() {
}
public static String extractLocalPath(final String url) {
final String path = VfsUtilCore.urlToPath(url);
final int jarSeparatorIndex = path.indexOf(ArchiveFileSystem.ARCHIVE_SEPARATOR);
if (jarSeparatorIndex > 0) {
return path.substring(0, jarSeparatorIndex);
}
return path;
}
private ModuleManager getModuleManager() {
return ModuleManager.getInstance(myProject);
}
void subscribeToRootProvider(OrderEntry owner, final RootProvider provider) {
Set<OrderEntry> owners = myRegisteredRootProviders.get(provider);
if (owners == null) {
owners = new HashSet<OrderEntry>();
myRegisteredRootProviders.put(provider, owners);
provider.addRootSetChangedListener(myRootProviderChangeListener);
}
owners.add(owner);
}
void unsubscribeFromRootProvider(OrderEntry owner, final RootProvider provider) {
Set<OrderEntry> owners = myRegisteredRootProviders.get(provider);
if (owners != null) {
owners.remove(owner);
if (owners.isEmpty()) {
provider.removeRootSetChangedListener(myRootProviderChangeListener);
myRegisteredRootProviders.remove(provider);
}
}
}
void addListenerForTable(LibraryTable.Listener libraryListener, final LibraryTable libraryTable) {
LibraryTableMultilistener multilistener = myLibraryTableMultilisteners.get(libraryTable);
if (multilistener == null) {
multilistener = new LibraryTableMultilistener(libraryTable);
}
multilistener.addListener(libraryListener);
}
public void addOrderWithTracking(@NotNull OrderEntryWithTracking orderEntry) {
myModuleExtensionWithSdkOrderEntries.add(orderEntry);
}
public void removeOrderWithTracking(@NotNull OrderEntryWithTracking orderEntry) {
myModuleExtensionWithSdkOrderEntries.remove(orderEntry);
}
void removeListenerForTable(LibraryTable.Listener libraryListener, final LibraryTable libraryTable) {
LibraryTableMultilistener multilistener = myLibraryTableMultilisteners.get(libraryTable);
if (multilistener == null) {
multilistener = new LibraryTableMultilistener(libraryTable);
}
multilistener.removeListener(libraryListener);
}
private final Map<LibraryTable, LibraryTableMultilistener> myLibraryTableMultilisteners = new HashMap<LibraryTable, LibraryTableMultilistener>();
private class LibraryTableMultilistener implements LibraryTable.Listener {
final List<LibraryTable.Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final LibraryTable myLibraryTable;
private LibraryTableMultilistener(LibraryTable libraryTable) {
myLibraryTable = libraryTable;
myLibraryTable.addListener(this);
myLibraryTableMultilisteners.put(myLibraryTable, this);
}
private void addListener(LibraryTable.Listener listener) {
myListeners.add(listener);
}
private void removeListener(LibraryTable.Listener listener) {
myListeners.remove(listener);
if (myListeners.isEmpty()) {
myLibraryTable.removeListener(this);
myLibraryTableMultilisteners.remove(myLibraryTable);
}
}
@Override
public void afterLibraryAdded(final Library newLibrary) {
incModificationCount();
mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
for (LibraryTable.Listener listener : myListeners) {
listener.afterLibraryAdded(newLibrary);
}
}
});
}
@Override
public void afterLibraryRenamed(final Library library) {
incModificationCount();
mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
for (LibraryTable.Listener listener : myListeners) {
listener.afterLibraryRenamed(library);
}
}
});
}
@Override
public void beforeLibraryRemoved(final Library library) {
incModificationCount();
mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
for (LibraryTable.Listener listener : myListeners) {
listener.beforeLibraryRemoved(library);
}
}
});
}
@Override
public void afterLibraryRemoved(final Library library) {
incModificationCount();
mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
for (LibraryTable.Listener listener : myListeners) {
listener.afterLibraryRemoved(library);
}
}
});
}
}
}