constructor reference: don't ignore constructor parameters during method reference...
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / javadoc / PsiDocMethodOrFieldRef.java
1 // Copyright 2000-2019 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.psi.impl.source.javadoc;
3
4 import com.intellij.lang.ASTNode;
5 import com.intellij.openapi.util.TextRange;
6 import com.intellij.openapi.util.text.StringUtil;
7 import com.intellij.psi.*;
8 import com.intellij.psi.impl.PsiManagerEx;
9 import com.intellij.psi.impl.PsiSuperMethodImplUtil;
10 import com.intellij.psi.impl.source.Constants;
11 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
12 import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
13 import com.intellij.psi.impl.source.tree.*;
14 import com.intellij.psi.infos.CandidateInfo;
15 import com.intellij.psi.javadoc.PsiDocTag;
16 import com.intellij.psi.javadoc.PsiDocTagValue;
17 import com.intellij.psi.scope.DelegatingScopeProcessor;
18 import com.intellij.psi.scope.ElementClassFilter;
19 import com.intellij.psi.scope.PsiScopeProcessor;
20 import com.intellij.psi.scope.processor.FilterScopeProcessor;
21 import com.intellij.psi.util.MethodSignature;
22 import com.intellij.psi.util.MethodSignatureUtil;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import com.intellij.psi.util.TypeConversionUtil;
25 import com.intellij.util.ArrayUtilRt;
26 import com.intellij.util.CharTable;
27 import com.intellij.util.IncorrectOperationException;
28 import com.intellij.util.SmartList;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import java.util.*;
34
35 /**
36  * @author mike
37  */
38 public class PsiDocMethodOrFieldRef extends CompositePsiElement implements PsiDocTagValue, Constants {
39   public PsiDocMethodOrFieldRef() {
40     super(DOC_METHOD_OR_FIELD_REF);
41   }
42
43   @Override
44   public void accept(@NotNull PsiElementVisitor visitor) {
45     if (visitor instanceof JavaElementVisitor) {
46       ((JavaElementVisitor)visitor).visitDocTagValue(this);
47     }
48     else {
49       visitor.visitElement(this);
50     }
51   }
52
53   @Override
54   public PsiReference getReference() {
55     final PsiClass scope = getScope();
56     final PsiElement element = getNameElement();
57     if (scope == null || element == null) return new MyReference(PsiElement.EMPTY_ARRAY);
58
59     PsiReference psiReference = getReferenceInScope(scope, element);
60     if (psiReference != null) return psiReference;
61
62     PsiClass classScope;
63     PsiClass containingClass = scope.getContainingClass();
64     while (containingClass != null) {
65       classScope = containingClass;
66       psiReference = getReferenceInScope(classScope, element);
67       if (psiReference != null) return psiReference;
68       containingClass = classScope.getContainingClass();
69     }
70     return new MyReference(PsiElement.EMPTY_ARRAY);
71   }
72
73   @Nullable
74   private PsiReference getReferenceInScope(PsiClass scope, PsiElement element) {
75     final String name = element.getText();
76     final String[] signature = getSignature();
77
78     if (signature == null) {
79       PsiField var = scope.findFieldByName(name, true);
80       if (var != null) {
81         return new MyReference(new PsiElement[]{var});
82       }
83     }
84
85     final MethodSignature methodSignature;
86     if (signature != null) {
87       final List<PsiType> types = new ArrayList<>(signature.length);
88       final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(element.getProject());
89       for (String s : signature) {
90         try {
91           types.add(elementFactory.createTypeFromText(s, element));
92         }
93         catch (IncorrectOperationException e) {
94           types.add(PsiType.NULL);
95         }
96       }
97       methodSignature = MethodSignatureUtil.createMethodSignature(name, types.toArray(PsiType.createArray(types.size())),
98                                                                   PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY,
99                                                                   name.equals(scope.getName()));
100     }
101     else {
102       methodSignature = null;
103     }
104
105     PsiMethod[] methods = findMethods(methodSignature, scope, name, getAllMethods(scope, this));
106
107     if (methods.length == 0) return null;
108
109     return new MyReference(methods) {
110
111       @Override
112       public void processVariants(@NotNull PsiScopeProcessor processor) {
113         super.processVariants(new DelegatingScopeProcessor(processor) {
114           @Override
115           public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
116             if (element instanceof PsiMethod && name.equals(((PsiMethod)element).getName())) {
117               return super.execute(element, state);
118             }
119             return true;
120           }
121         });
122       }
123     };
124   }
125
126   @Override
127   public int getTextOffset() {
128     final PsiElement element = getNameElement();
129     return element != null ? element.getTextRange().getStartOffset() : getTextRange().getEndOffset();
130   }
131
132   @Nullable
133   public PsiElement getNameElement() {
134     final ASTNode name = findChildByType(DOC_TAG_VALUE_TOKEN);
135     return name != null ? SourceTreeToPsiMap.treeToPsiNotNull(name) : null;
136   }
137
138   @Nullable
139   public String[] getSignature() {
140     PsiElement element = getNameElement();
141     if (element == null) return null;
142
143     element = element.getNextSibling();
144     while (element != null && !(element instanceof PsiDocTagValue)) {
145       element = element.getNextSibling();
146     }
147     if (element == null) return null;
148
149     List<String> types = new ArrayList<>();
150     for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
151       if (child.getNode().getElementType() == DOC_TYPE_HOLDER) {
152         final String[] typeStrings = child.getText().split("[, ]");  //avoid param types list parsing hmm method(paramType1, paramType2, ...) -> typeElement1, identifier2, ...
153         for (String type : typeStrings) {
154           if (!type.isEmpty()) {
155             types.add(type);
156           }
157         }
158       }
159     }
160
161     return ArrayUtilRt.toStringArray(types);
162   }
163
164   @Nullable
165   private PsiClass getScope(){
166     if (getFirstChildNode().getElementType() == JavaDocElementType.DOC_REFERENCE_HOLDER) {
167       final PsiElement firstChildPsi = SourceTreeToPsiMap.treeElementToPsi(getFirstChildNode().getFirstChildNode());
168       if (firstChildPsi instanceof PsiJavaCodeReferenceElement) {
169         PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)firstChildPsi;
170         final PsiElement referencedElement = referenceElement.resolve();
171         if (referencedElement instanceof PsiClass) return (PsiClass)referencedElement;
172         return null;
173       }
174       else if (firstChildPsi instanceof PsiKeyword) {
175         final PsiKeyword keyword = (PsiKeyword)firstChildPsi;
176
177         if (keyword.getTokenType().equals(THIS_KEYWORD)) {
178           return JavaResolveUtil.getContextClass(this);
179         } else if (keyword.getTokenType().equals(SUPER_KEYWORD)) {
180           final PsiClass contextClass = JavaResolveUtil.getContextClass(this);
181           if (contextClass != null) return contextClass.getSuperClass();
182           return null;
183         }
184       }
185     }
186     return JavaResolveUtil.getContextClass(this);
187   }
188
189   @NotNull
190   public static PsiMethod[] findMethods(@Nullable MethodSignature methodSignature,
191                                         @NotNull PsiClass scope,
192                                         @Nullable String name,
193                                         @NotNull PsiMethod[] allMethods) {
194     final PsiClass superClass = scope.getSuperClass();
195     final PsiSubstitutor substitutor = superClass == null ? PsiSubstitutor.EMPTY :
196                                        TypeConversionUtil.getSuperClassSubstitutor(superClass, scope, PsiSubstitutor.EMPTY);
197
198     final List<PsiMethod> candidates = new ArrayList<>(Arrays.asList(allMethods));
199     final Set<PsiMethod> filteredMethods = new HashSet<>();
200
201     for (PsiMethod method : candidates) {
202       if (filteredMethods.contains(method)) {
203         continue;
204       }
205
206       if (!method.getName().equals(name) ||
207           methodSignature != null && !MethodSignatureUtil.areSignaturesErasureEqual(method.getSignature(substitutor), methodSignature)) {
208         filteredMethods.add(method);
209       }
210
211       PsiSuperMethodImplUtil.getHierarchicalMethodSignature(method)
212         .getSuperSignatures()
213         .forEach(signature -> filteredMethods.add(signature.getMethod()));
214     }
215
216     candidates.removeAll(filteredMethods);
217     return candidates.toArray(PsiMethod.EMPTY_ARRAY);
218   }
219
220   @NotNull
221   public static PsiMethod[] getAllMethods(PsiElement scope, PsiElement place) {
222     final SmartList<PsiMethod> result = new SmartList<>();
223     scope.processDeclarations(new FilterScopeProcessor<>(ElementClassFilter.METHOD, result), ResolveState.initial(), null, place);
224     return result.toArray(PsiMethod.EMPTY_ARRAY);
225   }
226
227   public class MyReference implements PsiJavaReference {
228     private final PsiElement[] myReferredElements;
229
230     public MyReference(PsiElement[] referredElements) {
231       myReferredElements = referredElements;
232     }
233
234     @Override
235     public PsiElement resolve() {
236       if (myReferredElements.length == 1) return myReferredElements[0];
237       return null;
238     }
239
240     @Override
241     public void processVariants(@NotNull PsiScopeProcessor processor) {
242       PsiClass scope = getScope();
243       while (scope != null) {
244         if (!scope.processDeclarations(new DelegatingScopeProcessor(processor) {
245           @Override
246           public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
247             if (element instanceof PsiMethod || element instanceof PsiField) {
248               return super.execute(element, state);
249             }
250             return true;
251           }
252         }, ResolveState.initial(), null, PsiDocMethodOrFieldRef.this)) {
253           return;
254         }
255         scope = scope.getContainingClass();
256       }
257     }
258
259     @Override
260     @NotNull
261     public JavaResolveResult advancedResolve(boolean incompleteCode) {
262       return myReferredElements.length != 1 ? JavaResolveResult.EMPTY
263                                             : new CandidateInfo(myReferredElements[0], PsiSubstitutor.EMPTY);
264     }
265
266     @Override
267     @NotNull
268     public JavaResolveResult[] multiResolve(boolean incompleteCode) {
269       return Arrays.stream(myReferredElements)
270         .map(myReferredElement -> new CandidateInfo(myReferredElement, PsiSubstitutor.EMPTY))
271         .toArray(JavaResolveResult[]::new);
272     }
273
274     @Override
275     @NotNull
276     public PsiElement[] getVariants(){
277       throw new UnsupportedOperationException();
278     }
279
280     @Override
281     public boolean isSoft(){
282       return false;
283     }
284
285     @Override
286     @NotNull
287     public String getCanonicalText() {
288       final PsiElement nameElement = getNameElement();
289       assert nameElement != null;
290       return nameElement.getText();
291     }
292
293     @Override
294     public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
295       final PsiElement nameElement = getNameElement();
296       assert nameElement != null;
297       final ASTNode treeElement = SourceTreeToPsiMap.psiToTreeNotNull(nameElement);
298       final CharTable charTableByTree = SharedImplUtil.findCharTableByTree(treeElement);
299       final LeafElement newToken = Factory.createSingleLeafElement(DOC_TAG_VALUE_TOKEN, newElementName, charTableByTree, getManager());
300       ((CompositeElement)treeElement.getTreeParent()).replaceChildInternal(SourceTreeToPsiMap.psiToTreeNotNull(nameElement), newToken);
301       return SourceTreeToPsiMap.treeToPsiNotNull(newToken);
302     }
303
304     @Override
305     public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
306       if (isReferenceTo(element)) return PsiDocMethodOrFieldRef.this;
307       final PsiElement nameElement = getNameElement();
308       assert nameElement != null;
309       final String name = nameElement.getText();
310       final String newName;
311
312       final PsiMethod method;
313       final PsiField field;
314       final boolean hasSignature;
315       final PsiClass containingClass;
316       if (element instanceof PsiMethod) {
317         method = (PsiMethod)element;
318         hasSignature = getSignature() != null;
319         containingClass = method.getContainingClass();
320         newName = method.getName();
321       } else if (element instanceof PsiField) {
322         field = (PsiField) element;
323         hasSignature = false;
324         containingClass = field.getContainingClass();
325         method = null;
326         newName = field.getName();
327       } else {
328         throw new IncorrectOperationException();
329       }
330
331       final PsiElement child = getFirstChild();
332       if (containingClass != null && child != null && child.getNode().getElementType() == JavaDocElementType.DOC_REFERENCE_HOLDER) {
333         PsiElement ref = child.getFirstChild();
334         if (ref instanceof PsiJavaCodeReferenceElement) {
335           ((PsiJavaCodeReferenceElement)ref).bindToElement(containingClass);
336         }
337       }
338       else {
339         if (containingClass != null && !PsiTreeUtil.isAncestor(containingClass, PsiDocMethodOrFieldRef.this, true)) {
340           final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(containingClass.getProject());
341           final PsiReferenceExpression ref = elementFactory.createReferenceExpression(containingClass);
342           addAfter(ref, null);
343         }
344       }
345
346       if (hasSignature || !name.equals(newName)) {
347         String text = getText();
348
349         @NonNls StringBuffer newText = new StringBuffer();
350         newText.append("/** @see ");
351         if (name.equals(newName)) { // hasSignature is true here, so we can search for '('
352           newText.append(text, 0, text.indexOf('('));
353         }
354         else {
355           final int sharpIndex = text.indexOf('#');
356           if (sharpIndex >= 0) {
357             newText.append(text, 0, sharpIndex + 1);
358           }
359           newText.append(newName);
360         }
361         if (hasSignature) {
362           newText.append('(');
363           PsiParameter[] parameters = method.getParameterList().getParameters();
364           newText.append(StringUtil.join(parameters, parameter -> TypeConversionUtil.erasure(parameter.getType()).getCanonicalText(), ","));
365           newText.append(')');
366         }
367         newText.append("*/");
368
369         return bindToText(containingClass, newText);
370       }
371
372       return PsiDocMethodOrFieldRef.this;
373     }
374
375     public PsiElement bindToText(PsiClass containingClass, StringBuffer newText) {
376       PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(containingClass.getProject());
377       PsiComment comment = elementFactory.createCommentFromText(newText.toString(), null);
378       PsiElement tag = PsiTreeUtil.getChildOfType(comment, PsiDocTag.class);
379       PsiElement ref = PsiTreeUtil.getChildOfType(tag, PsiDocMethodOrFieldRef.class);
380       assert ref != null : newText;
381       return replace(ref);
382     }
383
384     @Override
385     public boolean isReferenceTo(@NotNull PsiElement element) {
386       PsiManagerEx manager = getManager();
387       for (PsiElement myReferredElement : myReferredElements) {
388         if (manager.areElementsEquivalent(element, myReferredElement)) return true;
389       }
390       return false;
391     }
392
393     @NotNull
394     @Override
395     public TextRange getRangeInElement() {
396       final ASTNode sharp = findChildByType(DOC_TAG_VALUE_SHARP_TOKEN);
397       if (sharp == null) return new TextRange(0, getTextLength());
398       final PsiElement nextSibling = SourceTreeToPsiMap.treeToPsiNotNull(sharp).getNextSibling();
399       if (nextSibling != null) {
400         final int startOffset = nextSibling.getTextRange().getStartOffset() - getTextRange().getStartOffset();
401         int endOffset = nextSibling.getTextRange().getEndOffset() - getTextRange().getStartOffset();
402         return new TextRange(startOffset, endOffset);
403       }
404       return new TextRange(getTextLength(), getTextLength());
405     }
406
407     @NotNull
408     @Override
409     public PsiElement getElement() {
410       return PsiDocMethodOrFieldRef.this;
411     }
412   }
413 }