2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInspection.reference;
18 import com.intellij.codeInsight.ExceptionUtil;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.util.Comparing;
21 import com.intellij.psi.*;
22 import com.intellij.psi.search.GlobalSearchScope;
23 import com.intellij.psi.util.*;
24 import com.intellij.util.IncorrectOperationException;
25 import com.intellij.util.SmartList;
26 import org.jetbrains.annotations.NonNls;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
36 public class RefMethodImpl extends RefJavaElementImpl implements RefMethod {
37 private static final List<RefMethod> EMPTY_METHOD_LIST = Collections.emptyList();
38 private static final RefParameter[] EMPTY_PARAMS_ARRAY = new RefParameter[0];
40 private static final int IS_APPMAIN_MASK = 0x10000;
41 private static final int IS_LIBRARY_OVERRIDE_MASK = 0x20000;
42 private static final int IS_CONSTRUCTOR_MASK = 0x40000;
43 private static final int IS_ABSTRACT_MASK = 0x80000;
44 private static final int IS_BODY_EMPTY_MASK = 0x100000;
45 private static final int IS_ONLY_CALLS_SUPER_MASK = 0x200000;
46 private static final int IS_RETURN_VALUE_USED_MASK = 0x400000;
48 private static final int IS_TEST_METHOD_MASK = 0x4000000;
49 private static final int IS_CALLED_ON_SUBCLASS_MASK = 0x8000000;
51 private static final String RETURN_VALUE_UNDEFINED = "#";
53 private List<RefMethod> mySuperMethods;
54 private List<RefMethod> myDerivedMethods;
55 private List<String> myUnThrownExceptions;
57 private RefParameter[] myParameters;
58 private String myReturnValueTemplate;
59 protected final RefClass myOwnerClass;
61 RefMethodImpl(@NotNull RefClass ownerClass, PsiMethod method, RefManager manager) {
62 super(method, manager);
64 ((RefClassImpl)ownerClass).add(this);
66 myOwnerClass = ownerClass;
69 // To be used only from RefImplicitConstructor.
70 protected RefMethodImpl(@NotNull String name, @NotNull RefClass ownerClass) {
71 super(name, ownerClass);
72 myOwnerClass = ownerClass;
73 ((RefClassImpl)ownerClass).add(this);
75 addOutReference(getOwnerClass());
76 ((RefClassImpl)getOwnerClass()).addInReference(this);
82 public void add(@NotNull RefEntity child) {
83 if (child instanceof RefParameter) {
90 public List<RefEntity> getChildren() {
91 List<RefEntity> superChildren = super.getChildren();
92 if (myParameters == null) return superChildren;
93 if (superChildren == null || superChildren.isEmpty()) return Arrays.<RefEntity>asList(myParameters);
95 List<RefEntity> allChildren = new ArrayList<RefEntity>(superChildren.size() + myParameters.length);
96 allChildren.addAll(superChildren);
97 Collections.addAll(allChildren, myParameters);
102 protected void initialize() {
103 final PsiMethod method = (PsiMethod)getElement();
104 LOG.assertTrue(method != null);
105 setConstructor(method.isConstructor());
106 final PsiType returnType = method.getReturnType();
107 setFlag(returnType == null ||
108 PsiType.VOID.equals(returnType) ||
109 returnType.equalsToText(CommonClassNames.JAVA_LANG_VOID), IS_RETURN_VALUE_USED_MASK);
111 if (!isReturnValueUsed()) {
112 myReturnValueTemplate = RETURN_VALUE_UNDEFINED;
115 if (isConstructor()) {
116 addReference(getOwnerClass(), getOwnerClass().getElement(), method, false, true, null);
119 if (getOwnerClass().isInterface()) {
122 setAbstract(method.hasModifierProperty(PsiModifier.ABSTRACT));
126 setAppMain(isAppMain(method, this));
127 setLibraryOverride(method.hasModifierProperty(PsiModifier.NATIVE));
129 initializeSuperMethods(method);
130 if (isExternalOverride()) {
131 ((RefClassImpl)getOwnerClass()).addLibraryOverrideMethod(this);
134 @NonNls final String name = method.getName();
135 if (getOwnerClass().isTestCase() && name.startsWith("test")) {
139 PsiParameter[] paramList = method.getParameterList().getParameters();
140 if (paramList.length > 0){
141 myParameters = new RefParameterImpl[paramList.length];
142 for (int i = 0; i < paramList.length; i++) {
143 PsiParameter parameter = paramList[i];
144 myParameters[i] = getRefJavaManager().getParameterReference(parameter, i);
148 if (method.hasModifierProperty(PsiModifier.NATIVE)) {
149 updateReturnValueTemplate(null);
150 updateThrowsList(null);
152 collectUncaughtExceptions(method);
155 private static boolean isAppMain(PsiMethod psiMethod, RefMethod refMethod) {
156 if (!refMethod.isStatic()) return false;
157 if (!PsiType.VOID.equals(psiMethod.getReturnType())) return false;
159 PsiMethod appMainPattern = ((RefMethodImpl)refMethod).getRefJavaManager().getAppMainPattern();
160 if (MethodSignatureUtil.areSignaturesEqual(psiMethod, appMainPattern)) return true;
162 PsiMethod appPremainPattern = ((RefMethodImpl)refMethod).getRefJavaManager().getAppPremainPattern();
163 if (MethodSignatureUtil.areSignaturesEqual(psiMethod, appPremainPattern)) return true;
165 PsiMethod appAgentmainPattern = ((RefMethodImpl)refMethod).getRefJavaManager().getAppAgentmainPattern();
166 return MethodSignatureUtil.areSignaturesEqual(psiMethod, appAgentmainPattern);
169 private void checkForSuperCall(PsiMethod method) {
170 if (isConstructor()) {
171 PsiCodeBlock body = method.getBody();
172 if (body == null) return;
173 PsiStatement[] statements = body.getStatements();
174 boolean isBaseExplicitlyCalled = false;
175 if (statements.length > 0) {
176 PsiStatement first = statements[0];
177 if (first instanceof PsiExpressionStatement) {
178 PsiExpression firstExpression = ((PsiExpressionStatement) first).getExpression();
179 if (firstExpression instanceof PsiMethodCallExpression) {
180 PsiExpression qualifierExpression = ((PsiMethodCallExpression)firstExpression).getMethodExpression().getQualifierExpression();
181 if (qualifierExpression instanceof PsiReferenceExpression) {
182 @NonNls String text = qualifierExpression.getText();
183 if ("super".equals(text) || text.equals("this")) {
184 isBaseExplicitlyCalled = true;
191 if (!isBaseExplicitlyCalled) {
192 for (RefClass superClass : getOwnerClass().getBaseClasses()) {
193 RefMethodImpl superDefaultConstructor = (RefMethodImpl)superClass.getDefaultConstructor();
195 if (superDefaultConstructor != null) {
196 superDefaultConstructor.addInReference(this);
197 addOutReference(superDefaultConstructor);
206 public Collection<RefMethod> getSuperMethods() {
207 if (mySuperMethods == null) return EMPTY_METHOD_LIST;
208 if (mySuperMethods.size() > 10) {
209 LOG.info("method: " + getName() + " owner:" + getOwnerClass().getQualifiedName());
211 if (getRefManager().isOfflineView()) {
212 LOG.debug("Should not traverse graph offline");
214 return mySuperMethods;
219 public Collection<RefMethod> getDerivedMethods() {
220 if (myDerivedMethods == null) return EMPTY_METHOD_LIST;
221 return myDerivedMethods;
225 public boolean isBodyEmpty() {
226 return checkFlag(IS_BODY_EMPTY_MASK);
230 public boolean isOnlyCallsSuper() {
231 return checkFlag(IS_ONLY_CALLS_SUPER_MASK);
235 public boolean hasBody() {
236 return !isAbstract() && !getOwnerClass().isInterface() || !isBodyEmpty();
239 private void initializeSuperMethods(PsiMethod method) {
240 if (getRefManager().isOfflineView()) return;
241 for (PsiMethod psiSuperMethod : method.findSuperMethods()) {
242 if (getRefManager().belongsToScope(psiSuperMethod)) {
243 RefMethodImpl refSuperMethod = (RefMethodImpl)getRefManager().getReference(psiSuperMethod);
244 if (refSuperMethod != null) {
245 addSuperMethod(refSuperMethod);
246 refSuperMethod.markExtended(this);
250 setLibraryOverride(true);
255 public void addSuperMethod(RefMethodImpl refSuperMethod) {
256 if (!getSuperMethods().contains(refSuperMethod) && !refSuperMethod.getSuperMethods().contains(this)) {
257 if (mySuperMethods == null){
258 mySuperMethods = new ArrayList<RefMethod>(1);
260 mySuperMethods.add(refSuperMethod);
264 public void markExtended(RefMethodImpl method) {
265 if (!getDerivedMethods().contains(method) && !method.getDerivedMethods().contains(this)) {
266 if (myDerivedMethods == null) {
267 myDerivedMethods = new ArrayList<RefMethod>(1);
269 myDerivedMethods.add(method);
275 public RefParameter[] getParameters() {
276 if (myParameters == null) return EMPTY_PARAMS_ARRAY;
281 public void buildReferences() {
282 // Work on code block to find what we're referencing...
283 PsiMethod method = (PsiMethod) getElement();
284 if (method == null) return;
285 PsiCodeBlock body = method.getBody();
286 final RefJavaUtil refUtil = RefJavaUtil.getInstance();
287 refUtil.addReferences(method, this, body);
288 refUtil.addReferences(method, this, method.getModifierList());
289 checkForSuperCall(method);
290 setOnlyCallsSuper(refUtil.isMethodOnlyCallsSuper(method));
292 setBodyEmpty(isOnlyCallsSuper() || !isExternalOverride() && (body == null || body.getStatements().length == 0));
294 refUtil.addTypeReference(method, method.getReturnType(), getRefManager(), this);
296 for (RefParameter parameter : getParameters()) {
297 refUtil.setIsFinal(parameter, parameter.getElement().hasModifierProperty(PsiModifier.FINAL));
300 getRefManager().fireBuildReferences(this);
303 private void collectUncaughtExceptions(@NotNull PsiMethod method) {
304 if (isExternalOverride()) return;
305 if (getRefManager().isOfflineView()) return;
306 @NonNls final String name = method.getName();
307 if (getOwnerClass().isTestCase() && name.startsWith("test")) return;
309 if (getSuperMethods().isEmpty()) {
310 PsiClassType[] throwsList = method.getThrowsList().getReferencedTypes();
311 if (throwsList.length > 0) {
312 myUnThrownExceptions = throwsList.length == 1 ? new SmartList<String>() : new ArrayList<String>(throwsList.length);
313 for (final PsiClassType type : throwsList) {
314 PsiClass aClass = type.resolve();
315 String fqn = aClass == null ? null : aClass.getQualifiedName();
317 myUnThrownExceptions.add(fqn);
323 final PsiCodeBlock body = method.getBody();
324 if (body == null) return;
326 final Collection<PsiClassType> exceptionTypes = ExceptionUtil.collectUnhandledExceptions(body, method, false);
327 for (final PsiClassType exceptionType : exceptionTypes) {
328 updateThrowsList(exceptionType);
332 public void removeUnThrownExceptions(PsiClass unThrownException) {
333 if (myUnThrownExceptions != null) {
334 myUnThrownExceptions.remove(unThrownException.getQualifiedName());
339 public void accept(@NotNull final RefVisitor visitor) {
340 if (visitor instanceof RefJavaVisitor) {
341 ApplicationManager.getApplication().runReadAction(() -> ((RefJavaVisitor)visitor).visitMethod(RefMethodImpl.this));
343 super.accept(visitor);
348 public boolean isExternalOverride() {
349 return isLibraryOverride(new HashSet<RefMethod>());
352 private boolean isLibraryOverride(@NotNull Collection<RefMethod> processed) {
353 if (!processed.add(this)) return false;
355 if (checkFlag(IS_LIBRARY_OVERRIDE_MASK)) return true;
356 for (RefMethod superMethod : getSuperMethods()) {
357 if (((RefMethodImpl)superMethod).isLibraryOverride(processed)) {
358 setFlag(true, IS_LIBRARY_OVERRIDE_MASK);
367 public boolean isAppMain() {
368 return checkFlag(IS_APPMAIN_MASK);
372 public boolean isAbstract() {
373 return checkFlag(IS_ABSTRACT_MASK);
377 public boolean hasSuperMethods() {
378 return !getSuperMethods().isEmpty() || isExternalOverride();
382 public boolean isReferenced() {
383 // Directly called from somewhere..
384 for (RefElement refCaller : getInReferences()) {
385 if (!getDerivedMethods().contains(refCaller)) return true;
388 // Library override probably called from library code.
389 return isExternalOverride();
393 public boolean hasSuspiciousCallers() {
394 // Directly called from somewhere..
395 for (RefElement refCaller : getInReferences()) {
396 if (((RefElementImpl)refCaller).isSuspicious() && !getDerivedMethods().contains(refCaller)) return true;
399 // Library override probably called from library code.
400 if (isExternalOverride()) return true;
402 // Class isn't instantiated. Most probably we have problem with class, not method.
403 if (!isStatic() && !isConstructor()) {
404 if (((RefClassImpl)getOwnerClass()).isSuspicious()) return true;
406 // Is an override. Probably called via reference to base class.
407 for (RefMethod refSuper : getSuperMethods()) {
408 if (((RefMethodImpl)refSuper).isSuspicious()) return true;
416 public boolean isConstructor() {
417 return checkFlag(IS_CONSTRUCTOR_MASK);
421 public RefClass getOwnerClass() {
422 return (RefClass) getOwner();
427 public String getName() {
429 final String[] result = new String[1];
430 final Runnable runnable = () -> {
431 PsiMethod psiMethod = (PsiMethod) getElement();
432 if (psiMethod instanceof SyntheticElement) {
433 result[0] = psiMethod.getName();
436 result[0] = PsiFormatUtil.formatMethod(psiMethod,
437 PsiSubstitutor.EMPTY,
438 PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS,
439 PsiFormatUtilBase.SHOW_TYPE
444 ApplicationManager.getApplication().runReadAction(runnable);
448 return super.getName();
453 public String getExternalName() {
454 final String[] result = new String[1];
455 final Runnable runnable = () -> {
456 final PsiMethod psiMethod = (PsiMethod)getElement();
457 LOG.assertTrue(psiMethod != null);
458 result[0] = PsiFormatUtil.getExternalName(psiMethod, true, Integer.MAX_VALUE);
461 ApplicationManager.getApplication().runReadAction(runnable);
467 public static RefMethod methodFromExternalName(RefManager manager, String externalName) {
468 return (RefMethod) manager.getReference(findPsiMethod(PsiManager.getInstance(manager.getProject()), externalName));
472 public static PsiMethod findPsiMethod(PsiManager manager, String externalName) {
473 final int spaceIdx = externalName.indexOf(' ');
474 final String className = externalName.substring(0, spaceIdx);
475 final PsiClass psiClass = ClassUtil.findPsiClass(manager, className);
476 if (psiClass == null) return null;
478 PsiElementFactory factory = JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory();
479 String methodSignature = externalName.substring(spaceIdx + 1);
480 PsiMethod patternMethod = factory.createMethodFromText(methodSignature, psiClass);
481 return psiClass.findMethodBySignature(patternMethod, false);
482 } catch (IncorrectOperationException e) {
483 // Do nothing. Returning null is acceptable in this case.
489 public void referenceRemoved() {
490 if (getOwnerClass() != null) {
491 ((RefClassImpl)getOwnerClass()).methodRemoved(this);
494 super.referenceRemoved();
496 for (RefMethod superMethod : getSuperMethods()) {
497 superMethod.getDerivedMethods().remove(this);
500 for (RefMethod subMethod : getDerivedMethods()) {
501 subMethod.getSuperMethods().remove(this);
504 ArrayList<RefElement> deletedRefs = new ArrayList<RefElement>();
505 for (RefParameter parameter : getParameters()) {
506 getRefManager().removeRefElement(parameter, deletedRefs);
511 public boolean isSuspicious() {
512 if (isConstructor() && PsiModifier.PRIVATE.equals(getAccessModifier()) && getParameters().length == 0 && getOwnerClass().getConstructors().size() == 1) return false;
513 return super.isSuspicious();
516 public void setReturnValueUsed(boolean value) {
517 if (checkFlag(IS_RETURN_VALUE_USED_MASK) == value) return;
518 setFlag(value, IS_RETURN_VALUE_USED_MASK);
519 for (RefMethod refSuper : getSuperMethods()) {
520 ((RefMethodImpl)refSuper).setReturnValueUsed(value);
525 public boolean isReturnValueUsed() {
526 return checkFlag(IS_RETURN_VALUE_USED_MASK);
529 public void updateReturnValueTemplate(PsiExpression expression) {
530 if (myReturnValueTemplate == null) return;
532 if (!getSuperMethods().isEmpty()) {
533 for (final RefMethod refMethod : getSuperMethods()) {
534 RefMethodImpl refSuper = (RefMethodImpl)refMethod;
535 refSuper.updateReturnValueTemplate(expression);
538 String newTemplate = null;
539 final RefJavaUtil refUtil = RefJavaUtil.getInstance();
540 if (expression instanceof PsiLiteralExpression) {
541 PsiLiteralExpression psiLiteralExpression = (PsiLiteralExpression) expression;
542 newTemplate = psiLiteralExpression.getText();
543 } else if (expression instanceof PsiReferenceExpression) {
544 PsiReferenceExpression referenceExpression = (PsiReferenceExpression) expression;
545 PsiElement resolved = referenceExpression.resolve();
546 if (resolved instanceof PsiField) {
547 PsiField psiField = (PsiField) resolved;
548 if (psiField.hasModifierProperty(PsiModifier.STATIC) &&
549 psiField.hasModifierProperty(PsiModifier.FINAL) &&
550 refUtil.compareAccess(refUtil.getAccessModifier(psiField), getAccessModifier()) >= 0) {
551 newTemplate = PsiFormatUtil.formatVariable(psiField, PsiFormatUtilBase.SHOW_NAME |
552 PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
553 PsiFormatUtilBase.SHOW_FQ_NAME, PsiSubstitutor.EMPTY);
556 } else if (refUtil.isCallToSuperMethod(expression, (PsiMethod) getElement())) return;
558 //noinspection StringEquality
559 if (myReturnValueTemplate == RETURN_VALUE_UNDEFINED) {
560 myReturnValueTemplate = newTemplate;
561 } else if (!Comparing.equal(myReturnValueTemplate, newTemplate)) {
562 myReturnValueTemplate = null;
567 public void updateParameterValues(PsiExpression[] args) {
568 if (isExternalOverride()) return;
570 if (!getSuperMethods().isEmpty()) {
571 for (RefMethod refSuper : getSuperMethods()) {
572 ((RefMethodImpl)refSuper).updateParameterValues(args);
575 final RefParameter[] params = getParameters();
576 if (params.length <= args.length && params.length > 0) {
577 for (int i = 0; i < args.length; i++) {
578 RefParameter refParameter;
579 if (params.length <= i){
580 refParameter = params[params.length - 1];
582 refParameter = params[i];
584 ((RefParameterImpl)refParameter).updateTemplateValue(args[i]);
591 public String getReturnValueIfSame() {
592 //noinspection StringEquality
593 if (myReturnValueTemplate == RETURN_VALUE_UNDEFINED) return null;
594 return myReturnValueTemplate;
597 public void updateThrowsList(PsiClassType exceptionType) {
598 if (!getSuperMethods().isEmpty()) {
599 for (RefMethod refSuper : getSuperMethods()) {
600 ((RefMethodImpl)refSuper).updateThrowsList(exceptionType);
603 else if (myUnThrownExceptions != null) {
604 if (exceptionType == null) {
605 myUnThrownExceptions = null;
608 PsiClass exceptionClass = exceptionType.resolve();
609 JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject());
610 for (int i = myUnThrownExceptions.size() - 1; i >= 0; i--) {
611 String exceptionFqn = myUnThrownExceptions.get(i);
612 PsiClass classType = facade.findClass(exceptionFqn, GlobalSearchScope.allScope(getRefManager().getProject()));
613 if (InheritanceUtil.isInheritorOrSelf(exceptionClass, classType, true) ||
614 InheritanceUtil.isInheritorOrSelf(classType, exceptionClass, true)) {
615 myUnThrownExceptions.remove(i);
619 if (myUnThrownExceptions.isEmpty()) myUnThrownExceptions = null;
625 public PsiClass[] getUnThrownExceptions() {
626 if (getRefManager().isOfflineView()) {
627 LOG.debug("Should not traverse graph offline");
629 if (myUnThrownExceptions == null) return null;
630 JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject());
631 List<PsiClass> result = new ArrayList<PsiClass>(myUnThrownExceptions.size());
632 for (String exception : myUnThrownExceptions) {
633 PsiClass element = facade.findClass(exception, GlobalSearchScope.allScope(myManager.getProject()));
634 if (element != null) result.add(element);
636 return result.toArray(new PsiClass[result.size()]);
640 public void setLibraryOverride(boolean libraryOverride) {
641 setFlag(libraryOverride, IS_LIBRARY_OVERRIDE_MASK);
644 private void setAppMain(boolean appMain) {
645 setFlag(appMain, IS_APPMAIN_MASK);
648 private void setAbstract(boolean anAbstract) {
649 setFlag(anAbstract, IS_ABSTRACT_MASK);
652 public void setBodyEmpty(boolean bodyEmpty) {
653 setFlag(bodyEmpty, IS_BODY_EMPTY_MASK);
656 private void setOnlyCallsSuper(boolean onlyCallsSuper) {
657 setFlag(onlyCallsSuper, IS_ONLY_CALLS_SUPER_MASK);
662 private void setConstructor(boolean constructor) {
663 setFlag(constructor, IS_CONSTRUCTOR_MASK);
667 public boolean isTestMethod() {
668 return checkFlag(IS_TEST_METHOD_MASK);
671 private void setTestMethod(boolean testMethod){
672 setFlag(testMethod, IS_TEST_METHOD_MASK);
676 public PsiModifierListOwner getElement() {
677 return (PsiModifierListOwner)super.getElement();
681 public boolean isCalledOnSubClass() {
682 return checkFlag(IS_CALLED_ON_SUBCLASS_MASK);
685 public void setCalledOnSubClass(boolean isCalledOnSubClass){
686 setFlag(isCalledOnSubClass, IS_CALLED_ON_SUBCLASS_MASK);