ef76238402a661f1d64f9dd494c351d676e0cc57
[idea/community.git] / plugins / InspectionGadgets / InspectionGadgetsAnalysis / src / com / siyeh / ig / classlayout / ClassMayBeInterfaceInspection.java
1 /*
2  * Copyright 2003-2015 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.classlayout;
17
18 import com.intellij.codeInsight.FileModificationService;
19 import com.intellij.codeInspection.ProblemDescriptor;
20 import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.WriteExternalException;
23 import com.intellij.psi.*;
24 import com.intellij.psi.search.SearchScope;
25 import com.intellij.psi.search.searches.ClassInheritorsSearch;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.PsiUtil;
28 import com.siyeh.InspectionGadgetsBundle;
29 import com.siyeh.ig.BaseInspection;
30 import com.siyeh.ig.BaseInspectionVisitor;
31 import com.siyeh.ig.InspectionGadgetsFix;
32 import org.jdom.Element;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import javax.swing.*;
37 import java.util.ArrayList;
38 import java.util.List;
39
40 public class ClassMayBeInterfaceInspection extends BaseInspection {
41
42   @SuppressWarnings("PublicField")
43   public boolean reportClassesWithNonAbstractMethods = false;
44
45   @Override
46   @NotNull
47   public String getDisplayName() {
48     return InspectionGadgetsBundle.message("class.may.be.interface.display.name");
49   }
50
51   @Override
52   @NotNull
53   protected String buildErrorString(Object... infos) {
54     return InspectionGadgetsBundle.message("class.may.be.interface.problem.descriptor");
55   }
56
57   @Nullable
58   @Override
59   public JComponent createOptionsPanel() {
60     return new SingleCheckboxOptionsPanel(InspectionGadgetsBundle.message("class.may.be.interface.java8.option"), this,
61                                           "reportClassesWithNonAbstractMethods");
62   }
63
64   @Override
65   public void writeSettings(@NotNull Element node) throws WriteExternalException {
66     if (reportClassesWithNonAbstractMethods) {
67       node.addContent(new Element("option").setAttribute("name", "reportClassesWithNonAbstractMethods").setAttribute("value", "true"));
68     }
69   }
70
71   @Override
72   protected InspectionGadgetsFix buildFix(Object... infos) {
73     return new ClassMayBeInterfaceFix();
74   }
75
76   private static class ClassMayBeInterfaceFix extends InspectionGadgetsFix {
77
78     @Override
79     @NotNull
80     public String getName() {
81       return InspectionGadgetsBundle.message("class.may.be.interface.convert.quickfix");
82     }
83     @Override
84     @NotNull
85     public String getFamilyName() {
86       return getName();
87     }
88
89     @Override
90     protected boolean prepareForWriting() {
91       return false;
92     }
93
94     @Override
95     public void doFix(Project project, ProblemDescriptor descriptor) {
96       final PsiIdentifier classNameIdentifier = (PsiIdentifier)descriptor.getPsiElement();
97       final PsiClass interfaceClass = (PsiClass)classNameIdentifier.getParent();
98       final SearchScope searchScope = interfaceClass.getUseScope();
99       final List<PsiClass> elements = new ArrayList();
100       elements.add(interfaceClass);
101       for (final PsiClass inheritor : ClassInheritorsSearch.search(interfaceClass, searchScope, false)) {
102         elements.add(inheritor);
103       }
104       if (!FileModificationService.getInstance().preparePsiElementsForWrite(elements)) {
105         return;
106       }
107       moveSubClassExtendsToImplements(elements);
108       changeClassToInterface(interfaceClass);
109       moveImplementsToExtends(interfaceClass);
110     }
111
112     private static void changeClassToInterface(PsiClass aClass) {
113       for (PsiMethod method : aClass.getMethods()) {
114         PsiUtil.setModifierProperty(method, PsiModifier.PUBLIC, false);
115         if (method.hasModifierProperty(PsiModifier.STATIC) || method.hasModifierProperty(PsiModifier.ABSTRACT)) {
116           continue;
117         }
118         PsiUtil.setModifierProperty(method, PsiModifier.DEFAULT, true);
119       }
120       for (PsiField field : aClass.getFields()) {
121         PsiUtil.setModifierProperty(field, PsiModifier.PUBLIC, false);
122         PsiUtil.setModifierProperty(field, PsiModifier.STATIC, false);
123         PsiUtil.setModifierProperty(field, PsiModifier.FINAL, false);
124       }
125       for (PsiClass innerClass : aClass.getInnerClasses()) {
126         PsiUtil.setModifierProperty(innerClass, PsiModifier.PUBLIC, false);
127       }
128       final PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
129       if (nameIdentifier == null) {
130         return;
131       }
132       final PsiKeyword classKeyword = PsiTreeUtil.getPrevSiblingOfType(nameIdentifier, PsiKeyword.class);
133       final PsiManager manager = aClass.getManager();
134       final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
135       final PsiKeyword interfaceKeyword = factory.createKeyword(PsiKeyword.INTERFACE);
136       if (classKeyword == null) {
137         return;
138       }
139       PsiUtil.setModifierProperty(aClass, PsiModifier.ABSTRACT, false);
140       PsiUtil.setModifierProperty(aClass, PsiModifier.FINAL, false);
141       classKeyword.replace(interfaceKeyword);
142     }
143
144     private static void moveImplementsToExtends(PsiClass anInterface) {
145       final PsiReferenceList extendsList = anInterface.getExtendsList();
146       if (extendsList == null) {
147         return;
148       }
149       final PsiReferenceList implementsList = anInterface.getImplementsList();
150       if (implementsList == null) {
151         return;
152       }
153       final PsiJavaCodeReferenceElement[] referenceElements = implementsList.getReferenceElements();
154       for (final PsiJavaCodeReferenceElement referenceElement : referenceElements) {
155         extendsList.add(referenceElement);
156         referenceElement.delete();
157       }
158     }
159
160     private static void moveSubClassExtendsToImplements(List<PsiClass> inheritors) {
161       final PsiClass oldClass = inheritors.get(0);
162       final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(oldClass.getProject()).getElementFactory();
163       final PsiJavaCodeReferenceElement classReference = elementFactory.createClassReferenceElement(oldClass);
164       for (int i = 1; i < inheritors.size(); i++) {
165         final PsiClass inheritor = inheritors.get(i);
166         final PsiReferenceList extendsList = inheritor.getExtendsList();
167         if (extendsList == null) {
168           continue;
169         }
170         final PsiReferenceList implementsList = inheritor.getImplementsList();
171         moveReference(extendsList, implementsList, classReference);
172       }
173     }
174
175     private static void moveReference(@NotNull PsiReferenceList source, @Nullable PsiReferenceList target,
176                                       @NotNull PsiJavaCodeReferenceElement reference) {
177       final PsiJavaCodeReferenceElement[] sourceReferences = source.getReferenceElements();
178       final String fqName = reference.getQualifiedName();
179       for (final PsiJavaCodeReferenceElement sourceReference : sourceReferences) {
180         final String implementsReferenceFqName = sourceReference.getQualifiedName();
181         if (fqName.equals(implementsReferenceFqName)) {
182           if (target != null) {
183             target.add(sourceReference);
184           }
185           sourceReference.delete();
186         }
187       }
188     }
189   }
190
191   @Override
192   public BaseInspectionVisitor buildVisitor() {
193     return new ClassMayBeInterfaceVisitor();
194   }
195
196   private class ClassMayBeInterfaceVisitor extends BaseInspectionVisitor {
197
198     @Override
199     public void visitClass(@NotNull PsiClass aClass) {
200       // no call to super, so that it doesn't drill down to inner classes
201       if (aClass.isInterface() || aClass.isAnnotationType() || aClass.isEnum()) {
202         return;
203       }
204       if (aClass instanceof PsiTypeParameter || aClass instanceof PsiAnonymousClass) {
205         return;
206       }
207       if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
208         return;
209       }
210       if (!mayBeInterface(aClass)) {
211         return;
212       }
213       registerClassError(aClass);
214     }
215
216     public boolean mayBeInterface(PsiClass aClass) {
217       final PsiReferenceList extendsList = aClass.getExtendsList();
218       if (extendsList != null) {
219         final PsiJavaCodeReferenceElement[] extendsElements = extendsList.getReferenceElements();
220         if (extendsElements.length > 0) {
221           return false;
222         }
223       }
224       final PsiClassInitializer[] initializers = aClass.getInitializers();
225       if (initializers.length > 0) {
226         return false;
227       }
228       return allMethodsPublicAbstract(aClass) && allFieldsPublicStaticFinal(aClass) && allInnerClassesPublic(aClass);
229     }
230
231     private boolean allFieldsPublicStaticFinal(PsiClass aClass) {
232       boolean allFieldsStaticFinal = true;
233       final PsiField[] fields = aClass.getFields();
234       for (final PsiField field : fields) {
235         if (!(field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL)
236               && field.hasModifierProperty(PsiModifier.PUBLIC))) {
237           allFieldsStaticFinal = false;
238         }
239       }
240       return allFieldsStaticFinal;
241     }
242
243     private boolean allMethodsPublicAbstract(PsiClass aClass) {
244       final PsiMethod[] methods = aClass.getMethods();
245       for (final PsiMethod method : methods) {
246         if (!method.hasModifierProperty(PsiModifier.ABSTRACT) &&
247             (!reportClassesWithNonAbstractMethods || !PsiUtil.isLanguageLevel8OrHigher(aClass))) {
248           return false;
249         }
250         else if (!method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.FINAL)) {
251           return false;
252         }
253       }
254       return true;
255     }
256
257     private boolean allInnerClassesPublic(PsiClass aClass) {
258       final PsiClass[] innerClasses = aClass.getInnerClasses();
259       for (PsiClass innerClass : innerClasses) {
260         if (!innerClass.hasModifierProperty(PsiModifier.PUBLIC)) {
261           return false;
262         }
263       }
264       return true;
265     }
266   }
267 }