cleanup
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / daemon / impl / TextEditorHighlightingPassRegistrarImpl.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.daemon.impl;
18
19 import com.intellij.codeHighlighting.DirtyScopeTrackingHighlightingPassFactory;
20 import com.intellij.codeHighlighting.Pass;
21 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
22 import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory;
23 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
24 import com.intellij.openapi.editor.Document;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.psi.PsiCompiledElement;
28 import com.intellij.psi.PsiDocumentManager;
29 import com.intellij.psi.PsiFile;
30 import com.intellij.util.ArrayUtil;
31 import gnu.trove.*;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Set;
38
39 /**
40  * User: anna
41  * Date: 19-Apr-2006
42  */
43 public class TextEditorHighlightingPassRegistrarImpl extends TextEditorHighlightingPassRegistrarEx {
44   private final TIntObjectHashMap<PassConfig> myRegisteredPassFactories = new TIntObjectHashMap<PassConfig>();
45   private final List<DirtyScopeTrackingHighlightingPassFactory> myDirtyScopeTrackingFactories = new ArrayList<DirtyScopeTrackingHighlightingPassFactory>();
46   private int nextAvailableId = Pass.LAST_PASS + 1;
47   private boolean checkedForCycles;
48   private final Project myProject;
49
50   public TextEditorHighlightingPassRegistrarImpl(Project project) {
51     myProject = project;
52   }
53
54   private static class PassConfig {
55     private final TextEditorHighlightingPassFactory passFactory;
56     private final int[] startingPredecessorIds;
57     private final int[] completionPredecessorIds;
58
59     private PassConfig(@NotNull TextEditorHighlightingPassFactory passFactory, @NotNull int[] completionPredecessorIds, @NotNull int[] startingPredecessorIds) {
60       this.completionPredecessorIds = completionPredecessorIds;
61       this.startingPredecessorIds = startingPredecessorIds;
62       this.passFactory = passFactory;
63     }
64   }
65
66   public void registerTextEditorHighlightingPass(TextEditorHighlightingPassFactory factory, int anchor, int anchorPass) {
67     Anchor anc = Anchor.FIRST;
68     switch (anchor) {
69       case FIRST : anc = Anchor.FIRST;
70         break;
71       case LAST : anc = Anchor.LAST;
72         break;
73       case BEFORE : anc = Anchor.BEFORE;
74         break;
75       case AFTER : anc = Anchor.AFTER;
76         break;
77     }
78     registerTextEditorHighlightingPass(factory, anc, anchorPass, true, true);
79   }
80
81   public synchronized int registerTextEditorHighlightingPass(@NotNull TextEditorHighlightingPassFactory factory,
82                                                              @Nullable int[] runAfterCompletionOf,
83                                                              @Nullable int[] runAfterOfStartingOf,
84                                                              boolean runIntentionsPassAfter,
85                                                              int forcedPassId) {
86     assert !checkedForCycles;
87     PassConfig info = new PassConfig(factory,
88                                      runAfterCompletionOf == null || runAfterCompletionOf.length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : runAfterCompletionOf,
89                                      runAfterOfStartingOf == null || runAfterOfStartingOf.length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : runAfterOfStartingOf);
90     int passId = forcedPassId == -1 ? nextAvailableId++ : forcedPassId;
91     PassConfig registered = myRegisteredPassFactories.get(passId);
92     assert registered == null: "Pass id "+passId +" has already been registered in: "+ registered.passFactory;
93     myRegisteredPassFactories.put(passId, info);
94     if (factory instanceof DirtyScopeTrackingHighlightingPassFactory) {
95       myDirtyScopeTrackingFactories.add((DirtyScopeTrackingHighlightingPassFactory) factory);
96     }
97     return passId;
98   }
99
100   @NotNull
101   public List<TextEditorHighlightingPass> instantiatePasses(@NotNull final PsiFile psiFile, @NotNull final Editor editor, @NotNull final int[] passesToIgnore) {
102     synchronized (this) {
103       if (!checkedForCycles) {
104         checkedForCycles = true;
105         checkForCycles();
106       }
107     }
108     PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
109     final Document document = editor.getDocument();
110     PsiFile fileFromDoc = documentManager.getPsiFile(document);
111     if (!(fileFromDoc instanceof PsiCompiledElement)) {
112       assert fileFromDoc == psiFile : "Files are different: " + psiFile + ";" + fileFromDoc;
113       Document documentFromFile = documentManager.getDocument(psiFile);
114       assert documentFromFile == document : "Documents are different: " + document + ";" + documentFromFile;
115     }
116     final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass = new TIntObjectHashMap<TextEditorHighlightingPass>();
117     final TIntArrayList passesRefusedToCreate = new TIntArrayList();
118     myRegisteredPassFactories.forEachKey(new TIntProcedure() {
119       public boolean execute(int passId) {
120         if (ArrayUtil.find(passesToIgnore, passId) != -1) {
121           return true;
122         }
123         PassConfig passConfig = myRegisteredPassFactories.get(passId);
124         TextEditorHighlightingPassFactory factory = passConfig.passFactory;
125         final TextEditorHighlightingPass pass = factory.createHighlightingPass(psiFile, editor);
126
127         if (pass == null) {
128           passesRefusedToCreate.add(passId);
129         }
130         else {
131           TIntArrayList ids = new TIntArrayList(passConfig.completionPredecessorIds.length);
132           for (int id : passConfig.completionPredecessorIds) {
133             if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
134           }
135           pass.setCompletionPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
136           ids = new TIntArrayList(passConfig.startingPredecessorIds.length);
137           for (int id : passConfig.startingPredecessorIds) {
138             if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
139           }
140           pass.setStartingPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
141           pass.setId(passId);
142           id2Pass.put(passId, pass);
143         }
144         return true;
145       }
146     });
147
148     final FileStatusMap statusMap = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject)).getFileStatusMap();
149     passesRefusedToCreate.forEach(new TIntProcedure() {
150       public boolean execute(int passId) {
151         statusMap.markFileUpToDate(document, psiFile, passId);
152         return true;
153       }
154     });
155
156     //sort is mainly for tests which expect passes to be run sequentially and produce correct results
157     return topoSort(id2Pass);
158   }
159
160   private static List<TextEditorHighlightingPass> topoSort(final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass) {
161     final Set<TextEditorHighlightingPass> topPasses = new THashSet<TextEditorHighlightingPass>(id2Pass.size());
162     id2Pass.forEachValue(new TObjectProcedure<TextEditorHighlightingPass>() {
163       public boolean execute(TextEditorHighlightingPass object) {
164         topPasses.add(object);
165         return true;
166       }
167     });
168     id2Pass.forEachValue(new TObjectProcedure<TextEditorHighlightingPass>() {
169       public boolean execute(TextEditorHighlightingPass pass) {
170         for (int id : pass.getCompletionPredecessorIds()) {
171           TextEditorHighlightingPass pred = id2Pass.get(id);
172           if (pred != null) {  //can be null if filtered out by passesToIgnore
173             topPasses.remove(pred);
174           }
175         }
176         for (int id : pass.getStartingPredecessorIds()) {
177           TextEditorHighlightingPass pred = id2Pass.get(id);
178           if (pred != null) {  //can be null if filtered out by passesToIgnore
179             topPasses.remove(pred);
180           }
181         }
182         return true;
183       }
184     });
185     List<TextEditorHighlightingPass> result = new ArrayList<TextEditorHighlightingPass>();
186     for (TextEditorHighlightingPass topPass : topPasses) {
187       layout(topPass, result, id2Pass);
188     }
189     return result;
190   }
191
192   private static void layout(@NotNull final TextEditorHighlightingPass pass,
193                              @NotNull final List<TextEditorHighlightingPass> result,
194                              @NotNull final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass) {
195     if (result.contains(pass)) return;
196     for (int id : pass.getCompletionPredecessorIds()) {
197       TextEditorHighlightingPass pred = id2Pass.get(id);
198       if (pred != null) {
199         layout(pred, result, id2Pass);
200       }
201     }
202     for (int id : pass.getStartingPredecessorIds()) {
203       TextEditorHighlightingPass pred = id2Pass.get(id);
204       if (pred != null) {
205         layout(pred, result, id2Pass);
206       }
207     }
208     result.add(pass);
209   }
210
211   private void checkForCycles() {
212     final TIntObjectHashMap<TIntHashSet> transitivePredecessors = new TIntObjectHashMap<TIntHashSet>();
213
214     myRegisteredPassFactories.forEachEntry(new TIntObjectProcedure<PassConfig>() {
215       public boolean execute(int passId, PassConfig config) {
216         TIntHashSet allPredecessors = new TIntHashSet(config.completionPredecessorIds);
217         allPredecessors.addAll(config.startingPredecessorIds);
218         transitivePredecessors.put(passId, allPredecessors);
219         allPredecessors.forEach(new TIntProcedure() {
220           public boolean execute(int predecessorId) {
221             PassConfig predecessor = myRegisteredPassFactories.get(predecessorId);
222             if (predecessor == null) return  true;
223             TIntHashSet transitives = transitivePredecessors.get(predecessorId);
224             if (transitives == null) {
225               transitives = new TIntHashSet();
226               transitivePredecessors.put(predecessorId, transitives);
227             }
228             transitives.addAll(predecessor.completionPredecessorIds);
229             transitives.addAll(predecessor.startingPredecessorIds);
230             return true;
231           }
232         });
233         return true;
234       }
235     });
236     transitivePredecessors.forEachKey(new TIntProcedure() {
237       public boolean execute(int passId) {
238         if (transitivePredecessors.get(passId).contains(passId)) {
239           throw new IllegalArgumentException("There is a cycle introduced involving pass " + myRegisteredPassFactories.get(passId).passFactory);
240         }
241         return true;
242       }
243     });
244   }
245
246   public List<DirtyScopeTrackingHighlightingPassFactory> getDirtyScopeTrackingFactories() {
247     return myDirtyScopeTrackingFactories;
248   }
249 }