2 * Created by IntelliJ IDEA.
6 * To change template for new class use
7 * Code Style | Class Templates options (Tools | IDE Options).
10 package com
.intellij
.codeInspection
.deadCode
;
12 import com
.intellij
.ExtensionPoints
;
13 import com
.intellij
.ui
.SeparatorFactory
;
14 import com
.intellij
.analysis
.AnalysisScope
;
15 import com
.intellij
.codeInsight
.AnnotationUtil
;
16 import com
.intellij
.codeInsight
.daemon
.GroupNames
;
17 import com
.intellij
.codeInsight
.daemon
.ImplicitUsageProvider
;
18 import com
.intellij
.codeInsight
.intention
.IntentionAction
;
19 import com
.intellij
.codeInspection
.*;
20 import com
.intellij
.codeInspection
.ex
.*;
21 import com
.intellij
.codeInspection
.reference
.*;
22 import com
.intellij
.codeInspection
.ui
.EntryPointsNode
;
23 import com
.intellij
.codeInspection
.ui
.InspectionNode
;
24 import com
.intellij
.codeInspection
.ui
.InspectionTreeNode
;
25 import com
.intellij
.codeInspection
.util
.RefFilter
;
26 import com
.intellij
.codeInspection
.util
.SpecialAnnotationsUtil
;
27 import com
.intellij
.lang
.annotation
.HighlightSeverity
;
28 import com
.intellij
.openapi
.application
.ApplicationManager
;
29 import com
.intellij
.openapi
.editor
.Document
;
30 import com
.intellij
.openapi
.editor
.Editor
;
31 import com
.intellij
.openapi
.extensions
.ExtensionPoint
;
32 import com
.intellij
.openapi
.extensions
.Extensions
;
33 import com
.intellij
.openapi
.progress
.ProgressManager
;
34 import com
.intellij
.openapi
.project
.Project
;
35 import com
.intellij
.openapi
.util
.*;
36 import com
.intellij
.psi
.*;
37 import com
.intellij
.psi
.impl
.PsiClassImplUtil
;
38 import com
.intellij
.psi
.search
.GlobalSearchScope
;
39 import com
.intellij
.psi
.search
.PsiNonJavaFileReferenceProcessor
;
40 import com
.intellij
.psi
.search
.PsiSearchHelper
;
41 import com
.intellij
.psi
.util
.PsiMethodUtil
;
42 import com
.intellij
.psi
.util
.PsiTreeUtil
;
43 import com
.intellij
.refactoring
.safeDelete
.SafeDeleteHandler
;
44 import com
.intellij
.util
.IncorrectOperationException
;
45 import com
.intellij
.util
.containers
.HashMap
;
46 import com
.intellij
.util
.text
.CharArrayUtil
;
47 import org
.jdom
.Element
;
48 import org
.jetbrains
.annotations
.NonNls
;
49 import org
.jetbrains
.annotations
.NotNull
;
50 import org
.jetbrains
.annotations
.Nullable
;
54 import java
.awt
.event
.ActionEvent
;
55 import java
.awt
.event
.ActionListener
;
56 import java
.awt
.event
.InputEvent
;
57 import java
.awt
.event
.KeyEvent
;
58 import java
.text
.SimpleDateFormat
;
60 import java
.util
.List
;
62 public class DeadCodeInspection
extends FilteringInspectionTool
{
63 public boolean ADD_MAINS_TO_ENTRIES
= true;
65 public boolean ADD_APPLET_TO_ENTRIES
= true;
66 public boolean ADD_SERVLET_TO_ENTRIES
= true;
67 public boolean ADD_NONJAVA_TO_ENTRIES
= true;
69 public JDOMExternalizableStringList ADDITIONAL_ANNOTATIONS
= new JDOMExternalizableStringList();
70 private static final String
[] ADDITIONAL_ANNOS
= {
74 private HashSet
<RefElement
> myProcessedSuspicious
= null;
76 private final QuickFixAction
[] myQuickFixActions
;
77 public static final String DISPLAY_NAME
= InspectionsBundle
.message("inspection.dead.code.display.name");
78 private WeakUnreferencedFilter myFilter
;
79 private DeadHTMLComposer myComposer
;
80 @NonNls public static final String SHORT_NAME
= "UnusedDeclaration";
82 private static final String COMMENT_OUT_QUICK_FIX
= InspectionsBundle
.message("inspection.dead.code.comment.quickfix");
83 private static final String DELETE_QUICK_FIX
= InspectionsBundle
.message("inspection.dead.code.safe.delete.quickfix");
85 @NonNls private static final String DELETE
= "delete";
86 @NonNls private static final String COMMENT
= "comment";
87 @NonNls private static final String
[] HINTS
= {COMMENT
, DELETE
};
89 public final UnusedCodeExtension
[] myExtensions
;
91 public DeadCodeInspection() {
92 ADDITIONAL_ANNOTATIONS
.addAll(Arrays
.asList(ADDITIONAL_ANNOS
));
93 myQuickFixActions
= new QuickFixAction
[]{new PermanentDeleteAction(), new CommentOutBin(), new MoveToEntries()};
94 ExtensionPoint
<UnusedCodeExtension
> point
= Extensions
.getRootArea().getExtensionPoint(ExtensionPoints
.DEAD_CODE_TOOL
);
95 final UnusedCodeExtension
[] deadCodeAddins
= point
.getExtensions();
96 Arrays
.sort(deadCodeAddins
, new Comparator
<UnusedCodeExtension
>() {
97 public int compare(final UnusedCodeExtension o1
, final UnusedCodeExtension o2
) {
98 return o1
.getDisplayName().compareToIgnoreCase(o2
.getDisplayName());
101 myExtensions
= deadCodeAddins
;
104 public void initialize(@NotNull final GlobalInspectionContextImpl context
) {
105 super.initialize(context
);
106 ((EntryPointsManagerImpl
)getEntryPointsManager()).setAddNonJavaEntries(ADD_NONJAVA_TO_ENTRIES
);
109 private class OptionsPanel
extends JPanel
{
110 private final JCheckBox myMainsCheckbox
;
111 private final JCheckBox myAppletToEntries
;
112 private final JCheckBox myServletToEntries
;
113 private final JCheckBox myNonJavaCheckbox
;
115 private OptionsPanel() {
116 super(new GridBagLayout());
117 GridBagConstraints gc
= new GridBagConstraints();
120 gc
.fill
= GridBagConstraints
.HORIZONTAL
;
121 gc
.anchor
= GridBagConstraints
.NORTHWEST
;
123 myMainsCheckbox
= new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option"));
124 myMainsCheckbox
.setSelected(ADD_MAINS_TO_ENTRIES
);
125 myMainsCheckbox
.addActionListener(new ActionListener() {
126 public void actionPerformed(ActionEvent e
) {
127 ADD_MAINS_TO_ENTRIES
= myMainsCheckbox
.isSelected();
132 add(myMainsCheckbox
, gc
);
134 myAppletToEntries
= new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option3"));
135 myAppletToEntries
.setSelected(ADD_APPLET_TO_ENTRIES
);
136 myAppletToEntries
.addActionListener(new ActionListener() {
137 public void actionPerformed(ActionEvent e
) {
138 ADD_APPLET_TO_ENTRIES
= myAppletToEntries
.isSelected();
142 add(myAppletToEntries
, gc
);
144 myServletToEntries
= new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option4"));
145 myServletToEntries
.setSelected(ADD_SERVLET_TO_ENTRIES
);
146 myServletToEntries
.addActionListener(new ActionListener(){
147 public void actionPerformed(ActionEvent e
) {
148 ADD_SERVLET_TO_ENTRIES
= myServletToEntries
.isSelected();
152 add(myServletToEntries
, gc
);
154 for (final UnusedCodeExtension extension
: myExtensions
) {
155 if (extension
.showUI()) {
156 final JCheckBox extCheckbox
= new JCheckBox(extension
.getDisplayName());
157 extCheckbox
.setSelected(extension
.isSelected());
158 extCheckbox
.addActionListener(new ActionListener() {
159 public void actionPerformed(ActionEvent e
) {
160 extension
.setSelected(extCheckbox
.isSelected());
164 add(extCheckbox
, gc
);
169 new JCheckBox(InspectionsBundle
.message("inspection.dead.code.option5"));
170 myNonJavaCheckbox
.setSelected(ADD_NONJAVA_TO_ENTRIES
);
171 myNonJavaCheckbox
.addActionListener(new ActionListener() {
172 public void actionPerformed(ActionEvent e
) {
173 ADD_NONJAVA_TO_ENTRIES
= myNonJavaCheckbox
.isSelected();
178 add(myNonJavaCheckbox
, gc
);
180 final JPanel listPanel
= SpecialAnnotationsUtil
.createSpecialAnnotationsListControl(ADDITIONAL_ANNOTATIONS
, InspectionsBundle
.message(
181 "inspections.dead.code.entry.points.annotations.list.title"));
188 public JComponent
createOptionsPanel() {
189 final JPanel scrollPane
= new JPanel(new BorderLayout());
190 scrollPane
.add(SeparatorFactory
.createSeparator("Entry points", null), BorderLayout
.NORTH
);
191 scrollPane
.add(new OptionsPanel(), BorderLayout
.CENTER
);
195 private boolean isAddMainsEnabled() {
196 return ADD_MAINS_TO_ENTRIES
;
199 private boolean isAddAppletEnabled() {
200 return ADD_APPLET_TO_ENTRIES
;
203 private boolean isAddServletEnabled() {
204 return ADD_SERVLET_TO_ENTRIES
;
207 private boolean isAddNonJavaUsedEnabled() {
208 return ADD_NONJAVA_TO_ENTRIES
;
212 public String
getDisplayName() {
217 public String
getGroupDisplayName() {
218 return GroupNames
.DECLARATION_REDUNDANCY
;
222 public String
getShortName() {
226 public void readSettings(Element node
) throws InvalidDataException
{
227 super.readSettings(node
);
228 for (UnusedCodeExtension extension
: myExtensions
) {
229 extension
.readExternal(node
);
233 public void writeSettings(Element node
) throws WriteExternalException
{
234 super.writeSettings(node
);
235 for (UnusedCodeExtension extension
: myExtensions
) {
236 extension
.writeExternal(node
);
240 private static boolean isSerializationImplicitlyUsedField(PsiField field
) {
241 @NonNls final String name
= field
.getName();
242 if (!"serialVersionUID".equals(name
) && !"serialPersistentFields".equals(name
)) return false;
243 if (!field
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
244 PsiClass aClass
= field
.getContainingClass();
245 return aClass
== null || isSerializable(aClass
);
248 private static boolean isWriteObjectMethod(PsiMethod method
) {
249 @NonNls final String name
= method
.getName();
250 if (!"writeObject".equals(name
)) return false;
251 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
252 if (parameters
.length
!= 1) return false;
253 if (!parameters
[0].getType().equalsToText("java.io.ObjectOutputStream")) return false;
254 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
255 PsiClass aClass
= method
.getContainingClass();
256 return !(aClass
!= null && !isSerializable(aClass
));
259 private static boolean isReadObjectMethod(PsiMethod method
) {
260 @NonNls final String name
= method
.getName();
261 if (!"readObject".equals(name
)) return false;
262 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
263 if (parameters
.length
!= 1) return false;
264 if (!parameters
[0].getType().equalsToText("java.io.ObjectInputStream")) return false;
265 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
266 PsiClass aClass
= method
.getContainingClass();
267 return !(aClass
!= null && !isSerializable(aClass
));
270 private static boolean isWriteReplaceMethod(PsiMethod method
) {
271 @NonNls final String name
= method
.getName();
272 if (!"writeReplace".equals(name
)) return false;
273 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
274 if (parameters
.length
!= 0) return false;
275 if (!method
.getReturnType().equalsToText("java.lang.Object")) return false;
276 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
277 PsiClass aClass
= method
.getContainingClass();
278 return !(aClass
!= null && !isSerializable(aClass
));
281 private static boolean isReadResolveMethod(PsiMethod method
) {
282 @NonNls final String name
= method
.getName();
283 if (!"readResolve".equals(name
)) return false;
284 PsiParameter
[] parameters
= method
.getParameterList().getParameters();
285 if (parameters
.length
!= 0) return false;
286 if (!method
.getReturnType().equalsToText("java.lang.Object")) return false;
287 if (method
.hasModifierProperty(PsiModifier
.STATIC
)) return false;
288 PsiClass aClass
= method
.getContainingClass();
289 return !(aClass
!= null && !isSerializable(aClass
));
292 private static boolean isSerializable(PsiClass aClass
) {
293 PsiClass serializableClass
= JavaPsiFacade
.getInstance(aClass
.getProject()).findClass("java.io.Serializable", aClass
.getResolveScope());
294 return serializableClass
!= null && aClass
.isInheritor(serializableClass
, true);
297 public void runInspection(final AnalysisScope scope
, final InspectionManager manager
) {
298 getRefManager().iterate(new RefJavaVisitor() {
299 @Override public void visitElement(final RefEntity refEntity
) {
300 if (refEntity
instanceof RefJavaElement
) {
301 final RefElementImpl refElement
= (RefElementImpl
)refEntity
;
302 final PsiElement element
= refElement
.getElement();
303 if (element
== null) return;
304 final boolean isSuppressed
= ((RefElementImpl
)refElement
).isSuppressed(getShortName());
305 if (!getContext().isToCheckMember(element
, DeadCodeInspection
.this) || isSuppressed
) {
306 if (isSuppressed
|| !scope
.contains(element
)) {
307 getEntryPointsManager().addEntryPoint(refElement
, false);
311 if (!refElement
.isSuspicious()) return;
312 refElement
.accept(new RefJavaVisitor() {
313 @Override public void visitElement(final RefEntity elem
) {
314 if (elem
instanceof RefElement
) {
315 final RefElement element
= (RefElement
)elem
;
316 if (isEntryPoint(element
)) {
317 getEntryPointsManager().addEntryPoint(element
, false);
322 @Override public void visitMethod(RefMethod method
) {
323 if (isAddMainsEnabled() && method
.isAppMain()) {
324 getEntryPointsManager().addEntryPoint(method
, false);
326 super.visitMethod(method
);
330 @Override public void visitClass(RefClass aClass
) {
331 final PsiClass psiClass
= aClass
.getElement();
333 isAddAppletEnabled() && aClass
.isApplet() ||
334 isAddServletEnabled() && aClass
.isServlet()) {
335 getEntryPointsManager().addEntryPoint(aClass
, false);
336 } else if (psiClass
.isAnnotationType()){
337 getEntryPointsManager().addEntryPoint(aClass
, false);
338 final PsiMethod
[] psiMethods
= psiClass
.getMethods();
339 for (PsiMethod psiMethod
: psiMethods
) {
340 getEntryPointsManager().addEntryPoint(getRefManager().getReference(psiMethod
), false);
342 } else if (psiClass
.isEnum()) {
343 getEntryPointsManager().addEntryPoint(aClass
, false);
345 super.visitClass(aClass
);
353 if (isAddNonJavaUsedEnabled()) {
354 checkForReachables();
355 ProgressManager
.getInstance().runProcess(new Runnable() {
357 final RefFilter filter
= new StrictUnreferencedFilter(DeadCodeInspection
.this);
358 final PsiSearchHelper helper
= PsiManager
.getInstance(getRefManager().getProject()).getSearchHelper();
359 getRefManager().iterate(new RefJavaVisitor() {
360 @Override public void visitElement(final RefEntity refEntity
) {
361 if (refEntity
instanceof RefClass
&& filter
.accepts((RefClass
)refEntity
)) {
362 findExternalClassReferences((RefClass
)refEntity
);
364 else if (refEntity
instanceof RefMethod
) {
365 RefMethod refMethod
= (RefMethod
)refEntity
;
366 if (refMethod
.isConstructor() && filter
.accepts(refMethod
)) {
367 findExternalClassReferences(refMethod
.getOwnerClass());
372 private void findExternalClassReferences(final RefClass refElement
) {
373 PsiClass psiClass
= refElement
.getElement();
374 String qualifiedName
= psiClass
.getQualifiedName();
375 if (qualifiedName
!= null) {
376 helper
.processUsagesInNonJavaFiles(qualifiedName
,
377 new PsiNonJavaFileReferenceProcessor() {
378 public boolean process(PsiFile file
, int startOffset
, int endOffset
) {
379 getEntryPointsManager().addEntryPoint(refElement
, false);
383 GlobalSearchScope
.projectScope(getContext().getProject()));
391 myProcessedSuspicious
= new HashSet
<RefElement
>();
395 private boolean isEntryPoint(final RefElement owner
) {
396 if (RefUtil
.isEntryPoint(owner
)) return true;
397 final PsiElement element
= owner
.getElement();
398 if (element
instanceof PsiModifierListOwner
399 && AnnotationUtil
.isAnnotated((PsiModifierListOwner
)element
, ADDITIONAL_ANNOTATIONS
)) {
402 for (UnusedCodeExtension extension
: myExtensions
) {
403 if (extension
.isEntryPoint(owner
)) {
410 public boolean isEntryPoint(@NotNull PsiElement element
) {
411 final Project project
= element
.getProject();
412 final JavaPsiFacade psiFacade
= JavaPsiFacade
.getInstance(project
);
413 if (element
instanceof PsiMethod
&& isAddMainsEnabled() && PsiClassImplUtil
.isMainMethod((PsiMethod
)element
)) {
416 if (element
instanceof PsiClass
) {
417 PsiClass aClass
= (PsiClass
)element
;
418 if (aClass
.isAnnotationType()) {
422 if (aClass
.isEnum()) {
425 final PsiClass applet
= psiFacade
.findClass("java.applet.Applet", GlobalSearchScope
.allScope(project
));
426 if (isAddAppletEnabled() && applet
!= null && aClass
.isInheritor(applet
, true)) {
430 final PsiClass servlet
= psiFacade
.findClass("javax.servlet.Servlet", GlobalSearchScope
.allScope(project
));
431 if (isAddServletEnabled() && servlet
!= null && aClass
.isInheritor(servlet
, true)) {
434 if (isAddMainsEnabled() && PsiMethodUtil
.hasMainMethod(aClass
)) return true;
436 if (element
instanceof PsiModifierListOwner
437 && AnnotationUtil
.checkAnnotatedUsingPatterns((PsiModifierListOwner
)element
, ADDITIONAL_ANNOTATIONS
)) {
440 for (UnusedCodeExtension extension
: myExtensions
) {
441 if (extension
.isEntryPoint(element
)) {
445 final ImplicitUsageProvider
[] implicitUsageProviders
= Extensions
.getExtensions(ImplicitUsageProvider
.EP_NAME
);
446 for (ImplicitUsageProvider provider
: implicitUsageProviders
) {
447 if (provider
.isImplicitUsage(element
)) return true;
452 private static class StrictUnreferencedFilter
extends UnreferencedFilter
{
453 private StrictUnreferencedFilter(final InspectionTool tool
) {
457 public int getElementProblemCount(RefJavaElement refElement
) {
458 final int problemCount
= super.getElementProblemCount(refElement
);
459 if (problemCount
> -1) return problemCount
;
460 return refElement
.isReferenced() ?
0 : 1;
464 private static class WeakUnreferencedFilter
extends UnreferencedFilter
{
465 private WeakUnreferencedFilter(final InspectionTool tool
) {
469 public int getElementProblemCount(final RefJavaElement refElement
) {
470 final int problemCount
= super.getElementProblemCount(refElement
);
471 if (problemCount
> - 1) return problemCount
;
472 if (!((RefElementImpl
)refElement
).hasSuspiciousCallers() || ((RefJavaElementImpl
)refElement
).isSuspiciousRecursive()) return 1;
477 public boolean queryExternalUsagesRequests(final InspectionManager manager
) {
478 checkForReachables();
479 final RefFilter filter
= myPhase
== 1 ?
new StrictUnreferencedFilter(this) : new RefUnreachableFilter(this);
480 final boolean[] requestAdded
= {false};
482 getRefManager().iterate(new RefJavaVisitor() {
483 @Override public void visitElement(RefEntity refEntity
) {
484 if (!(refEntity
instanceof RefJavaElement
)) return;
485 if (refEntity
instanceof RefClass
&& ((RefClass
)refEntity
).isAnonymous()) return;
486 RefJavaElement refElement
= (RefJavaElement
)refEntity
;
487 if (filter
.accepts(refElement
) && !myProcessedSuspicious
.contains(refElement
)) {
488 refEntity
.accept(new RefJavaVisitor() {
489 @Override public void visitField(final RefField refField
) {
490 myProcessedSuspicious
.add(refField
);
491 PsiField psiField
= refField
.getElement();
492 if (isSerializationImplicitlyUsedField(psiField
)) {
493 getEntryPointsManager().addEntryPoint(refField
, false);
496 getJavaContext().enqueueFieldUsagesProcessor(refField
, new GlobalJavaInspectionContext
.UsagesProcessor() {
497 public boolean process(PsiReference psiReference
) {
498 getEntryPointsManager().addEntryPoint(refField
, false);
502 requestAdded
[0] = true;
506 @Override public void visitMethod(final RefMethod refMethod
) {
507 myProcessedSuspicious
.add(refMethod
);
508 if (refMethod
instanceof RefImplicitConstructor
) {
509 visitClass(refMethod
.getOwnerClass());
512 PsiMethod psiMethod
= (PsiMethod
)refMethod
.getElement();
513 if (isSerializablePatternMethod(psiMethod
)) {
514 getEntryPointsManager().addEntryPoint(refMethod
, false);
516 else if (!refMethod
.isExternalOverride() && refMethod
.getAccessModifier() != PsiModifier
.PRIVATE
) {
517 for (final RefMethod derivedMethod
: refMethod
.getDerivedMethods()) {
518 myProcessedSuspicious
.add(derivedMethod
);
521 enqueueMethodUsages(refMethod
);
522 requestAdded
[0] = true;
527 @Override public void visitClass(final RefClass refClass
) {
528 myProcessedSuspicious
.add(refClass
);
529 if (!refClass
.isAnonymous()) {
530 getJavaContext().enqueueDerivedClassesProcessor(refClass
, new GlobalJavaInspectionContext
.DerivedClassesProcessor() {
531 public boolean process(PsiClass inheritor
) {
532 getEntryPointsManager().addEntryPoint(refClass
, false);
537 getJavaContext().enqueueClassUsagesProcessor(refClass
, new GlobalJavaInspectionContext
.UsagesProcessor() {
538 public boolean process(PsiReference psiReference
) {
539 getEntryPointsManager().addEntryPoint(refClass
, false);
543 requestAdded
[0] = true;
551 if (!requestAdded
[0]) {
553 myProcessedSuspicious
= null;
564 private static boolean isSerializablePatternMethod(PsiMethod psiMethod
) {
565 return isReadObjectMethod(psiMethod
) || isWriteObjectMethod(psiMethod
) || isReadResolveMethod(psiMethod
) ||
566 isWriteReplaceMethod(psiMethod
);
569 private void enqueueMethodUsages(final RefMethod refMethod
) {
570 if (refMethod
.getSuperMethods().isEmpty()) {
571 getJavaContext().enqueueMethodUsagesProcessor(refMethod
, new GlobalJavaInspectionContext
.UsagesProcessor() {
572 public boolean process(PsiReference psiReference
) {
573 getEntryPointsManager().addEntryPoint(refMethod
, false);
579 for (RefMethod refSuper
: refMethod
.getSuperMethods()) {
580 enqueueMethodUsages(refSuper
);
585 public GlobalJavaInspectionContext
getJavaContext() {
586 return getContext().getExtension(GlobalJavaInspectionContext
.CONTEXT
);
589 public RefFilter
getFilter() {
590 if (myFilter
== null) {
591 myFilter
= new WeakUnreferencedFilter(this);
596 public HTMLComposerImpl
getComposer() {
597 if (myComposer
== null) {
598 myComposer
= new DeadHTMLComposer(this);
603 public void exportResults(final Element parentNode
) {
604 final WeakUnreferencedFilter filter
= new WeakUnreferencedFilter(this);
605 getRefManager().iterate(new RefJavaVisitor() {
606 @Override public void visitElement(RefEntity refEntity
) {
607 if (!(refEntity
instanceof RefJavaElement
)) return;
608 if (!getIgnoredRefElements().contains(refEntity
) && filter
.accepts((RefJavaElement
)refEntity
)) {
609 if (refEntity
instanceof RefImplicitConstructor
) refEntity
= ((RefImplicitConstructor
)refEntity
).getOwnerClass();
610 Element element
= refEntity
.getRefManager().export(refEntity
, parentNode
, -1);
611 @NonNls Element problemClassElement
= new Element(InspectionsBundle
.message("inspection.export.results.problem.element.tag"));
613 if (refEntity
instanceof RefElement
) {
614 final RefElement refElement
= (RefElement
)refEntity
;
615 final HighlightSeverity severity
= getCurrentSeverity(refElement
);
616 final String attributeKey
= getTextAttributeKey(refElement
.getElement().getProject(), severity
, ProblemHighlightType
.LIKE_UNUSED_SYMBOL
);
617 problemClassElement
.setAttribute("severity", severity
.myName
);
618 problemClassElement
.setAttribute("attribute_key", attributeKey
);
621 problemClassElement
.addContent(InspectionsBundle
.message("inspection.export.results.dead.code"));
622 element
.addContent(problemClassElement
);
624 @NonNls Element hintsElement
= new Element("hints");
626 for (String hint
: HINTS
) {
627 @NonNls Element hintElement
= new Element("hint");
628 hintElement
.setAttribute("value", hint
);
629 hintsElement
.addContent(hintElement
);
631 element
.addContent(hintsElement
);
634 Element descriptionElement
= new Element(InspectionsBundle
.message("inspection.export.results.description.tag"));
635 StringBuffer buf
= new StringBuffer();
636 DeadHTMLComposer
.appendProblemSynopsis((RefElement
)refEntity
, buf
);
637 descriptionElement
.addContent(buf
.toString());
638 element
.addContent(descriptionElement
);
644 public QuickFixAction
[] getQuickFixes(final RefEntity
[] refElements
) {
645 return myQuickFixActions
;
649 public JobDescriptor
[] getJobDescriptors() {
650 return new JobDescriptor
[]{GlobalInspectionContextImpl
.BUILD_GRAPH
, GlobalInspectionContextImpl
.FIND_EXTERNAL_USAGES
};
653 private static void commentOutDead(PsiElement psiElement
) {
654 PsiFile psiFile
= psiElement
.getContainingFile();
656 if (psiFile
!= null) {
657 Document doc
= PsiDocumentManager
.getInstance(psiElement
.getProject()).getDocument(psiFile
);
659 TextRange textRange
= psiElement
.getTextRange();
660 SimpleDateFormat format
= new SimpleDateFormat();
661 String date
= format
.format(new Date());
663 int startOffset
= textRange
.getStartOffset();
664 CharSequence chars
= doc
.getCharsSequence();
665 while (CharArrayUtil
.regionMatches(chars
, startOffset
, InspectionsBundle
.message("inspection.dead.code.comment"))) {
666 int line
= doc
.getLineNumber(startOffset
) + 1;
667 if (line
< doc
.getLineCount()) {
668 startOffset
= doc
.getLineStartOffset(line
);
669 startOffset
= CharArrayUtil
.shiftForward(chars
, startOffset
, " \t");
673 int endOffset
= textRange
.getEndOffset();
675 int line1
= doc
.getLineNumber(startOffset
);
676 int line2
= doc
.getLineNumber(endOffset
- 1);
678 if (line1
== line2
) {
679 doc
.insertString(startOffset
, InspectionsBundle
.message("inspection.dead.code.date.comment", date
));
682 for (int i
= line1
; i
<= line2
; i
++) {
683 doc
.insertString(doc
.getLineStartOffset(i
), "//");
686 doc
.insertString(doc
.getLineStartOffset(Math
.min(line2
+ 1, doc
.getLineCount() - 1)),
687 InspectionsBundle
.message("inspection.dead.code.stop.comment", date
));
688 doc
.insertString(doc
.getLineStartOffset(line1
), InspectionsBundle
.message("inspection.dead.code.start.comment", date
));
695 public IntentionAction
findQuickFixes(final CommonProblemDescriptor descriptor
, final String hint
) {
696 if (descriptor
instanceof ProblemDescriptor
) {
697 if (DELETE
.equals(hint
)) {
698 return new PermanentDeleteFix(((ProblemDescriptor
)descriptor
).getPsiElement());
699 } else if (COMMENT
.equals(hint
)) {
700 return new CommentOutFix(((ProblemDescriptor
)descriptor
).getPsiElement());
706 private class PermanentDeleteAction
extends QuickFixAction
{
707 private PermanentDeleteAction() {
708 super(DELETE_QUICK_FIX
, IconLoader
.getIcon("/actions/cancel.png"), KeyStroke
.getKeyStroke(KeyEvent
.VK_DELETE
, 0), DeadCodeInspection
.this);
711 protected boolean applyFix(final RefElement
[] refElements
) {
712 if (!super.applyFix(refElements
)) return false;
713 final ArrayList
<PsiElement
> psiElements
= new ArrayList
<PsiElement
>();
714 for (RefElement refElement
: refElements
) {
715 PsiElement psiElement
= refElement
.getElement();
716 if (psiElement
== null) continue;
717 if (myFilter
.getElementProblemCount((RefJavaElement
)refElement
) == 0) continue;
718 psiElements
.add(psiElement
);
721 ApplicationManager
.getApplication().invokeLater(new Runnable() {
723 final Project project
= getContext().getProject();
724 SafeDeleteHandler
.invoke(project
, psiElements
.toArray(new PsiElement
[psiElements
.size()]), false, new Runnable(){
726 removeElements(refElements
, project
, DeadCodeInspection
.this);
732 return false; //refresh after safe delete dialog is closed
736 private static class PermanentDeleteFix
implements IntentionAction
{
737 private final PsiElement myElement
;
739 private PermanentDeleteFix(final PsiElement element
) {
744 public String
getText() {
745 return DELETE_QUICK_FIX
;
749 public String
getFamilyName() {
753 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
757 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
758 if (myElement
!= null && myElement
.isValid()) {
759 ApplicationManager
.getApplication().invokeLater(new Runnable() {
762 .invoke(myElement
.getProject(), new PsiElement
[]{PsiTreeUtil
.getParentOfType(myElement
, PsiModifierListOwner
.class)}, false);
768 public boolean startInWriteAction() {
773 private class CommentOutBin
extends QuickFixAction
{
774 private CommentOutBin() {
775 super(COMMENT_OUT_QUICK_FIX
, null, KeyStroke
.getKeyStroke(KeyEvent
.VK_SLASH
, SystemInfo
.isMac ? InputEvent
.META_MASK
: InputEvent
.CTRL_MASK
),
776 DeadCodeInspection
.this);
779 protected boolean applyFix(RefElement
[] refElements
) {
780 if (!super.applyFix(refElements
)) return false;
781 ArrayList
<RefElement
> deletedRefs
= new ArrayList
<RefElement
>(1);
782 for (RefElement refElement
: refElements
) {
783 PsiElement psiElement
= refElement
.getElement();
784 if (psiElement
== null) continue;
785 if (myFilter
.getElementProblemCount((RefJavaElement
)refElement
) == 0) continue;
786 commentOutDead(psiElement
);
787 refElement
.getRefManager().removeRefElement(refElement
, deletedRefs
);
790 EntryPointsManager entryPointsManager
= getEntryPointsManager();
791 for (RefElement refElement
: deletedRefs
) {
792 entryPointsManager
.removeEntryPoint(refElement
);
799 private static class CommentOutFix
implements IntentionAction
{
800 private final PsiElement myElement
;
802 private CommentOutFix(final PsiElement element
) {
807 public String
getText() {
808 return COMMENT_OUT_QUICK_FIX
;
812 public String
getFamilyName() {
816 public boolean isAvailable(@NotNull Project project
, Editor editor
, PsiFile file
) {
820 public void invoke(@NotNull Project project
, Editor editor
, PsiFile file
) throws IncorrectOperationException
{
821 if (myElement
!= null && myElement
.isValid()) {
822 commentOutDead(PsiTreeUtil
.getParentOfType(myElement
, PsiModifierListOwner
.class));
826 public boolean startInWriteAction() {
831 private class MoveToEntries
extends QuickFixAction
{
832 private MoveToEntries() {
833 super(InspectionsBundle
.message("inspection.dead.code.entry.point.quickfix"), null, KeyStroke
.getKeyStroke(KeyEvent
.VK_INSERT
, 0), DeadCodeInspection
.this);
836 protected boolean applyFix(RefElement
[] refElements
) {
837 final EntryPointsManager entryPointsManager
= getEntryPointsManager();
838 for (RefElement refElement
: refElements
) {
839 entryPointsManager
.addEntryPoint(refElement
, true);
848 private void checkForReachables() {
849 CodeScanner codeScanner
= new CodeScanner();
851 // Cleanup previous reachability information.
852 getRefManager().iterate(new RefJavaVisitor() {
853 @Override public void visitElement(RefEntity refEntity
) {
854 if (refEntity
instanceof RefJavaElement
) {
855 final RefJavaElementImpl refElement
= (RefJavaElementImpl
)refEntity
;
856 final PsiElement element
= refElement
.getElement();
857 if (element
== null) return;
858 if (!getContext().isToCheckMember(refElement
, DeadCodeInspection
.this)) return;
859 refElement
.setReachable(false);
865 for (RefElement entry
: getEntryPointsManager().getEntryPoints()) {
866 entry
.accept(codeScanner
);
869 while (codeScanner
.newlyInstantiatedClassesCount() != 0) {
870 codeScanner
.cleanInstantiatedClassesCount();
871 codeScanner
.processDelayedMethods();
875 private EntryPointsManager
getEntryPointsManager() {
876 return getContext().getExtension(GlobalJavaInspectionContext
.CONTEXT
).getEntryPointsManager(getContext().getRefManager());
879 private static class CodeScanner
extends RefJavaVisitor
{
880 private final HashMap
<RefClass
, HashSet
<RefMethod
>> myClassIDtoMethods
;
881 private final HashSet
<RefClass
> myInstantiatedClasses
;
882 private int myInstantiatedClassesCount
;
883 private final HashSet
<RefMethod
> myProcessedMethods
;
885 private CodeScanner() {
886 myClassIDtoMethods
= new HashMap
<RefClass
, HashSet
<RefMethod
>>();
887 myInstantiatedClasses
= new HashSet
<RefClass
>();
888 myProcessedMethods
= new HashSet
<RefMethod
>();
889 myInstantiatedClassesCount
= 0;
892 @Override public void visitMethod(RefMethod method
) {
893 if (!myProcessedMethods
.contains(method
)) {
894 // Process class's static intitializers
895 if (method
.isStatic() || method
.isConstructor()) {
896 if (method
.isConstructor()) {
897 addInstantiatedClass(method
.getOwnerClass());
900 ((RefClassImpl
)method
.getOwnerClass()).setReachable(true);
902 myProcessedMethods
.add(method
);
903 makeContentReachable((RefJavaElementImpl
)method
);
904 makeClassInitializersReachable(method
.getOwnerClass());
907 if (isClassInstantiated(method
.getOwnerClass())) {
908 myProcessedMethods
.add(method
);
909 makeContentReachable((RefJavaElementImpl
)method
);
912 addDelayedMethod(method
);
915 for (RefMethod refSub
: method
.getDerivedMethods()) {
922 @Override public void visitClass(RefClass refClass
) {
923 boolean alreadyActive
= refClass
.isReachable();
924 ((RefClassImpl
)refClass
).setReachable(true);
926 if (!alreadyActive
) {
927 // Process class's static intitializers.
928 makeClassInitializersReachable(refClass
);
931 addInstantiatedClass(refClass
);
934 @Override public void visitField(RefField field
) {
935 // Process class's static intitializers.
936 if (!field
.isReachable()) {
937 makeContentReachable((RefJavaElementImpl
)field
);
938 makeClassInitializersReachable(field
.getOwnerClass());
942 private void addInstantiatedClass(RefClass refClass
) {
943 if (myInstantiatedClasses
.add(refClass
)) {
944 ((RefClassImpl
)refClass
).setReachable(true);
945 myInstantiatedClassesCount
++;
947 final List
<RefMethod
> refMethods
= refClass
.getLibraryMethods();
948 for (RefMethod refMethod
: refMethods
) {
949 refMethod
.accept(this);
951 for (RefClass baseClass
: refClass
.getBaseClasses()) {
952 addInstantiatedClass(baseClass
);
957 private void makeContentReachable(RefJavaElementImpl refElement
) {
958 refElement
.setReachable(true);
959 for (RefElement refCallee
: refElement
.getOutReferences()) {
960 refCallee
.accept(this);
964 private void makeClassInitializersReachable(RefClass refClass
) {
965 for (RefElement refCallee
: refClass
.getOutReferences()) {
966 refCallee
.accept(this);
970 private void addDelayedMethod(RefMethod refMethod
) {
971 HashSet
<RefMethod
> methods
= myClassIDtoMethods
.get(refMethod
.getOwnerClass());
972 if (methods
== null) {
973 methods
= new HashSet
<RefMethod
>();
974 myClassIDtoMethods
.put(refMethod
.getOwnerClass(), methods
);
976 methods
.add(refMethod
);
979 private boolean isClassInstantiated(RefClass refClass
) {
980 return myInstantiatedClasses
.contains(refClass
);
983 private int newlyInstantiatedClassesCount() {
984 return myInstantiatedClassesCount
;
987 private void cleanInstantiatedClassesCount() {
988 myInstantiatedClassesCount
= 0;
991 private void processDelayedMethods() {
992 RefClass
[] instClasses
= myInstantiatedClasses
.toArray(new RefClass
[myInstantiatedClasses
.size()]);
993 for (RefClass refClass
: instClasses
) {
994 if (isClassInstantiated(refClass
)) {
995 HashSet
<RefMethod
> methods
= myClassIDtoMethods
.get(refClass
);
996 if (methods
!= null) {
997 RefMethod
[] arMethods
= methods
.toArray(new RefMethod
[methods
.size()]);
998 for (RefMethod arMethod
: arMethods
) {
999 arMethod
.accept(this);
1007 public void updateContent() {
1008 checkForReachables();
1009 super.updateContent();
1012 public InspectionNode
createToolNode(final InspectionRVContentProvider provider
, final InspectionTreeNode parentNode
, final boolean showStructure
) {
1013 final InspectionNode toolNode
= super.createToolNode(provider
, parentNode
, showStructure
);
1014 final EntryPointsNode entryPointsNode
= new EntryPointsNode(this);
1015 provider
.appendToolNodeContent(entryPointsNode
, toolNode
, showStructure
);
1016 return entryPointsNode
;