reduce number of DelegateDisposables
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / daemon / impl / FileStatusMap.java
blobb524011472eee5ba60ce7f33eba9fc4dfd4f82bb
2 /*
3 * Copyright 2000-2009 JetBrains s.r.o.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package com.intellij.codeInsight.daemon.impl;
20 import com.intellij.codeHighlighting.DirtyScopeTrackingHighlightingPassFactory;
21 import com.intellij.codeHighlighting.Pass;
22 import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar;
23 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.editor.RangeMarker;
29 import com.intellij.openapi.editor.ex.DocumentEx;
30 import com.intellij.openapi.editor.ex.RangeMarkerEx;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.TextRange;
33 import com.intellij.psi.PsiDocumentManager;
34 import com.intellij.psi.PsiFile;
35 import com.intellij.util.containers.WeakHashMap;
36 import gnu.trove.TIntObjectHashMap;
37 import gnu.trove.TIntObjectProcedure;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.jetbrains.annotations.TestOnly;
42 import java.util.Map;
43 import java.util.concurrent.atomic.AtomicInteger;
45 public class FileStatusMap implements Disposable {
46 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.FileStatusMap");
47 private final Project myProject;
48 private final Map<Document,FileStatus> myDocumentToStatusMap = new WeakHashMap<Document, FileStatus>(); // all dirty if absent
49 private final AtomicInteger myClearModificationCount = new AtomicInteger();
51 public FileStatusMap(@NotNull Project project) {
52 myProject = project;
55 public void dispose() {
56 markAllFilesDirty();
59 @Nullable
60 public static TextRange getDirtyTextRange(@NotNull Editor editor, int passId) {
61 Document document = editor.getDocument();
63 FileStatusMap me = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(editor.getProject())).getFileStatusMap();
64 TextRange dirtyScope = me.getFileDirtyScope(document, passId);
65 if (dirtyScope == null) return null;
66 TextRange documentRange = TextRange.from(0, document.getTextLength());
67 return documentRange.intersection(dirtyScope);
70 public void setErrorFoundFlag(@NotNull Document document, boolean errorFound) {
71 //GHP has found error. Flag is used by ExternalToolPass to decide whether to run or not
72 synchronized(myDocumentToStatusMap) {
73 FileStatus status = myDocumentToStatusMap.get(document);
74 if (status == null){
75 if (!errorFound) return;
76 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
77 assert file != null : document;
78 status = new FileStatus(file,document);
79 myDocumentToStatusMap.put(document, status);
81 status.errorFound = errorFound;
85 public boolean wasErrorFound(@NotNull Document document) {
86 synchronized(myDocumentToStatusMap) {
87 FileStatus status = myDocumentToStatusMap.get(document);
88 return status != null && status.errorFound;
92 private static class FileStatus {
93 public boolean defensivelyMarked; // file marked dirty without knowledge of specific dirty region. Subsequent markScopeDirty can refine dirty scope, not extend it
94 private boolean wolfPassFinfished;
95 private final TIntObjectHashMap<RangeMarker> dirtyScopes = new TIntObjectHashMap<RangeMarker>();
96 private boolean errorFound;
98 private FileStatus(@NotNull PsiFile file, @NotNull Document document) {
99 markWholeFile(file, document, file.getProject());
102 private void markWholeFile(PsiFile file, Document document, Project project) {
103 dirtyScopes.put(Pass.UPDATE_ALL, createWholeFileMarker(file, document));
104 dirtyScopes.put(Pass.EXTERNAL_TOOLS, createWholeFileMarker(file, document));
105 dirtyScopes.put(Pass.LOCAL_INSPECTIONS, createWholeFileMarker(file, document));
106 TextEditorHighlightingPassRegistrarImpl registrar = (TextEditorHighlightingPassRegistrarImpl) TextEditorHighlightingPassRegistrar.getInstance(project);
107 for(DirtyScopeTrackingHighlightingPassFactory factory: registrar.getDirtyScopeTrackingFactories()) {
108 dirtyScopes.put(factory.getPassId(), createWholeFileMarker(file, document));
112 private static RangeMarker createWholeFileMarker(PsiFile file, Document document) {
113 int length = file == null ? -1 : Math.min(file.getTextLength(), document.getTextLength());
114 return length == -1 ? null : document.createRangeMarker(0, length);
117 public boolean allDirtyScopesAreNull() {
118 for (Object o : dirtyScopes.getValues()) {
119 if (o != null) return false;
121 return true;
124 public void combineScopesWith(final TextRange scope, final int fileLength, final Document document) {
125 dirtyScopes.forEachEntry(new TIntObjectProcedure<RangeMarker>() {
126 public boolean execute(int id, RangeMarker oldScope) {
127 RangeMarker newScope = combineScopes(oldScope, scope, fileLength, document);
128 if (newScope != oldScope) {
129 dirtyScopes.put(id, newScope);
131 return true;
136 @Override
137 public String toString() {
138 final StringBuilder s = new StringBuilder();
139 s.append("defensivelyMarked = " + defensivelyMarked);
140 s.append("; wolfPassFinfished = " + wolfPassFinfished);
141 s.append("; errorFound = " + errorFound);
142 s.append("; dirtyScopes: (");
143 dirtyScopes.forEachEntry(new TIntObjectProcedure<RangeMarker>() {
144 public boolean execute(int passId, RangeMarker rangeMarker) {
145 s.append(" pass: " + passId + " -> " + rangeMarker + ";");
146 return true;
149 s.append(")");
150 return s.toString();
154 public void markAllFilesDirty() {
155 LOG.debug("********************************* Mark all dirty");
156 synchronized(myDocumentToStatusMap){
157 myDocumentToStatusMap.clear();
159 myClearModificationCount.incrementAndGet();
162 public void markFileUpToDate(@NotNull Document document, @NotNull PsiFile file, int passId) {
163 synchronized(myDocumentToStatusMap){
164 FileStatus status = myDocumentToStatusMap.get(document);
165 if (status == null){
166 status = new FileStatus(file,document);
167 myDocumentToStatusMap.put(document, status);
169 status.defensivelyMarked=false;
170 if (passId == Pass.WOLF) {
171 status.wolfPassFinfished = true;
173 else if (status.dirtyScopes.containsKey(passId)) {
174 RangeMarker marker = status.dirtyScopes.get(passId);
175 if (marker != null) {
176 ((DocumentEx)document).removeRangeMarker((RangeMarkerEx)marker);
177 status.dirtyScopes.put(passId, null);
184 * @param document
185 * @param passId
186 * @return null for processed file, whole file for untouched or entirely dirty file, range(usually code block) for dirty region (optimization)
188 @Nullable
189 public TextRange getFileDirtyScope(@NotNull Document document, int passId) {
190 synchronized(myDocumentToStatusMap){
191 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
192 if (CollectHighlightsUtil.isOutsideSourceRootJavaFile(file)) return null;
193 FileStatus status = myDocumentToStatusMap.get(document);
194 if (status == null){
195 return file == null ? null : file.getTextRange();
197 if (status.defensivelyMarked) {
198 //PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
199 status.markWholeFile(file, document, myProject);
200 status.defensivelyMarked = false;
202 LOG.assertTrue(status.dirtyScopes.containsKey(passId), "Unknown pass " + passId);
203 RangeMarker marker = status.dirtyScopes.get(passId);
204 return marker == null ? null : marker.isValid() ? new TextRange(marker.getStartOffset(), marker.getEndOffset()) : new TextRange(0, document.getTextLength());
208 public void markFileScopeDirty(@NotNull Document document, int passId) {
209 synchronized(myDocumentToStatusMap){
210 FileStatus status = myDocumentToStatusMap.get(document);
211 if (status == null){
212 return;
214 if (passId == Pass.WOLF) {
215 status.wolfPassFinfished = false;
217 else {
218 LOG.assertTrue(status.dirtyScopes.containsKey(passId));
219 RangeMarker marker = status.dirtyScopes.get(passId);
220 if (marker != null) {
221 ((DocumentEx)document).removeRangeMarker((RangeMarkerEx)marker);
223 marker = document.createRangeMarker(0, document.getTextLength());
224 status.dirtyScopes.put(passId, marker);
229 public void markFileScopeDirtyDefensively(@NotNull PsiFile file) {
230 if (LOG.isDebugEnabled()) {
231 LOG.debug("********************************* Mark dirty file defensively: "+file.getName());
233 // mark whole file dirty in case no subsequent PSI events will come, but file requires rehighlighting nevertheless
234 // e.g. in the case of quick typing/backspacing char
235 synchronized(myDocumentToStatusMap){
236 Document document = PsiDocumentManager.getInstance(myProject).getCachedDocument(file);
237 if (document == null) return;
238 FileStatus status = myDocumentToStatusMap.get(document);
239 if (status == null) return; // all dirty already
240 status.defensivelyMarked = true;
244 public void markFileScopeDirty(@NotNull Document document, @NotNull TextRange scope, int fileLength) {
245 if (LOG.isDebugEnabled()) {
246 LOG.debug("********************************* Mark dirty: "+scope);
248 synchronized(myDocumentToStatusMap) {
249 FileStatus status = myDocumentToStatusMap.get(document);
250 if (status == null) return; // all dirty already
251 if (status.defensivelyMarked) {
252 status.defensivelyMarked = false;
254 status.combineScopesWith(scope, fileLength,document);
258 private static RangeMarker combineScopes(RangeMarker old, TextRange scope, int textLength, @NotNull Document document) {
259 if (scope == null) return old;
260 if (old == null) {
261 return document.createRangeMarker(scope);
263 TextRange oldRange = !old.isValid() || scope.getEndOffset() >= textLength ? new TextRange(0, textLength) : new TextRange(old.getStartOffset(), old.getEndOffset());
264 TextRange union = scope.union(oldRange);
265 if (old.isValid() && union.equals(oldRange)) {
266 return old;
268 else {
269 ((DocumentEx)document).removeRangeMarker((RangeMarkerEx)old);
270 return document.createRangeMarker(union);
274 public boolean allDirtyScopesAreNull(@NotNull Document document) {
275 synchronized (myDocumentToStatusMap) {
276 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
277 if (CollectHighlightsUtil.isOutsideSourceRootJavaFile(file)) return true;
279 FileStatus status = myDocumentToStatusMap.get(document);
280 return status != null && !status.defensivelyMarked && status.wolfPassFinfished && status.allDirtyScopesAreNull();
284 @TestOnly
285 public void assertAllDirtyScopesAreNull(@NotNull Document document) {
286 synchronized (myDocumentToStatusMap) {
287 FileStatus status = myDocumentToStatusMap.get(document);
288 assert status != null && !status.defensivelyMarked && status.wolfPassFinfished && status.allDirtyScopesAreNull() : status;