mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-05 03:38:53 +07:00
1073 lines
41 KiB
Java
1073 lines
41 KiB
Java
/*
|
|
* Copyright 2000-2009 JetBrains s.r.o.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/*
|
|
* Created by IntelliJ IDEA.
|
|
* User: max
|
|
* Date: Oct 12, 2001
|
|
* Time: 9:40:45 PM
|
|
* To change template for new class use
|
|
* Code Style | Class Templates options (Tools | IDE Options).
|
|
*/
|
|
|
|
package com.intellij.codeInspection.deadCode;
|
|
|
|
import com.intellij.ExtensionPoints;
|
|
import com.intellij.analysis.AnalysisScope;
|
|
import com.intellij.codeInsight.AnnotationUtil;
|
|
import com.intellij.codeInsight.daemon.GroupNames;
|
|
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
|
|
import com.intellij.codeInsight.intention.IntentionAction;
|
|
import com.intellij.codeInspection.*;
|
|
import com.intellij.codeInspection.ex.*;
|
|
import com.intellij.codeInspection.reference.*;
|
|
import com.intellij.codeInspection.ui.EntryPointsNode;
|
|
import com.intellij.codeInspection.ui.InspectionNode;
|
|
import com.intellij.codeInspection.ui.InspectionTreeNode;
|
|
import com.intellij.codeInspection.util.RefFilter;
|
|
import com.intellij.lang.annotation.HighlightSeverity;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.Document;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.extensions.ExtensionPoint;
|
|
import com.intellij.openapi.extensions.Extensions;
|
|
import com.intellij.openapi.progress.ProgressManager;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.*;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.impl.PsiClassImplUtil;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor;
|
|
import com.intellij.psi.search.PsiSearchHelper;
|
|
import com.intellij.psi.util.PsiMethodUtil;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.intellij.psi.util.PsiUtilBase;
|
|
import com.intellij.refactoring.safeDelete.SafeDeleteHandler;
|
|
import com.intellij.ui.IdeBorderFactory;
|
|
import com.intellij.ui.SeparatorFactory;
|
|
import com.intellij.util.IncorrectOperationException;
|
|
import com.intellij.util.containers.HashMap;
|
|
import com.intellij.util.text.CharArrayUtil;
|
|
import com.intellij.util.text.DateFormatUtil;
|
|
import org.jdom.Element;
|
|
import org.jetbrains.annotations.NonNls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.InputEvent;
|
|
import java.awt.event.KeyEvent;
|
|
import java.util.*;
|
|
import java.util.List;
|
|
|
|
public class UnusedDeclarationInspection extends FilteringInspectionTool {
|
|
public boolean ADD_MAINS_TO_ENTRIES = true;
|
|
|
|
public boolean ADD_APPLET_TO_ENTRIES = true;
|
|
public boolean ADD_SERVLET_TO_ENTRIES = true;
|
|
public boolean ADD_NONJAVA_TO_ENTRIES = true;
|
|
|
|
private HashSet<RefElement> myProcessedSuspicious = null;
|
|
private int myPhase;
|
|
private final QuickFixAction[] myQuickFixActions;
|
|
public static final String DISPLAY_NAME = InspectionsBundle.message("inspection.dead.code.display.name");
|
|
private WeakUnreferencedFilter myFilter;
|
|
private DeadHTMLComposer myComposer;
|
|
@NonNls public static final String SHORT_NAME = "UnusedDeclaration";
|
|
|
|
private static final String COMMENT_OUT_QUICK_FIX = InspectionsBundle.message("inspection.dead.code.comment.quickfix");
|
|
private static final String DELETE_QUICK_FIX = InspectionsBundle.message("inspection.dead.code.safe.delete.quickfix");
|
|
|
|
@NonNls private static final String DELETE = "delete";
|
|
@NonNls private static final String COMMENT = "comment";
|
|
@NonNls private static final String [] HINTS = {COMMENT, DELETE};
|
|
|
|
public final EntryPoint[] myExtensions;
|
|
private static final Logger LOG = Logger.getInstance("#" + UnusedDeclarationInspection.class.getName());
|
|
|
|
public UnusedDeclarationInspection() {
|
|
|
|
myQuickFixActions = new QuickFixAction[]{new PermanentDeleteAction(), new CommentOutBin(), new MoveToEntries()};
|
|
ExtensionPoint<EntryPoint> point = Extensions.getRootArea().getExtensionPoint(ExtensionPoints.DEAD_CODE_TOOL);
|
|
final EntryPoint[] deadCodeAddins = new EntryPoint[point.getExtensions().length];
|
|
EntryPoint[] extensions = point.getExtensions();
|
|
for (int i = 0, extensionsLength = extensions.length; i < extensionsLength; i++) {
|
|
EntryPoint entryPoint = extensions[i];
|
|
try {
|
|
deadCodeAddins[i] = entryPoint.clone();
|
|
}
|
|
catch (CloneNotSupportedException e) {
|
|
LOG.error(e);
|
|
}
|
|
}
|
|
Arrays.sort(deadCodeAddins, new Comparator<EntryPoint>() {
|
|
public int compare(final EntryPoint o1, final EntryPoint o2) {
|
|
return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
|
|
}
|
|
});
|
|
myExtensions = deadCodeAddins;
|
|
}
|
|
|
|
public void initialize(@NotNull final GlobalInspectionContextImpl context) {
|
|
super.initialize(context);
|
|
((EntryPointsManagerImpl)getEntryPointsManager()).setAddNonJavaEntries(ADD_NONJAVA_TO_ENTRIES);
|
|
}
|
|
|
|
private class OptionsPanel extends JPanel {
|
|
private final JCheckBox myMainsCheckbox;
|
|
private final JCheckBox myAppletToEntries;
|
|
private final JCheckBox myServletToEntries;
|
|
private final JCheckBox myNonJavaCheckbox;
|
|
|
|
private OptionsPanel() {
|
|
super(new GridBagLayout());
|
|
GridBagConstraints gc = new GridBagConstraints();
|
|
gc.weightx = 1;
|
|
gc.weighty = 0;
|
|
gc.insets = new Insets(0, IdeBorderFactory.TITLED_BORDER_INDENT, 2, 0);
|
|
gc.fill = GridBagConstraints.HORIZONTAL;
|
|
gc.anchor = GridBagConstraints.NORTHWEST;
|
|
|
|
myMainsCheckbox = new JCheckBox(InspectionsBundle.message("inspection.dead.code.option"));
|
|
myMainsCheckbox.setSelected(ADD_MAINS_TO_ENTRIES);
|
|
myMainsCheckbox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
ADD_MAINS_TO_ENTRIES = myMainsCheckbox.isSelected();
|
|
}
|
|
});
|
|
|
|
gc.gridy = 0;
|
|
add(myMainsCheckbox, gc);
|
|
|
|
myAppletToEntries = new JCheckBox(InspectionsBundle.message("inspection.dead.code.option3"));
|
|
myAppletToEntries.setSelected(ADD_APPLET_TO_ENTRIES);
|
|
myAppletToEntries.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
ADD_APPLET_TO_ENTRIES = myAppletToEntries.isSelected();
|
|
}
|
|
});
|
|
gc.gridy++;
|
|
add(myAppletToEntries, gc);
|
|
|
|
myServletToEntries = new JCheckBox(InspectionsBundle.message("inspection.dead.code.option4"));
|
|
myServletToEntries.setSelected(ADD_SERVLET_TO_ENTRIES);
|
|
myServletToEntries.addActionListener(new ActionListener(){
|
|
public void actionPerformed(ActionEvent e) {
|
|
ADD_SERVLET_TO_ENTRIES = myServletToEntries.isSelected();
|
|
}
|
|
});
|
|
gc.gridy++;
|
|
add(myServletToEntries, gc);
|
|
|
|
for (final EntryPoint extension : myExtensions) {
|
|
if (extension.showUI()) {
|
|
final JCheckBox extCheckbox = new JCheckBox(extension.getDisplayName());
|
|
extCheckbox.setSelected(extension.isSelected());
|
|
extCheckbox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
extension.setSelected(extCheckbox.isSelected());
|
|
}
|
|
});
|
|
gc.gridy++;
|
|
add(extCheckbox, gc);
|
|
}
|
|
}
|
|
|
|
myNonJavaCheckbox =
|
|
new JCheckBox(InspectionsBundle.message("inspection.dead.code.option5"));
|
|
myNonJavaCheckbox.setSelected(ADD_NONJAVA_TO_ENTRIES);
|
|
myNonJavaCheckbox.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
ADD_NONJAVA_TO_ENTRIES = myNonJavaCheckbox.isSelected();
|
|
}
|
|
});
|
|
|
|
gc.gridy++;
|
|
add(myNonJavaCheckbox, gc);
|
|
|
|
final JButton configureAnnotations = EntryPointsManagerImpl.createConfigureAnnotationsBtn(this);
|
|
gc.fill = GridBagConstraints.NONE;
|
|
gc.gridy++;
|
|
gc.insets.top = 10;
|
|
gc.weighty = 1;
|
|
|
|
add(configureAnnotations, gc);
|
|
}
|
|
}
|
|
|
|
public JComponent createOptionsPanel() {
|
|
final JPanel scrollPane = new JPanel(new BorderLayout());
|
|
scrollPane.add(SeparatorFactory.createSeparator("Entry points", null), BorderLayout.NORTH);
|
|
scrollPane.add(new OptionsPanel(), BorderLayout.CENTER);
|
|
return scrollPane;
|
|
}
|
|
|
|
private boolean isAddMainsEnabled() {
|
|
return ADD_MAINS_TO_ENTRIES;
|
|
}
|
|
|
|
private boolean isAddAppletEnabled() {
|
|
return ADD_APPLET_TO_ENTRIES;
|
|
}
|
|
|
|
private boolean isAddServletEnabled() {
|
|
return ADD_SERVLET_TO_ENTRIES;
|
|
}
|
|
|
|
private boolean isAddNonJavaUsedEnabled() {
|
|
return ADD_NONJAVA_TO_ENTRIES;
|
|
}
|
|
|
|
@NotNull
|
|
public String getDisplayName() {
|
|
return DISPLAY_NAME;
|
|
}
|
|
|
|
@NotNull
|
|
public String getGroupDisplayName() {
|
|
return GroupNames.DECLARATION_REDUNDANCY;
|
|
}
|
|
|
|
@NotNull
|
|
public String getShortName() {
|
|
return SHORT_NAME;
|
|
}
|
|
|
|
public void readSettings(Element node) throws InvalidDataException {
|
|
super.readSettings(node);
|
|
for (EntryPoint extension : myExtensions) {
|
|
extension.readExternal(node);
|
|
}
|
|
}
|
|
|
|
public void writeSettings(Element node) throws WriteExternalException {
|
|
super.writeSettings(node);
|
|
for (EntryPoint extension : myExtensions) {
|
|
extension.writeExternal(node);
|
|
}
|
|
}
|
|
|
|
private static boolean isSerializationImplicitlyUsedField(PsiField field) {
|
|
@NonNls final String name = field.getName();
|
|
if (!"serialVersionUID".equals(name) && !"serialPersistentFields".equals(name)) return false;
|
|
if (!field.hasModifierProperty(PsiModifier.STATIC)) return false;
|
|
PsiClass aClass = field.getContainingClass();
|
|
return aClass == null || isSerializable(aClass, null);
|
|
}
|
|
|
|
private static boolean isWriteObjectMethod(PsiMethod method, RefClass refClass) {
|
|
@NonNls final String name = method.getName();
|
|
if (!"writeObject".equals(name)) return false;
|
|
PsiParameter[] parameters = method.getParameterList().getParameters();
|
|
if (parameters.length != 1) return false;
|
|
if (!parameters[0].getType().equalsToText("java.io.ObjectOutputStream")) return false;
|
|
if (method.hasModifierProperty(PsiModifier.STATIC)) return false;
|
|
PsiClass aClass = method.getContainingClass();
|
|
return !(aClass != null && !isSerializable(aClass, refClass));
|
|
}
|
|
|
|
private static boolean isReadObjectMethod(PsiMethod method, RefClass refClass) {
|
|
@NonNls final String name = method.getName();
|
|
if (!"readObject".equals(name)) return false;
|
|
PsiParameter[] parameters = method.getParameterList().getParameters();
|
|
if (parameters.length != 1) return false;
|
|
if (!parameters[0].getType().equalsToText("java.io.ObjectInputStream")) return false;
|
|
if (method.hasModifierProperty(PsiModifier.STATIC)) return false;
|
|
PsiClass aClass = method.getContainingClass();
|
|
return !(aClass != null && !isSerializable(aClass, refClass));
|
|
}
|
|
|
|
private static boolean isWriteReplaceMethod(PsiMethod method, RefClass refClass) {
|
|
@NonNls final String name = method.getName();
|
|
if (!"writeReplace".equals(name)) return false;
|
|
PsiParameter[] parameters = method.getParameterList().getParameters();
|
|
if (parameters.length != 0) return false;
|
|
if (!method.getReturnType().equalsToText("java.lang.Object")) return false;
|
|
if (method.hasModifierProperty(PsiModifier.STATIC)) return false;
|
|
PsiClass aClass = method.getContainingClass();
|
|
return !(aClass != null && !isSerializable(aClass, refClass));
|
|
}
|
|
|
|
private static boolean isReadResolveMethod(PsiMethod method, RefClass refClass) {
|
|
@NonNls final String name = method.getName();
|
|
if (!"readResolve".equals(name)) return false;
|
|
PsiParameter[] parameters = method.getParameterList().getParameters();
|
|
if (parameters.length != 0) return false;
|
|
if (!method.getReturnType().equalsToText("java.lang.Object")) return false;
|
|
if (method.hasModifierProperty(PsiModifier.STATIC)) return false;
|
|
final PsiClass aClass = method.getContainingClass();
|
|
return !(aClass != null && !isSerializable(aClass, refClass));
|
|
}
|
|
|
|
private static boolean isSerializable(PsiClass aClass, @Nullable RefClass refClass) {
|
|
final PsiClass serializableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.io.Serializable", aClass.getResolveScope());
|
|
if (serializableClass != null) {
|
|
return isSerializable(aClass, refClass, serializableClass);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean isSerializable(PsiClass aClass, RefClass refClass, PsiClass serializableClass) {
|
|
if (aClass == null) return false;
|
|
if (aClass.isInheritor(serializableClass, true)) return true;
|
|
if (refClass != null) {
|
|
final Set<RefClass> subClasses = refClass.getSubClasses();
|
|
for (RefClass subClass : subClasses) {
|
|
if (isSerializable(subClass.getElement(), subClass, serializableClass)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void runInspection(@NotNull final AnalysisScope scope, @NotNull final InspectionManager manager) {
|
|
getRefManager().iterate(new RefJavaVisitor() {
|
|
@Override public void visitElement(final RefEntity refEntity) {
|
|
if (refEntity instanceof RefJavaElement) {
|
|
final RefElementImpl refElement = (RefElementImpl)refEntity;
|
|
if (!refElement.isSuspicious()) return;
|
|
|
|
PsiFile file = refElement.getContainingFile();
|
|
|
|
if (file == null) return;
|
|
final boolean isSuppressed = refElement.isSuppressed(getShortName());
|
|
if (!getContext().isToCheckFile(file, UnusedDeclarationInspection.this) || isSuppressed) {
|
|
if (isSuppressed || !scope.contains(file)) {
|
|
getEntryPointsManager().addEntryPoint(refElement, false);
|
|
}
|
|
return;
|
|
}
|
|
refElement.accept(new RefJavaVisitor() {
|
|
@Override public void visitElement(final RefEntity elem) {
|
|
if (elem instanceof RefElement) {
|
|
final RefElement element = (RefElement)elem;
|
|
if (isEntryPoint(element)) {
|
|
getEntryPointsManager().addEntryPoint(element, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override public void visitMethod(RefMethod method) {
|
|
if (isAddMainsEnabled() && method.isAppMain()) {
|
|
getEntryPointsManager().addEntryPoint(method, false);
|
|
} else {
|
|
super.visitMethod(method);
|
|
}
|
|
}
|
|
|
|
@Override public void visitClass(RefClass aClass) {
|
|
final PsiClass psiClass = aClass.getElement();
|
|
if (psiClass == null) return;
|
|
if (
|
|
isAddAppletEnabled() && aClass.isApplet() ||
|
|
isAddServletEnabled() && aClass.isServlet()) {
|
|
getEntryPointsManager().addEntryPoint(aClass, false);
|
|
} else if (psiClass.isAnnotationType()){
|
|
getEntryPointsManager().addEntryPoint(aClass, false);
|
|
final PsiMethod[] psiMethods = psiClass.getMethods();
|
|
for (PsiMethod psiMethod : psiMethods) {
|
|
getEntryPointsManager().addEntryPoint(getRefManager().getReference(psiMethod), false);
|
|
}
|
|
} else if (psiClass.isEnum()) {
|
|
getEntryPointsManager().addEntryPoint(aClass, false);
|
|
} else {
|
|
super.visitClass(aClass);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
if (isAddNonJavaUsedEnabled()) {
|
|
checkForReachables();
|
|
ProgressManager.getInstance().runProcess(new Runnable() {
|
|
public void run() {
|
|
final RefFilter filter = new StrictUnreferencedFilter(UnusedDeclarationInspection.this);
|
|
final PsiSearchHelper helper = PsiSearchHelper.SERVICE.getInstance(getRefManager().getProject());
|
|
getRefManager().iterate(new RefJavaVisitor() {
|
|
@Override public void visitElement(final RefEntity refEntity) {
|
|
if (refEntity instanceof RefClass && filter.accepts((RefClass)refEntity)) {
|
|
findExternalClassReferences((RefClass)refEntity);
|
|
}
|
|
else if (refEntity instanceof RefMethod) {
|
|
RefMethod refMethod = (RefMethod)refEntity;
|
|
if (refMethod.isConstructor() && filter.accepts(refMethod)) {
|
|
findExternalClassReferences(refMethod.getOwnerClass());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void findExternalClassReferences(final RefClass refElement) {
|
|
PsiClass psiClass = refElement.getElement();
|
|
String qualifiedName = psiClass.getQualifiedName();
|
|
if (qualifiedName != null) {
|
|
helper.processUsagesInNonJavaFiles(qualifiedName,
|
|
new PsiNonJavaFileReferenceProcessor() {
|
|
public boolean process(PsiFile file, int startOffset, int endOffset) {
|
|
getEntryPointsManager().addEntryPoint(refElement, false);
|
|
return false;
|
|
}
|
|
},
|
|
GlobalSearchScope.projectScope(getContext().getProject()));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}, null);
|
|
}
|
|
|
|
myProcessedSuspicious = new HashSet<RefElement>();
|
|
myPhase = 1;
|
|
}
|
|
|
|
private boolean isEntryPoint(final RefElement owner) {
|
|
final PsiElement element = owner.getElement();
|
|
if (RefUtil.isImplicitUsage(element)) return true;
|
|
if (element instanceof PsiModifierListOwner) {
|
|
final EntryPointsManagerImpl entryPointsManager = EntryPointsManagerImpl.getInstance(element.getProject());
|
|
if (entryPointsManager.isEntryPoint((PsiModifierListOwner)element)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (EntryPoint extension : myExtensions) {
|
|
if (extension.isEntryPoint(owner, element)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean isEntryPoint(@NotNull PsiElement element) {
|
|
final Project project = element.getProject();
|
|
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
|
|
if (element instanceof PsiMethod && isAddMainsEnabled() && PsiClassImplUtil.isMainMethod((PsiMethod)element)) {
|
|
return true;
|
|
}
|
|
if (element instanceof PsiClass) {
|
|
PsiClass aClass = (PsiClass)element;
|
|
/*
|
|
if (aClass.isAnnotationType()) {
|
|
return true;
|
|
}
|
|
|
|
if (aClass.isEnum()) {
|
|
return true;
|
|
}
|
|
*/
|
|
final PsiClass applet = psiFacade.findClass("java.applet.Applet", GlobalSearchScope.allScope(project));
|
|
if (isAddAppletEnabled() && applet != null && aClass.isInheritor(applet, true)) {
|
|
return true;
|
|
}
|
|
|
|
final PsiClass servlet = psiFacade.findClass("javax.servlet.Servlet", GlobalSearchScope.allScope(project));
|
|
if (isAddServletEnabled() && servlet != null && aClass.isInheritor(servlet, true)) {
|
|
return true;
|
|
}
|
|
if (isAddMainsEnabled() && PsiMethodUtil.hasMainMethod(aClass)) return true;
|
|
}
|
|
if (element instanceof PsiModifierListOwner) {
|
|
final EntryPointsManagerImpl entryPointsManager = EntryPointsManagerImpl.getInstance(project);
|
|
if (AnnotationUtil
|
|
.checkAnnotatedUsingPatterns((PsiModifierListOwner)element, entryPointsManager.ADDITIONAL_ANNOTATIONS) ||
|
|
AnnotationUtil
|
|
.checkAnnotatedUsingPatterns((PsiModifierListOwner)element, entryPointsManager.getAdditionalAnnotations())) {
|
|
return true;
|
|
}
|
|
}
|
|
for (EntryPoint extension : myExtensions) {
|
|
if (extension.isEntryPoint(element)) {
|
|
return true;
|
|
}
|
|
}
|
|
final ImplicitUsageProvider[] implicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
|
|
for (ImplicitUsageProvider provider : implicitUsageProviders) {
|
|
if (provider.isImplicitUsage(element)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static class StrictUnreferencedFilter extends UnreferencedFilter {
|
|
private StrictUnreferencedFilter(final InspectionTool tool) {
|
|
super(tool);
|
|
}
|
|
|
|
public int getElementProblemCount(RefJavaElement refElement) {
|
|
final int problemCount = super.getElementProblemCount(refElement);
|
|
if (problemCount > -1) return problemCount;
|
|
return refElement.isReferenced() ? 0 : 1;
|
|
}
|
|
}
|
|
|
|
private static class WeakUnreferencedFilter extends UnreferencedFilter {
|
|
private WeakUnreferencedFilter(final InspectionTool tool) {
|
|
super(tool);
|
|
}
|
|
|
|
public int getElementProblemCount(final RefJavaElement refElement) {
|
|
final int problemCount = super.getElementProblemCount(refElement);
|
|
if (problemCount > - 1) return problemCount;
|
|
if (!((RefElementImpl)refElement).hasSuspiciousCallers() || ((RefJavaElementImpl)refElement).isSuspiciousRecursive()) return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public boolean queryExternalUsagesRequests(final InspectionManager manager) {
|
|
checkForReachables();
|
|
final RefFilter filter = myPhase == 1 ? new StrictUnreferencedFilter(this) : new RefUnreachableFilter(this);
|
|
final boolean[] requestAdded = {false};
|
|
|
|
getRefManager().iterate(new RefJavaVisitor() {
|
|
@Override public void visitElement(RefEntity refEntity) {
|
|
if (!(refEntity instanceof RefJavaElement)) return;
|
|
if (refEntity instanceof RefClass && ((RefClass)refEntity).isAnonymous()) return;
|
|
RefJavaElement refElement= (RefJavaElement)refEntity;
|
|
if (filter.accepts(refElement) && !myProcessedSuspicious.contains(refElement)) {
|
|
refEntity.accept(new RefJavaVisitor() {
|
|
@Override public void visitField(final RefField refField) {
|
|
myProcessedSuspicious.add(refField);
|
|
PsiField psiField = refField.getElement();
|
|
if (isSerializationImplicitlyUsedField(psiField)) {
|
|
getEntryPointsManager().addEntryPoint(refField, false);
|
|
}
|
|
else {
|
|
getJavaContext().enqueueFieldUsagesProcessor(refField, new GlobalJavaInspectionContext.UsagesProcessor() {
|
|
public boolean process(PsiReference psiReference) {
|
|
getEntryPointsManager().addEntryPoint(refField, false);
|
|
return false;
|
|
}
|
|
});
|
|
requestAdded[0] = true;
|
|
}
|
|
}
|
|
|
|
@Override public void visitMethod(final RefMethod refMethod) {
|
|
myProcessedSuspicious.add(refMethod);
|
|
if (refMethod instanceof RefImplicitConstructor) {
|
|
visitClass(refMethod.getOwnerClass());
|
|
}
|
|
else {
|
|
PsiMethod psiMethod = (PsiMethod)refMethod.getElement();
|
|
if (isSerializablePatternMethod(psiMethod, refMethod.getOwnerClass())) {
|
|
getEntryPointsManager().addEntryPoint(refMethod, false);
|
|
}
|
|
else if (!refMethod.isExternalOverride() && !PsiModifier.PRIVATE.equals(refMethod.getAccessModifier())) {
|
|
for (final RefMethod derivedMethod : refMethod.getDerivedMethods()) {
|
|
myProcessedSuspicious.add(derivedMethod);
|
|
}
|
|
|
|
enqueueMethodUsages(refMethod);
|
|
requestAdded[0] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override public void visitClass(final RefClass refClass) {
|
|
myProcessedSuspicious.add(refClass);
|
|
if (!refClass.isAnonymous()) {
|
|
getJavaContext().enqueueDerivedClassesProcessor(refClass, new GlobalJavaInspectionContext.DerivedClassesProcessor() {
|
|
public boolean process(PsiClass inheritor) {
|
|
getEntryPointsManager().addEntryPoint(refClass, false);
|
|
return false;
|
|
}
|
|
});
|
|
|
|
getJavaContext().enqueueClassUsagesProcessor(refClass, new GlobalJavaInspectionContext.UsagesProcessor() {
|
|
public boolean process(PsiReference psiReference) {
|
|
getEntryPointsManager().addEntryPoint(refClass, false);
|
|
return false;
|
|
}
|
|
});
|
|
requestAdded[0] = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!requestAdded[0]) {
|
|
if (myPhase == 2) {
|
|
myProcessedSuspicious = null;
|
|
return false;
|
|
}
|
|
else {
|
|
myPhase = 2;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static boolean isSerializablePatternMethod(PsiMethod psiMethod, RefClass refClass) {
|
|
return isReadObjectMethod(psiMethod, refClass) || isWriteObjectMethod(psiMethod, refClass) || isReadResolveMethod(psiMethod, refClass) ||
|
|
isWriteReplaceMethod(psiMethod, refClass);
|
|
}
|
|
|
|
private void enqueueMethodUsages(final RefMethod refMethod) {
|
|
if (refMethod.getSuperMethods().isEmpty()) {
|
|
getJavaContext().enqueueMethodUsagesProcessor(refMethod, new GlobalJavaInspectionContext.UsagesProcessor() {
|
|
public boolean process(PsiReference psiReference) {
|
|
getEntryPointsManager().addEntryPoint(refMethod, false);
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
for (RefMethod refSuper : refMethod.getSuperMethods()) {
|
|
enqueueMethodUsages(refSuper);
|
|
}
|
|
}
|
|
}
|
|
|
|
public GlobalJavaInspectionContext getJavaContext() {
|
|
return getContext().getExtension(GlobalJavaInspectionContext.CONTEXT);
|
|
}
|
|
|
|
public RefFilter getFilter() {
|
|
if (myFilter == null) {
|
|
myFilter = new WeakUnreferencedFilter(this);
|
|
}
|
|
return myFilter;
|
|
}
|
|
|
|
public HTMLComposerImpl getComposer() {
|
|
if (myComposer == null) {
|
|
myComposer = new DeadHTMLComposer(this);
|
|
}
|
|
return myComposer;
|
|
}
|
|
|
|
public void exportResults(@NotNull final Element parentNode) {
|
|
final WeakUnreferencedFilter filter = new WeakUnreferencedFilter(this);
|
|
getRefManager().iterate(new RefJavaVisitor() {
|
|
@Override public void visitElement(RefEntity refEntity) {
|
|
if (!(refEntity instanceof RefJavaElement)) return;
|
|
if (!getIgnoredRefElements().contains(refEntity) && filter.accepts((RefJavaElement)refEntity)) {
|
|
if (refEntity instanceof RefImplicitConstructor) refEntity = ((RefImplicitConstructor)refEntity).getOwnerClass();
|
|
Element element = refEntity.getRefManager().export(refEntity, parentNode, -1);
|
|
@NonNls Element problemClassElement = new Element(InspectionsBundle.message("inspection.export.results.problem.element.tag"));
|
|
|
|
if (refEntity instanceof RefElement) {
|
|
final RefElement refElement = (RefElement)refEntity;
|
|
final HighlightSeverity severity = getCurrentSeverity(refElement);
|
|
final String attributeKey = getTextAttributeKey(refElement.getElement().getProject(), severity, ProblemHighlightType.LIKE_UNUSED_SYMBOL);
|
|
problemClassElement.setAttribute("severity", severity.myName);
|
|
problemClassElement.setAttribute("attribute_key", attributeKey);
|
|
}
|
|
|
|
problemClassElement.addContent(InspectionsBundle.message("inspection.export.results.dead.code"));
|
|
element.addContent(problemClassElement);
|
|
|
|
@NonNls Element hintsElement = new Element("hints");
|
|
|
|
for (String hint : HINTS) {
|
|
@NonNls Element hintElement = new Element("hint");
|
|
hintElement.setAttribute("value", hint);
|
|
hintsElement.addContent(hintElement);
|
|
}
|
|
element.addContent(hintsElement);
|
|
|
|
|
|
Element descriptionElement = new Element(InspectionsBundle.message("inspection.export.results.description.tag"));
|
|
StringBuffer buf = new StringBuffer();
|
|
DeadHTMLComposer.appendProblemSynopsis((RefElement)refEntity, buf);
|
|
descriptionElement.addContent(buf.toString());
|
|
element.addContent(descriptionElement);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public QuickFixAction[] getQuickFixes(final RefEntity[] refElements) {
|
|
return myQuickFixActions;
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public JobDescriptor[] getJobDescriptors(GlobalInspectionContext context) {
|
|
return new JobDescriptor[]{((GlobalInspectionContextImpl)context).BUILD_GRAPH, ((GlobalInspectionContextImpl)context).FIND_EXTERNAL_USAGES};
|
|
}
|
|
|
|
private static void commentOutDead(PsiElement psiElement) {
|
|
PsiFile psiFile = psiElement.getContainingFile();
|
|
|
|
if (psiFile != null) {
|
|
Document doc = PsiDocumentManager.getInstance(psiElement.getProject()).getDocument(psiFile);
|
|
if (doc != null) {
|
|
TextRange textRange = psiElement.getTextRange();
|
|
String date = DateFormatUtil.formatDateTime(new Date());
|
|
|
|
int startOffset = textRange.getStartOffset();
|
|
CharSequence chars = doc.getCharsSequence();
|
|
while (CharArrayUtil.regionMatches(chars, startOffset, InspectionsBundle.message("inspection.dead.code.comment"))) {
|
|
int line = doc.getLineNumber(startOffset) + 1;
|
|
if (line < doc.getLineCount()) {
|
|
startOffset = doc.getLineStartOffset(line);
|
|
startOffset = CharArrayUtil.shiftForward(chars, startOffset, " \t");
|
|
}
|
|
}
|
|
|
|
int endOffset = textRange.getEndOffset();
|
|
|
|
int line1 = doc.getLineNumber(startOffset);
|
|
int line2 = doc.getLineNumber(endOffset - 1);
|
|
|
|
if (line1 == line2) {
|
|
doc.insertString(startOffset, InspectionsBundle.message("inspection.dead.code.date.comment", date));
|
|
}
|
|
else {
|
|
for (int i = line1; i <= line2; i++) {
|
|
doc.insertString(doc.getLineStartOffset(i), "//");
|
|
}
|
|
|
|
doc.insertString(doc.getLineStartOffset(Math.min(line2 + 1, doc.getLineCount() - 1)),
|
|
InspectionsBundle.message("inspection.dead.code.stop.comment", date));
|
|
doc.insertString(doc.getLineStartOffset(line1), InspectionsBundle.message("inspection.dead.code.start.comment", date));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public IntentionAction findQuickFixes(final CommonProblemDescriptor descriptor, final String hint) {
|
|
if (descriptor instanceof ProblemDescriptor) {
|
|
if (DELETE.equals(hint)) {
|
|
return new PermanentDeleteFix(((ProblemDescriptor)descriptor).getPsiElement());
|
|
} else if (COMMENT.equals(hint)) {
|
|
return new CommentOutFix(((ProblemDescriptor)descriptor).getPsiElement());
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private class PermanentDeleteAction extends QuickFixAction {
|
|
private PermanentDeleteAction() {
|
|
super(DELETE_QUICK_FIX, IconLoader.getIcon("/actions/cancel.png"), KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), UnusedDeclarationInspection.this);
|
|
}
|
|
|
|
protected boolean applyFix(final RefElement[] refElements) {
|
|
if (!super.applyFix(refElements)) return false;
|
|
final ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>();
|
|
for (RefElement refElement : refElements) {
|
|
PsiElement psiElement = refElement.getElement();
|
|
if (psiElement == null) continue;
|
|
if (getFilter().getElementProblemCount((RefJavaElement)refElement) == 0) continue;
|
|
psiElements.add(psiElement);
|
|
}
|
|
|
|
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
|
public void run() {
|
|
final Project project = getContext().getProject();
|
|
SafeDeleteHandler.invoke(project, PsiUtilBase.toPsiElementArray(psiElements), false, new Runnable() {
|
|
public void run() {
|
|
removeElements(refElements, project, UnusedDeclarationInspection.this);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return false; //refresh after safe delete dialog is closed
|
|
}
|
|
}
|
|
|
|
private static class PermanentDeleteFix implements IntentionAction {
|
|
private final PsiElement myElement;
|
|
|
|
private PermanentDeleteFix(final PsiElement element) {
|
|
myElement = element;
|
|
}
|
|
|
|
@NotNull
|
|
public String getText() {
|
|
return DELETE_QUICK_FIX;
|
|
}
|
|
|
|
@NotNull
|
|
public String getFamilyName() {
|
|
return getText();
|
|
}
|
|
|
|
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
|
return true;
|
|
}
|
|
|
|
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
|
if (myElement != null && myElement.isValid()) {
|
|
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
|
public void run() {
|
|
SafeDeleteHandler
|
|
.invoke(myElement.getProject(), new PsiElement[]{PsiTreeUtil.getParentOfType(myElement, PsiModifierListOwner.class)}, false);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public boolean startInWriteAction() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private class CommentOutBin extends QuickFixAction {
|
|
private CommentOutBin() {
|
|
super(COMMENT_OUT_QUICK_FIX, null, KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK),
|
|
UnusedDeclarationInspection.this);
|
|
}
|
|
|
|
protected boolean applyFix(RefElement[] refElements) {
|
|
if (!super.applyFix(refElements)) return false;
|
|
ArrayList<RefElement> deletedRefs = new ArrayList<RefElement>(1);
|
|
for (RefElement refElement : refElements) {
|
|
PsiElement psiElement = refElement.getElement();
|
|
if (psiElement == null) continue;
|
|
if (getFilter().getElementProblemCount((RefJavaElement)refElement) == 0) continue;
|
|
commentOutDead(psiElement);
|
|
refElement.getRefManager().removeRefElement(refElement, deletedRefs);
|
|
}
|
|
|
|
EntryPointsManager entryPointsManager = getEntryPointsManager();
|
|
for (RefElement refElement : deletedRefs) {
|
|
entryPointsManager.removeEntryPoint(refElement);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static class CommentOutFix implements IntentionAction {
|
|
private final PsiElement myElement;
|
|
|
|
private CommentOutFix(final PsiElement element) {
|
|
myElement = element;
|
|
}
|
|
|
|
@NotNull
|
|
public String getText() {
|
|
return COMMENT_OUT_QUICK_FIX;
|
|
}
|
|
|
|
@NotNull
|
|
public String getFamilyName() {
|
|
return getText();
|
|
}
|
|
|
|
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
|
return true;
|
|
}
|
|
|
|
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
|
if (myElement != null && myElement.isValid()) {
|
|
commentOutDead(PsiTreeUtil.getParentOfType(myElement, PsiModifierListOwner.class));
|
|
}
|
|
}
|
|
|
|
public boolean startInWriteAction() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private class MoveToEntries extends QuickFixAction {
|
|
private MoveToEntries() {
|
|
super(InspectionsBundle.message("inspection.dead.code.entry.point.quickfix"), null, KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), UnusedDeclarationInspection.this);
|
|
}
|
|
|
|
protected boolean applyFix(RefElement[] refElements) {
|
|
final EntryPointsManager entryPointsManager = getEntryPointsManager();
|
|
for (RefElement refElement : refElements) {
|
|
entryPointsManager.addEntryPoint(refElement, true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
private void checkForReachables() {
|
|
CodeScanner codeScanner = new CodeScanner();
|
|
|
|
// Cleanup previous reachability information.
|
|
getRefManager().iterate(new RefJavaVisitor() {
|
|
@Override public void visitElement(RefEntity refEntity) {
|
|
if (refEntity instanceof RefJavaElement) {
|
|
final RefJavaElementImpl refElement = (RefJavaElementImpl)refEntity;
|
|
final PsiElement element = refElement.getElement();
|
|
if (element == null) return;
|
|
if (!getContext().isToCheckMember(refElement, UnusedDeclarationInspection.this)) return;
|
|
refElement.setReachable(false);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
for (RefElement entry : getEntryPointsManager().getEntryPoints()) {
|
|
entry.accept(codeScanner);
|
|
}
|
|
|
|
while (codeScanner.newlyInstantiatedClassesCount() != 0) {
|
|
codeScanner.cleanInstantiatedClassesCount();
|
|
codeScanner.processDelayedMethods();
|
|
}
|
|
}
|
|
|
|
private EntryPointsManager getEntryPointsManager() {
|
|
return getContext().getExtension(GlobalJavaInspectionContext.CONTEXT).getEntryPointsManager(getContext().getRefManager());
|
|
}
|
|
|
|
private static class CodeScanner extends RefJavaVisitor {
|
|
private final HashMap<RefClass, HashSet<RefMethod>> myClassIDtoMethods;
|
|
private final HashSet<RefClass> myInstantiatedClasses;
|
|
private int myInstantiatedClassesCount;
|
|
private final HashSet<RefMethod> myProcessedMethods;
|
|
|
|
private CodeScanner() {
|
|
myClassIDtoMethods = new HashMap<RefClass, HashSet<RefMethod>>();
|
|
myInstantiatedClasses = new HashSet<RefClass>();
|
|
myProcessedMethods = new HashSet<RefMethod>();
|
|
myInstantiatedClassesCount = 0;
|
|
}
|
|
|
|
@Override public void visitMethod(RefMethod method) {
|
|
if (!myProcessedMethods.contains(method)) {
|
|
// Process class's static intitializers
|
|
if (method.isStatic() || method.isConstructor()) {
|
|
if (method.isConstructor()) {
|
|
addInstantiatedClass(method.getOwnerClass());
|
|
}
|
|
else {
|
|
((RefClassImpl)method.getOwnerClass()).setReachable(true);
|
|
}
|
|
myProcessedMethods.add(method);
|
|
makeContentReachable((RefJavaElementImpl)method);
|
|
makeClassInitializersReachable(method.getOwnerClass());
|
|
}
|
|
else {
|
|
if (isClassInstantiated(method.getOwnerClass())) {
|
|
myProcessedMethods.add(method);
|
|
makeContentReachable((RefJavaElementImpl)method);
|
|
}
|
|
else {
|
|
addDelayedMethod(method);
|
|
}
|
|
|
|
for (RefMethod refSub : method.getDerivedMethods()) {
|
|
visitMethod(refSub);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override public void visitClass(RefClass refClass) {
|
|
boolean alreadyActive = refClass.isReachable();
|
|
((RefClassImpl)refClass).setReachable(true);
|
|
|
|
if (!alreadyActive) {
|
|
// Process class's static intitializers.
|
|
makeClassInitializersReachable(refClass);
|
|
}
|
|
|
|
addInstantiatedClass(refClass);
|
|
}
|
|
|
|
@Override public void visitField(RefField field) {
|
|
// Process class's static intitializers.
|
|
if (!field.isReachable()) {
|
|
makeContentReachable((RefJavaElementImpl)field);
|
|
makeClassInitializersReachable(field.getOwnerClass());
|
|
}
|
|
}
|
|
|
|
private void addInstantiatedClass(RefClass refClass) {
|
|
if (myInstantiatedClasses.add(refClass)) {
|
|
((RefClassImpl)refClass).setReachable(true);
|
|
myInstantiatedClassesCount++;
|
|
|
|
final List<RefMethod> refMethods = refClass.getLibraryMethods();
|
|
for (RefMethod refMethod : refMethods) {
|
|
refMethod.accept(this);
|
|
}
|
|
for (RefClass baseClass : refClass.getBaseClasses()) {
|
|
addInstantiatedClass(baseClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void makeContentReachable(RefJavaElementImpl refElement) {
|
|
refElement.setReachable(true);
|
|
for (RefElement refCallee : refElement.getOutReferences()) {
|
|
refCallee.accept(this);
|
|
}
|
|
}
|
|
|
|
private void makeClassInitializersReachable(RefClass refClass) {
|
|
for (RefElement refCallee : refClass.getOutReferences()) {
|
|
refCallee.accept(this);
|
|
}
|
|
}
|
|
|
|
private void addDelayedMethod(RefMethod refMethod) {
|
|
HashSet<RefMethod> methods = myClassIDtoMethods.get(refMethod.getOwnerClass());
|
|
if (methods == null) {
|
|
methods = new HashSet<RefMethod>();
|
|
myClassIDtoMethods.put(refMethod.getOwnerClass(), methods);
|
|
}
|
|
methods.add(refMethod);
|
|
}
|
|
|
|
private boolean isClassInstantiated(RefClass refClass) {
|
|
return myInstantiatedClasses.contains(refClass);
|
|
}
|
|
|
|
private int newlyInstantiatedClassesCount() {
|
|
return myInstantiatedClassesCount;
|
|
}
|
|
|
|
private void cleanInstantiatedClassesCount() {
|
|
myInstantiatedClassesCount = 0;
|
|
}
|
|
|
|
private void processDelayedMethods() {
|
|
RefClass[] instClasses = myInstantiatedClasses.toArray(new RefClass[myInstantiatedClasses.size()]);
|
|
for (RefClass refClass : instClasses) {
|
|
if (isClassInstantiated(refClass)) {
|
|
HashSet<RefMethod> methods = myClassIDtoMethods.get(refClass);
|
|
if (methods != null) {
|
|
RefMethod[] arMethods = methods.toArray(new RefMethod[methods.size()]);
|
|
for (RefMethod arMethod : arMethods) {
|
|
arMethod.accept(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void updateContent() {
|
|
checkForReachables();
|
|
super.updateContent();
|
|
}
|
|
|
|
public InspectionNode createToolNode(final InspectionRVContentProvider provider, final InspectionTreeNode parentNode, final boolean showStructure) {
|
|
final InspectionNode toolNode = super.createToolNode(provider, parentNode, showStructure);
|
|
final EntryPointsNode entryPointsNode = new EntryPointsNode(this);
|
|
provider.appendToolNodeContent(entryPointsNode, toolNode, showStructure);
|
|
return entryPointsNode;
|
|
}
|
|
}
|