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
;
35 import javax
.swing
.event
.ChangeEvent
;
36 import javax
.swing
.event
.ChangeListener
;
39 import java
.util
.List
;
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()]);
78 private List
<ProblemDescriptor
> checkCodeBlock(final PsiCodeBlock body
, InspectionManager manager
) {
79 if (body
== null) return null;
80 final ControlFlow flow
;
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
;
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
) {
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
);
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()){
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
));
222 problems
.add(manager
.createProblemDescriptor(problemElement
,
223 InspectionsBundle
.message("inspection.can.be.local.variable.problem.descriptor"),
224 myQuickFix
, ProblemHighlightType
.GENERIC_ERROR_OR_WARNING
));
232 public String
getDisplayName() {
233 return InspectionsBundle
.message("inspection.local.can.be.final.display.name");
237 public String
getGroupDisplayName() {
238 return GroupNames
.STYLE_GROUP_NAME
;
242 public String
getShortName() {
246 private static class AcceptSuggested
implements LocalQuickFix
{
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;
260 psiVariable
.normalizeDeclaration();
261 PsiUtil
.setModifierProperty(psiVariable
, PsiModifier
.FINAL
, true);
263 catch (IncorrectOperationException e
) {
269 public String
getFamilyName() {
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();
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();
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();
320 add(myReportParametersCheckbox
, gc
);
324 public boolean isEnabledByDefault() {