IDEA-61662 (Inspection "Class is a singleton" - false positive)
[idea/community.git] / plugins / InspectionGadgets / src / com / siyeh / ig / psiutils / SingletonUtil.java
index 19980f61a46059ae03663d945514745277a49c20..852ea4a333ff7bedd7a7ad3721f132f94f05affb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2003-2005 Dave Griffith
+ * Copyright 2003-2010 Dave Griffith, Bas Leijdekkers
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 package com.siyeh.ig.psiutils;
 
 import com.intellij.psi.*;
+import com.intellij.psi.search.searches.MethodReferencesSearch;
+import com.intellij.util.Processor;
+import com.intellij.util.Query;
 import org.jetbrains.annotations.NotNull;
 
 public class SingletonUtil {
-    private SingletonUtil() {
-        super();
-    }
+
+    private SingletonUtil() {}
 
     public static boolean isSingleton(@NotNull PsiClass aClass) {
-        if (aClass.isInterface() || aClass.isEnum() || aClass.isAnnotationType()) {
+        if (aClass.isInterface() || aClass.isEnum() ||
+                aClass.isAnnotationType()) {
             return false;
         }
         if(aClass instanceof PsiTypeParameter ||
                 aClass instanceof PsiAnonymousClass){
             return false;
-        }                         
-        if (!hasConstructor(aClass)) {
+        }
+        final PsiMethod[] constructors = getIfOnlyInvisibleConstructors(aClass);
+        if (constructors.length == 0) {
             return false;
         }
-        if (hasVisibleConstructor(aClass)) {
+        final PsiField selfInstance = getIfOneStaticSelfInstance(aClass);
+        if (selfInstance == null) {
             return false;
         }
-        return containsOneStaticSelfInstance(aClass);
+        return newOnlyAssignsToStaticSelfInstance(constructors[0], selfInstance);
     }
 
-    private static boolean containsOneStaticSelfInstance(PsiClass aClass) {
+    private static PsiField getIfOneStaticSelfInstance(PsiClass aClass) {
         final PsiField[] fields = aClass.getFields();
-        int numSelfInstances = 0;
+        PsiField result = null;
         for(final PsiField field : fields){
             final String className = aClass.getQualifiedName();
-            if(field.hasModifierProperty(PsiModifier.STATIC)){
-                final PsiType type = field.getType();
-                final String fieldTypeName = type.getCanonicalText();
-                if(fieldTypeName.equals(className)){
-                    numSelfInstances++;
-                }
+            if (!field.hasModifierProperty(PsiModifier.STATIC)) {
+                continue;
             }
+            final PsiType type = field.getType();
+            final String fieldTypeName = type.getCanonicalText();
+            if (!fieldTypeName.equals(className)) {
+                continue;
+            }
+            if (result != null) {
+                return null;
+            }
+            result = field;
         }
-        return numSelfInstances == 1;
+        return result;
     }
 
-    private static boolean hasConstructor(PsiClass aClass) {
-        return aClass.getConstructors().length>0;
+    private static PsiMethod[] getIfOnlyInvisibleConstructors(PsiClass aClass) {
+        final PsiMethod[] constructors = aClass.getConstructors();
+        if (constructors.length == 0) {
+            return PsiMethod.EMPTY_ARRAY;
+        }
+        for(final PsiMethod constructor : constructors){
+            if(constructor.hasModifierProperty(PsiModifier.PUBLIC)){
+                return PsiMethod.EMPTY_ARRAY;
+            }
+            if(!constructor.hasModifierProperty(PsiModifier.PRIVATE) &&
+                    !constructor.hasModifierProperty(PsiModifier.PROTECTED)){
+                return PsiMethod.EMPTY_ARRAY;
+            }
+        }
+        return constructors;
     }
 
-    private static boolean hasVisibleConstructor(PsiClass aClass) {
-        final PsiMethod[] methods = aClass.getConstructors();
-        for(final PsiMethod method : methods){
-            if(method.hasModifierProperty(PsiModifier.PUBLIC)){
-                return true;
+    private static boolean newOnlyAssignsToStaticSelfInstance(
+            PsiMethod method, final PsiField field) {
+        final Query<PsiReference> search =
+                MethodReferencesSearch.search(method, field.getUseScope(),
+                        false);
+        final NewOnlyAssignedToFieldProcessor processor =
+                new NewOnlyAssignedToFieldProcessor(field);
+        search.forEach(processor);
+        return processor.isNewOnlyAssignedToField();
+    }
+
+    private static class NewOnlyAssignedToFieldProcessor
+            implements Processor<PsiReference> {
+
+        private boolean newOnlyAssignedToField = true;
+        private final PsiField field;
+
+        public NewOnlyAssignedToFieldProcessor(PsiField field) {
+            this.field = field;
+        }
+
+        public boolean process(PsiReference reference) {
+            final PsiElement element = reference.getElement();
+            final PsiElement parent = element.getParent();
+            if (!(parent instanceof PsiNewExpression)) {
+                newOnlyAssignedToField = false;
+                return false;
             }
-            if(!method.hasModifierProperty(PsiModifier.PRIVATE) &&
-                    !method.hasModifierProperty(PsiModifier.PROTECTED)){
+            final PsiElement grandParent = parent.getParent();
+            if (field.equals(grandParent)) {
                 return true;
             }
+            if (!(grandParent instanceof PsiAssignmentExpression)) {
+                newOnlyAssignedToField = false;
+                return false;
+            }
+            final PsiAssignmentExpression assignmentExpression =
+                    (PsiAssignmentExpression) grandParent;
+            final PsiExpression lhs = assignmentExpression.getLExpression();
+            if (!(lhs instanceof PsiReferenceExpression)) {
+                newOnlyAssignedToField = false;
+                return false;
+            }
+            final PsiReferenceExpression referenceExpression =
+                    (PsiReferenceExpression) lhs;
+            final PsiElement target = referenceExpression.resolve();
+            if (!field.equals(target)) {
+                newOnlyAssignedToField = false;
+                return false;
+            }
+            return true;
+        }
+
+        public boolean isNewOnlyAssignedToField() {
+            return newOnlyAssignedToField;
         }
-        return false;
     }
 }