Merge remote-tracking branch 'origin/master' into develar/is
[idea/community.git] / platform / analysis-impl / src / com / intellij / codeInsight / daemon / impl / SeverityRegistrar.java
1 /*
2  * Copyright 2000-2016 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.HighlightDisplayLevel;
20 import com.intellij.lang.annotation.HighlightSeverity;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.markup.TextAttributes;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.JDOMExternalizableStringList;
26 import com.intellij.profile.codeInspection.InspectionProfileManager;
27 import com.intellij.util.IncorrectOperationException;
28 import com.intellij.util.concurrency.AtomicFieldUpdater;
29 import com.intellij.util.containers.ContainerUtil;
30 import com.intellij.util.messages.MessageBus;
31 import com.intellij.util.messages.Topic;
32 import gnu.trove.TIntFunction;
33 import gnu.trove.TObjectIntHashMap;
34 import gnu.trove.TObjectIntProcedure;
35 import org.jdom.Element;
36 import org.jetbrains.annotations.NonNls;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import javax.swing.*;
41 import java.awt.*;
42 import java.util.*;
43 import java.util.List;
44
45 /**
46  * User: anna
47  * Date: 24-Feb-2006
48  */
49 public class SeverityRegistrar implements Comparator<HighlightSeverity> {
50   private final static Logger LOG = Logger.getInstance(SeverityRegistrar.class);
51
52   @NonNls private static final String INFO_TAG = "info";
53   @NonNls private static final String COLOR_ATTRIBUTE = "color";
54   private final Map<String, SeverityBasedTextAttributes> myMap = ContainerUtil.newConcurrentMap();
55   private final Map<String, Color> myRendererColors = ContainerUtil.newConcurrentMap();
56   public static final Topic<Runnable> SEVERITIES_CHANGED_TOPIC =
57     Topic.create("SEVERITIES_CHANGED_TOPIC", Runnable.class, Topic.BroadcastDirection.TO_PARENT);
58   @NotNull private final MessageBus myMessageBus;
59
60   private volatile OrderMap myOrderMap;
61   private JDOMExternalizableStringList myReadOrder;
62
63   private static final Map<String, HighlightInfoType> STANDARD_SEVERITIES = ContainerUtil.newConcurrentMap();
64
65   public SeverityRegistrar(@NotNull MessageBus messageBus) {
66     myMessageBus = messageBus;
67   }
68
69   static {
70     registerStandard(HighlightInfoType.ERROR, HighlightSeverity.ERROR);
71     registerStandard(HighlightInfoType.WARNING, HighlightSeverity.WARNING);
72     registerStandard(HighlightInfoType.INFO, HighlightSeverity.INFO);
73     registerStandard(HighlightInfoType.WEAK_WARNING, HighlightSeverity.WEAK_WARNING);
74     registerStandard(HighlightInfoType.GENERIC_WARNINGS_OR_ERRORS_FROM_SERVER, HighlightSeverity.GENERIC_SERVER_ERROR_OR_WARNING);
75     STANDARD_SEVERITIES.put(HighlightDisplayLevel.DO_NOT_SHOW.getName(), HighlightInfoType.INFORMATION);
76   }
77
78   public static void registerStandard(@NotNull HighlightInfoType highlightInfoType, @NotNull HighlightSeverity highlightSeverity) {
79     STANDARD_SEVERITIES.put(highlightSeverity.getName(), highlightInfoType);
80   }
81
82   @NotNull
83   public static SeverityRegistrar getSeverityRegistrar(@Nullable Project project) {
84     return project == null
85            ? InspectionProfileManager.getInstance().getSeverityRegistrar()
86            : InspectionProfileManager.getInstance(project).getSeverityRegistrar();
87   }
88
89   public void registerSeverity(@NotNull SeverityBasedTextAttributes info, Color renderColor) {
90     final HighlightSeverity severity = info.getType().getSeverity(null);
91     myMap.put(severity.getName(), info);
92     if (renderColor != null) {
93       myRendererColors.put(severity.getName(), renderColor);
94     }
95     myOrderMap = null;
96     HighlightDisplayLevel.registerSeverity(severity, getHighlightInfoTypeBySeverity(severity).getAttributesKey(), null);
97     severitiesChanged();
98   }
99
100   private void severitiesChanged() {
101     myMessageBus.syncPublisher(SEVERITIES_CHANGED_TOPIC).run();
102   }
103
104   public SeverityBasedTextAttributes unregisterSeverity(@NotNull HighlightSeverity severity){
105     return myMap.remove(severity.getName());
106   }
107
108   @NotNull
109   public HighlightInfoType.HighlightInfoTypeImpl getHighlightInfoTypeBySeverity(@NotNull HighlightSeverity severity) {
110     HighlightInfoType infoType = STANDARD_SEVERITIES.get(severity.getName());
111     if (infoType != null) {
112       return (HighlightInfoType.HighlightInfoTypeImpl)infoType;
113     }
114
115     if (severity == HighlightSeverity.INFORMATION){
116       return (HighlightInfoType.HighlightInfoTypeImpl)HighlightInfoType.INFORMATION;
117     }
118
119     final SeverityBasedTextAttributes type = getAttributesBySeverity(severity);
120     return (HighlightInfoType.HighlightInfoTypeImpl)(type == null ? HighlightInfoType.WARNING : type.getType());
121   }
122
123   private SeverityBasedTextAttributes getAttributesBySeverity(@NotNull HighlightSeverity severity) {
124     return myMap.get(severity.getName());
125   }
126
127   @Nullable
128   public TextAttributes getTextAttributesBySeverity(@NotNull HighlightSeverity severity) {
129     final SeverityBasedTextAttributes infoType = getAttributesBySeverity(severity);
130     if (infoType != null) {
131       return infoType.getAttributes();
132     }
133     return null;
134   }
135
136
137   public void readExternal(Element element) {
138     myMap.clear();
139     myRendererColors.clear();
140     for (Element infoElement : element.getChildren(INFO_TAG)) {
141       SeverityBasedTextAttributes highlightInfo = new SeverityBasedTextAttributes(infoElement);
142       String colorStr = infoElement.getAttributeValue(COLOR_ATTRIBUTE);
143       @SuppressWarnings("UseJBColor")
144       Color color = colorStr == null ? null : new Color(Integer.parseInt(colorStr, 16));
145       registerSeverity(highlightInfo, color);
146     }
147     myReadOrder = new JDOMExternalizableStringList();
148     myReadOrder.readExternal(element);
149     List<HighlightSeverity> read = new ArrayList<HighlightSeverity>(myReadOrder.size());
150     final List<HighlightSeverity> knownSeverities = getDefaultOrder();
151     for (String name : myReadOrder) {
152       HighlightSeverity severity = getSeverity(name);
153       if (severity == null || !knownSeverities.contains(severity)) continue;
154       read.add(severity);
155     }
156     OrderMap orderMap = fromList(read);
157     if (orderMap.isEmpty()) {
158       orderMap = fromList(knownSeverities);
159     }
160     else {
161       //enforce include all known
162       List<HighlightSeverity> list = getOrderAsList(orderMap);
163       for (int i = 0; i < knownSeverities.size(); i++) {
164         HighlightSeverity stdSeverity = knownSeverities.get(i);
165         if (!list.contains(stdSeverity)) {
166           for (int oIdx = 0; oIdx < list.size(); oIdx++) {
167             HighlightSeverity orderSeverity = list.get(oIdx);
168             HighlightInfoType type = STANDARD_SEVERITIES.get(orderSeverity.getName());
169             if (type != null && knownSeverities.indexOf(type.getSeverity(null)) > i) {
170               list.add(oIdx, stdSeverity);
171               myReadOrder = null;
172               break;
173             }
174           }
175         }
176       }
177       orderMap = fromList(list);
178     }
179     myOrderMap = orderMap;
180     severitiesChanged();
181   }
182
183   public void writeExternal(Element element) {
184     List<HighlightSeverity> list = getOrderAsList(getOrderMap());
185     for (HighlightSeverity severity : list) {
186       Element info = new Element(INFO_TAG);
187       String severityName = severity.getName();
188       final SeverityBasedTextAttributes infoType = getAttributesBySeverity(severity);
189       if (infoType != null) {
190         infoType.writeExternal(info);
191         final Color color = myRendererColors.get(severityName);
192         if (color != null) {
193           info.setAttribute(COLOR_ATTRIBUTE, Integer.toString(color.getRGB() & 0xFFFFFF, 16));
194         }
195         element.addContent(info);
196       }
197     }
198
199     if (myReadOrder != null && !myReadOrder.isEmpty()) {
200       myReadOrder.writeExternal(element);
201     }
202     else if (!getDefaultOrder().equals(list)) {
203       final JDOMExternalizableStringList ext = new JDOMExternalizableStringList(Collections.nCopies(getOrderMap().size(), ""));
204       getOrderMap().forEachEntry(new TObjectIntProcedure<HighlightSeverity>() {
205         @Override
206         public boolean execute(HighlightSeverity orderSeverity, int oIdx) {
207           ext.set(oIdx, orderSeverity.getName());
208           return true;
209         }
210       });
211       ext.writeExternal(element);
212     }
213   }
214
215   @NotNull
216   private static List<HighlightSeverity> getOrderAsList(@NotNull final OrderMap orderMap) {
217     List<HighlightSeverity> list = new ArrayList<HighlightSeverity>();
218     for (Object o : orderMap.keys()) {
219       list.add((HighlightSeverity)o);
220     }
221     Collections.sort(list, (o1, o2) -> compare(o1, o2, orderMap));
222     return list;
223   }
224
225   public int getSeveritiesCount() {
226     return createCurrentSeverityNames().size();
227   }
228
229   public HighlightSeverity getSeverityByIndex(final int i) {
230     final HighlightSeverity[] found = new HighlightSeverity[1];
231     getOrderMap().forEachEntry(new TObjectIntProcedure<HighlightSeverity>() {
232       @Override
233       public boolean execute(HighlightSeverity severity, int order) {
234         if (order == i) {
235           found[0] = severity;
236           return false;
237         }
238         return true;
239       }
240     });
241     return found[0];
242   }
243
244   public int getSeverityMaxIndex() {
245     int[] values = getOrderMap().getValues();
246     int max = values[0];
247     for(int i = 1; i < values.length; ++i) if (values[i] > max) max = values[i];
248
249     return max;
250   }
251
252   @Nullable
253   public HighlightSeverity getSeverity(@NotNull String name) {
254     final HighlightInfoType type = STANDARD_SEVERITIES.get(name);
255     if (type != null) return type.getSeverity(null);
256     final SeverityBasedTextAttributes attributes = myMap.get(name);
257     if (attributes != null) return attributes.getSeverity();
258     return null;
259   }
260
261   @NotNull
262   private List<String> createCurrentSeverityNames() {
263     List<String> list = new ArrayList<String>();
264     list.addAll(STANDARD_SEVERITIES.keySet());
265     list.addAll(myMap.keySet());
266     ContainerUtil.sort(list);
267     return list;
268   }
269
270   public Icon getRendererIconByIndex(int i) {
271     final HighlightSeverity severity = getSeverityByIndex(i);
272     HighlightDisplayLevel level = HighlightDisplayLevel.find(severity);
273     if (level != null) {
274       return level.getIcon();
275     }
276
277     return HighlightDisplayLevel.createIconByMask(myRendererColors.get(severity.getName()));
278   }
279
280   public boolean isSeverityValid(@NotNull String severityName) {
281     return createCurrentSeverityNames().contains(severityName);
282   }
283
284   @Override
285   public int compare(@NotNull HighlightSeverity s1, @NotNull HighlightSeverity s2) {
286     return compare(s1, s2, getOrderMap());
287   }
288
289   private static int compare(@NotNull HighlightSeverity s1, @NotNull HighlightSeverity s2, @NotNull OrderMap orderMap) {
290     int o1 = orderMap.getOrder(s1, -1);
291     int o2 = orderMap.getOrder(s2, -1);
292     return o1 - o2;
293   }
294
295
296   @NotNull
297   private OrderMap getOrderMap() {
298     OrderMap orderMap;
299     OrderMap defaultOrder = null;
300     while ((orderMap = myOrderMap) == null) {
301       if (defaultOrder == null) {
302         defaultOrder = fromList(getDefaultOrder());
303       }
304       boolean replaced = ORDER_MAP_UPDATER.compareAndSet(this, null, defaultOrder);
305       if (replaced) {
306         orderMap = defaultOrder;
307         break;
308       }
309     }
310     return orderMap;
311   }
312
313   private static final AtomicFieldUpdater<SeverityRegistrar, OrderMap> ORDER_MAP_UPDATER = AtomicFieldUpdater.forFieldOfType(SeverityRegistrar.class, OrderMap.class);
314
315   @NotNull
316   private static OrderMap fromList(@NotNull List<HighlightSeverity> orderList) {
317     if (orderList.size() != new HashSet<HighlightSeverity>(orderList).size()) {
318       LOG.error("Severities order list MUST contain only unique severities: " + orderList);
319     }
320     TObjectIntHashMap<HighlightSeverity> map = new TObjectIntHashMap<HighlightSeverity>();
321     for (int i = 0; i < orderList.size(); i++) {
322       HighlightSeverity severity = orderList.get(i);
323       map.put(severity, i);
324     }
325     return new OrderMap(map);
326   }
327
328   @NotNull
329   private List<HighlightSeverity> getDefaultOrder() {
330     Collection<SeverityBasedTextAttributes> values = myMap.values();
331     List<HighlightSeverity> order = new ArrayList<HighlightSeverity>(STANDARD_SEVERITIES.size() + values.size());
332     for (HighlightInfoType type : STANDARD_SEVERITIES.values()) {
333       order.add(type.getSeverity(null));
334     }
335     for (SeverityBasedTextAttributes attributes : values) {
336       order.add(attributes.getSeverity());
337     }
338     ContainerUtil.sort(order);
339     return order;
340   }
341
342   public void setOrder(@NotNull List<HighlightSeverity> orderList) {
343     myOrderMap = fromList(orderList);
344     myReadOrder = null;
345     severitiesChanged();
346   }
347
348   public int getSeverityIdx(@NotNull HighlightSeverity severity) {
349     return getOrderMap().getOrder(severity, -1);
350   }
351
352   public boolean isDefaultSeverity(@NotNull HighlightSeverity severity) {
353     return STANDARD_SEVERITIES.containsKey(severity.myName);
354   }
355
356   public static boolean isGotoBySeverityEnabled(@NotNull HighlightSeverity minSeverity) {
357     for (SeveritiesProvider provider : Extensions.getExtensions(SeveritiesProvider.EP_NAME)) {
358       if (provider.isGotoBySeverityEnabled(minSeverity)) return true;
359     }
360     return minSeverity != HighlightSeverity.INFORMATION;
361   }
362
363   private static class OrderMap extends TObjectIntHashMap<HighlightSeverity> {
364     private OrderMap(@NotNull TObjectIntHashMap<HighlightSeverity> map) {
365       super(map.size());
366       map.forEachEntry(new TObjectIntProcedure<HighlightSeverity>() {
367         @Override
368         public boolean execute(HighlightSeverity key, int value) {
369           OrderMap.super.put(key, value);
370           return true;
371         }
372       });
373       trimToSize();
374     }
375
376     private int getOrder(@NotNull HighlightSeverity severity, int defaultOrder) {
377       int index = index(severity);
378       return index < 0 ? defaultOrder : _values[index];
379     }
380
381
382     @Override
383     public void clear() {
384       throw new IncorrectOperationException("readonly");
385     }
386
387     @Override
388     protected void removeAt(int index) {
389       throw new IncorrectOperationException("readonly");
390     }
391
392     @Override
393     public void transformValues(TIntFunction function) {
394       throw new IncorrectOperationException("readonly");
395     }
396
397     @Override
398     public boolean adjustValue(HighlightSeverity key, int amount) {
399       throw new IncorrectOperationException("readonly");
400     }
401
402     @Override
403     public int put(HighlightSeverity key, int value) {
404       throw new IncorrectOperationException("readonly");
405     }
406
407     @Override
408     public int remove(HighlightSeverity key) {
409       throw new IncorrectOperationException("readonly");
410     }
411   }
412
413   public static class SeverityBasedTextAttributes {
414     private final TextAttributes myAttributes;
415     private final HighlightInfoType.HighlightInfoTypeImpl myType;
416
417     //read external
418     public SeverityBasedTextAttributes(@NotNull Element element)  {
419       this(new TextAttributes(element), new HighlightInfoType.HighlightInfoTypeImpl(element));
420     }
421
422     public SeverityBasedTextAttributes(@NotNull TextAttributes attributes, @NotNull HighlightInfoType.HighlightInfoTypeImpl type) {
423       myAttributes = attributes;
424       myType = type;
425     }
426
427     @NotNull
428     public TextAttributes getAttributes() {
429       return myAttributes;
430     }
431
432     @NotNull
433     public HighlightInfoType.HighlightInfoTypeImpl getType() {
434       return myType;
435     }
436
437     private void writeExternal(@NotNull Element element) {
438       myAttributes.writeExternal(element);
439       myType.writeExternal(element);
440     }
441
442     @NotNull
443     public HighlightSeverity getSeverity() {
444       return myType.getSeverity(null);
445     }
446
447     @Override
448     public boolean equals(final Object o) {
449       if (this == o) return true;
450       if (o == null || getClass() != o.getClass()) return false;
451
452       final SeverityBasedTextAttributes that = (SeverityBasedTextAttributes)o;
453
454       if (!myAttributes.equals(that.myAttributes)) return false;
455       if (!myType.equals(that.myType)) return false;
456
457       return true;
458     }
459
460     @Override
461     public int hashCode() {
462       int result = myAttributes.hashCode();
463       result = 31 * result + myType.hashCode();
464       return result;
465     }
466   }
467
468   @NotNull
469   Collection<SeverityBasedTextAttributes> allRegisteredAttributes() {
470     return new ArrayList<SeverityBasedTextAttributes>(myMap.values());
471   }
472   @NotNull
473   Collection<HighlightInfoType> standardSeverities() {
474     return STANDARD_SEVERITIES.values();
475   }
476 }