blob: b631df7fef90cd1a6b21bac6de23c532110a69c7 [file] [log] [blame]
/*
* Copyright 2000-2014 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.compiler.impl;
import com.intellij.compiler.server.BuildManager;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.util.Function;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collection;
import java.util.Set;
/**
* @author Eugene Zhuravlev
* @since Jun 3, 2008
*
* A source file is scheduled for recompilation if
* 1. its timestamp has changed
* 2. one of its corresponding output files was deleted
* 3. output root of containing module has changed
*
* An output file is scheduled for deletion if:
* 1. corresponding source file has been scheduled for recompilation (see above)
* 2. corresponding source file has been deleted
*/
public class TranslatingCompilerFilesMonitor implements ApplicationComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.TranslatingCompilerFilesMonitor");
public static boolean ourDebugMode = false;
public TranslatingCompilerFilesMonitor(VirtualFileManager vfsManager, Application application) {
vfsManager.addVirtualFileListener(new MyVfsListener(), application);
}
public static TranslatingCompilerFilesMonitor getInstance() {
return ApplicationManager.getApplication().getComponent(TranslatingCompilerFilesMonitor.class);
}
@NotNull
public String getComponentName() {
return "TranslatingCompilerFilesMonitor";
}
public void initComponent() {
}
public void disposeComponent() {
}
private interface FileProcessor {
void execute(VirtualFile file);
}
private static void processRecursively(final VirtualFile fromFile, final boolean dbOnly, final FileProcessor processor) {
if (!(fromFile.getFileSystem() instanceof LocalFileSystem)) {
return;
}
final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
VfsUtilCore.visitChildrenRecursively(fromFile, new VirtualFileVisitor() {
@NotNull @Override
public Result visitFileEx(@NotNull VirtualFile file) {
if (fileTypeManager.isFileIgnored(file)) {
return SKIP_CHILDREN;
}
if (!file.isDirectory()) {
processor.execute(file);
}
return CONTINUE;
}
@Nullable
@Override
public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
if (dbOnly) {
return file.isDirectory()? ((NewVirtualFile)file).iterInDbChildren() : null;
}
if (file.equals(fromFile) || !file.isDirectory()) {
return null; // skipping additional checks for the initial file and non-directory files
}
// optimization: for all files that are not under content of currently opened projects iterate over DB children
return isInContentOfOpenedProject(file)? null : ((NewVirtualFile)file).iterInDbChildren();
}
});
}
private static boolean isInContentOfOpenedProject(@NotNull final VirtualFile file) {
// probably need a read action to ensure that the project was not disposed during the iteration over the project list
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
if (!project.isInitialized() || !BuildManager.getInstance().isProjectWatched(project)) {
continue;
}
if (ProjectRootManager.getInstance(project).getFileIndex().isInContent(file)) {
return true;
}
}
return false;
}
private class MyVfsListener extends VirtualFileAdapter {
public void propertyChanged(@NotNull final VirtualFilePropertyEvent event) {
if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) {
final VirtualFile eventFile = event.getFile();
if (isInContentOfOpenedProject(eventFile)) {
final VirtualFile parent = event.getParent();
if (parent != null) {
final String oldName = (String)event.getOldValue();
final String root = parent.getPath() + "/" + oldName;
final Set<File> toMark = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
if (eventFile.isDirectory()) {
VfsUtilCore.visitChildrenRecursively(eventFile, new VirtualFileVisitor() {
private StringBuilder filePath = new StringBuilder(root);
@Override
public boolean visitFile(@NotNull VirtualFile child) {
if (child.isDirectory()) {
if (!Comparing.equal(child, eventFile)) {
filePath.append("/").append(child.getName());
}
}
else {
String childPath = filePath.toString();
if (!Comparing.equal(child, eventFile)) {
childPath += "/" + child.getName();
}
toMark.add(new File(childPath));
}
return true;
}
@Override
public void afterChildrenVisited(@NotNull VirtualFile file) {
if (file.isDirectory() && !Comparing.equal(file, eventFile)) {
filePath.delete(filePath.length() - file.getName().length() - 1, filePath.length());
}
}
});
}
else {
toMark.add(new File(root));
}
notifyFilesDeleted(toMark);
}
collectPathsAndNotify(eventFile, NOTIFY_CHANGED);
}
}
}
public void contentsChanged(@NotNull final VirtualFileEvent event) {
collectPathsAndNotify(event.getFile(), NOTIFY_CHANGED);
}
public void fileCreated(@NotNull final VirtualFileEvent event) {
collectPathsAndNotify(event.getFile(), NOTIFY_CHANGED);
}
public void fileCopied(@NotNull final VirtualFileCopyEvent event) {
collectPathsAndNotify(event.getFile(), NOTIFY_CHANGED);
}
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
collectPathsAndNotify(event.getFile(), NOTIFY_CHANGED);
}
public void beforeFileDeletion(@NotNull final VirtualFileEvent event) {
collectPathsAndNotify(event.getFile(), NOTIFY_DELETED);
}
public void beforeFileMovement(@NotNull final VirtualFileMoveEvent event) {
collectPathsAndNotify(event.getFile(), NOTIFY_DELETED);
}
}
private static final Function<Collection<File>, Void> NOTIFY_CHANGED = new Function<Collection<File>, Void>() {
public Void fun(Collection<File> files) {
notifyFilesChanged(files);
return null;
}
};
private static final Function<Collection<File>, Void> NOTIFY_DELETED = new Function<Collection<File>, Void>() {
public Void fun(Collection<File> files) {
notifyFilesDeleted(files);
return null;
}
};
private static void collectPathsAndNotify(final VirtualFile file, final Function<Collection<File>, Void> notification) {
final Set<File> pathsToMark = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
final boolean inContent = isInContentOfOpenedProject(file);
if (inContent || !isIgnoredOrUnderIgnoredDirectory(file)) {
processRecursively(file, !inContent, new FileProcessor() {
public void execute(final VirtualFile file) {
pathsToMark.add(new File(file.getPath()));
}
});
}
if (!pathsToMark.isEmpty()) {
notification.fun(pathsToMark);
}
}
private static boolean isIgnoredOrUnderIgnoredDirectory(final VirtualFile file) {
final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
if (fileTypeManager.isFileIgnored(file)) {
return true;
}
VirtualFile current = file.getParent();
while (current != null) {
if (fileTypeManager.isFileIgnored(current)) {
return true;
}
current = current.getParent();
}
return false;
}
private static void notifyFilesChanged(Collection<File> paths) {
if (!paths.isEmpty()) {
BuildManager.getInstance().notifyFilesChanged(paths);
}
}
private static void notifyFilesDeleted(Collection<File> paths) {
if (!paths.isEmpty()) {
BuildManager.getInstance().notifyFilesDeleted(paths);
}
}
}