Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / ide / util / gotoByName / DefaultSymbolNavigationContributor.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.ide.util.gotoByName;
3
4 import com.intellij.ide.actions.JavaQualifiedNameProvider;
5 import com.intellij.ide.util.DefaultPsiElementCellRenderer;
6 import com.intellij.navigation.ChooseByNameContributor;
7 import com.intellij.navigation.ChooseByNameContributorEx;
8 import com.intellij.navigation.GotoClassContributor;
9 import com.intellij.navigation.NavigationItem;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.progress.ProgressManager;
12 import com.intellij.openapi.project.Project;
13 import com.intellij.openapi.util.Condition;
14 import com.intellij.openapi.util.Conditions;
15 import com.intellij.openapi.util.registry.Registry;
16 import com.intellij.openapi.util.text.StringUtil;
17 import com.intellij.psi.*;
18 import com.intellij.psi.codeStyle.MinusculeMatcher;
19 import com.intellij.psi.codeStyle.NameUtil;
20 import com.intellij.psi.search.GlobalSearchScope;
21 import com.intellij.psi.search.PsiSearchScopeUtil;
22 import com.intellij.psi.search.PsiShortNamesCache;
23 import com.intellij.psi.util.InheritanceUtil;
24 import com.intellij.psi.util.PsiUtil;
25 import com.intellij.util.ArrayUtil;
26 import com.intellij.util.Processor;
27 import com.intellij.util.indexing.FindSymbolParameters;
28 import com.intellij.util.indexing.IdFilter;
29 import gnu.trove.THashSet;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import java.util.*;
34
35 public class DefaultSymbolNavigationContributor implements ChooseByNameContributorEx, GotoClassContributor {
36   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.gotoByName.DefaultSymbolNavigationContributor");
37
38   @Override
39   @NotNull
40   public String[] getNames(Project project, boolean includeNonProjectItems) {
41     PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
42     Set<String> set = new HashSet<>();
43     Collections.addAll(set, cache.getAllMethodNames());
44     Collections.addAll(set, cache.getAllFieldNames());
45     Collections.addAll(set, cache.getAllClassNames());
46     return ArrayUtil.toStringArray(set);
47   }
48
49   @Override
50   @NotNull
51   public NavigationItem[] getItemsByName(String name, final String pattern, Project project, boolean includeNonProjectItems) {
52     GlobalSearchScope scope = includeNonProjectItems ? GlobalSearchScope.allScope(project) : GlobalSearchScope.projectScope(project);
53     PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
54
55     Condition<PsiMember> qualifiedMatcher = getQualifiedNameMatcher(pattern);
56
57     List<PsiMember> result = new ArrayList<>();
58     for (PsiMethod method : cache.getMethodsByName(name, scope)) {
59       if (!method.isConstructor() && isOpenable(method) && !hasSuperMethod(method, scope, qualifiedMatcher, pattern)) {
60         result.add(method);
61       }
62     }
63     for (PsiField field : cache.getFieldsByName(name, scope)) {
64       if (isOpenable(field)) {
65         result.add(field);
66       }
67     }
68     for (PsiClass aClass : cache.getClassesByName(name, scope)) {
69       if (isOpenable(aClass)) {
70         result.add(aClass);
71       }
72     }
73     PsiMember[] array = result.toArray(PsiMember.EMPTY_ARRAY);
74     Arrays.sort(array, MyComparator.INSTANCE);
75     return array;
76   }
77
78   @Nullable
79   @Override
80   public String getQualifiedName(NavigationItem item) {
81     if (item instanceof PsiClass) {
82       return DefaultClassNavigationContributor.getQualifiedNameForClass((PsiClass)item);
83     }
84     return null;
85   }
86
87   @Nullable
88   @Override
89   public String getQualifiedNameSeparator() {
90     return "$";
91   }
92
93   private static boolean isOpenable(PsiMember member) {
94     final PsiFile file = member.getContainingFile();
95     return file != null && file.getVirtualFile() != null;
96   }
97
98   private static boolean hasSuperMethodCandidates(final PsiMethod method,
99                                                   final GlobalSearchScope scope,
100                                                   final Condition<? super PsiMember> qualifiedMatcher) {
101     if (method.hasModifierProperty(PsiModifier.PRIVATE) || method.hasModifierProperty(PsiModifier.STATIC)) return false;
102
103     final PsiClass containingClass = method.getContainingClass();
104     if (containingClass == null) return false;
105
106     final int parametersCount = method.getParameterList().getParametersCount();
107     return !InheritanceUtil.processSupers(containingClass, false, superClass -> {
108       if (PsiSearchScopeUtil.isInScope(scope, superClass)) {
109         for (PsiMethod candidate : superClass.findMethodsByName(method.getName(), false)) {
110           if (parametersCount == candidate.getParameterList().getParametersCount() &&
111               !candidate.hasModifierProperty(PsiModifier.PRIVATE) &&
112               !candidate.hasModifierProperty(PsiModifier.STATIC) &&
113               qualifiedMatcher.value(candidate)) {
114             return false;
115           }
116         }
117       }
118       return true;
119     });
120
121   }
122
123   private static boolean hasSuperMethod(PsiMethod method, GlobalSearchScope scope, Condition<PsiMember> qualifiedMatcher, String pattern) {
124     if (pattern.contains(".") && Registry.is("ide.goto.symbol.include.overrides.on.qualified.patterns")) {
125       return false;
126     }
127
128     if (!hasSuperMethodCandidates(method, scope, qualifiedMatcher)) {
129       return false;
130     }
131
132     for (HierarchicalMethodSignature signature : method.getHierarchicalMethodSignature().getSuperSignatures()) {
133       PsiMethod superMethod = signature.getMethod();
134       if (PsiSearchScopeUtil.isInScope(scope, superMethod) && qualifiedMatcher.value(superMethod)) {
135         return true;
136       }
137     }
138     return false;
139   }
140
141   @Override
142   public void processNames(@NotNull Processor<String> processor, @NotNull GlobalSearchScope scope, @Nullable IdFilter filter) {
143     PsiShortNamesCache cache = PsiShortNamesCache.getInstance(scope.getProject());
144     cache.processAllClassNames(processor, scope, filter);
145     cache.processAllFieldNames(processor, scope, filter);
146     cache.processAllMethodNames(processor, scope, filter);
147   }
148
149   @Override
150   public void processElementsWithName(@NotNull String name,
151                                       @NotNull final Processor<NavigationItem> processor,
152                                       @NotNull final FindSymbolParameters parameters) {
153
154     GlobalSearchScope scope = parameters.getSearchScope();
155     IdFilter filter = parameters.getIdFilter();
156     PsiShortNamesCache cache = PsiShortNamesCache.getInstance(scope.getProject());
157
158     String completePattern = parameters.getCompletePattern();
159     final Condition<PsiMember> qualifiedMatcher = getQualifiedNameMatcher(completePattern);
160
161     //noinspection UnusedDeclaration
162     final Set<PsiMethod> collectedMethods = new THashSet<>();
163     boolean success = cache.processFieldsWithName(name, field -> {
164       if (isOpenable(field) && qualifiedMatcher.value(field)) return processor.process(field);
165       return true;
166     }, scope, filter) &&
167                       cache.processClassesWithName(name, aClass -> {
168                         if (isOpenable(aClass) && qualifiedMatcher.value(aClass)) return processor.process(aClass);
169                         return true;
170                       }, scope, filter) &&
171                       cache.processMethodsWithName(name, method -> {
172                       if(!method.isConstructor() && isOpenable(method) && qualifiedMatcher.value(method)) {
173                         collectedMethods.add(method);
174                       }
175                       return true;
176                     }, scope, filter);
177     if (success) {
178       // hashSuperMethod accesses index and can not be invoked without risk of the deadlock in processMethodsWithName
179       Iterator<PsiMethod> iterator = collectedMethods.iterator();
180       while(iterator.hasNext()) {
181         PsiMethod method = iterator.next();
182         if (!hasSuperMethod(method, scope, qualifiedMatcher, completePattern) && !processor.process(method)) return;
183         ProgressManager.checkCanceled();
184         iterator.remove();
185       }
186     }
187   }
188
189   private static Condition<PsiMember> getQualifiedNameMatcher(String completePattern) {
190     if (completePattern.contains("#") && completePattern.endsWith(")")) {
191       return member -> member instanceof PsiMethod && JavaQualifiedNameProvider.hasQualifiedName(completePattern, (PsiMethod)member);
192     }
193
194     if (completePattern.contains(".") || completePattern.contains("#")) {
195       String normalized = StringUtil.replace(StringUtil.replace(completePattern, "#", ".*"), ".", ".*");
196       MinusculeMatcher matcher = NameUtil.buildMatcher("*" + normalized).build();
197       return member -> {
198         String qualifiedName = PsiUtil.getMemberQualifiedName(member);
199         return qualifiedName != null && matcher.matches(qualifiedName);
200       };
201     }
202     return Conditions.alwaysTrue();
203   }
204
205   private static class MyComparator implements Comparator<PsiModifierListOwner>{
206     public static final MyComparator INSTANCE = new MyComparator();
207
208     private final DefaultPsiElementCellRenderer myRenderer = new DefaultPsiElementCellRenderer();
209
210     @Override
211     public int compare(PsiModifierListOwner element1, PsiModifierListOwner element2) {
212       if (element1 == element2) return 0;
213
214       PsiModifierList modifierList1 = element1.getModifierList();
215       PsiModifierList modifierList2 = element2.getModifierList();
216
217       int level1 = modifierList1 == null ? PsiUtil.ACCESS_LEVEL_PUBLIC : PsiUtil.getAccessLevel(modifierList1);
218       int level2 = modifierList2 == null ? PsiUtil.ACCESS_LEVEL_PUBLIC : PsiUtil.getAccessLevel(modifierList2);
219       if (level1 != level2) return level2 - level1;
220
221       int kind1 = getElementTypeLevel(element1);
222       int kind2 = getElementTypeLevel(element2);
223       if (kind1 != kind2) return kind1 - kind2;
224
225       if (element1 instanceof PsiMethod){
226         LOG.assertTrue(element2 instanceof PsiMethod);
227         PsiParameter[] params1 = ((PsiMethod)element1).getParameterList().getParameters();
228         PsiParameter[] params2 = ((PsiMethod)element2).getParameterList().getParameters();
229
230         if (params1.length != params2.length) return params1.length - params2.length;
231       }
232
233       String text1 = myRenderer.getElementText(element1);
234       String text2 = myRenderer.getElementText(element2);
235       if (!text1.equals(text2)) return text1.compareTo(text2);
236
237       String containerText1 = myRenderer.getContainerText(element1, text1);
238       String containerText2 = myRenderer.getContainerText(element2, text2);
239       if (containerText1 == null) containerText1 = "";
240       if (containerText2 == null) containerText2 = "";
241       return containerText1.compareTo(containerText2);
242     }
243
244     private static int getElementTypeLevel(PsiElement element){
245       if (element instanceof PsiMethod){
246         return 1;
247       }
248       else if (element instanceof PsiField){
249         return 2;
250       }
251       else if (element instanceof PsiClass){
252         return 3;
253       }
254       else{
255         LOG.error(element);
256         return 0;
257       }
258     }
259   }
260
261   public static class JavadocSeparatorContributor implements ChooseByNameContributor, GotoClassContributor {
262     @Nullable
263     @Override
264     public String getQualifiedName(NavigationItem item) {
265       return null;
266     }
267
268     @Nullable
269     @Override
270     public String getQualifiedNameSeparator() {
271       return "#";
272     }
273
274     @NotNull
275     @Override
276     public String[] getNames(Project project, boolean includeNonProjectItems) {
277       return ArrayUtil.EMPTY_STRING_ARRAY;
278     }
279
280     @NotNull
281     @Override
282     public NavigationItem[] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) {
283       return NavigationItem.EMPTY_NAVIGATION_ITEM_ARRAY;
284     }
285   }
286
287 }