EA-30271 - NPE: CanBeFinalInspection.checkElement
[idea/community.git] / java / java-impl / src / com / intellij / codeInspection / canBeFinal / CanBeFinalInspection.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
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
17 /*
18  * Created by IntelliJ IDEA.
19  * User: max
20  * Date: Dec 24, 2001
21  * Time: 2:46:32 PM
22  * To change template for new class use
23  * Code Style | Class Templates options (Tools | IDE Options).
24  */
25 package com.intellij.codeInspection.canBeFinal;
26
27 import com.intellij.analysis.AnalysisScope;
28 import com.intellij.codeInsight.CodeInsightUtilBase;
29 import com.intellij.codeInsight.daemon.GroupNames;
30 import com.intellij.codeInspection.*;
31 import com.intellij.codeInspection.reference.*;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.psi.*;
35 import com.intellij.psi.util.PsiTreeUtil;
36 import com.intellij.psi.util.PsiUtil;
37 import com.intellij.util.IncorrectOperationException;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import javax.swing.*;
43 import javax.swing.event.ChangeEvent;
44 import javax.swing.event.ChangeListener;
45 import java.awt.*;
46
47 public class CanBeFinalInspection extends GlobalJavaInspectionTool {
48   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.canBeFinal.CanBeFinalInspection");
49
50   public boolean REPORT_CLASSES = false;
51   public boolean REPORT_METHODS = false;
52   public boolean REPORT_FIELDS = true;
53   public static final String DISPLAY_NAME = InspectionsBundle.message("inspection.can.be.final.display.name");
54   @NonNls public static final String SHORT_NAME = "CanBeFinal";
55   @NonNls private static final String QUICK_FIX_NAME = InspectionsBundle.message("inspection.can.be.final.accept.quickfix");
56
57   private class OptionsPanel extends JPanel {
58     private final JCheckBox myReportClassesCheckbox;
59     private final JCheckBox myReportMethodsCheckbox;
60     private final JCheckBox myReportFieldsCheckbox;
61
62     private OptionsPanel() {
63       super(new GridBagLayout());
64
65       GridBagConstraints gc = new GridBagConstraints();
66       gc.weighty = 0;
67       gc.weightx = 1;
68       gc.fill = GridBagConstraints.HORIZONTAL;
69       gc.anchor = GridBagConstraints.NORTHWEST;
70
71
72       myReportClassesCheckbox = new JCheckBox(InspectionsBundle.message("inspection.can.be.final.option"));
73       myReportClassesCheckbox.setSelected(REPORT_CLASSES);
74       myReportClassesCheckbox.getModel().addChangeListener(new ChangeListener() {
75         public void stateChanged(ChangeEvent e) {
76           REPORT_CLASSES = myReportClassesCheckbox.isSelected();
77         }
78       });
79       gc.gridy = 0;
80       add(myReportClassesCheckbox, gc);
81
82       myReportMethodsCheckbox = new JCheckBox(InspectionsBundle.message("inspection.can.be.final.option1"));
83       myReportMethodsCheckbox.setSelected(REPORT_METHODS);
84       myReportMethodsCheckbox.getModel().addChangeListener(new ChangeListener() {
85         public void stateChanged(ChangeEvent e) {
86           REPORT_METHODS = myReportMethodsCheckbox.isSelected();
87         }
88       });
89       gc.gridy++;
90       add(myReportMethodsCheckbox, gc);
91
92       myReportFieldsCheckbox = new JCheckBox(InspectionsBundle.message("inspection.can.be.final.option2"));
93       myReportFieldsCheckbox.setSelected(REPORT_FIELDS);
94       myReportFieldsCheckbox.getModel().addChangeListener(new ChangeListener() {
95         public void stateChanged(ChangeEvent e) {
96           REPORT_FIELDS = myReportFieldsCheckbox.isSelected();
97         }
98       });
99
100       gc.weighty = 1;
101       gc.gridy++;
102       add(myReportFieldsCheckbox, gc);
103     }
104   }
105
106   public boolean isReportClasses() {
107     return REPORT_CLASSES;
108   }
109
110   public boolean isReportMethods() {
111     return REPORT_METHODS;
112   }
113
114   public boolean isReportFields() {
115     return REPORT_FIELDS;
116   }
117
118   public JComponent createOptionsPanel() {
119     return new OptionsPanel();
120   }
121
122   @Nullable
123   public RefGraphAnnotator getAnnotator(final RefManager refManager) {
124     return new CanBeFinalAnnotator(refManager);
125   }
126
127
128   @Nullable
129   public CommonProblemDescriptor[] checkElement(final RefEntity refEntity,
130                                                 final AnalysisScope scope,
131                                                 final InspectionManager manager,
132                                                 final GlobalInspectionContext globalContext,
133                                                 final ProblemDescriptionsProcessor processor) {
134     if (refEntity instanceof RefJavaElement) {
135       final RefJavaElement refElement = (RefJavaElement)refEntity;
136       if (refElement instanceof RefParameter) return null;
137       if (!refElement.isReferenced()) return null;
138       if (refElement.isSyntheticJSP()) return null;
139       if (refElement.isFinal()) return null;
140       if (!((RefElementImpl)refElement).checkFlag(CanBeFinalAnnotator.CAN_BE_FINAL_MASK)) return null;
141
142       final PsiMember psiMember = (PsiMember)refElement.getElement();
143       if (psiMember == null || !CanBeFinalHandler.allowToBeFinal(psiMember)) return null;
144
145       PsiIdentifier psiIdentifier = null;
146       if (refElement instanceof RefClass) {
147         RefClass refClass = (RefClass)refElement;
148         if (refClass.isInterface() || refClass.isAnonymous() || refClass.isAbstract()) return null;
149         if (!isReportClasses()) return null;
150         psiIdentifier = ((PsiClass)psiMember).getNameIdentifier();
151       }
152       else if (refElement instanceof RefMethod) {
153         RefMethod refMethod = (RefMethod)refElement;
154         if (refMethod.getOwnerClass().isFinal()) return null;
155         if (!isReportMethods()) return null;
156         psiIdentifier = ((PsiMethod)psiMember).getNameIdentifier();
157       }
158       else if (refElement instanceof RefField) {
159         if (!isReportFields()) return null;
160         psiIdentifier = ((PsiField)psiMember).getNameIdentifier();
161       }
162
163
164       if (psiIdentifier != null) {
165         return new ProblemDescriptor[]{manager.createProblemDescriptor(psiIdentifier, InspectionsBundle.message(
166           "inspection.export.results.can.be.final.description"), new AcceptSuggested(globalContext.getRefManager()),
167                                                                  ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)};
168       }
169     }
170     return null;
171   }
172
173   protected boolean queryExternalUsagesRequests(final RefManager manager, final GlobalJavaInspectionContext globalContext,
174                                                 final ProblemDescriptionsProcessor problemsProcessor) {
175     for (RefElement entryPoint : globalContext.getEntryPointsManager(manager).getEntryPoints()) {
176       problemsProcessor.ignoreElement(entryPoint);
177     }
178
179     manager.iterate(new RefJavaVisitor() {
180       @Override public void visitElement(RefEntity refEntity) {
181         if (problemsProcessor.getDescriptions(refEntity) == null) return;
182         refEntity.accept(new RefJavaVisitor() {
183           @Override public void visitMethod(final RefMethod refMethod) {
184             if (!refMethod.isStatic() && !PsiModifier.PRIVATE.equals(refMethod.getAccessModifier()) &&
185                 !(refMethod instanceof RefImplicitConstructor)) {
186               globalContext.enqueueDerivedMethodsProcessor(refMethod, new GlobalJavaInspectionContext.DerivedMethodsProcessor() {
187                 public boolean process(PsiMethod derivedMethod) {
188                   ((RefElementImpl)refMethod).setFlag(false, CanBeFinalAnnotator.CAN_BE_FINAL_MASK);
189                   problemsProcessor.ignoreElement(refMethod);
190                   return false;
191                 }
192               });
193             }
194           }
195
196           @Override public void visitClass(final RefClass refClass) {
197             if (!refClass.isAnonymous()) {
198               globalContext.enqueueDerivedClassesProcessor(refClass, new GlobalJavaInspectionContext.DerivedClassesProcessor() {
199                 public boolean process(PsiClass inheritor) {
200                   ((RefClassImpl)refClass).setFlag(false, CanBeFinalAnnotator.CAN_BE_FINAL_MASK);
201                   problemsProcessor.ignoreElement(refClass);
202                   return false;
203                 }
204               });
205             }
206           }
207
208           @Override public void visitField(final RefField refField) {
209             globalContext.enqueueFieldUsagesProcessor(refField, new GlobalJavaInspectionContext.UsagesProcessor() {
210               public boolean process(PsiReference psiReference) {
211                 PsiElement expression = psiReference.getElement();
212                 if (expression instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression)expression)) {
213                   ((RefFieldImpl)refField).setFlag(false, CanBeFinalAnnotator.CAN_BE_FINAL_MASK);
214                   problemsProcessor.ignoreElement(refField);
215                   return false;
216                 }
217                 return true;
218               }
219             });
220           }
221         });
222
223       }
224     });
225
226     return false;
227   }
228
229
230   @Nullable
231   public QuickFix getQuickFix(final String hint) {
232     return new AcceptSuggested(null);
233   }
234
235   @NotNull
236   public String getDisplayName() {
237     return DISPLAY_NAME;
238   }
239
240   @NotNull
241   public String getGroupDisplayName() {
242     return GroupNames.DECLARATION_REDUNDANCY;
243   }
244
245   @NotNull
246   public String getShortName() {
247     return SHORT_NAME;
248   }
249
250   private static class AcceptSuggested implements LocalQuickFix {
251     private final RefManager myManager;
252
253     public AcceptSuggested(final RefManager manager) {
254       myManager = manager;
255     }
256
257     @NotNull
258     public String getName() {
259       return QUICK_FIX_NAME;
260     }
261
262     @NotNull
263     public String getFamilyName() {
264       return getName();
265     }
266
267     public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
268       if (!CodeInsightUtilBase.preparePsiElementForWrite(descriptor.getPsiElement())) return;
269       final PsiElement element = descriptor.getPsiElement();
270       final PsiModifierListOwner psiElement = PsiTreeUtil.getParentOfType(element, PsiModifierListOwner.class);
271       if (psiElement != null) {
272         RefJavaElement refElement = (RefJavaElement)(myManager != null ? myManager.getReference(psiElement) : null);
273         try {
274           if (psiElement instanceof PsiVariable) {
275             ((PsiVariable)psiElement).normalizeDeclaration();
276           }
277           final PsiModifierList modifierList = psiElement.getModifierList();
278           LOG.assertTrue(modifierList != null);
279           modifierList.setModifierProperty(PsiModifier.FINAL, true);
280         }
281         catch (IncorrectOperationException e) {
282           LOG.error(e);
283         }
284
285         if (refElement != null) {
286           RefJavaUtil.getInstance().setIsFinal(refElement, true);
287         }
288       }
289     }
290   }
291
292 }