diff --git a/bin/linux/fsnotifier b/bin/linux/fsnotifier index e248974f4a94..f7a73126a13e 100755 Binary files a/bin/linux/fsnotifier and b/bin/linux/fsnotifier differ diff --git a/bin/linux/fsnotifier64 b/bin/linux/fsnotifier64 index efba253b08b3..1fc4f32ff7ef 100755 Binary files a/bin/linux/fsnotifier64 and b/bin/linux/fsnotifier64 differ diff --git a/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationInspection.java b/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationInspection.java index d68b2a10970d..5a00b75a97f0 100644 --- a/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationInspection.java @@ -343,11 +343,12 @@ public class UnusedDeclarationInspection extends FilteringInspectionTool { final RefElementImpl refElement = (RefElementImpl)refEntity; if (!refElement.isSuspicious()) return; - final PsiElement element = refElement.getElement(); - if (element == null) return; + PsiFile file = refElement.getContainingFile(); + + if (file == null) return; final boolean isSuppressed = refElement.isSuppressed(getShortName()); - if (!getContext().isToCheckMember(element, UnusedDeclarationInspection.this) || isSuppressed) { - if (isSuppressed || !scope.contains(element)) { + if (!getContext().isToCheckFile(file, UnusedDeclarationInspection.this) || isSuppressed) { + if (isSuppressed || !scope.contains(file)) { getEntryPointsManager().addEntryPoint(refElement, false); } return; diff --git a/native/fsNotifier/linux/fsnotifier.h b/native/fsNotifier/linux/fsnotifier.h index 0ee04cd12639..f3234e2fde9d 100644 --- a/native/fsNotifier/linux/fsnotifier.h +++ b/native/fsNotifier/linux/fsnotifier.h @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * 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. @@ -24,6 +24,8 @@ // logging void userlog(int priority, const char* format, ...); +#define CHECK_NULL(p, r) if (p == NULL) { userlog(LOG_ERR, "out of memory"); return r; } + // variable-length array typedef struct __array array; @@ -38,7 +40,7 @@ void array_delete(array* a); void array_delete_vs_data(array* a); -// key/value pairs table +// poor man's hash table typedef struct __table table; table* table_create(int capacity); diff --git a/native/fsNotifier/linux/inotify.c b/native/fsNotifier/linux/inotify.c index ab605424d66c..04d749e48d83 100644 --- a/native/fsNotifier/linux/inotify.c +++ b/native/fsNotifier/linux/inotify.c @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * 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. @@ -32,8 +32,6 @@ #define DEFAULT_SUBDIR_COUNT 5 -#define CHECK_NULL(p) if (p == NULL) { userlog(LOG_ERR, "out of memory"); return ERR_ABORT; } - typedef struct __watch_node { char* name; int wd; @@ -153,9 +151,9 @@ static int add_watch(const char* path, watch_node* parent) { node = malloc(sizeof(watch_node)); - CHECK_NULL(node); + CHECK_NULL(node, ERR_ABORT); node->name = strdup(path); - CHECK_NULL(node->name); + CHECK_NULL(node->name, ERR_ABORT); node->wd = wd; node->parent = parent; node->kids = NULL; @@ -163,9 +161,9 @@ static int add_watch(const char* path, watch_node* parent) { if (parent != NULL) { if (parent->kids == NULL) { parent->kids = array_create(DEFAULT_SUBDIR_COUNT); - CHECK_NULL(parent->kids); + CHECK_NULL(parent->kids, ERR_ABORT); } - CHECK_NULL(array_push(parent->kids, node)); + CHECK_NULL(array_push(parent->kids, node), ERR_ABORT); } if (table_put(watches, wd, node) == NULL) { @@ -238,25 +236,31 @@ static bool is_ignored(const char* path, array* ignores) { return false; } -static int walk_tree(const char* path, watch_node* parent, array* ignores) { +static int walk_tree(const char* path, watch_node* parent, array* ignores, bool recursive) { if (is_ignored(path, ignores)) { return ERR_IGNORE; } - DIR* dir = opendir(path); - if (dir == NULL) { - if (errno == EACCES) { - return ERR_IGNORE; + DIR* dir; + if (recursive) { + dir = opendir(path); + if (dir == NULL) { + if (errno == EACCES) { + return ERR_IGNORE; + } + else if (errno == ENOTDIR) { // "future" root + return add_watch(path, parent); + } + userlog(LOG_ERR, "opendir(%s): %s", path, strerror(errno)); + return ERR_CONTINUE; } - else if (errno == ENOTDIR) { // flat root - return add_watch(path, parent); - } - userlog(LOG_ERR, "opendir(%s): %s", path, strerror(errno)); - return ERR_CONTINUE; } int id = add_watch(path, parent); - if (id < 0) { + if (!recursive) { + return id; + } + else if (id < 0) { closedir(dir); return id; } @@ -279,7 +283,7 @@ static int walk_tree(const char* path, watch_node* parent, array* ignores) { continue; } - int subdir_id = walk_tree(subdir, table_get(watches, id), ignores); + int subdir_id = walk_tree(subdir, table_get(watches, id), ignores, recursive); if (subdir_id < 0 && subdir_id != ERR_IGNORE) { rm_watch(id, true); id = subdir_id; @@ -293,9 +297,15 @@ static int walk_tree(const char* path, watch_node* parent, array* ignores) { int watch(const char* root, array* ignores) { + bool recursive = true; + if (root[0] == '|') { + root++; + recursive = false; + } + char buf[PATH_MAX]; const char* normalized = realpath(root, buf); - return walk_tree((normalized != NULL ? normalized : root), NULL, ignores); + return walk_tree((normalized != NULL ? normalized : root), NULL, ignores, recursive); } @@ -323,7 +333,7 @@ static bool process_inotify_event(struct inotify_event* event) { } if ((event->mask & IN_CREATE || event->mask & IN_MOVED_TO) && event->mask & IN_ISDIR) { - int result = walk_tree(path, node, NULL); + int result = walk_tree(path, node, NULL, true); if (result < 0 && result != ERR_IGNORE) { return false; } diff --git a/native/fsNotifier/linux/main.c b/native/fsNotifier/linux/main.c index 3bef18521151..81dcdfead702 100644 --- a/native/fsNotifier/linux/main.c +++ b/native/fsNotifier/linux/main.c @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * 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. @@ -33,7 +33,7 @@ #define LOG_ENV_ERROR "error" #define LOG_ENV_OFF "off" -#define VERSION "1.0" +#define VERSION "1.1" #define VERSION_MSG "fsnotifier " VERSION "\n" #define USAGE_MSG \ @@ -58,11 +58,8 @@ typedef struct { static array* roots = NULL; static bool show_warning = true; - static bool self_test = false; -#define CHECK_NULL(p) if (p == NULL) { userlog(LOG_ERR, "out of memory"); return false; } - static void init_log(); static void run_self_test(); static void main_loop(); @@ -223,7 +220,7 @@ static bool read_input() { if (strcmp(line, "ROOTS") == 0) { array* new_roots = array_create(20); - CHECK_NULL(new_roots); + CHECK_NULL(new_roots, false); while (1) { line = read_line(stdin); @@ -235,12 +232,9 @@ static bool read_input() { break; } else { - if (line[0] == '|') line++; // flat roots will be differentiated later - int l = strlen(line); if (l > 1 && line[l-1] == '/') line[l-1] = '\0'; - - CHECK_NULL(array_push(new_roots, strdup(line))); + CHECK_NULL(array_push(new_roots, strdup(line)), false); } } @@ -266,7 +260,7 @@ static bool update_roots(array* new_roots) { } array* unwatchable = array_create(20); - CHECK_NULL(unwatchable); + CHECK_NULL(unwatchable, false); if (!unwatchable_mounts(unwatchable)) { return false; } @@ -312,10 +306,10 @@ static bool register_roots(array* new_roots, array* unwatchable) { } else if (id >= 0) { watch_root* root = malloc(sizeof(watch_root)); - CHECK_NULL(root); + CHECK_NULL(root, false); root->id = id; root->name = new_root; - CHECK_NULL(array_push(roots, root)); + CHECK_NULL(array_push(roots, root), false); } else { if (show_warning && watch_limit_reached()) { @@ -324,7 +318,7 @@ static bool register_roots(array* new_roots, array* unwatchable) { output("MESSAGE\n" INOTIFY_LIMIT_MSG, limit); show_warning = false; // warn only once } - CHECK_NULL(array_push(unwatchable, new_root)); + CHECK_NULL(array_push(unwatchable, new_root), false); } } @@ -362,7 +356,7 @@ static bool unwatchable_mounts(array* mounts) { } if (!is_watchable(dev, point, fs)) { - CHECK_NULL(array_push(mounts, strdup(point))); + CHECK_NULL(array_push(mounts, strdup(point)), false); } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java index 8f3d92c65875..49de7ada2039 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/Descriptor.java @@ -19,6 +19,7 @@ package com.intellij.codeInspection.ex; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInspection.InspectionProfileEntry; +import com.intellij.codeInspection.LocalDummyInspectionTool; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.WriteExternalException; import com.intellij.psi.search.scope.packageSet.NamedScope; @@ -43,6 +44,7 @@ public class Descriptor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.Descriptor"); private final ScopeToolState myState; private final InspectionProfileImpl myInspectionProfile; + private boolean myShouldBeShown = true; public Descriptor(ScopeToolState pair, InspectionProfileImpl inspectionProfile) { myState = pair; @@ -56,6 +58,14 @@ public class Descriptor { myEnabled = ((InspectionProfileImpl)inspectionProfile).isToolEnabled(myKey, pair.getScope()); myTool = tool; myScope = pair.getScope(); + + if (tool instanceof InspectionToolWrapper) { + InspectionProfileEntry inspection = ((InspectionToolWrapper)tool).getTool(); + + if (inspection instanceof LocalDummyInspectionTool) { + myShouldBeShown = ((LocalDummyInspectionTool)inspection).shouldBeShownInInspectionProfile(); + } + } } public boolean equals(Object obj) { @@ -80,6 +90,10 @@ public class Descriptor { myEnabled = enabled; } + public boolean shouldBeShown() { + return myShouldBeShown; + } + public String getText() { return myText; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java index 6f083ee1eff2..61633adec49c 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java @@ -71,7 +71,10 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; public class GlobalInspectionContextImpl extends UserDataHolderBase implements GlobalInspectionContext { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl"); @@ -346,16 +349,15 @@ public class GlobalInspectionContextImpl extends UserDataHolderBase implements G public boolean isToCheckMember(@NotNull RefElement owner, InspectionProfileEntry tool) { - final PsiElement element = owner.getElement(); - return isToCheckMember(element, tool) && !((RefElementImpl)owner).isSuppressed(tool.getShortName()); + return isToCheckFile(((RefElementImpl)owner).getContainingFile(), tool) && !((RefElementImpl)owner).isSuppressed(tool.getShortName()); } - public boolean isToCheckMember(final PsiElement element, final InspectionProfileEntry tool) { + public boolean isToCheckFile(PsiFile file, final InspectionProfileEntry tool) { final Tools tools = myTools.get(tool.getShortName()); if (tools != null) { for (ScopeToolState state : tools.getTools()) { - final NamedScope namedScope = state.getScope(element.getProject()); - if (namedScope == null || namedScope.getValue().contains(element.getContainingFile(), getCurrentProfile().getProfileManager().getScopesManager())) { + final NamedScope namedScope = state.getScope(file.getProject()); + if (namedScope == null || namedScope.getValue().contains(file, getCurrentProfile().getProfileManager().getScopesManager())) { if (state.isEnabled()) { final InspectionProfileEntry entry = state.getTool(); if (entry instanceof InspectionToolWrapper && ((InspectionToolWrapper)entry).getTool() == tool) return true; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionProfileImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionProfileImpl.java index 42c07c5d5d43..401c3110e0f1 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionProfileImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionProfileImpl.java @@ -444,10 +444,6 @@ public class InspectionProfileImpl extends ProfileEx implements ModifiableModel, return false; } for (InspectionToolWrapper tool : tools) { - if (tool.getTool() instanceof LocalDummyInspectionTool && !((LocalDummyInspectionTool)tool.getTool()).shouldBeShownInInspectionProfile()) { - continue; - } - final String shortName = tool.getShortName(); HighlightDisplayKey key = HighlightDisplayKey.find(shortName); if (key == null) { diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java index c93c098d9f67..a8d9f80553ab 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java @@ -21,14 +21,8 @@ import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; -import com.intellij.codeInspection.InspectionProfile; -import com.intellij.codeInspection.InspectionProfileEntry; -import com.intellij.codeInspection.InspectionsBundle; -import com.intellij.codeInspection.ModifiableModel; -import com.intellij.codeInspection.ex.Descriptor; -import com.intellij.codeInspection.ex.InspectionProfileImpl; -import com.intellij.codeInspection.ex.InspectionToolRegistrar; -import com.intellij.codeInspection.ex.ScopeToolState; +import com.intellij.codeInspection.*; +import com.intellij.codeInspection.ex.*; import com.intellij.ide.CommonActionsManager; import com.intellij.ide.DefaultTreeExpander; import com.intellij.ide.IdeTooltipManager; @@ -685,7 +679,7 @@ public class SingleInspectionProfilePanel extends JPanel { keySetList.addAll(SearchUtil.findKeys(filter, quated)); } for (Descriptor descriptor : myDescriptors.keySet()) { - if (filter != null && filter.length() > 0 && !isDescriptorAccepted(descriptor, filter, forceInclude, keySetList, quated)) { + if (!descriptor.shouldBeShown() || filter != null && filter.length() > 0 && !isDescriptorAccepted(descriptor, filter, forceInclude, keySetList, quated)) { continue; } final List nonDefaultTools = mySelectedProfile.getNonDefaultTools(descriptor.getKey().toString()); diff --git a/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java index b65b5e289ff8..f54194be7251 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java @@ -173,6 +173,9 @@ public abstract class BaseRefactoringProcessor { } catch (Throwable e) { anyException.set(Boolean.TRUE); + if (ApplicationManager.getApplication().isUnitTestMode()) { + LOG.error(e); + } } } }; diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java index e11872c5a048..62b8a1a7ddf5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java @@ -20,34 +20,32 @@ import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; +import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.io.FileSystemUtil; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.watcher.ChangeKind; -import com.intellij.util.PairFunction; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import javax.swing.event.HyperlinkEvent; import java.io.*; import java.util.*; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * @author max */ public class FileWatcher { - @NonNls public static final String PROPERTY_WATCHER_DISABLED = "filewatcher.disabled"; - @NonNls private static final String PROPERTY_WATCHER_EXECUTABLE_PATH = "idea.filewatcher.executable.path"; + @NonNls public static final String PROPERTY_WATCHER_DISABLED = "idea.filewatcher.disabled"; + @NonNls public static final String PROPERTY_WATCHER_EXECUTABLE_PATH = "idea.filewatcher.executable.path"; private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.FileWatcher"); @@ -59,15 +57,7 @@ public class FileWatcher { @NonNls private static final String EXIT_COMMAND = "EXIT"; @NonNls private static final String MESSAGE_COMMAND = "MESSAGE"; - private static final PairFunction PATH_COMPARATOR = new PairFunction() { - @Override - public Boolean fun(final String s1, final String s2) { - return SystemInfo.isFileSystemCaseSensitive ? s1.equals(s2) : s1.equalsIgnoreCase(s2); - } - }; - private final Object LOCK = new Object(); - private final Lock SET_ROOTS_LOCK = new ReentrantLock(true); private List myDirtyPaths = new ArrayList(); private List myDirtyRecursivePaths = new ArrayList(); @@ -75,21 +65,23 @@ public class FileWatcher { private List myManualWatchRoots = new ArrayList(); private final List> myMapping = new ArrayList>(); - private List> myCanonicalMapping = new ArrayList>(); - private List myRecursiveWatchRoots = new ArrayList(); private List myFlatWatchRoots = new ArrayList(); + private final Collection myAllPaths = new ArrayList(2); + private final Collection myWatchedPaths = new ArrayList(2); + + private File executable; private volatile Process notifierProcess; private volatile BufferedReader notifierReader; - private volatile BufferedWriter notifierWriter; + private volatile BufferedWriter notifierWriter; private boolean myFailureShownToTheUser = false; private int attemptCount = 0; private static final int MAX_PROCESS_LAUNCH_ATTEMPT_COUNT = 10; private boolean isShuttingDown = false; - private final ManagingFS myManagingFS; + private final ManagingFS myManagingFS; private static final FileWatcher ourInstance = new FileWatcher(); public static FileWatcher getInstance() { @@ -140,7 +132,6 @@ public class FileWatcher { myDirtyRecursivePaths = new ArrayList(); return result; } - } public List getDirtyDirs() { @@ -158,26 +149,16 @@ public class FileWatcher { } public void setWatchRoots(final List recursive, final List flat) { - SET_ROOTS_LOCK.lock(); - try { - synchronized (LOCK) { - if (myRecursiveWatchRoots.equals(recursive) && myFlatWatchRoots.equals(flat)) return; - } - - final List> mapping = new ArrayList>(); - long t = System.nanoTime(); - final List checkedRecursive = checkPaths(recursive, mapping); - final List checkedFlat = checkPaths(flat, mapping); - t = (System.nanoTime() - t) / 1000; - LOG.info((recursive.size() + flat.size()) + " paths checked, " + mapping.size() + " mapped, " + t + " mks"); + synchronized (LOCK) { + if (myRecursiveWatchRoots.equals(recursive) && myFlatWatchRoots.equals(flat)) return; if (isAlive()) { try { writeLine(ROOTS_COMMAND); - for (String path : checkedRecursive) { + for (String path : recursive) { writeLine(path); } - for (String path : checkedFlat) { + for (String path : flat) { writeLine("|" + path); } writeLine("#"); @@ -187,37 +168,10 @@ public class FileWatcher { } } - synchronized (LOCK) { - myRecursiveWatchRoots = recursive; - myFlatWatchRoots = flat; - myMapping.clear(); - myCanonicalMapping = mapping; - } + myRecursiveWatchRoots = recursive; + myFlatWatchRoots = flat; + myMapping.clear(); } - finally { - SET_ROOTS_LOCK.unlock(); - } - } - - private static List checkPaths(final List paths, final List> mapping) { - if (!SystemInfo.areSymLinksSupported) return paths; - - final List checkedPaths = new ArrayList(paths.size()); - for (String path : paths) { - String watched = path; - final String canonical = getCanonicalPath(path); - //noinspection ConstantConditions - if (!PATH_COMPARATOR.fun(path, canonical)) { - mapping.add(Pair.create((watched = canonical), path)); - } - checkedPaths.add(watched); - } - return checkedPaths; - } - - private static String getCanonicalPath(final String path) { - final String realPath = FileSystemUtil.resolveSymLink(path); - return realPath != null ? realPath : path; } private boolean isAlive() { @@ -247,43 +201,34 @@ public class FileWatcher { shutdownProcess(); - String execPath = null; + if (executable == null) { + executable = getExecutable(); - final String altExecPath = System.getProperty(PROPERTY_WATCHER_EXECUTABLE_PATH); - if (altExecPath != null && new File(altExecPath).isFile()) { - execPath = FileUtil.toSystemDependentName(altExecPath); - } - - if (execPath == null) { - final String execName; - execName = getExecutableName(); - if (execName == null) { + if (executable == null) { myFailureShownToTheUser = true; // ignore unsupported platforms return; } - execPath = PathManager.getBinPath() + File.separatorChar + execName; + + if (!executable.exists()) { + notifyOnFailure("File watcher is not found at path: " + executable, null); + return; + } + + if (!executable.canExecute()) { + final String message = "File watcher is not executable: " + executable + ""; + final File exec = executable; + notifyOnFailure(message, new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + ShowFilePathAction.openFile(exec); + } + }); + return; + } } - final File exec = new File(execPath); - if (!exec.exists()) { - notifyOnFailure("File watcher is not found at path: " + execPath, null); - return; - } - - if (!exec.canExecute()) { - notifyOnFailure("File watcher is not executable: " + execPath +"", new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - ShowFilePathAction.openFile(exec); - } - }); - return; - } - - LOG.info("Starting file watcher: " + execPath); - - notifierProcess = Runtime.getRuntime().exec(new String[]{execPath}); - + LOG.info("Starting file watcher: " + executable); + notifierProcess = Runtime.getRuntime().exec(executable.getAbsolutePath()); notifierReader = new BufferedReader(new InputStreamReader(notifierProcess.getInputStream())); notifierWriter = new BufferedWriter(new OutputStreamWriter(notifierProcess.getOutputStream())); @@ -299,15 +244,43 @@ public class FileWatcher { } @Nullable - private static String getExecutableName() { + private static File getExecutable() { + String execPath = null; + + final String altExecPath = System.getProperty(PROPERTY_WATCHER_EXECUTABLE_PATH); + if (altExecPath != null && new File(altExecPath).isFile()) { + execPath = FileUtil.toSystemDependentName(altExecPath); + } + + if (execPath == null) { + final String execName = getExecutableName(false); + if (execName == null) { + return null; + } + execPath = FileUtil.join(PathManager.getBinPath(), execName); + } + + File exec = new File(execPath); + if (!exec.exists()) { + String homePath = PathManager.getHomePath(); + if (new File(homePath, "community").exists()) { + homePath += File.separator + "community"; + } + exec = new File(FileUtil.join(homePath, "bin", getExecutableName(true))); + } + return exec; + } + + @Nullable + private static String getExecutableName(final boolean withSubDir) { if (SystemInfo.isWindows) { - return "fsnotifier.exe"; + return (withSubDir ? "win" + File.separator : "") + "fsnotifier.exe"; } else if (SystemInfo.isMac) { - return "fsnotifier"; + return (withSubDir ? "mac" + File.separator : "") + "fsnotifier"; } else if (SystemInfo.isLinux) { - return SystemInfo.isAMD64 ? "fsnotifier64" : "fsnotifier"; + return (withSubDir ? "linux" + File.separator : "") + (SystemInfo.isAMD64 ? "fsnotifier64" : "fsnotifier"); } return null; @@ -316,7 +289,8 @@ public class FileWatcher { private void notifyOnFailure(String cause, @Nullable NotificationListener listener) { if (!myFailureShownToTheUser) { myFailureShownToTheUser = true; - Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "External file sync may be slow", cause, NotificationType.WARNING, listener)); + Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "External file sync may be slow", + cause, NotificationType.WARNING, listener)); } } @@ -339,6 +313,32 @@ public class FileWatcher { return notifierProcess != null; } + @TestOnly + public void startup() throws IOException { + final Application app = ApplicationManager.getApplication(); + assert app != null && app.isUnitTestMode() : app; + + myFailureShownToTheUser = true; + attemptCount = 0; + startupProcess(false); + attemptCount = 2 * MAX_PROCESS_LAUNCH_ATTEMPT_COUNT; + if (notifierProcess != null) { + new WatchForChangesThread().start(); + } + } + + @TestOnly + public void shutdown() throws InterruptedException { + final Application app = ApplicationManager.getApplication(); + assert app != null && app.isUnitTestMode() : app; + + final Process process = notifierProcess; + if (process != null) { + shutdownProcess(); + process.waitFor(); + } + } + private class WatchForChangesThread extends Thread { public WatchForChangesThread() { //noinspection HardCodedStringLiteral @@ -396,7 +396,7 @@ public class FileWatcher { final String pathB = readLine(); if (pathB == null || "#".equals(pathB)) break; - pairs.add(new Pair(ensureEndsWithSlash(pathA), ensureEndsWithSlash(pathB))); + pairs.add(Pair.create(preparePathForMapping(pathA), preparePathForMapping(pathB))); } while (true); @@ -414,10 +414,10 @@ public class FileWatcher { } synchronized (LOCK) { - final String watchedPath = checkWatchable(path); - if (watchedPath != null) { + final Collection watchedPaths = checkWatchable(path); + if (!watchedPaths.isEmpty()) { try { - onPathChange(ChangeKind.valueOf(command), watchedPath); + onPathChange(ChangeKind.valueOf(command), watchedPaths); } catch (IllegalArgumentException e) { LOG.error("Illegal watcher command: " + command); @@ -438,9 +438,9 @@ public class FileWatcher { } } - private static String ensureEndsWithSlash(String path) { - if (path.endsWith("/") || path.endsWith(File.separator)) return path; - return path + '/'; + private static String preparePathForMapping(final String path) { + final String localPath = FileUtil.toSystemDependentName(path); + return localPath.endsWith(File.separator) ? localPath : localPath + File.separator; } private void writeLine(String line) throws IOException { @@ -486,64 +486,76 @@ public class FileWatcher { return line; } - public boolean isWatched(VirtualFile file) { - return isOperational() && checkWatchable(file.getPresentableUrl()) != null; + public boolean isWatched(@NotNull final VirtualFile file) { + if (isOperational()) { + synchronized (LOCK) { + return !checkWatchable(file.getPresentableUrl()).isEmpty(); + } + } + return false; } - @Nullable - private String checkWatchable(String path) { - if (path == null) return null; + @NotNull + private Collection checkWatchable(final String reportedPath) { + if (reportedPath == null) return Collections.emptyList(); - for (Pair mapping : myCanonicalMapping) { - if (path.startsWith(mapping.first)) { - path = mapping.second + path.substring(mapping.first.length()); - break; + myAllPaths.clear(); + myAllPaths.add(reportedPath); + for (Pair map : myMapping) { + if (FileUtil.startsWith(reportedPath, map.first)) { + myAllPaths.add(map.second + reportedPath.substring(map.first.length())); + } + else if (FileUtil.startsWith(reportedPath, map.second)) { + myAllPaths.add(map.first + reportedPath.substring(map.second.length())); } } - for (String root : myRecursiveWatchRoots) { - if (FileUtil.startsWith(path, root)) { - return path; + myWatchedPaths.clear(); + ext: + for (String path : myAllPaths) { + for (String root : myRecursiveWatchRoots) { + if (FileUtil.startsWith(path, root)) { + myWatchedPaths.add(path); + continue ext; + } + } + + for (String root : myFlatWatchRoots) { + if (FileUtil.pathsEqual(path, root)) { + myWatchedPaths.add(path); + continue ext; + } + final File parentFile = new File(path).getParentFile(); + if (parentFile != null && FileUtil.pathsEqual(parentFile.getPath(), root)) { + myWatchedPaths.add(path); + continue ext; + } } } - - for (String root : myFlatWatchRoots) { - if (FileUtil.pathsEqual(path, root)) { - return path; - } - final File parentFile = new File(path).getParentFile(); - if (parentFile != null && FileUtil.pathsEqual(parentFile.getPath(), root)) { - return path; - } - } - - return null; + return myWatchedPaths; } - private void onPathChange(final ChangeKind changeKind, final String path) { + private void onPathChange(final ChangeKind changeKind, final Collection paths) { switch (changeKind) { case STATS: case CHANGE: - addPath(path, myDirtyPaths); + myDirtyPaths.addAll(paths); break; case CREATE: case DELETE: - final File parentFile = new File(path).getParentFile(); - if (parentFile != null) { - addPath(parentFile.getPath(), myDirtyPaths); - } - else { - addPath(path, myDirtyPaths); + for (String path : paths) { + final File parent = new File(path).getParentFile(); + myDirtyPaths.add(parent != null ? parent.getPath() : path); } break; case DIRTY: - addPath(path, myDirtyDirs); + myDirtyDirs.addAll(paths); break; case RECDIRTY: - addPath(path, myDirtyRecursivePaths); + myDirtyRecursivePaths.addAll(paths); break; case RESET: @@ -552,19 +564,6 @@ public class FileWatcher { } } - private void addPath(String path, List list) { - list.add(path); - - for (Pair map : myMapping) { - if (FileUtil.startsWith(path, map.getFirst())) { - list.add(map.getSecond() + path.substring(map.getFirst().length())); - } - else if (FileUtil.startsWith(path, map.getSecond())) { - list.add(map.getFirst() + path.substring(map.getSecond().length())); - } - } - } - private void reset() { synchronized (LOCK) { myDirtyPaths.clear(); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java new file mode 100644 index 000000000000..074522040598 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java @@ -0,0 +1,438 @@ +/* + * 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.vfs.local; + +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.idea.Bombed; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.impl.local.FileWatcher; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.testFramework.PlatformLangTestCase; +import com.intellij.util.Function; +import com.intellij.util.TimeoutUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.*; + +public class FileWatcherTest extends PlatformLangTestCase { + private static final int NATIVE_PROCESS_DELAY = 500; // time to event to be caught by native watcher and passed to watcher thread + + private FileWatcher myWatcher; + private LocalFileSystem myFileSystem; + private MessageBusConnection myConnection; + private final List myEvents = new ArrayList(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + + myWatcher = FileWatcher.getInstance(); + assertNotNull(myWatcher); + assertFalse(myWatcher.isOperational()); + myWatcher.startup(); + assertTrue(myWatcher.isOperational()); + + myFileSystem = LocalFileSystem.getInstance(); + assertNotNull(myFileSystem); + + myConnection = ApplicationManager.getApplication().getMessageBus().connect(); + myConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + @Override + public void before(@NotNull List events) { + } + + @Override + public void after(@NotNull List events) { + synchronized (myEvents) { + myEvents.addAll(events); + myEvents.notifyAll(); + } + } + }); + } + + @Override + protected void tearDown() throws Exception { + try { + myConnection.disconnect(); + myWatcher.shutdown(); + } + finally { + myFileSystem = null; + myWatcher = null; + super.tearDown(); + } + } + + + public void testFileRoot() throws Exception { + final File file = FileUtil.createTempFile("test.", ".txt"); + refresh(file); + final LocalFileSystem.WatchRequest request = watch(file); + try { + FileUtil.writeToFile(file, "new content"); + assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath()); + + FileUtil.delete(file); + assertEvent(VFileDeleteEvent.class, file.getAbsolutePath()); + + FileUtil.writeToFile(file, "re-creation"); + assertEvent(VFileCreateEvent.class, file.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoot(request); + FileUtil.delete(file); + } + } + + public void testNonCanonicallyNamedFileRoot() throws Exception { + if (SystemInfo.isFileSystemCaseSensitive) { + System.out.println("Ignored: case-insensitive FS required"); + return; + } + + final File file = FileUtil.createTempFile("test.", ".txt"); + refresh(file); + + final String watchRoot = file.getAbsolutePath().toUpperCase(Locale.US); + final LocalFileSystem.WatchRequest request = watch(new File(watchRoot)); + try { + FileUtil.writeToFile(file, "new content"); + assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath()); + + FileUtil.delete(file); + assertEvent(VFileDeleteEvent.class, file.getAbsolutePath()); + + FileUtil.writeToFile(file, "re-creation"); + assertEvent(VFileCreateEvent.class, file.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoot(request); + FileUtil.delete(file); + } + } + + public void testDirectoryRecursive() throws Exception { + final File topDir = FileUtil.createTempDirectory("top.", null); + refresh(topDir); + + final LocalFileSystem.WatchRequest request = watch(topDir); + try { + final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null); + assertEvent(VFileCreateEvent.class, subDir.getAbsolutePath()); + refresh(subDir); + + final File file = FileUtil.createTempFile(subDir, "test.", ".txt", true, false); + assertEvent(VFileCreateEvent.class, file.getAbsolutePath()); + + FileUtil.writeToFile(file, "new content"); + assertEvent(VFileContentChangeEvent.class, file.getAbsolutePath()); + + FileUtil.delete(file); + assertEvent(VFileDeleteEvent.class, file.getAbsolutePath()); + + FileUtil.writeToFile(file, "re-creation"); + assertEvent(VFileCreateEvent.class, file.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoot(request); + FileUtil.delete(topDir); + } + } + + public void testDirectoryFlat() throws Exception { + final File topDir = FileUtil.createTempDirectory("top.", null); + final File watchedFile = FileUtil.createTempFile(topDir, "test.", ".txt", true, false); + final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null); + final File unwatchedFile = FileUtil.createTempFile(subDir, "test.", ".txt", true, false); + refresh(topDir); + + final LocalFileSystem.WatchRequest request = watch(topDir, false); + try { + FileUtil.writeToFile(watchedFile, "new content"); + assertEvent(VFileContentChangeEvent.class, watchedFile.getAbsolutePath()); + + FileUtil.writeToFile(unwatchedFile, "new content"); + assertEvent(VFileEvent.class); + } + finally { + myFileSystem.removeWatchedRoot(request); + FileUtil.delete(topDir); + } + } + + public void testDirectoryNonExisting() throws Exception { + final File topDir = FileUtil.createTempDirectory("top.", null); + final File subDir = new File(topDir, "subDir"); + final File file = new File(subDir, "file.txt"); + refresh(topDir); + + final LocalFileSystem.WatchRequest request = watch(subDir); + try { + assertTrue(subDir.toString(), subDir.mkdir()); + assertEvent(VFileCreateEvent.class, subDir.getAbsolutePath()); + refresh(subDir); + + FileUtil.writeToFile(file, "new content"); + assertEvent(VFileCreateEvent.class, file.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoot(request); + FileUtil.delete(topDir); + } + } + + public void testDirectoryOverlapping() throws Exception { + final File topDir = FileUtil.createTempDirectory("top.", null); + final File file1 = FileUtil.createTempFile(topDir, "file1.", ".txt", true, false); + final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null); + final File file2 = FileUtil.createTempFile(subDir, "file2.", ".txt", true, false); + final File sideDir = FileUtil.createTempDirectory("side.", null); + final File file3 = FileUtil.createTempFile(sideDir, "file3.", ".txt", true, false); + refresh(topDir); + refresh(sideDir); + + final LocalFileSystem.WatchRequest request1 = watch(subDir); + final LocalFileSystem.WatchRequest request2 = watch(sideDir); + try { + FileUtil.writeToFile(file1, "new content"); + FileUtil.writeToFile(file2, "new content"); + FileUtil.writeToFile(file3, "new content"); + assertEvent(VFileContentChangeEvent.class, file2.getAbsolutePath(), file3.getAbsolutePath()); + + final LocalFileSystem.WatchRequest request3 = watch(topDir); + try { + FileUtil.writeToFile(file1, "newer content"); + FileUtil.writeToFile(file2, "newer content"); + FileUtil.writeToFile(file3, "newer content"); + assertEvent(VFileContentChangeEvent.class, file1.getAbsolutePath(), file2.getAbsolutePath(), file3.getAbsolutePath()); + } + finally { + unwatch(request3); + } + + FileUtil.writeToFile(file1, "newest content"); + FileUtil.writeToFile(file2, "newest content"); + FileUtil.writeToFile(file3, "newest content"); + assertEvent(VFileContentChangeEvent.class, file2.getAbsolutePath(), file3.getAbsolutePath()); + + FileUtil.delete(file1); + FileUtil.delete(file2); + FileUtil.delete(file3); + assertEvent(VFileDeleteEvent.class, file1.getAbsolutePath(), file2.getAbsolutePath(), file3.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoots(Arrays.asList(request1, request2)); + FileUtil.delete(topDir); + } + } + + @Bombed(user = "roman.shevchenko", year = 2012, month = Calendar.MAY, day = 1) + public void testSymlinkAboveWatchRoot() throws Exception { + final File topDir = FileUtil.createTempDirectory("top.", null); + final File topLink = SymlinkHandlingTest.createTempLink(topDir.getAbsolutePath(), "link"); + final File subDir = FileUtil.createTempDirectory(topDir, "sub.", null); + final File file = FileUtil.createTempFile(subDir, "test.", ".txt", true, false); + final File fileLink = new File(new File(topLink, subDir.getName()), file.getName()); + refresh(topDir); + refresh(topLink); + + final LocalFileSystem.WatchRequest request = watch(topLink); + try { + FileUtil.writeToFile(file, "new content"); + assertEvent(VFileContentChangeEvent.class, fileLink.getAbsolutePath()); + + FileUtil.delete(file); + assertEvent(VFileDeleteEvent.class, fileLink.getAbsolutePath()); + + FileUtil.writeToFile(file, "re-creation"); + assertEvent(VFileCreateEvent.class, fileLink.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoot(request); + FileUtil.delete(topLink); + FileUtil.delete(topDir); + } + } + + @Bombed(user = "roman.shevchenko", year = 2012, month = Calendar.MAY, day = 1) + public void testSymlinkBelowWatchRoot() throws Exception { + final File targetDir = FileUtil.createTempDirectory("top.", null); + final File file = FileUtil.createTempFile(targetDir, "test.", ".txt", true, false); + final File linkDir = FileUtil.createTempDirectory("link.", null); + final File link = new File(linkDir, "link"); + SymlinkHandlingTest.createTempLink(targetDir.getAbsolutePath(), link.getAbsolutePath()); + final File fileLink = new File(link, file.getName()); + refresh(targetDir); + refresh(linkDir); + + final LocalFileSystem.WatchRequest request = watch(linkDir); + try { + FileUtil.writeToFile(file, "new content"); + assertEvent(VFileContentChangeEvent.class, fileLink.getAbsolutePath()); + + FileUtil.delete(file); + assertEvent(VFileDeleteEvent.class, fileLink.getAbsolutePath()); + + FileUtil.writeToFile(file, "re-creation"); + assertEvent(VFileCreateEvent.class, fileLink.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoot(request); + FileUtil.delete(linkDir); + FileUtil.delete(targetDir); + } + } + + public void testSubst() throws Exception { + if (!SystemInfo.isWindows) { + System.out.println("Ignored: Windows required"); + return; + } + + final Set roots = ContainerUtil.map2Set(File.listRoots(), new Function() { + @Override + public Character fun(File root) { + return root.getPath().toLowerCase(Locale.US).charAt(0); + } + }); + char subst = 0; + for (char c = 'e'; c <= 'z'; c++) { + if (!roots.contains(c)) { + subst = c; + break; + } + } + assertFalse("Occupied: " + roots.toString(), subst == 0); + + final File targetDir = FileUtil.createTempDirectory("top.", null); + final File subDir = FileUtil.createTempDirectory(targetDir, "sub.", null); + final File file = FileUtil.createTempFile(subDir, "test.", ".txt", true, false); + final int rv = new GeneralCommandLine("subst", subst + ":", targetDir.getAbsolutePath()).createProcess().waitFor(); + assertEquals(0, rv); + + final File substDir = new File((subst + ":\\").toUpperCase(Locale.US), subDir.getName()); + final File substFile = new File(substDir, file.getName()); + refresh(targetDir); + refresh(substDir); + + final LocalFileSystem.WatchRequest request = watch(substDir); + try { + FileUtil.writeToFile(file, "new content"); + assertEvent(VFileContentChangeEvent.class, substFile.getAbsolutePath()); + + final LocalFileSystem.WatchRequest request2 = watch(targetDir); + try { + FileUtil.delete(file); + assertEvent(VFileDeleteEvent.class, file.getAbsolutePath(), substFile.getAbsolutePath()); + } + finally { + unwatch(request2); + } + + FileUtil.writeToFile(file, "re-creation"); + assertEvent(VFileCreateEvent.class, substFile.getAbsolutePath()); + } + finally { + myFileSystem.removeWatchedRoot(request); + new GeneralCommandLine("subst", subst + ":", "/d").createProcess().waitFor(); + FileUtil.delete(targetDir); + } + } + + + private List getEvents() throws InterruptedException { + TimeoutUtil.sleep(NATIVE_PROCESS_DELAY); + myFileSystem.refresh(false); + + synchronized (myEvents) { + final ArrayList result = new ArrayList(myEvents); + myEvents.clear(); + return result; + } + } + + private void clearEvents() { + myFileSystem.refresh(false); + synchronized (myEvents) { + myEvents.clear(); + } + } + + @NotNull + private LocalFileSystem.WatchRequest watch(final File watchFile) throws InterruptedException { + return watch(watchFile, true); + } + + @NotNull + private LocalFileSystem.WatchRequest watch(final File watchFile, final boolean recursive) throws InterruptedException { + final LocalFileSystem.WatchRequest request = myFileSystem.addRootToWatch(watchFile.getAbsolutePath(), recursive); + assertNotNull(request); + TimeoutUtil.sleep(NATIVE_PROCESS_DELAY); + clearEvents(); + return request; + } + + private void unwatch(final LocalFileSystem.WatchRequest request) throws InterruptedException { + myFileSystem.removeWatchedRoot(request); + TimeoutUtil.sleep(NATIVE_PROCESS_DELAY); + clearEvents(); + } + + private VirtualFile refresh(final File file) { + final VirtualFile vFile = myFileSystem.refreshAndFindFileByIoFile(file); + assertNotNull(file.toString(), vFile); + VfsUtilCore.visitChildrenRecursively(vFile, new VirtualFileVisitor() { + @Override + public boolean visitFile(@NotNull VirtualFile file) { + file.getChildren(); + return true; + } + }); + return vFile; + } + + private void assertEvent(final Class type, final String... paths) throws InterruptedException { + final List events = getEvents(); + assertEquals(events.toString(), paths.length, events.size()); + + final Set pathSet = ContainerUtil.map2Set(paths, new Function() { + @Override + public String fun(final String path) { + return FileUtil.toSystemIndependentName(path); + } + }); + + for (final VFileEvent event : events) { + assertTrue(event.toString(), type.isInstance(event)); + + final VirtualFile eventFile = event.getFile(); + assertNotNull(event.toString(), eventFile); + + assertTrue(eventFile + " not in " + Arrays.toString(paths), pathSet.remove(eventFile.getPath())); + } + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java index 025a888e1a20..2a8cb8d5d453 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/SymlinkHandlingTest.java @@ -266,7 +266,7 @@ public class SymlinkHandlingTest extends LightPlatformLangTestCase { } // todo[r.sh] use NIO2 API after migration to JDK 7 - private static File createTempLink(final String target, final String link) throws InterruptedException, ExecutionException { + public static File createTempLink(final String target, final String link) throws InterruptedException, ExecutionException { final boolean isAbsolute = SystemInfo.isUnix && StringUtil.startsWithChar(link, '/') || SystemInfo.isWindows && link.matches("^[c-zC-Z]:[/\\\\].*$"); final File linkFile = isAbsolute ? new File(link) : new File(FileUtil.getTempDirectory(), link); diff --git a/plugins/android/common/src/org/jetbrains/android/util/AndroidCommonUtils.java b/plugins/android/common/src/org/jetbrains/android/util/AndroidCommonUtils.java index d6db275a63c7..f52243482aa3 100644 --- a/plugins/android/common/src/org/jetbrains/android/util/AndroidCommonUtils.java +++ b/plugins/android/common/src/org/jetbrains/android/util/AndroidCommonUtils.java @@ -52,7 +52,9 @@ public class AndroidCommonUtils { private static final Pattern WARNING_PATTERN = Pattern.compile(".*warning.*"); private static final Pattern ERROR_PATTERN = Pattern.compile(".*error.*"); private static final Pattern EXCEPTION_PATTERN = Pattern.compile(".*exception.*"); + private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); + private static Pattern MANIFEST_PATTERN = Pattern.compile("Manifest(\\$.*)?\\.class"); public static final Pattern COMPILER_MESSAGE_PATTERN = Pattern.compile("(.+):(\\d+):.+"); @@ -204,7 +206,7 @@ public class AndroidCommonUtils { private static void addFileToJar(@NotNull File file, @NotNull File rootDirectory, - boolean packRClasses, + boolean packRAndManifestClasses, @NotNull List> files) throws IOException { @@ -213,7 +215,7 @@ public class AndroidCommonUtils { if (children != null) { for (File child : children) { - addFileToJar(child, rootDirectory, packRClasses, files); + addFileToJar(child, rootDirectory, packRAndManifestClasses, files); } } } @@ -222,7 +224,9 @@ public class AndroidCommonUtils { return; } - if (!packRClasses && R_PATTERN.matcher(file.getName()).matches()) { + if (!packRAndManifestClasses && + (R_PATTERN.matcher(file.getName()).matches() || + MANIFEST_PATTERN.matcher(file.getName()).matches())) { return; } diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchUiUtil.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchUiUtil.java index cbb17dfc7a05..9f5341545ec4 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchUiUtil.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchUiUtil.java @@ -77,7 +77,7 @@ public class GitBranchUiUtil { */ @Nullable public static String getNewBranchNameFromUser(@NotNull Project project, @NotNull Collection repositories, @NotNull String dialogTitle) { - return Messages.showInputDialog(project, "Enter the name of new branch", dialogTitle, Messages.getQuestionIcon(), "", + return Messages.showInputDialog(project, "Enter the name of new branch:", dialogTitle, Messages.getQuestionIcon(), "", GitNewBranchNameValidator.newInstance(repositories)); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/indices/MavenRepositoriesConfigurable.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/indices/MavenRepositoriesConfigurable.java index 5db81b975933..01bed087383f 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/indices/MavenRepositoriesConfigurable.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/indices/MavenRepositoriesConfigurable.java @@ -95,8 +95,10 @@ public class MavenRepositoriesConfigurable extends BaseConfigurable implements S final String value = (String)myServiceList.getSelectedValue(); final String text = Messages.showInputDialog("Artifactory or Nexus Service URL", "Add Service URL", Messages.getQuestionIcon(), value == null ? "http://" : value, new URLInputVaslidator()); - myModel.add(text); - myServiceList.setSelectedValue(text, true); + if (StringUtil.isNotEmpty(text)) { + myModel.add(text); + myServiceList.setSelectedValue(text, true); + } } }); myEditButton.addActionListener(new ActionListener() { @@ -105,7 +107,7 @@ public class MavenRepositoriesConfigurable extends BaseConfigurable implements S final int index = myServiceList.getSelectedIndex(); final String text = Messages.showInputDialog("Artifactory or Nexus Service URL", "Edit Service URL", Messages.getQuestionIcon(), myModel.getElementAt(index), new URLInputVaslidator()); - if (text != null) { + if (StringUtil.isNotEmpty(text)) { myModel.setElementAt(text, index); } } diff --git a/plugins/properties/src/com/intellij/codeInspection/duplicatePropertyInspection/DuplicatePropertyInspection.java b/plugins/properties/src/com/intellij/codeInspection/duplicatePropertyInspection/DuplicatePropertyInspection.java index e46ee8da92c3..085a8a9f5e25 100644 --- a/plugins/properties/src/com/intellij/codeInspection/duplicatePropertyInspection/DuplicatePropertyInspection.java +++ b/plugins/properties/src/com/intellij/codeInspection/duplicatePropertyInspection/DuplicatePropertyInspection.java @@ -145,7 +145,7 @@ public class DuplicatePropertyInspection extends GlobalSimpleInspectionTool { private void checkFile(final PsiFile file, final InspectionManager manager, GlobalInspectionContextImpl context, final RefManager refManager, final ProblemDescriptionsProcessor processor) { if (!(file instanceof PropertiesFile)) return; - if (!context.isToCheckMember(file, this)) return; + if (!context.isToCheckFile(file, this)) return; final PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(file.getProject()); final PropertiesFile propertiesFile = (PropertiesFile)file; final List properties = propertiesFile.getProperties(); diff --git a/plugins/spellchecker/src/com/intellij/spellchecker/english.dic b/plugins/spellchecker/src/com/intellij/spellchecker/english.dic index 788f32996ce3..b6d95a792ff3 100644 --- a/plugins/spellchecker/src/com/intellij/spellchecker/english.dic +++ b/plugins/spellchecker/src/com/intellij/spellchecker/english.dic @@ -133140,6 +133140,7 @@ unwarrantably unwarranted unwary unwashed +unwatch unwatchable unwatched unwavering