IDEA-61662 (Inspection "Class is a singleton" - false positive)
[idea/community.git] / plugins / InspectionGadgets / src / com / siyeh / ig / psiutils / SingletonUtil.java
1 /*
2  * Copyright 2003-2010 Dave Griffith, Bas Leijdekkers
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.siyeh.ig.psiutils;
17
18 import com.intellij.psi.*;
19 import com.intellij.psi.search.searches.MethodReferencesSearch;
20 import com.intellij.util.Processor;
21 import com.intellij.util.Query;
22 import org.jetbrains.annotations.NotNull;
23
24 public class SingletonUtil {
25
26     private SingletonUtil() {}
27
28     public static boolean isSingleton(@NotNull PsiClass aClass) {
29         if (aClass.isInterface() || aClass.isEnum() ||
30                 aClass.isAnnotationType()) {
31             return false;
32         }
33         if(aClass instanceof PsiTypeParameter ||
34                 aClass instanceof PsiAnonymousClass){
35             return false;
36         }
37         final PsiMethod[] constructors = getIfOnlyInvisibleConstructors(aClass);
38         if (constructors.length == 0) {
39             return false;
40         }
41         final PsiField selfInstance = getIfOneStaticSelfInstance(aClass);
42         if (selfInstance == null) {
43             return false;
44         }
45         return newOnlyAssignsToStaticSelfInstance(constructors[0], selfInstance);
46     }
47
48     private static PsiField getIfOneStaticSelfInstance(PsiClass aClass) {
49         final PsiField[] fields = aClass.getFields();
50         PsiField result = null;
51         for(final PsiField field : fields){
52             final String className = aClass.getQualifiedName();
53             if (!field.hasModifierProperty(PsiModifier.STATIC)) {
54                 continue;
55             }
56             final PsiType type = field.getType();
57             final String fieldTypeName = type.getCanonicalText();
58             if (!fieldTypeName.equals(className)) {
59                 continue;
60             }
61             if (result != null) {
62                 return null;
63             }
64             result = field;
65         }
66         return result;
67     }
68
69     private static PsiMethod[] getIfOnlyInvisibleConstructors(PsiClass aClass) {
70         final PsiMethod[] constructors = aClass.getConstructors();
71         if (constructors.length == 0) {
72             return PsiMethod.EMPTY_ARRAY;
73         }
74         for(final PsiMethod constructor : constructors){
75             if(constructor.hasModifierProperty(PsiModifier.PUBLIC)){
76                 return PsiMethod.EMPTY_ARRAY;
77             }
78             if(!constructor.hasModifierProperty(PsiModifier.PRIVATE) &&
79                     !constructor.hasModifierProperty(PsiModifier.PROTECTED)){
80                 return PsiMethod.EMPTY_ARRAY;
81             }
82         }
83         return constructors;
84     }
85
86     private static boolean newOnlyAssignsToStaticSelfInstance(
87             PsiMethod method, final PsiField field) {
88         final Query<PsiReference> search =
89                 MethodReferencesSearch.search(method, field.getUseScope(),
90                         false);
91         final NewOnlyAssignedToFieldProcessor processor =
92                 new NewOnlyAssignedToFieldProcessor(field);
93         search.forEach(processor);
94         return processor.isNewOnlyAssignedToField();
95     }
96
97     private static class NewOnlyAssignedToFieldProcessor
98             implements Processor<PsiReference> {
99
100         private boolean newOnlyAssignedToField = true;
101         private final PsiField field;
102
103         public NewOnlyAssignedToFieldProcessor(PsiField field) {
104             this.field = field;
105         }
106
107         public boolean process(PsiReference reference) {
108             final PsiElement element = reference.getElement();
109             final PsiElement parent = element.getParent();
110             if (!(parent instanceof PsiNewExpression)) {
111                 newOnlyAssignedToField = false;
112                 return false;
113             }
114             final PsiElement grandParent = parent.getParent();
115             if (field.equals(grandParent)) {
116                 return true;
117             }
118             if (!(grandParent instanceof PsiAssignmentExpression)) {
119                 newOnlyAssignedToField = false;
120                 return false;
121             }
122             final PsiAssignmentExpression assignmentExpression =
123                     (PsiAssignmentExpression) grandParent;
124             final PsiExpression lhs = assignmentExpression.getLExpression();
125             if (!(lhs instanceof PsiReferenceExpression)) {
126                 newOnlyAssignedToField = false;
127                 return false;
128             }
129             final PsiReferenceExpression referenceExpression =
130                     (PsiReferenceExpression) lhs;
131             final PsiElement target = referenceExpression.resolve();
132             if (!field.equals(target)) {
133                 newOnlyAssignedToField = false;
134                 return false;
135             }
136             return true;
137         }
138
139         public boolean isNewOnlyAssignedToField() {
140             return newOnlyAssignedToField;
141         }
142     }
143 }