update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInspection / localCanBeFinal / LocalCanBeFinal.java
blob88582e71e8d501a7a6401b065e016bb103bc73f0
1 /*
2 * Copyright 2000-2009 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.localCanBeFinal;
18 import com.intellij.codeInsight.daemon.GroupNames;
19 import com.intellij.codeInspection.*;
20 import com.intellij.codeInspection.ex.BaseLocalInspectionTool;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
24 import com.intellij.psi.*;
25 import com.intellij.psi.controlFlow.*;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.PsiUtilBase;
28 import com.intellij.psi.util.PsiUtil;
29 import com.intellij.util.IncorrectOperationException;
30 import org.jetbrains.annotations.NonNls;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
34 import javax.swing.*;
35 import javax.swing.event.ChangeEvent;
36 import javax.swing.event.ChangeListener;
37 import java.awt.*;
38 import java.util.*;
39 import java.util.List;
41 /**
42 * @author max
44 public class LocalCanBeFinal extends BaseLocalInspectionTool {
45 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.localCanBeFinal.LocalCanBeFinal");
47 public boolean REPORT_VARIABLES = true;
48 public boolean REPORT_PARAMETERS = true;
50 private final LocalQuickFix myQuickFix;
51 @NonNls public static final String SHORT_NAME = "LocalCanBeFinal";
53 public LocalCanBeFinal() {
54 myQuickFix = new AcceptSuggested();
57 public ProblemDescriptor[] checkMethod(@NotNull PsiMethod method, @NotNull InspectionManager manager, boolean isOnTheFly) {
58 List<ProblemDescriptor> list = checkCodeBlock(method.getBody(), manager);
59 return list == null ? null : list.toArray(new ProblemDescriptor[list.size()]);
62 public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
63 List<ProblemDescriptor> allProblems = null;
64 final PsiClassInitializer[] initializers = aClass.getInitializers();
65 for (PsiClassInitializer initializer : initializers) {
66 final List<ProblemDescriptor> problems = checkCodeBlock(initializer.getBody(), manager);
67 if (problems != null) {
68 if (allProblems == null) {
69 allProblems = new ArrayList<ProblemDescriptor>(1);
71 allProblems.addAll(problems);
74 return allProblems == null ? null : allProblems.toArray(new ProblemDescriptor[allProblems.size()]);
77 @Nullable
78 private List<ProblemDescriptor> checkCodeBlock(final PsiCodeBlock body, InspectionManager manager) {
79 if (body == null) return null;
80 final ControlFlow flow;
81 try {
82 ControlFlowPolicy policy = new ControlFlowPolicy() {
83 public PsiVariable getUsedVariable(PsiReferenceExpression refExpr) {
84 if (refExpr.isQualified()) return null;
86 PsiElement refElement = refExpr.resolve();
87 if (refElement instanceof PsiLocalVariable || refElement instanceof PsiParameter) {
88 if (!isVariableDeclaredInMethod((PsiVariable)refElement)) return null;
89 return (PsiVariable)refElement;
92 return null;
95 public boolean isParameterAccepted(PsiParameter psiParameter) {
96 return isVariableDeclaredInMethod(psiParameter);
99 public boolean isLocalVariableAccepted(PsiLocalVariable psiVariable) {
100 return isVariableDeclaredInMethod(psiVariable);
103 private boolean isVariableDeclaredInMethod(PsiVariable psiVariable) {
104 return PsiTreeUtil.getParentOfType(psiVariable, PsiClass.class) == PsiTreeUtil.getParentOfType(body, PsiClass.class);
107 flow = ControlFlowFactory.getInstance(body.getProject()).getControlFlow(body, policy, false);
109 catch (AnalysisCanceledException e) {
110 return null;
113 int start = flow.getStartOffset(body);
114 int end = flow.getEndOffset(body);
116 final List<PsiVariable> writtenVariables = new ArrayList<PsiVariable>(ControlFlowUtil.getWrittenVariables(flow, start, end, false));
118 final HashSet<PsiVariable> ssaVarsSet = new HashSet<PsiVariable>();
119 body.accept(new JavaRecursiveElementWalkingVisitor() {
120 @Override public void visitCodeBlock(PsiCodeBlock block) {
121 super.visitCodeBlock(block);
122 PsiElement anchor = block;
123 if (block.getParent() instanceof PsiSwitchStatement) {
124 anchor = block.getParent();
126 int from = flow.getStartOffset(anchor);
127 int end = flow.getEndOffset(anchor);
128 List<PsiVariable> ssa = ControlFlowUtil.getSSAVariables(flow, from, end, true);
129 HashSet<PsiElement> declared = getDeclaredVariables(block);
130 for (PsiVariable psiVariable : ssa) {
131 if (declared.contains(psiVariable)) {
132 ssaVarsSet.add(psiVariable);
137 @Override public void visitForeachStatement(PsiForeachStatement statement) {
138 super.visitForeachStatement(statement);
139 final PsiParameter param = statement.getIterationParameter();
140 final PsiStatement body = statement.getBody();
141 int from = flow.getStartOffset(body);
142 int end = flow.getEndOffset(body);
143 if (!ControlFlowUtil.getWrittenVariables(flow, from, end, false).contains(param)) {
144 writtenVariables.remove(param);
145 ssaVarsSet.add(param);
149 private HashSet<PsiElement> getDeclaredVariables(PsiCodeBlock block) {
150 final HashSet<PsiElement> result = new HashSet<PsiElement>();
151 PsiElement[] children = block.getChildren();
152 for (PsiElement child : children) {
153 child.accept(new JavaElementVisitor() {
154 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
155 visitReferenceElement(expression);
158 @Override public void visitDeclarationStatement(PsiDeclarationStatement statement) {
159 PsiElement[] declaredElements = statement.getDeclaredElements();
160 for (PsiElement declaredElement : declaredElements) {
161 if (declaredElement instanceof PsiVariable) result.add(declaredElement);
167 return result;
170 @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
174 ArrayList<PsiVariable> result = new ArrayList<PsiVariable>(ssaVarsSet);
176 if (body.getParent() instanceof PsiMethod) {
177 PsiMethod method = (PsiMethod)body.getParent();
178 PsiParameter[] parameters = method.getParameterList().getParameters();
179 for (PsiParameter parameter : parameters) {
180 if (!result.contains(parameter)) result.add(parameter);
184 PsiVariable[] psiVariables = result.toArray(new PsiVariable[result.size()]);
185 for (PsiVariable psiVariable : psiVariables) {
186 if (!isReportParameters() && psiVariable instanceof PsiParameter || !isReportVariables() && psiVariable instanceof PsiLocalVariable ||
187 psiVariable.hasModifierProperty(PsiModifier.FINAL)) {
188 result.remove(psiVariable);
191 if (psiVariable instanceof PsiLocalVariable) {
192 PsiDeclarationStatement decl = (PsiDeclarationStatement)psiVariable.getParent();
193 if (decl != null && decl.getParent() instanceof PsiForStatement) {
194 result.remove(psiVariable);
199 for (PsiVariable writtenVariable : writtenVariables) {
200 if (writtenVariable instanceof PsiParameter) {
201 result.remove(writtenVariable);
205 if (result.isEmpty()) return null;
206 for (Iterator<PsiVariable> iterator = result.iterator(); iterator.hasNext();) {
207 final PsiVariable variable = iterator.next();
208 if (!variable.isPhysical()){
209 iterator.remove();
212 List<ProblemDescriptor> problems = new ArrayList<ProblemDescriptor>(result.size());
213 for (PsiVariable variable : result) {
214 final PsiIdentifier nameIdenitier = variable.getNameIdentifier();
215 PsiElement problemElement = nameIdenitier != null ? nameIdenitier : variable;
216 if (variable instanceof PsiParameter && !(((PsiParameter)variable).getDeclarationScope() instanceof PsiForeachStatement)) {
217 problems.add(manager.createProblemDescriptor(problemElement,
218 InspectionsBundle.message("inspection.can.be.local.parameter.problem.descriptor"),
219 myQuickFix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING));
221 else {
222 problems.add(manager.createProblemDescriptor(problemElement,
223 InspectionsBundle.message("inspection.can.be.local.variable.problem.descriptor"),
224 myQuickFix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING));
228 return problems;
231 @NotNull
232 public String getDisplayName() {
233 return InspectionsBundle.message("inspection.local.can.be.final.display.name");
236 @NotNull
237 public String getGroupDisplayName() {
238 return GroupNames.STYLE_GROUP_NAME;
241 @NotNull
242 public String getShortName() {
243 return SHORT_NAME;
246 private static class AcceptSuggested implements LocalQuickFix {
247 @NotNull
248 public String getName() {
249 return InspectionsBundle.message("inspection.can.be.final.accept.quickfix");
252 public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problem) {
253 if (ReadonlyStatusHandler.getInstance(project)
254 .ensureFilesWritable(PsiUtilBase.getVirtualFile(problem.getPsiElement())).hasReadonlyFiles()) return;
255 PsiElement nameIdentifier = problem.getPsiElement();
256 if (nameIdentifier == null) return;
257 PsiVariable psiVariable = (PsiVariable)nameIdentifier.getParent();
258 if (psiVariable == null) return;
259 try {
260 psiVariable.normalizeDeclaration();
261 PsiUtil.setModifierProperty(psiVariable, PsiModifier.FINAL, true);
263 catch (IncorrectOperationException e) {
264 LOG.error(e);
268 @NotNull
269 public String getFamilyName() {
270 return getName();
274 public JComponent createOptionsPanel() {
275 return new OptionsPanel();
278 private boolean isReportVariables() {
279 return REPORT_VARIABLES;
282 private boolean isReportParameters() {
283 return REPORT_PARAMETERS;
286 private class OptionsPanel extends JPanel {
287 private final JCheckBox myReportVariablesCheckbox;
288 private final JCheckBox myReportParametersCheckbox;
290 private OptionsPanel() {
291 super(new GridBagLayout());
293 GridBagConstraints gc = new GridBagConstraints();
294 gc.weighty = 0;
295 gc.weightx = 1;
296 gc.fill = GridBagConstraints.HORIZONTAL;
297 gc.anchor = GridBagConstraints.NORTHWEST;
300 myReportVariablesCheckbox = new JCheckBox(InspectionsBundle.message("inspection.local.can.be.final.option"));
301 myReportVariablesCheckbox.setSelected(REPORT_VARIABLES);
302 myReportVariablesCheckbox.getModel().addChangeListener(new ChangeListener() {
303 public void stateChanged(ChangeEvent e) {
304 REPORT_VARIABLES = myReportVariablesCheckbox.isSelected();
307 gc.gridy = 0;
308 add(myReportVariablesCheckbox, gc);
310 myReportParametersCheckbox = new JCheckBox(InspectionsBundle.message("inspection.local.can.be.final.option1"));
311 myReportParametersCheckbox.setSelected(REPORT_PARAMETERS);
312 myReportParametersCheckbox.getModel().addChangeListener(new ChangeListener() {
313 public void stateChanged(ChangeEvent e) {
314 REPORT_PARAMETERS = myReportParametersCheckbox.isSelected();
318 gc.weighty = 1;
319 gc.gridy++;
320 add(myReportParametersCheckbox, gc);
324 public boolean isEnabledByDefault() {
325 return false;