e69f64fc07bc070c35c6d4795759fe6ecaefbdf3
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / JavaLineMarkerProvider.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 package com.intellij.codeInsight.daemon.impl;
17
18 import com.intellij.codeHighlighting.Pass;
19 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
20 import com.intellij.codeInsight.daemon.LineMarkerInfo;
21 import com.intellij.codeInsight.daemon.LineMarkerProvider;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.editor.colors.CodeInsightColors;
24 import com.intellij.openapi.editor.colors.EditorColorsManager;
25 import com.intellij.openapi.editor.colors.EditorColorsScheme;
26 import com.intellij.openapi.editor.markup.GutterIconRenderer;
27 import com.intellij.openapi.editor.markup.SeparatorPlacement;
28 import com.intellij.openapi.progress.ProgressManager;
29 import com.intellij.openapi.project.DumbAware;
30 import com.intellij.openapi.project.DumbService;
31 import com.intellij.openapi.project.IndexNotReadyException;
32 import com.intellij.openapi.util.IconLoader;
33 import com.intellij.openapi.util.Pair;
34 import com.intellij.psi.*;
35 import com.intellij.psi.search.searches.AllOverridingMethodsSearch;
36 import com.intellij.psi.search.searches.ClassInheritorsSearch;
37 import com.intellij.psi.search.searches.SuperMethodsSearch;
38 import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
39 import com.intellij.psi.util.PsiUtil;
40 import com.intellij.util.NullableFunction;
41 import com.intellij.util.Processor;
42 import com.intellij.util.containers.HashSet;
43 import gnu.trove.THashSet;
44 import org.jetbrains.annotations.Nullable;
45
46 import javax.swing.*;
47 import java.util.Collection;
48 import java.util.List;
49 import java.util.Set;
50
51 public class JavaLineMarkerProvider implements LineMarkerProvider, DumbAware {
52   private static final Icon OVERRIDING_METHOD_ICON = IconLoader.getIcon("/gutter/overridingMethod.png");
53   private static final Icon IMPLEMENTING_METHOD_ICON = IconLoader.getIcon("/gutter/implementingMethod.png");
54
55   private static final Icon OVERRIDEN_METHOD_MARKER_RENDERER = IconLoader.getIcon("/gutter/overridenMethod.png");
56   private static final Icon IMPLEMENTED_METHOD_MARKER_RENDERER = IconLoader.getIcon("/gutter/implementedMethod.png");
57   private static final Icon IMPLEMENTED_INTERFACE_MARKER_RENDERER = IMPLEMENTED_METHOD_MARKER_RENDERER;
58   private static final Icon SUBCLASSED_CLASS_MARKER_RENDERER = OVERRIDEN_METHOD_MARKER_RENDERER;
59
60   private final DaemonCodeAnalyzerSettings myDaemonSettings;
61   private final EditorColorsManager myColorsManager;
62
63   public JavaLineMarkerProvider(DaemonCodeAnalyzerSettings daemonSettings, EditorColorsManager colorsManager) {
64     myDaemonSettings = daemonSettings;
65     myColorsManager = colorsManager;
66   }
67
68   @Nullable
69   public LineMarkerInfo getLineMarkerInfo(final PsiElement element) {
70     if (element instanceof PsiIdentifier && element.getParent() instanceof PsiMethod) {
71       PsiMethod method = (PsiMethod)element.getParent();
72       MethodSignatureBackedByPsiMethod superSignature = null;
73       try {
74         superSignature = SuperMethodsSearch.search(method, null, true, false).findFirst();
75       }
76       catch (IndexNotReadyException e) {
77         //some searchers (EJB) require indices. What shall we do?
78       }
79       if (superSignature != null) {
80         boolean overrides =
81           method.hasModifierProperty(PsiModifier.ABSTRACT) == superSignature.getMethod().hasModifierProperty(PsiModifier.ABSTRACT);
82
83         final Icon icon = overrides ? OVERRIDING_METHOD_ICON : IMPLEMENTING_METHOD_ICON;
84         final MarkerType type = MarkerType.OVERRIDING_METHOD;
85         return new LineMarkerInfo<PsiElement>(element, element.getTextRange(), icon, Pass.UPDATE_ALL, type.getTooltip(), type.getNavigationHandler(), GutterIconRenderer.Alignment.LEFT);
86       }
87     }
88
89     if (myDaemonSettings.SHOW_METHOD_SEPARATORS && element.getFirstChild() == null) {
90       PsiElement element1 = element;
91       boolean isMember = false;
92       while (element1 != null && !(element1 instanceof PsiFile) && element1.getPrevSibling() == null) {
93         element1 = element1.getParent();
94         if (element1 instanceof PsiMember) {
95           isMember = true;
96           break;
97         }
98       }
99       if (isMember && !(element1 instanceof PsiAnonymousClass || element1.getParent() instanceof PsiAnonymousClass)) {
100         boolean drawSeparator = false;
101         int category = getCategory(element1);
102         for (PsiElement child = element1.getPrevSibling(); child != null; child = child.getPrevSibling()) {
103           int category1 = getCategory(child);
104           if (category1 == 0) continue;
105           drawSeparator = category != 1 || category1 != 1;
106           break;
107         }
108
109         if (drawSeparator) {
110           LineMarkerInfo info = new LineMarkerInfo<PsiElement>(element, element.getTextRange(), null, Pass.UPDATE_ALL, NullableFunction.NULL, null, GutterIconRenderer.Alignment.RIGHT);
111           EditorColorsScheme scheme = myColorsManager.getGlobalScheme();
112           info.separatorColor = scheme.getColor(CodeInsightColors.METHOD_SEPARATORS_COLOR);
113           info.separatorPlacement = SeparatorPlacement.TOP;
114           return info;
115         }
116       }
117     }
118
119     return null;
120   }
121
122   private static int getCategory(PsiElement element) {
123     if (element instanceof PsiField) return 1;
124     if (element instanceof PsiClass || element instanceof PsiClassInitializer) return 2;
125     if (element instanceof PsiMethod) {
126       if (((PsiMethod)element).hasModifierProperty(PsiModifier.ABSTRACT)) {
127         return 1;
128       }
129       String text = element.getText();
130       if (text.indexOf('\n') < 0 && text.indexOf('\r') < 0) {
131         return 1;
132       }
133       else {
134         return 2;
135       }
136     }
137     return 0;
138   }
139
140   public void collectSlowLineMarkers(final List<PsiElement> elements, final Collection<LineMarkerInfo> result) {
141     ApplicationManager.getApplication().assertReadAccessAllowed();
142
143     if (elements.isEmpty() || DumbService.getInstance(elements.get(0).getProject()).isDumb()) {
144       return;
145     }
146
147     Set<PsiMethod> methods = new HashSet<PsiMethod>();
148     for (PsiElement element : elements) {
149       ProgressManager.checkCanceled();
150       if (element instanceof PsiMethod) {
151         final PsiMethod method = (PsiMethod)element;
152         if (PsiUtil.canBeOverriden(method)) {
153           methods.add(method);
154         }
155       }
156       else if (element instanceof PsiClass && !(element instanceof PsiTypeParameter)) {
157         collectInheritingClasses((PsiClass)element, result);
158       }
159     }
160     if (!methods.isEmpty()) {
161       collectOverridingMethods(methods, result);
162     }
163   }
164
165   private static void collectInheritingClasses(PsiClass aClass, Collection<LineMarkerInfo> result) {
166     if (!aClass.hasModifierProperty(PsiModifier.FINAL)) {
167       if ("java.lang.Object".equals(aClass.getQualifiedName())) return; // It's useless to have overriden markers for object.
168
169       final PsiClass inheritor = ClassInheritorsSearch.search(aClass, false).findFirst();
170       if (inheritor != null) {
171         final Icon icon = aClass.isInterface() ? IMPLEMENTED_INTERFACE_MARKER_RENDERER : SUBCLASSED_CLASS_MARKER_RENDERER;
172         final MarkerType type = MarkerType.SUBCLASSED_CLASS;
173         PsiElement range = aClass.getNameIdentifier();
174         if (range == null) range = aClass;
175         LineMarkerInfo info = new LineMarkerInfo<PsiElement>(range, range.getTextRange(), icon, Pass.UPDATE_OVERRIDEN_MARKERS, type.getTooltip(), type.getNavigationHandler(),
176                                                            GutterIconRenderer.Alignment.RIGHT);
177         result.add(info);
178       }
179     }
180   }
181
182   private static void collectOverridingMethods(final Set<PsiMethod> methods, Collection<LineMarkerInfo> result) {
183     final Set<PsiMethod> overridden = new HashSet<PsiMethod>();
184     Set<PsiClass> classes = new THashSet<PsiClass>();
185     for (PsiMethod method : methods) {
186       ProgressManager.checkCanceled();
187       final PsiClass parentClass = method.getContainingClass();
188       if (!"java.lang.Object".equals(parentClass.getQualifiedName())) {
189         classes.add(parentClass);
190       }
191     }
192
193     for (final PsiClass aClass : classes) {
194       AllOverridingMethodsSearch.search(aClass).forEach(new Processor<Pair<PsiMethod, PsiMethod>>() {
195         public boolean process(final Pair<PsiMethod, PsiMethod> pair) {
196           final PsiMethod superMethod = pair.getFirst();
197           if (superMethod.isPhysical() && pair.getSecond().isPhysical() //groovy, scala
198               && methods.remove(superMethod)) {
199             overridden.add(superMethod);
200           }
201           return !methods.isEmpty();
202         }
203       });
204     }
205
206     for (PsiMethod method : overridden) {
207       boolean overrides = !method.hasModifierProperty(PsiModifier.ABSTRACT);
208
209       final Icon icon = overrides ? OVERRIDEN_METHOD_MARKER_RENDERER : IMPLEMENTED_METHOD_MARKER_RENDERER;
210       final MarkerType type = MarkerType.OVERRIDEN_METHOD;
211       PsiElement range = method.getNameIdentifier();
212       if (range == null) range = method;
213       LineMarkerInfo info = new LineMarkerInfo<PsiElement>(range, range.getTextRange(), icon, Pass.UPDATE_OVERRIDEN_MARKERS, type.getTooltip(), type.getNavigationHandler(),
214                                                           GutterIconRenderer.Alignment.RIGHT);
215       result.add(info);
216     }
217   }
218 }