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.
18 * Created by IntelliJ IDEA.
22 * To change template for new class use
23 * Code Style | Class Templates options (Tools | IDE Options).
26 package com
.intellij
.codeInspection
.deadCode
;
28 import com
.intellij
.ExtensionPoints
;
29 import com
.intellij
.ui
.SeparatorFactory
;
30 import com
.intellij
.analysis
.AnalysisScope
;
31 import com
.intellij
.codeInsight
.AnnotationUtil
;
32 import com
.intellij
.codeInsight
.daemon
.GroupNames
;
33 import com
.intellij
.codeInsight
.daemon
.ImplicitUsageProvider
;
34 import com
.intellij
.codeInsight
.intention
.IntentionAction
;
35 import com
.intellij
.codeInspection
.*;
36 import com
.intellij
.codeInspection
.ex
.*;
37 import com
.intellij
.codeInspection
.reference
.*;
38 import com
.intellij
.codeInspection
.ui
.EntryPointsNode
;
39 import com
.intellij
.codeInspection
.ui
.InspectionNode
;
40 import com
.intellij
.codeInspection
.ui
.InspectionTreeNode
;
41 import com
.intellij
.codeInspection
.util
.RefFilter
;
42 import com
.intellij
.codeInspection
.util
.SpecialAnnotationsUtil
;
43 import com
.intellij
.lang
.annotation
.HighlightSeverity
;
44 import com
.intellij
.openapi
.application
.ApplicationManager
;
45 import com
.intellij
.openapi
.editor
.Document
;
46 import com
.intellij
.openapi
.editor
.Editor
;
47 import com
.intellij
.openapi
.extensions
.ExtensionPoint
;
48 import com
.intellij
.openapi
.extensions
.Extensions
;
49 import com
.intellij
.openapi
.progress
.ProgressManager
;
50 import com
.intellij
.openapi
.project
.Project
;
51 import com
.intellij
.openapi
.util
.*;
52 import com
.intellij
.psi
.*;
53 import com
.intellij
.psi
.impl
.PsiClassImplUtil
;
54 import com
.intellij
.psi
.search
.GlobalSearchScope
;
55 import com
.intellij
.psi
.search
.PsiNonJavaFileReferenceProcessor
;
56 import com
.intellij
.psi
.search
.PsiSearchHelper
;
57 import com
.intellij
.psi
.util
.PsiMethodUtil
;
58 import com
.intellij
.psi
.util
.PsiTreeUtil
;
59 import com
.intellij
.refactoring
.safeDelete
.SafeDeleteHandler
;
60 import com
.intellij
.util
.IncorrectOperationException
;
61 import com
.intellij
.util
.containers
.HashMap
;
62 import com
.intellij
.util
.text
.CharArrayUtil
;
63 import org
.jdom
.Element
;
64 import org
.jetbrains
.annotations
.NonNls
;
65 import org
.jetbrains
.annotations
.NotNull
;
66 import org
.jetbrains
.annotations
.Nullable
;
70 import java
.awt
.event
.ActionEvent
;
71 import java
.awt
.event
.ActionListener
;
72 import java
.awt
.event
.InputEvent
;
73 import java
.awt
.event
.KeyEvent
;
74 import java
.text
.SimpleDateFormat
;
76 import java
.util
.List
;
78 public class UnusedDeclarationInspection
extends FilteringInspectionTool
{
79 public boolean ADD_MAINS_TO_ENTRIES
= true;
81 public boolean ADD_APPLET_TO_ENTRIES
= true;
82 public boolean ADD_SERVLET_TO_ENTRIES
= true;
83 public boolean ADD_NONJAVA_TO_ENTRIES
= true;
85 public JDOMExternalizableStringList ADDITIONAL_ANNOTATIONS
= new JDOMExternalizableStringList();
86 @NonNls private static final String
[] ADDITIONAL_ANNOS
= {
90 private HashSet
<RefElement
> myProcessedSuspicious
= null;
92 private final QuickFixAction
[] myQuickFixActions
;
93 public static final String DISPLAY_NAME
= InspectionsBundle
.message("inspection.dead.code.display.name");
94 private WeakUnreferencedFilter myFilter
;
95 private DeadHTMLComposer myComposer
;
96 @NonNls public static final String SHORT_NAME
= "UnusedDeclaration";
98 private static final String COMMENT_OUT_QUICK_FIX
= InspectionsBundle
.message("inspection.dead.code.comment.quickfix");
99 private static final String DELETE_QUICK_FIX
= InspectionsBundle
.message("inspection.dead.code.safe.delete.quickfix");
101 @NonNls private static final String DELETE
= "delete";
102 @NonNls private static final String COMMENT
= "comment";
103 @NonNls private static final String
[] HINTS
= {COMMENT
, DELETE
};
105 public final UnusedCodeExtension
[] myExtensions
;
107 public UnusedDeclarationInspection() {
108 ADDITIONAL_ANNOTATIONS
.addAll(Arrays
.asList(ADDITIONAL_ANNOS
));
109 myQuickFixActions
= new QuickFixAction
[]{new PermanentDeleteAction(), new CommentOutBin(), new MoveToEntries()};
110 ExtensionPoint
<UnusedCodeExtension
> point
= Extensions
.getRootArea().getExtensionPoint(ExtensionPoints
.DEAD_CODE_TOOL
);
111 final UnusedCodeExtension
[] deadCodeAddins
= point
.getExtensions();
112 Arrays
.sort(deadCodeAddins
, new Comparator
<UnusedCodeExtension
>() {
113 public int compare(final UnusedCodeExtension o1
, final UnusedCodeExtension o2
) {
114 return o1
.getDisplayName().compareToIgnoreCase(o2
.getDisplayName());
117 myExtensions
= deadCodeAddins
;
120 public void initialize(@NotNull final GlobalInspectionContextImpl context
) {
121 super.initialize(context
);
122 ((EntryPointsManagerImpl
)getEntryPointsManager()).setAddNonJavaEntries(ADD_NONJAVA_TO_ENTRIES
);
125 private class OptionsPanel
extends JPanel
{
126 private final JCheckBox myMainsCheckbox
;
127 private final JCheckBox myAppletToEntries
;
128 private final JCheckBox myServletToEntries
;
129 private final JCheckBox myNonJavaCheckbox
;
131 private OptionsPanel() {
132 super(new GridBagLayout());
133 GridBagConstraints gc
= new GridBagConstraints();
136 gc
.fill
= GridBagConstraints
.HORIZONTAL
;
137 gc
.anchor
= GridBagConstraints
.NORTHWEST
;
139 myMainsCheckbox
= new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option"));
140 myMainsCheckbox
.setSelected(ADD_MAINS_TO_ENTRIES
);
141 myMainsCheckbox
.addActionListener(new ActionListener() {
142 public void actionPerformed(ActionEvent e
) {
143 ADD_MAINS_TO_ENTRIES
= myMainsCheckbox
.isSelected();
148 add(myMainsCheckbox
, gc
);
150 myAppletToEntries
= new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option3"));
151 myAppletToEntries
.setSelected(ADD_APPLET_TO_ENTRIES
);
152 myAppletToEntries
.addActionListener(new ActionListener() {
153 public void actionPerformed(ActionEvent e
) {
154 ADD_APPLET_TO_ENTRIES
= myAppletToEntries
.isSelected();
158 add(myAppletToEntries
, gc
);
160 myServletToEntries
= new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option4"));
161 myServletToEntries
.setSelected(ADD_SERVLET_TO_ENTRIES
);
162 myServletToEntries
.addActionListener(new ActionListener(){
163 public void actionPerformed(ActionEvent e
) {
164 ADD_SERVLET_TO_ENTRIES
= myServletToEntries
.isSelected();
168 add(myServletToEntries
, gc
);
170 for (final UnusedCodeExtension extension
: myExtensions
) {
171 if (extension
.showUI()) {
172 final JCheckBox extCheckbox
= new JCheckBox(extension
.getDisplayName());
173 extCheckbox
.setSelected(extension
.isSelected());
174 extCheckbox
.addActionListener(new ActionListener() {
175 public void actionPerformed(ActionEvent e
) {
176 extension
.setSelected(extCheckbox
.isSelected());
180 add(extCheckbox
, gc
);
185 new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option5"));
186 myNonJavaCheckbox
.setSelected(ADD_NONJAVA_TO_ENTRIES
);
187 myNonJavaCheckbox
.addActionListener(new ActionListener() {
188 public void actionPerformed(ActionEvent e
) {
189 ADD_NONJAVA_TO_ENTRIES
= myNonJavaCheckbox
.isSelected();
194 add(myNonJavaCheckbox
, gc
);
196 final JPanel listPanel
= SpecialAnnotationsUtil
.createSpecialAnnotationsListControl(ADDITIONAL_ANNOTATIONS
, InspectionsBundle
.message(
197 "inspections.dead.code.entry.points.annotations.list.title"));
204 public JComponent
createOptionsPanel() {
205 final JPanel scrollPane
= new JPanel(new BorderLayout());
206 scrollPane
.add(SeparatorFactory
.createSeparator("Entry points", null), BorderLayout
.NORTH
);
207 scrollPane
.add(new OptionsPanel(), BorderLayout
.CENTER
);
211 private boolean isAddMainsEnabled() {
212 return ADD_MAINS_TO_ENTRIES
;
215 private boolean isAddAppletEnabled() {
216 return ADD_APPLET_TO_ENTRIES
;
219 private boolean isAddServletEnabled() {
220 return ADD_SERVLET_TO_ENTRIES
;
223 private boolean isAddNonJavaUsedEnabled() {
224 return ADD_NONJAVA_TO_ENTRIES
;
228 public String
getDisplayName() {
233 public String
getGroupDisplayName() {
234 return GroupNames
.DECLARATION_REDUNDANCY
;
238 public String
getShortName() {
242 public void readSettings(Element node
) throws InvalidDataException
{
243 super.readSettings(node
);
244 for (UnusedCodeExtension extension
: myExtensions
) {
245 extension
.readExternal(node
);
249 public void writeSettings(Element node
) throws WriteExternalException
{
250 super.writeSettings(node
);
251 for (UnusedCodeExtension extension
: myExtensions
) {
252 extension
.writeExternal(node
);
256 private static boolean isSerializationImplicitlyUsedField(PsiField field
) {
257 @NonNls final String name
= field
.getName();
258 if (!"serialVersionUID".equals(name
) && !"serialPersistentFields".equals(name
)) return false;
259 if (!field
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
260 PsiClass aClass
= field
.getContainingClass();
261 return aClass
== null || isSerializable(aClass
);
264 private static boolean isWriteObjectMethod(PsiMethod method
) {
265 @NonNls final String name
= method
.getName();
266 if (!"writeObject".equals(name
)) return false;
267 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
268 if (parameters
.length
!= 1) return false;
269 if (!parameters
[0].getType().equalsToText("java.io.ObjectOutputStream")) return false;
270 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
271 PsiClass aClass
= method
.getContainingClass();
272 return !(aClass
!= null && !isSerializable(aClass
));
275 private static boolean isReadObjectMethod(PsiMethod method
) {
276 @NonNls final String name
= method
.getName();
277 if (!"readObject".equals(name
)) return false;
278 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
279 if (parameters
.length
!= 1) return false;
280 if (!parameters
[0].getType().equalsToText("java.io.ObjectInputStream")) return false;
281 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
282 PsiClass aClass
= method
.getContainingClass();
283 return !(aClass
!= null && !isSerializable(aClass
));
286 private static boolean isWriteReplaceMethod(PsiMethod method
) {
287 @NonNls final String name
= method
.getName();
288 if (!"writeReplace".equals(name
)) return false;
289 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
290 if (parameters
.length
!= 0) return false;
291 if (!method
.getReturnType().equalsToText("java.lang.Object")) return false;
292 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
293 PsiClass aClass
= method
.getContainingClass();
294 return !(aClass
!= null && !isSerializable(aClass
));
297 private static boolean isReadResolveMethod(PsiMethod method
) {
298 @NonNls final String name
= method
.getName();
299 if (!"readResolve".equals(name
)) return false;
300 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
301 if (parameters
.length
!= 0) return false;
302 if (!method
.getReturnType().equalsToText("java.lang.Object")) return false;
303 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
304 PsiClass aClass
= method
.getContainingClass();
305 return !(aClass
!= null && !isSerializable(aClass
));
308 private static boolean isSerializable(PsiClass aClass
) {
309 PsiClass serializableClass
= JavaPsiFacade
.getInstance(aClass
.getProject()).findClass("java.io.Serializable", aClass
.getResolveScope());
310 return serializableClass
!= null && aClass
.isInheritor(serializableClass
, true);
313 public void runInspection(final AnalysisScope scope
, final InspectionManager manager
) {
314 getRefManager().iterate(new RefJavaVisitor() {
315 @Override public void visitElement(final RefEntity refEntity
) {
316 if (refEntity
instanceof RefJavaElement
) {
317 final RefElementImpl refElement
= (RefElementImpl
)refEntity
;
318 final PsiElement element
= refElement
.getElement();
319 if (element
== null) return;
320 final boolean isSuppressed
= refElement
.isSuppressed(getShortName());
321 if (!getContext().isToCheckMember(element
, UnusedDeclarationInspection
.this) || isSuppressed
) {
322 if (isSuppressed
|| !scope
.contains(element
)) {
323 getEntryPointsManager().addEntryPoint(refElement
, false);
327 if (!refElement
.isSuspicious()) return;
328 refElement
.accept(new RefJavaVisitor() {
329 @Override public void visitElement(final RefEntity elem
) {
330 if (elem
instanceof RefElement
) {
331 final RefElement element
= (RefElement
)elem
;
332 if (isEntryPoint(element
)) {
333 getEntryPointsManager().addEntryPoint(element
, false);
338 @Override public void visitMethod(RefMethod method
) {
339 if (isAddMainsEnabled() && method
.isAppMain()) {
340 getEntryPointsManager().addEntryPoint(method
, false);
342 super.visitMethod(method
);
346 @Override public void visitClass(RefClass aClass
) {
347 final PsiClass psiClass
= aClass
.getElement();
349 isAddAppletEnabled() && aClass
.isApplet() ||
350 isAddServletEnabled() && aClass
.isServlet()) {
351 getEntryPointsManager().addEntryPoint(aClass
, false);
352 } else if (psiClass
.isAnnotationType()){
353 getEntryPointsManager().addEntryPoint(aClass
, false);
354 final PsiMethod
[] psiMethods
= psiClass
.getMethods();
355 for (PsiMethod psiMethod
: psiMethods
) {
356 getEntryPointsManager().addEntryPoint(getRefManager().getReference(psiMethod
), false);
358 } else if (psiClass
.isEnum()) {
359 getEntryPointsManager().addEntryPoint(aClass
, false);
361 super.visitClass(aClass
);
369 if (isAddNonJavaUsedEnabled()) {
370 checkForReachables();
371 ProgressManager
.getInstance().runProcess(new Runnable() {
373 final RefFilter filter
= new StrictUnreferencedFilter(UnusedDeclarationInspection
.this);
374 final PsiSearchHelper helper
= PsiManager
.getInstance(getRefManager().getProject()).getSearchHelper();
375 getRefManager().iterate(new RefJavaVisitor() {
376 @Override public void visitElement(final RefEntity refEntity
) {
377 if (refEntity
instanceof RefClass
&& filter
.accepts((RefClass
)refEntity
)) {
378 findExternalClassReferences((RefClass
)refEntity
);
380 else if (refEntity
instanceof RefMethod
) {
381 RefMethod refMethod
= (RefMethod
)refEntity
;
382 if (refMethod
.isConstructor() && filter
.accepts(refMethod
)) {
383 findExternalClassReferences(refMethod
.getOwnerClass());
388 private void findExternalClassReferences(final RefClass refElement
) {
389 PsiClass psiClass
= refElement
.getElement();
390 String qualifiedName
= psiClass
.getQualifiedName();
391 if (qualifiedName
!= null) {
392 helper
.processUsagesInNonJavaFiles(qualifiedName
,
393 new PsiNonJavaFileReferenceProcessor() {
394 public boolean process(PsiFile file
, int startOffset
, int endOffset
) {
395 getEntryPointsManager().addEntryPoint(refElement
, false);
399 GlobalSearchScope
.projectScope(getContext().getProject()));
407 myProcessedSuspicious
= new HashSet
<RefElement
>();
411 private boolean isEntryPoint(final RefElement owner
) {
412 if (RefUtil
.isEntryPoint(owner
)) return true;
413 final PsiElement element
= owner
.getElement();
414 if (element
instanceof PsiModifierListOwner
415 && AnnotationUtil
.isAnnotated((PsiModifierListOwner
)element
, ADDITIONAL_ANNOTATIONS
)) {
418 for (UnusedCodeExtension extension
: myExtensions
) {
419 if (extension
.isEntryPoint(owner
)) {
426 public boolean isEntryPoint(@NotNull PsiElement element
) {
427 final Project project
= element
.getProject();
428 final JavaPsiFacade psiFacade
= JavaPsiFacade
.getInstance(project
);
429 if (element
instanceof PsiMethod
&& isAddMainsEnabled() && PsiClassImplUtil
.isMainMethod((PsiMethod
)element
)) {
432 if (element
instanceof PsiClass
) {
433 PsiClass aClass
= (PsiClass
)element
;
434 if (aClass
.isAnnotationType()) {
438 if (aClass
.isEnum()) {
441 final PsiClass applet
= psiFacade
.findClass("java.applet.Applet", GlobalSearchScope
.allScope(project
));
442 if (isAddAppletEnabled() && applet
!= null && aClass
.isInheritor(applet
, true)) {
446 final PsiClass servlet
= psiFacade
.findClass("javax.servlet.Servlet", GlobalSearchScope
.allScope(project
));
447 if (isAddServletEnabled() && servlet
!= null && aClass
.isInheritor(servlet
, true)) {
450 if (isAddMainsEnabled() && PsiMethodUtil
.hasMainMethod(aClass
)) return true;
452 if (element
instanceof PsiModifierListOwner
453 && AnnotationUtil
.checkAnnotatedUsingPatterns((PsiModifierListOwner
)element
, ADDITIONAL_ANNOTATIONS
)) {
456 for (UnusedCodeExtension extension
: myExtensions
) {
457 if (extension
.isEntryPoint(element
)) {
461 final ImplicitUsageProvider
[] implicitUsageProviders
= Extensions
.getExtensions(ImplicitUsageProvider
.EP_NAME
);
462 for (ImplicitUsageProvider provider
: implicitUsageProviders
) {
463 if (provider
.isImplicitUsage(element
)) return true;
468 private static class StrictUnreferencedFilter
extends UnreferencedFilter
{
469 private StrictUnreferencedFilter(final InspectionTool tool
) {
473 public int getElementProblemCount(RefJavaElement refElement
) {
474 final int problemCount
= super.getElementProblemCount(refElement
);
475 if (problemCount
> -1) return problemCount
;
476 return refElement
.isReferenced() ?
0 : 1;
480 private static class WeakUnreferencedFilter
extends UnreferencedFilter
{
481 private WeakUnreferencedFilter(final InspectionTool tool
) {
485 public int getElementProblemCount(final RefJavaElement refElement
) {
486 final int problemCount
= super.getElementProblemCount(refElement
);
487 if (problemCount
> - 1) return problemCount
;
488 if (!((RefElementImpl
)refElement
).hasSuspiciousCallers() || ((RefJavaElementImpl
)refElement
).isSuspiciousRecursive()) return 1;
493 public boolean queryExternalUsagesRequests(final InspectionManager manager
) {
494 checkForReachables();
495 final RefFilter filter
= myPhase
== 1 ?
new StrictUnreferencedFilter(this) : new RefUnreachableFilter(this);
496 final boolean[] requestAdded
= {false};
498 getRefManager().iterate(new RefJavaVisitor() {
499 @Override public void visitElement(RefEntity refEntity
) {
500 if (!(refEntity
instanceof RefJavaElement
)) return;
501 if (refEntity
instanceof RefClass
&& ((RefClass
)refEntity
).isAnonymous()) return;
502 RefJavaElement refElement
= (RefJavaElement
)refEntity
;
503 if (filter
.accepts(refElement
) && !myProcessedSuspicious
.contains(refElement
)) {
504 refEntity
.accept(new RefJavaVisitor() {
505 @Override public void visitField(final RefField refField
) {
506 myProcessedSuspicious
.add(refField
);
507 PsiField psiField
= refField
.getElement();
508 if (isSerializationImplicitlyUsedField(psiField
)) {
509 getEntryPointsManager().addEntryPoint(refField
, false);
512 getJavaContext().enqueueFieldUsagesProcessor(refField
, new GlobalJavaInspectionContext
.UsagesProcessor() {
513 public boolean process(PsiReference psiReference
) {
514 getEntryPointsManager().addEntryPoint(refField
, false);
518 requestAdded
[0] = true;
522 @Override public void visitMethod(final RefMethod refMethod
) {
523 myProcessedSuspicious
.add(refMethod
);
524 if (refMethod
instanceof RefImplicitConstructor
) {
525 visitClass(refMethod
.getOwnerClass());
528 PsiMethod psiMethod
= (PsiMethod
)refMethod
.getElement();
529 if (isSerializablePatternMethod(psiMethod
)) {
530 getEntryPointsManager().addEntryPoint(refMethod
, false);
532 else if (!refMethod
.isExternalOverride() && !PsiModifier
.PRIVATE
.equals(refMethod
.getAccessModifier())) {
533 for (final RefMethod derivedMethod
: refMethod
.getDerivedMethods()) {
534 myProcessedSuspicious
.add(derivedMethod
);
537 enqueueMethodUsages(refMethod
);
538 requestAdded
[0] = true;
543 @Override public void visitClass(final RefClass refClass
) {
544 myProcessedSuspicious
.add(refClass
);
545 if (!refClass
.isAnonymous()) {
546 getJavaContext().enqueueDerivedClassesProcessor(refClass
, new GlobalJavaInspectionContext
.DerivedClassesProcessor() {
547 public boolean process(PsiClass inheritor
) {
548 getEntryPointsManager().addEntryPoint(refClass
, false);
553 getJavaContext().enqueueClassUsagesProcessor(refClass
, new GlobalJavaInspectionContext
.UsagesProcessor() {
554 public boolean process(PsiReference psiReference
) {
555 getEntryPointsManager().addEntryPoint(refClass
, false);
559 requestAdded
[0] = true;
567 if (!requestAdded
[0]) {
569 myProcessedSuspicious
= null;
580 private static boolean isSerializablePatternMethod(PsiMethod psiMethod
) {
581 return isReadObjectMethod(psiMethod
) || isWriteObjectMethod(psiMethod
) || isReadResolveMethod(psiMethod
) ||
582 isWriteReplaceMethod(psiMethod
);
585 private void enqueueMethodUsages(final RefMethod refMethod
) {
586 if (refMethod
.getSuperMethods().isEmpty()) {
587 getJavaContext().enqueueMethodUsagesProcessor(refMethod
, new GlobalJavaInspectionContext
.UsagesProcessor() {
588 public boolean process(PsiReference psiReference
) {
589 getEntryPointsManager().addEntryPoint(refMethod
, false);
595 for (RefMethod refSuper
: refMethod
.getSuperMethods()) {
596 enqueueMethodUsages(refSuper
);
601 public GlobalJavaInspectionContext
getJavaContext() {
602 return getContext().getExtension(GlobalJavaInspectionContext
.CONTEXT
);
605 public RefFilter
getFilter() {
606 if (myFilter
== null) {
607 myFilter
= new WeakUnreferencedFilter(this);
612 public HTMLComposerImpl
getComposer() {
613 if (myComposer
== null) {
614 myComposer
= new DeadHTMLComposer(this);
619 public void exportResults(final Element parentNode
) {
620 final WeakUnreferencedFilter filter
= new WeakUnreferencedFilter(this);
621 getRefManager().iterate(new RefJavaVisitor() {
622 @Override public void visitElement(RefEntity refEntity
) {
623 if (!(refEntity
instanceof RefJavaElement
)) return;
624 if (!getIgnoredRefElements().contains(refEntity
) && filter
.accepts((RefJavaElement
)refEntity
)) {
625 if (refEntity
instanceof RefImplicitConstructor
) refEntity
= ((RefImplicitConstructor
)refEntity
).getOwnerClass();
626 Element element
= refEntity
.getRefManager().export(refEntity
, parentNode
, -1);
627 @NonNls Element problemClassElement
= new Element(InspectionsBundle
.message("inspection.export.results.problem.element.tag"));
629 if (refEntity
instanceof RefElement
) {
630 final RefElement refElement
= (RefElement
)refEntity
;
631 final HighlightSeverity severity
= getCurrentSeverity(refElement
);
632 final String attributeKey
= getTextAttributeKey(refElement
.getElement().getProject(), severity
, ProblemHighlightType
.LIKE_UNUSED_SYMBOL
);
633 problemClassElement
.setAttribute("severity", severity
.myName
);
634 problemClassElement
.setAttribute("attribute_key", attributeKey
);
637 problemClassElement
.addContent(InspectionsBundle
.message("inspection.export.results.dead.code"));
638 element
.addContent(problemClassElement
);
640 @NonNls Element hintsElement
= new Element("hints");
642 for (String hint
: HINTS
) {
643 @NonNls Element hintElement
= new Element("hint");
644 hintElement
.setAttribute("value", hint
);
645 hintsElement
.addContent(hintElement
);
647 element
.addContent(hintsElement
);
650 Element descriptionElement
= new Element(InspectionsBundle
.message("inspection.export.results.description.tag"));
651 StringBuffer buf
= new StringBuffer();
652 DeadHTMLComposer
.appendProblemSynopsis((RefElement
)refEntity
, buf
);
653 descriptionElement
.addContent(buf
.toString());
654 element
.addContent(descriptionElement
);
660 public QuickFixAction
[] getQuickFixes(final RefEntity
[] refElements
) {
661 return myQuickFixActions
;
665 public JobDescriptor
[] getJobDescriptors() {
666 return new JobDescriptor
[]{GlobalInspectionContextImpl
.BUILD_GRAPH
, GlobalInspectionContextImpl
.FIND_EXTERNAL_USAGES
};
669 private static void commentOutDead(PsiElement psiElement
) {
670 PsiFile psiFile
= psiElement
.getContainingFile();
672 if (psiFile
!= null) {
673 Document doc
= PsiDocumentManager
.getInstance(psiElement
.getProject()).getDocument(psiFile
);
675 TextRange textRange
= psiElement
.getTextRange();
676 SimpleDateFormat format
= new SimpleDateFormat();
677 String date
= format
.format(new Date());
679 int startOffset
= textRange
.getStartOffset();
680 CharSequence chars
= doc
.getCharsSequence();
681 while (CharArrayUtil
.regionMatches(chars
, startOffset
, InspectionsBundle
.message("inspection.dead.code.comment"))) {
682 int line
= doc
.getLineNumber(startOffset
) + 1;
683 if (line
< doc
.getLineCount()) {
684 startOffset
= doc
.getLineStartOffset(line
);
685 startOffset
= CharArrayUtil
.shiftForward(chars
, startOffset
, " \t");
689 int endOffset
= textRange
.getEndOffset();
691 int line1
= doc
.getLineNumber(startOffset
);
692 int line2
= doc
.getLineNumber(endOffset
- 1);
694 if (line1
== line2
) {
695 doc
.insertString(startOffset
, InspectionsBundle
.message("inspection.dead.code.date.comment", date
));
698 for (int i
= line1
; i
<= line2
; i
++) {
699 doc
.insertString(doc
.getLineStartOffset(i
), "//");
702 doc
.insertString(doc
.getLineStartOffset(Math
.min(line2
+ 1, doc
.getLineCount() - 1)),
703 InspectionsBundle
.message("inspection.dead.code.stop.comment", date
));
704 doc
.insertString(doc
.getLineStartOffset(line1
), InspectionsBundle
.message("inspection.dead.code.start.comment", date
));
711 public IntentionAction
findQuickFixes(final CommonProblemDescriptor descriptor
, final String hint
) {
712 if (descriptor
instanceof ProblemDescriptor
) {
713 if (DELETE
.equals(hint
)) {
714 return new PermanentDeleteFix(((ProblemDescriptor
)descriptor
).getPsiElement());
715 } else if (COMMENT
.equals(hint
)) {
716 return new CommentOutFix(((ProblemDescriptor
)descriptor
).getPsiElement());
722 private class PermanentDeleteAction
extends QuickFixAction
{
723 private PermanentDeleteAction() {
724 super(DELETE_QUICK_FIX
, IconLoader
.getIcon("/actions/cancel.png"), KeyStroke
.getKeyStroke(KeyEvent
.VK_DELETE
, 0), UnusedDeclarationInspection
.this);
727 protected boolean applyFix(final RefElement
[] refElements
) {
728 if (!super.applyFix(refElements
)) return false;
729 final ArrayList
<PsiElement
> psiElements
= new ArrayList
<PsiElement
>();
730 for (RefElement refElement
: refElements
) {
731 PsiElement psiElement
= refElement
.getElement();
732 if (psiElement
== null) continue;
733 if (myFilter
.getElementProblemCount((RefJavaElement
)refElement
) == 0) continue;
734 psiElements
.add(psiElement
);
737 ApplicationManager
.getApplication().invokeLater(new Runnable() {
739 final Project project
= getContext().getProject();
740 SafeDeleteHandler
.invoke(project
, psiElements
.toArray(new PsiElement
[psiElements
.size()]), false, new Runnable(){
742 removeElements(refElements
, project
, UnusedDeclarationInspection
.this);
748 return false; //refresh after safe delete dialog is closed
752 private static class PermanentDeleteFix
implements IntentionAction
{
753 private final PsiElement myElement
;
755 private PermanentDeleteFix(final PsiElement element
) {
760 public String
getText() {
761 return DELETE_QUICK_FIX
;
765 public String
getFamilyName() {
769 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
773 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
774 if (myElement
!= null && myElement
.isValid()) {
775 ApplicationManager
.getApplication().invokeLater(new Runnable() {
778 .invoke(myElement
.getProject(), new PsiElement
[]{PsiTreeUtil
.getParentOfType(myElement
, PsiModifierListOwner
.class)}, false);
784 public boolean startInWriteAction() {
789 private class CommentOutBin
extends QuickFixAction
{
790 private CommentOutBin() {
791 super(COMMENT_OUT_QUICK_FIX
, null, KeyStroke
.getKeyStroke(KeyEvent
.VK_SLASH
, SystemInfo
.isMac ? InputEvent
.META_MASK
: InputEvent
.CTRL_MASK
),
792 UnusedDeclarationInspection
.this);
795 protected boolean applyFix(RefElement
[] refElements
) {
796 if (!super.applyFix(refElements
)) return false;
797 ArrayList
<RefElement
> deletedRefs
= new ArrayList
<RefElement
>(1);
798 for (RefElement refElement
: refElements
) {
799 PsiElement psiElement
= refElement
.getElement();
800 if (psiElement
== null) continue;
801 if (myFilter
.getElementProblemCount((RefJavaElement
)refElement
) == 0) continue;
802 commentOutDead(psiElement
);
803 refElement
.getRefManager().removeRefElement(refElement
, deletedRefs
);
806 EntryPointsManager entryPointsManager
= getEntryPointsManager();
807 for (RefElement refElement
: deletedRefs
) {
808 entryPointsManager
.removeEntryPoint(refElement
);
815 private static class CommentOutFix
implements IntentionAction
{
816 private final PsiElement myElement
;
818 private CommentOutFix(final PsiElement element
) {
823 public String
getText() {
824 return COMMENT_OUT_QUICK_FIX
;
828 public String
getFamilyName() {
832 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
836 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
837 if (myElement
!= null && myElement
.isValid()) {
838 commentOutDead(PsiTreeUtil
.getParentOfType(myElement
, PsiModifierListOwner
.class));
842 public boolean startInWriteAction() {
847 private class MoveToEntries
extends QuickFixAction
{
848 private MoveToEntries() {
849 super(InspectionsBundle
.message("inspection.dead.code.entry.point.quickfix"), null, KeyStroke
.getKeyStroke(KeyEvent
.VK_INSERT
, 0), UnusedDeclarationInspection
.this);
852 protected boolean applyFix(RefElement
[] refElements
) {
853 final EntryPointsManager entryPointsManager
= getEntryPointsManager();
854 for (RefElement refElement
: refElements
) {
855 entryPointsManager
.addEntryPoint(refElement
, true);
864 private void checkForReachables() {
865 CodeScanner codeScanner
= new CodeScanner();
867 // Cleanup previous reachability information.
868 getRefManager().iterate(new RefJavaVisitor() {
869 @Override public void visitElement(RefEntity refEntity
) {
870 if (refEntity
instanceof RefJavaElement
) {
871 final RefJavaElementImpl refElement
= (RefJavaElementImpl
)refEntity
;
872 final PsiElement element
= refElement
.getElement();
873 if (element
== null) return;
874 if (!getContext().isToCheckMember(refElement
, UnusedDeclarationInspection
.this)) return;
875 refElement
.setReachable(false);
881 for (RefElement entry
: getEntryPointsManager().getEntryPoints()) {
882 entry
.accept(codeScanner
);
885 while (codeScanner
.newlyInstantiatedClassesCount() != 0) {
886 codeScanner
.cleanInstantiatedClassesCount();
887 codeScanner
.processDelayedMethods();
891 private EntryPointsManager
getEntryPointsManager() {
892 return getContext().getExtension(GlobalJavaInspectionContext
.CONTEXT
).getEntryPointsManager(getContext().getRefManager());
895 private static class CodeScanner
extends RefJavaVisitor
{
896 private final HashMap
<RefClass
, HashSet
<RefMethod
>> myClassIDtoMethods
;
897 private final HashSet
<RefClass
> myInstantiatedClasses
;
898 private int myInstantiatedClassesCount
;
899 private final HashSet
<RefMethod
> myProcessedMethods
;
901 private CodeScanner() {
902 myClassIDtoMethods
= new HashMap
<RefClass
, HashSet
<RefMethod
>>();
903 myInstantiatedClasses
= new HashSet
<RefClass
>();
904 myProcessedMethods
= new HashSet
<RefMethod
>();
905 myInstantiatedClassesCount
= 0;
908 @Override public void visitMethod(RefMethod method
) {
909 if (!myProcessedMethods
.contains(method
)) {
910 // Process class's static intitializers
911 if (method
.isStatic() || method
.isConstructor()) {
912 if (method
.isConstructor()) {
913 addInstantiatedClass(method
.getOwnerClass());
916 ((RefClassImpl
)method
.getOwnerClass()).setReachable(true);
918 myProcessedMethods
.add(method
);
919 makeContentReachable((RefJavaElementImpl
)method
);
920 makeClassInitializersReachable(method
.getOwnerClass());
923 if (isClassInstantiated(method
.getOwnerClass())) {
924 myProcessedMethods
.add(method
);
925 makeContentReachable((RefJavaElementImpl
)method
);
928 addDelayedMethod(method
);
931 for (RefMethod refSub
: method
.getDerivedMethods()) {
938 @Override public void visitClass(RefClass refClass
) {
939 boolean alreadyActive
= refClass
.isReachable();
940 ((RefClassImpl
)refClass
).setReachable(true);
942 if (!alreadyActive
) {
943 // Process class's static intitializers.
944 makeClassInitializersReachable(refClass
);
947 addInstantiatedClass(refClass
);
950 @Override public void visitField(RefField field
) {
951 // Process class's static intitializers.
952 if (!field
.isReachable()) {
953 makeContentReachable((RefJavaElementImpl
)field
);
954 makeClassInitializersReachable(field
.getOwnerClass());
958 private void addInstantiatedClass(RefClass refClass
) {
959 if (myInstantiatedClasses
.add(refClass
)) {
960 ((RefClassImpl
)refClass
).setReachable(true);
961 myInstantiatedClassesCount
++;
963 final List
<RefMethod
> refMethods
= refClass
.getLibraryMethods();
964 for (RefMethod refMethod
: refMethods
) {
965 refMethod
.accept(this);
967 for (RefClass baseClass
: refClass
.getBaseClasses()) {
968 addInstantiatedClass(baseClass
);
973 private void makeContentReachable(RefJavaElementImpl refElement
) {
974 refElement
.setReachable(true);
975 for (RefElement refCallee
: refElement
.getOutReferences()) {
976 refCallee
.accept(this);
980 private void makeClassInitializersReachable(RefClass refClass
) {
981 for (RefElement refCallee
: refClass
.getOutReferences()) {
982 refCallee
.accept(this);
986 private void addDelayedMethod(RefMethod refMethod
) {
987 HashSet
<RefMethod
> methods
= myClassIDtoMethods
.get(refMethod
.getOwnerClass());
988 if (methods
== null) {
989 methods
= new HashSet
<RefMethod
>();
990 myClassIDtoMethods
.put(refMethod
.getOwnerClass(), methods
);
992 methods
.add(refMethod
);
995 private boolean isClassInstantiated(RefClass refClass
) {
996 return myInstantiatedClasses
.contains(refClass
);
999 private int newlyInstantiatedClassesCount() {
1000 return myInstantiatedClassesCount
;
1003 private void cleanInstantiatedClassesCount() {
1004 myInstantiatedClassesCount
= 0;
1007 private void processDelayedMethods() {
1008 RefClass
[] instClasses
= myInstantiatedClasses
.toArray(new RefClass
[myInstantiatedClasses
.size()]);
1009 for (RefClass refClass
: instClasses
) {
1010 if (isClassInstantiated(refClass
)) {
1011 HashSet
<RefMethod
> methods
= myClassIDtoMethods
.get(refClass
);
1012 if (methods
!= null) {
1013 RefMethod
[] arMethods
= methods
.toArray(new RefMethod
[methods
.size()]);
1014 for (RefMethod arMethod
: arMethods
) {
1015 arMethod
.accept(this);
1023 public void updateContent() {
1024 checkForReachables();
1025 super.updateContent();
1028 public InspectionNode
createToolNode(final InspectionRVContentProvider provider
, final InspectionTreeNode parentNode
, final boolean showStructure
) {
1029 final InspectionNode toolNode
= super.createToolNode(provider
, parentNode
, showStructure
);
1030 final EntryPointsNode entryPointsNode
= new EntryPointsNode(this);
1031 provider
.appendToolNodeContent(entryPointsNode
, toolNode
, showStructure
);
1032 return entryPointsNode
;