52c4cccdd4eb415298e9811727dc578bc55133a9
[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     PsiFile fileFromDoc = documentManager.getPsiFile(editor.getDocument());
110     if (!(fileFromDoc instanceof PsiCompiledElement)) {
111       assert fileFromDoc == psiFile : "Files are different: " + psiFile + ";" + fileFromDoc;
112       Document documentFromFile = documentManager.getDocument(psiFile);
113       assert documentFromFile == editor.getDocument() : "Documents are different: " + editor.getDocument() + ";" + documentFromFile;
114     }
115     final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass = new TIntObjectHashMap<TextEditorHighlightingPass>();
116     final TIntArrayList passesRefusedToCreate = new TIntArrayList();
117     myRegisteredPassFactories.forEachKey(new TIntProcedure() {
118       public boolean execute(int passId) {
119         if (ArrayUtil.find(passesToIgnore, passId) != -1) {
120           return true;
121         }
122         PassConfig passConfig = myRegisteredPassFactories.get(passId);
123         TextEditorHighlightingPassFactory factory = passConfig.passFactory;
124         final TextEditorHighlightingPass pass = factory.createHighlightingPass(psiFile, editor);
125
126         if (pass == null) {
127           passesRefusedToCreate.add(passId);
128         }
129         else {
130           TIntArrayList ids = new TIntArrayList(passConfig.completionPredecessorIds.length);
131           for (int id : passConfig.completionPredecessorIds) {
132             if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
133           }
134           pass.setCompletionPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
135           ids = new TIntArrayList(passConfig.startingPredecessorIds.length);
136           for (int id : passConfig.startingPredecessorIds) {
137             if (myRegisteredPassFactories.containsKey(id)) ids.add(id);
138           }
139           pass.setStartingPredecessorIds(ids.isEmpty() ? ArrayUtil.EMPTY_INT_ARRAY : ids.toNativeArray());
140           pass.setId(passId);
141           id2Pass.put(passId, pass);
142         }
143         return true;
144       }
145     });
146
147     final FileStatusMap statusMap = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject)).getFileStatusMap();
148     passesRefusedToCreate.forEach(new TIntProcedure() {
149       public boolean execute(int passId) {
150         statusMap.markFileUpToDate(editor.getDocument(), psiFile, passId);
151         return true;
152       }
153     });
154
155     //sort is mainly for tests which expect passes to be run sequentially and produce correct results
156     return topoSort(id2Pass);
157   }
158
159   private static List<TextEditorHighlightingPass> topoSort(final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass) {
160     final Set<TextEditorHighlightingPass> topPasses = new THashSet<TextEditorHighlightingPass>(id2Pass.size());
161     id2Pass.forEachValue(new TObjectProcedure<TextEditorHighlightingPass>() {
162       public boolean execute(TextEditorHighlightingPass object) {
163         topPasses.add(object);
164         return true;
165       }
166     });
167     id2Pass.forEachValue(new TObjectProcedure<TextEditorHighlightingPass>() {
168       public boolean execute(TextEditorHighlightingPass pass) {
169         for (int id : pass.getCompletionPredecessorIds()) {
170           TextEditorHighlightingPass pred = id2Pass.get(id);
171           if (pred != null) {  //can be null if filtered out by passesToIgnore
172             topPasses.remove(pred);
173           }
174         }
175         for (int id : pass.getStartingPredecessorIds()) {
176           TextEditorHighlightingPass pred = id2Pass.get(id);
177           if (pred != null) {  //can be null if filtered out by passesToIgnore
178             topPasses.remove(pred);
179           }
180         }
181         return true;
182       }
183     });
184     List<TextEditorHighlightingPass> result = new ArrayList<TextEditorHighlightingPass>();
185     for (TextEditorHighlightingPass topPass : topPasses) {
186       layout(topPass, result, id2Pass);
187     }
188     return result;
189   }
190
191   private static void layout(@NotNull final TextEditorHighlightingPass pass,
192                              @NotNull final List<TextEditorHighlightingPass> result,
193                              @NotNull final TIntObjectHashMap<TextEditorHighlightingPass> id2Pass) {
194     if (result.contains(pass)) return;
195     for (int id : pass.getCompletionPredecessorIds()) {
196       TextEditorHighlightingPass pred = id2Pass.get(id);
197       if (pred != null) {
198         layout(pred, result, id2Pass);
199       }
200     }
201     for (int id : pass.getStartingPredecessorIds()) {
202       TextEditorHighlightingPass pred = id2Pass.get(id);
203       if (pred != null) {
204         layout(pred, result, id2Pass);
205       }
206     }
207     result.add(pass);
208   }
209
210   private void checkForCycles() {
211     final TIntObjectHashMap<TIntHashSet> transitivePredecessors = new TIntObjectHashMap<TIntHashSet>();
212
213     myRegisteredPassFactories.forEachEntry(new TIntObjectProcedure<PassConfig>() {
214       public boolean execute(int passId, PassConfig config) {
215         TIntHashSet allPredecessors = new TIntHashSet(config.completionPredecessorIds);
216         allPredecessors.addAll(config.startingPredecessorIds);
217         transitivePredecessors.put(passId, allPredecessors);
218         allPredecessors.forEach(new TIntProcedure() {
219           public boolean execute(int predecessorId) {
220             PassConfig predecessor = myRegisteredPassFactories.get(predecessorId);
221             if (predecessor == null) return  true;
222             TIntHashSet transitives = transitivePredecessors.get(predecessorId);
223             if (transitives == null) {
224               transitives = new TIntHashSet();
225               transitivePredecessors.put(predecessorId, transitives);
226             }
227             transitives.addAll(predecessor.completionPredecessorIds);
228             transitives.addAll(predecessor.startingPredecessorIds);
229             return true;
230           }
231         });
232         return true;
233       }
234     });
235     transitivePredecessors.forEachKey(new TIntProcedure() {
236       public boolean execute(int passId) {
237         if (transitivePredecessors.get(passId).contains(passId)) {
238           throw new IllegalArgumentException("There is a cycle introduced involving pass " + myRegisteredPassFactories.get(passId).passFactory);
239         }
240         return true;
241       }
242     });
243   }
244
245   public List<DirtyScopeTrackingHighlightingPassFactory> getDirtyScopeTrackingFactories() {
246     return myDirtyScopeTrackingFactories;
247   }
248 }