annotations patterns support in DeadCodeInspection (IDEA-24043 Jersey request methods...
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInspection / deadCode / DeadCodeInspection.java
blob50d38b890652aba8fda94e484c40213d96782b21
1 /*
2 * Created by IntelliJ IDEA.
3 * User: max
4 * Date: Oct 12, 2001
5 * Time: 9:40:45 PM
6 * To change template for new class use
7 * Code Style | Class Templates options (Tools | IDE Options).
8 */
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;
52 import javax.swing.*;
53 import java.awt.*;
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;
59 import java.util.*;
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 = {
71 "javax.ws.rs.*"
74 private HashSet<RefElement> myProcessedSuspicious = null;
75 private int myPhase;
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();
118 gc.weightx = 1;
119 gc.weighty = 0;
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();
131 gc.gridy = 0;
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();
141 gc.gridy++;
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();
151 gc.gridy++;
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());
163 gc.gridy++;
164 add(extCheckbox, gc);
168 myNonJavaCheckbox =
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();
177 gc.gridy++;
178 add(myNonJavaCheckbox, gc);
180 final JPanel listPanel = SpecialAnnotationsUtil.createSpecialAnnotationsListControl(ADDITIONAL_ANNOTATIONS, InspectionsBundle.message(
181 "inspections.dead.code.entry.points.annotations.list.title"));
182 gc.gridy++;
183 gc.weighty = 1;
184 add(listPanel, gc);
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);
192 return scrollPane;
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;
211 @NotNull
212 public String getDisplayName() {
213 return DISPLAY_NAME;
216 @NotNull
217 public String getGroupDisplayName() {
218 return GroupNames.DECLARATION_REDUNDANCY;
221 @NotNull
222 public String getShortName() {
223 return SHORT_NAME;
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);
309 return;
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);
325 } else {
326 super.visitMethod(method);
330 @Override public void visitClass(RefClass aClass) {
331 final PsiClass psiClass = aClass.getElement();
332 if (
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);
344 } else {
345 super.visitClass(aClass);
353 if (isAddNonJavaUsedEnabled()) {
354 checkForReachables();
355 ProgressManager.getInstance().runProcess(new Runnable() {
356 public void run() {
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);
380 return false;
383 GlobalSearchScope.projectScope(getContext().getProject()));
388 }, null);
391 myProcessedSuspicious = new HashSet<RefElement>();
392 myPhase = 1;
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)) {
400 return true;
402 for (UnusedCodeExtension extension : myExtensions) {
403 if (extension.isEntryPoint(owner)) {
404 return true;
407 return false;
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)) {
414 return true;
416 if (element instanceof PsiClass) {
417 PsiClass aClass = (PsiClass)element;
418 if (aClass.isAnnotationType()) {
419 return true;
422 if (aClass.isEnum()) {
423 return true;
425 final PsiClass applet = psiFacade.findClass("java.applet.Applet", GlobalSearchScope.allScope(project));
426 if (isAddAppletEnabled() && applet != null && aClass.isInheritor(applet, true)) {
427 return true;
430 final PsiClass servlet = psiFacade.findClass("javax.servlet.Servlet", GlobalSearchScope.allScope(project));
431 if (isAddServletEnabled() && servlet != null && aClass.isInheritor(servlet, true)) {
432 return true;
434 if (isAddMainsEnabled() && PsiMethodUtil.hasMainMethod(aClass)) return true;
436 if (element instanceof PsiModifierListOwner
437 && AnnotationUtil.checkAnnotatedUsingPatterns((PsiModifierListOwner)element, ADDITIONAL_ANNOTATIONS)) {
438 return true;
440 for (UnusedCodeExtension extension : myExtensions) {
441 if (extension.isEntryPoint(element)) {
442 return true;
445 final ImplicitUsageProvider[] implicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
446 for (ImplicitUsageProvider provider : implicitUsageProviders) {
447 if (provider.isImplicitUsage(element)) return true;
449 return false;
452 private static class StrictUnreferencedFilter extends UnreferencedFilter {
453 private StrictUnreferencedFilter(final InspectionTool tool) {
454 super(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) {
466 super(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;
473 return 0;
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);
495 else {
496 getJavaContext().enqueueFieldUsagesProcessor(refField, new GlobalJavaInspectionContext.UsagesProcessor() {
497 public boolean process(PsiReference psiReference) {
498 getEntryPointsManager().addEntryPoint(refField, false);
499 return 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());
511 else {
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);
533 return false;
537 getJavaContext().enqueueClassUsagesProcessor(refClass, new GlobalJavaInspectionContext.UsagesProcessor() {
538 public boolean process(PsiReference psiReference) {
539 getEntryPointsManager().addEntryPoint(refClass, false);
540 return false;
543 requestAdded[0] = true;
551 if (!requestAdded[0]) {
552 if (myPhase == 2) {
553 myProcessedSuspicious = null;
554 return false;
556 else {
557 myPhase = 2;
561 return true;
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);
574 return false;
578 else {
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);
593 return myFilter;
596 public HTMLComposerImpl getComposer() {
597 if (myComposer == null) {
598 myComposer = new DeadHTMLComposer(this);
600 return myComposer;
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;
648 @NotNull
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);
658 if (doc != null) {
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));
681 else {
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));
694 @Nullable
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());
703 return null;
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() {
722 public void run() {
723 final Project project = getContext().getProject();
724 SafeDeleteHandler.invoke(project, psiElements.toArray(new PsiElement[psiElements.size()]), false, new Runnable(){
725 public void run() {
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) {
740 myElement = element;
743 @NotNull
744 public String getText() {
745 return DELETE_QUICK_FIX;
748 @NotNull
749 public String getFamilyName() {
750 return getText();
753 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
754 return true;
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() {
760 public void run() {
761 SafeDeleteHandler
762 .invoke(myElement.getProject(), new PsiElement[]{PsiTreeUtil.getParentOfType(myElement, PsiModifierListOwner.class)}, false);
768 public boolean startInWriteAction() {
769 return true;
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);
795 return true;
799 private static class CommentOutFix implements IntentionAction {
800 private final PsiElement myElement;
802 private CommentOutFix(final PsiElement element) {
803 myElement = element;
806 @NotNull
807 public String getText() {
808 return COMMENT_OUT_QUICK_FIX;
811 @NotNull
812 public String getFamilyName() {
813 return getText();
816 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
817 return true;
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() {
827 return true;
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);
842 return 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());
899 else {
900 ((RefClassImpl)method.getOwnerClass()).setReachable(true);
902 myProcessedMethods.add(method);
903 makeContentReachable((RefJavaElementImpl)method);
904 makeClassInitializersReachable(method.getOwnerClass());
906 else {
907 if (isClassInstantiated(method.getOwnerClass())) {
908 myProcessedMethods.add(method);
909 makeContentReachable((RefJavaElementImpl)method);
911 else {
912 addDelayedMethod(method);
915 for (RefMethod refSub : method.getDerivedMethods()) {
916 visitMethod(refSub);
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;