From 4e5c53992aa23e658561dbc6a4256f1f6529c363 Mon Sep 17 00:00:00 2001 From: Alexander Kirillin Date: Tue, 10 Apr 2012 20:36:59 +0400 Subject: [PATCH 1/9] Provided a way not to show some inspections in the inspections profile view --- .../com/intellij/codeInspection/ex/Descriptor.java | 14 ++++++++++++++ .../codeInspection/ex/InspectionProfileImpl.java | 4 ---- .../ui/SingleInspectionProfilePanel.java | 12 +++--------- 3 files changed, 17 insertions(+), 13 deletions(-) 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/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()); From 4448f982c690fcd10b4a1de8b37ec5cb6b10c1cd Mon Sep 17 00:00:00 2001 From: "kirill.safonov" Date: Thu, 5 Apr 2012 23:11:22 +0400 Subject: [PATCH 2/9] cosmetics --- plugins/git4idea/src/git4idea/ui/branch/GitBranchUiUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)); } From b7bbb9bcbfe05c544032621d36584f9be03a694b Mon Sep 17 00:00:00 2001 From: "kirill.safonov" Date: Tue, 10 Apr 2012 20:17:01 +0400 Subject: [PATCH 3/9] assert then refactoring processor does not throw exceptions (in test mode for now), see IDEA-84373 --- .../src/com/intellij/refactoring/BaseRefactoringProcessor.java | 3 +++ 1 file changed, 3 insertions(+) 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); + } } } }; From 5d8433e70f03fa9bf01ed9fac490ece963181210 Mon Sep 17 00:00:00 2001 From: Maxim Shafirov Date: Tue, 10 Apr 2012 21:06:27 +0400 Subject: [PATCH 4/9] Looks like I've optimized some code, that we don't need at all. refElement.getPsiElement(), which is slow is only used to get it's file to check if it belongs to search scope. --- .../deadCode/UnusedDeclarationInspection.java | 9 +++++---- .../ex/GlobalInspectionContextImpl.java | 14 ++++++++------ .../DuplicatePropertyInspection.java | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) 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/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/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(); From 5269a132b463f2a0cde9735b049cbc656cab140d Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Tue, 10 Apr 2012 20:08:36 +0400 Subject: [PATCH 5/9] IDEA-84320 do not pack Manifest class from library project into apk --- .../org/jetbrains/android/util/AndroidCommonUtils.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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; } From dfe12bae587833908c885ce5aa981801ababdf09 Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Tue, 10 Apr 2012 21:27:37 +0400 Subject: [PATCH 6/9] IDEA-84044 null URL allowed in maven service configurable --- .../idea/maven/indices/MavenRepositoriesConfigurable.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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); } } From 1f63ce03a5c351c664def212662f3b6cc38d0565 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 10 Apr 2012 17:06:08 +0200 Subject: [PATCH 7/9] To English dic. --- plugins/spellchecker/src/com/intellij/spellchecker/english.dic | 1 + 1 file changed, 1 insertion(+) 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 From 8487c658e408b5fa6c94e7accea5948bc6b9f3fc Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 10 Apr 2012 17:00:21 +0200 Subject: [PATCH 8/9] Linux file watcher: do not dive into flat roots --- bin/linux/fsnotifier | Bin 22566 -> 22566 bytes bin/linux/fsnotifier64 | Bin 24816 -> 24816 bytes native/fsNotifier/linux/fsnotifier.h | 6 ++-- native/fsNotifier/linux/inotify.c | 52 ++++++++++++++++----------- native/fsNotifier/linux/main.c | 24 +++++-------- 5 files changed, 44 insertions(+), 38 deletions(-) diff --git a/bin/linux/fsnotifier b/bin/linux/fsnotifier index e248974f4a941feb95fe52f71338c24739812fb7..f7a73126a13e38a3b51fd93601b8594961195074 100755 GIT binary patch delta 4536 zcmai2dsI|)9=~_Ez*PrkcnBT_Pgz228uSpQZB2Thuu#WzrETkUY{`<^JE7Zn}jkgV$jA?vzlwmdSSVwNCO?33gQCW_BSuR3-(%iWQi;M%%d4&4;rezA6F zsOvTH6-n&tS{70viQQfMCME=htZtX&2mI{gup_k};HYL^4g{2*s)zx}-uXJeSrW~!g1%m(!zsa;qSJ#q*m~3T zhPYQ*yQ$nV3^gM%`CASFm<<81dYYNd%kmb*2tu0~?x;s52tq@TsUb2*5LyWXcN>BM zK%k`=3`vs&p@lpLm$HSuXiha;p2!ZF7mAzNgovct7HDlfaZHlkl3gvfApU;k-2P1+ zl58sKcNf~Z2af}U!cg*Hmq8F5$CY!YhAD_!PpKOWj^pZG7kIuJ!p101gQDKd@m-=x zIRh2Y6iu3pc-AUCZe?j85U5&Dp*CxV;J={G=Z1WTaQ1i$z~WH~@l+bTm3WyN_7c%~9-aY!C+hN29HU8TplmBOy&ao-;M z|KsjNOcb{>7g`akA2GQHlz=l`&%sL+>0Rfwj`=!rZd-H4{Kx8b;{fr8KJdE17U zcRK~){*3L3EL&5e3Av-Lf_(^jJp%dp3D7@RVBNUHd;xiNGqN-C2vSMSr*4R(000SQ zKpRPLz&($59mn|CJyEgCmteHi8uGi^E>Uh;No-W@fG#zi6dIx8D2>A$s8IKr^rj+j zilR_Co9AUPwtX!QCcGX9k4a6?T~l2*oZkvWabsP6&Gz4gxcg zJebQ~iq5KSA!0jVw^Qrd7-;a-0r83fGv?$PY^1ee9Mg2Nw(1q>8prj-aql>e0^+!7 zZF|9(mSWO+KY3#;M(k zou}SNEe6JIU{rcAehoX(i|$dk(XKwhcE(z!W}sbQklfr$KF7i!R;L3om|p|D3*Hg& z&+NJa~|BMeHlr^TB=f7oKct6Mj4mi!zh zrTyd=fd~?!f_jv&1HZ=Zr~ICChGT6^q(y5|KGic^j+lJgbU@G6P=}NztHC6L<^8fw zv=Qfn0CblXwm)uKTsg`G+qkF%HHAE^;Qe07F2*HX9~Y)YHHxlS=qWu#k)AaQLSBE( z<1`pLsugT#Exnx_tT=wIxQIO&pB~Xgfqn$Fo>D&v-^1RCPr6GY{$AbE6k^$?S?29{ zP|F5i6ccF)&$BO7Be>P-cj4+F3EVTLGX&anM-H=D2^s0bqk%wMAFd{)XG50yq28hLr{$9|)<>pAS)3o9offhNsSr(IKG)07y&DiaThoM2xi zSYn%bvZ_+XQ-pmiJkcIr4DmLbK8{ydL1Od_p_7~DQND(q)XK?dE-NB!vx8UZ=@hGG zkp5;?ml!9WVb3K#Sn=CC>Q0%_Y@Q-{i|o2kZQ9=JtrY1{ngt9pRgq?)`+#hX^bx4^mwysIYZKiBwe z`tm6vItO*!V!C0U(QxllBFW}tHZNtm_)qpgO7;T^s$hBxW#rYQDtNtQ)qP0ag5j(` zf7P@#2jRK<4Tx7SAL}(Dm{8KOq6&+&_}TK*nI?-$@mBSEN+fK8J6UsTni$Xar&_08pu7JV zxyaXLKNxAQ$)#*0fn6lGSFHv8sYu5801bsC8|>aQkMh4u$$AvBTl9IdG^ zdc6KGp=_z9taDtBBUyLa9LVBKEQNDO)Etf2|wzu$wcpts4Qt<=OBnC!nr?F?g8)3y#5H8QZxj zb8gh73@p)8NuG!84sUlHvZZchLz&NolmV~ivnQtBBIa}*np!R5l|Ma8!t3Gej|>=` zyjl5TT-SzKYmCg8pDFI>%FPc*;ukEp&>Fl0Kg@TqI}6i-dvNMubvS7s=j}K(qSTFD zgM}l89AZScLXd#pnfN1idI!;`2mVNi-a$eHy@bZD(~I&&LuMD7xgtEDLbl5Z^& zTiCi=cScaksgWZPxF1{g7)vg*8qB-c{Ib+AClF*W6NWn3x-!auw=6rtzgv>=4;N0` i1e${#Da(%O8RyXG5&JSQQ@>#A3$t&smq`kv? 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 RD@&-8O_9AFJL)#Z{0Gno4B-F( diff --git a/bin/linux/fsnotifier64 b/bin/linux/fsnotifier64 index efba253b08b3a52e22e1bc09f1d93e5bd1f6fca5..1fc4f32ff7effedcfc451430f228bdf99e76d15f 100755 GIT binary patch delta 5194 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 delta 5151 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 CLt9w@ 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); } } From a1e4dc645eb2fd8d0bd4753b459d1916f7a23a19 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 10 Apr 2012 19:21:03 +0200 Subject: [PATCH 9/9] File watcher: fix subst mapping; remove symlink mapping; clean locks; test --- .../openapi/vfs/impl/local/FileWatcher.java | 305 ++++++------ .../openapi/vfs/local/FileWatcherTest.java | 438 ++++++++++++++++++ .../vfs/local/SymlinkHandlingTest.java | 2 +- 3 files changed, 591 insertions(+), 154 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java 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);