renamed
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInspection / deadCode / UnusedDeclarationInspection.java
blob5d8d41d6226c0e54194b23ffea073900dac17381
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Created by IntelliJ IDEA.
19 * User: max
20 * Date: Oct 12, 2001
21 * Time: 9:40:45 PM
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;
68 import javax.swing.*;
69 import java.awt.*;
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;
75 import java.util.*;
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 = {
87 "javax.ws.rs.*"
90 private HashSet<RefElement> myProcessedSuspicious = null;
91 private int myPhase;
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();
134 gc.weightx = 1;
135 gc.weighty = 0;
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();
147 gc.gridy = 0;
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();
157 gc.gridy++;
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();
167 gc.gridy++;
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());
179 gc.gridy++;
180 add(extCheckbox, gc);
184 myNonJavaCheckbox =
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();
193 gc.gridy++;
194 add(myNonJavaCheckbox, gc);
196 final JPanel listPanel = SpecialAnnotationsUtil.createSpecialAnnotationsListControl(ADDITIONAL_ANNOTATIONS, InspectionsBundle.message(
197 "inspections.dead.code.entry.points.annotations.list.title"));
198 gc.gridy++;
199 gc.weighty = 1;
200 add(listPanel, gc);
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);
208 return scrollPane;
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;
227 @NotNull
228 public String getDisplayName() {
229 return DISPLAY_NAME;
232 @NotNull
233 public String getGroupDisplayName() {
234 return GroupNames.DECLARATION_REDUNDANCY;
237 @NotNull
238 public String getShortName() {
239 return SHORT_NAME;
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);
325 return;
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);
341 } else {
342 super.visitMethod(method);
346 @Override public void visitClass(RefClass aClass) {
347 final PsiClass psiClass = aClass.getElement();
348 if (
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);
360 } else {
361 super.visitClass(aClass);
369 if (isAddNonJavaUsedEnabled()) {
370 checkForReachables();
371 ProgressManager.getInstance().runProcess(new Runnable() {
372 public void run() {
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);
396 return false;
399 GlobalSearchScope.projectScope(getContext().getProject()));
404 }, null);
407 myProcessedSuspicious = new HashSet<RefElement>();
408 myPhase = 1;
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)) {
416 return true;
418 for (UnusedCodeExtension extension : myExtensions) {
419 if (extension.isEntryPoint(owner)) {
420 return true;
423 return false;
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)) {
430 return true;
432 if (element instanceof PsiClass) {
433 PsiClass aClass = (PsiClass)element;
434 if (aClass.isAnnotationType()) {
435 return true;
438 if (aClass.isEnum()) {
439 return true;
441 final PsiClass applet = psiFacade.findClass("java.applet.Applet", GlobalSearchScope.allScope(project));
442 if (isAddAppletEnabled() && applet != null && aClass.isInheritor(applet, true)) {
443 return true;
446 final PsiClass servlet = psiFacade.findClass("javax.servlet.Servlet", GlobalSearchScope.allScope(project));
447 if (isAddServletEnabled() && servlet != null && aClass.isInheritor(servlet, true)) {
448 return true;
450 if (isAddMainsEnabled() && PsiMethodUtil.hasMainMethod(aClass)) return true;
452 if (element instanceof PsiModifierListOwner
453 && AnnotationUtil.checkAnnotatedUsingPatterns((PsiModifierListOwner)element, ADDITIONAL_ANNOTATIONS)) {
454 return true;
456 for (UnusedCodeExtension extension : myExtensions) {
457 if (extension.isEntryPoint(element)) {
458 return true;
461 final ImplicitUsageProvider[] implicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
462 for (ImplicitUsageProvider provider : implicitUsageProviders) {
463 if (provider.isImplicitUsage(element)) return true;
465 return false;
468 private static class StrictUnreferencedFilter extends UnreferencedFilter {
469 private StrictUnreferencedFilter(final InspectionTool tool) {
470 super(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) {
482 super(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;
489 return 0;
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);
511 else {
512 getJavaContext().enqueueFieldUsagesProcessor(refField, new GlobalJavaInspectionContext.UsagesProcessor() {
513 public boolean process(PsiReference psiReference) {
514 getEntryPointsManager().addEntryPoint(refField, false);
515 return 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());
527 else {
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);
549 return false;
553 getJavaContext().enqueueClassUsagesProcessor(refClass, new GlobalJavaInspectionContext.UsagesProcessor() {
554 public boolean process(PsiReference psiReference) {
555 getEntryPointsManager().addEntryPoint(refClass, false);
556 return false;
559 requestAdded[0] = true;
567 if (!requestAdded[0]) {
568 if (myPhase == 2) {
569 myProcessedSuspicious = null;
570 return false;
572 else {
573 myPhase = 2;
577 return true;
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);
590 return false;
594 else {
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);
609 return myFilter;
612 public HTMLComposerImpl getComposer() {
613 if (myComposer == null) {
614 myComposer = new DeadHTMLComposer(this);
616 return myComposer;
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;
664 @NotNull
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);
674 if (doc != null) {
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));
697 else {
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));
710 @Nullable
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());
719 return null;
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() {
738 public void run() {
739 final Project project = getContext().getProject();
740 SafeDeleteHandler.invoke(project, psiElements.toArray(new PsiElement[psiElements.size()]), false, new Runnable(){
741 public void run() {
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) {
756 myElement = element;
759 @NotNull
760 public String getText() {
761 return DELETE_QUICK_FIX;
764 @NotNull
765 public String getFamilyName() {
766 return getText();
769 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
770 return true;
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() {
776 public void run() {
777 SafeDeleteHandler
778 .invoke(myElement.getProject(), new PsiElement[]{PsiTreeUtil.getParentOfType(myElement, PsiModifierListOwner.class)}, false);
784 public boolean startInWriteAction() {
785 return true;
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);
811 return true;
815 private static class CommentOutFix implements IntentionAction {
816 private final PsiElement myElement;
818 private CommentOutFix(final PsiElement element) {
819 myElement = element;
822 @NotNull
823 public String getText() {
824 return COMMENT_OUT_QUICK_FIX;
827 @NotNull
828 public String getFamilyName() {
829 return getText();
832 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
833 return true;
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() {
843 return true;
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);
858 return 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());
915 else {
916 ((RefClassImpl)method.getOwnerClass()).setReachable(true);
918 myProcessedMethods.add(method);
919 makeContentReachable((RefJavaElementImpl)method);
920 makeClassInitializersReachable(method.getOwnerClass());
922 else {
923 if (isClassInstantiated(method.getOwnerClass())) {
924 myProcessedMethods.add(method);
925 makeContentReachable((RefJavaElementImpl)method);
927 else {
928 addDelayedMethod(method);
931 for (RefMethod refSub : method.getDerivedMethods()) {
932 visitMethod(refSub);
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;