blob: d46ebee90c5597a233f79c30e8eb5a8be544f187 [file] [log] [blame]
/*
* Copyright 2000-2014 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.ToolExtensionPoints;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInsight.daemon.GroupNames;
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtilBase;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.EntryPointsManager;
import com.intellij.codeInspection.ex.GlobalInspectionContextBase;
import com.intellij.codeInspection.ex.JobDescriptor;
import com.intellij.codeInspection.reference.*;
import com.intellij.codeInspection.util.RefFilter;
import com.intellij.openapi.diagnostic.Logger;
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.project.ProjectManager;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
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.util.containers.HashMap;
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.util.*;
import java.util.List;
public class UnusedDeclarationInspection extends GlobalInspectionTool {
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 Set<RefElement> myProcessedSuspicious = null;
private int myPhase;
public static final String DISPLAY_NAME = InspectionsBundle.message("inspection.dead.code.display.name");
@NonNls public static final String SHORT_NAME = "UnusedDeclaration";
@NonNls private static final String ALTERNATIVE_ID = "unused";
public final EntryPoint[] myExtensions;
private static final Logger LOG = Logger.getInstance("#" + UnusedDeclarationInspection.class.getName());
private GlobalInspectionContext myContext;
public UnusedDeclarationInspection() {
ExtensionPoint<EntryPoint> point = Extensions.getRootArea().getExtensionPoint(ToolExtensionPoints.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>() {
@Override
public int compare(final EntryPoint o1, final EntryPoint o2) {
return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
}
});
myExtensions = deadCodeAddins;
}
private GlobalInspectionContext getContext() {
return myContext;
}
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, 20, 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() {
@Override
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() {
@Override
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(){
@Override
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() {
@Override
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() {
@Override
public void actionPerformed(ActionEvent e) {
ADD_NONJAVA_TO_ENTRIES = myNonJavaCheckbox.isSelected();
}
});
gc.gridy++;
add(myNonJavaCheckbox, gc);
Project project = guessProject();
JButton configureAnnotations = EntryPointsManager.getInstance(project).createConfigureAnnotationsBtn();
gc.fill = GridBagConstraints.NONE;
gc.gridy++;
gc.insets.top = 10;
gc.weighty = 1;
add(configureAnnotations, gc);
}
}
private Project guessProject() {
Project project = myContext == null ? null : myContext.getProject();
if (project == null) {
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
project = openProjects.length == 0 ? ProjectManager.getInstance().getDefaultProject() : openProjects[0];
}
return project;
}
@Override
public JComponent createOptionsPanel() {
final JPanel scrollPane = new JPanel(new BorderLayout());
scrollPane.add(new JLabel("Entry points:"), 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;
}
@Override
@NotNull
public String getDisplayName() {
return DISPLAY_NAME;
}
@Override
@NotNull
public String getGroupDisplayName() {
return GroupNames.DECLARATION_REDUNDANCY;
}
@Override
@NotNull
public String getShortName() {
return SHORT_NAME;
}
@Override
public void readSettings(@NotNull Element node) throws InvalidDataException {
super.readSettings(node);
for (EntryPoint extension : myExtensions) {
extension.readExternal(node);
}
}
@Override
public void writeSettings(@NotNull Element node) throws WriteExternalException {
super.writeSettings(node);
for (EntryPoint extension : myExtensions) {
extension.writeExternal(node);
}
}
private static boolean isExternalizableNoParameterConstructor(@NotNull PsiMethod method, RefClass refClass) {
if (!method.isConstructor()) return false;
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) return false;
final PsiParameterList parameterList = method.getParameterList();
if (parameterList.getParametersCount() != 0) return false;
final PsiClass aClass = method.getContainingClass();
return aClass == null || isExternalizable(aClass, refClass);
}
private static boolean isSerializationImplicitlyUsedField(@NotNull PsiField field) {
@NonNls final String name = field.getName();
if (!HighlightUtilBase.SERIAL_VERSION_UID_FIELD_NAME.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(@NotNull 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(@NotNull 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(@NotNull 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(CommonClassNames.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(@NotNull 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(CommonClassNames.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());
return serializableClass != null && isSerializable(aClass, refClass, serializableClass);
}
private static boolean isExternalizable(@NotNull PsiClass aClass, RefClass refClass) {
final GlobalSearchScope scope = aClass.getResolveScope();
final PsiClass externalizableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.io.Externalizable", scope);
return externalizableClass != null && isSerializable(aClass, refClass, externalizableClass);
}
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;
}
@Override
public void runInspection(@NotNull final AnalysisScope scope,
@NotNull InspectionManager manager,
@NotNull final GlobalInspectionContext globalContext,
@NotNull ProblemDescriptionsProcessor problemDescriptionsProcessor) {
globalContext.getRefManager().iterate(new RefJavaVisitor() {
@Override
public void visitElement(@NotNull 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(), ALTERNATIVE_ID);
if (isSuppressed || !((GlobalInspectionContextBase)globalContext).isToCheckFile(file, UnusedDeclarationInspection.this)) {
if (isSuppressed || !scope.contains(file)) {
getEntryPointsManager().addEntryPoint(refElement, false);
}
return;
}
refElement.accept(new RefJavaVisitor() {
@Override
public void visitMethod(@NotNull RefMethod method) {
if (isAddMainsEnabled() && method.isAppMain()) {
getEntryPointsManager().addEntryPoint(method, false);
}
}
@Override
public void visitClass(@NotNull RefClass aClass) {
if (isAddAppletEnabled() && aClass.isApplet() ||
isAddServletEnabled() && aClass.isServlet()) {
getEntryPointsManager().addEntryPoint(aClass, false);
}
}
});
}
}
});
if (isAddNonJavaUsedEnabled()) {
checkForReachables(globalContext);
final StrictUnreferencedFilter strictUnreferencedFilter = new StrictUnreferencedFilter(this, globalContext);
ProgressManager.getInstance().runProcess(new Runnable() {
@Override
public void run() {
final PsiSearchHelper helper = PsiSearchHelper.SERVICE.getInstance(globalContext.getRefManager().getProject());
globalContext.getRefManager().iterate(new RefJavaVisitor() {
@Override
public void visitElement(@NotNull final RefEntity refEntity) {
if (refEntity instanceof RefClass && strictUnreferencedFilter.accepts((RefClass)refEntity)) {
findExternalClassReferences((RefClass)refEntity);
}
else if (refEntity instanceof RefMethod) {
RefMethod refMethod = (RefMethod)refEntity;
if (refMethod.isConstructor() && strictUnreferencedFilter.accepts(refMethod)) {
findExternalClassReferences(refMethod.getOwnerClass());
}
}
}
private void findExternalClassReferences(final RefClass refElement) {
PsiClass psiClass = refElement.getElement();
String qualifiedName = refElement != null ? psiClass.getQualifiedName() : null;
if (qualifiedName != null) {
helper.processUsagesInNonJavaFiles(qualifiedName,
new PsiNonJavaFileReferenceProcessor() {
@Override
public boolean process(PsiFile file, int startOffset, int endOffset) {
getEntryPointsManager().addEntryPoint(refElement, false);
return false;
}
},
GlobalSearchScope.projectScope(globalContext.getProject()));
}
}
});
}
}, null);
}
myProcessedSuspicious = new HashSet<RefElement>();
myPhase = 1;
}
public boolean isEntryPoint(@NotNull RefElement owner) {
final PsiElement element = owner.getElement();
if (RefUtil.isImplicitUsage(element)) return true;
if (element instanceof PsiModifierListOwner) {
final EntryPointsManager entryPointsManager = EntryPointsManager.getInstance(element.getProject());
if (entryPointsManager.isEntryPoint(element)) {
return true;
}
}
if (element != null) {
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.isMainOrPremainMethod((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 EntryPointsManager entryPointsManager = EntryPointsManager.getInstance(project);
if (entryPointsManager.isEntryPoint(element)) 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(@NotNull UnusedDeclarationInspection tool, @NotNull GlobalInspectionContext context) {
super(tool, context);
}
@Override
public int getElementProblemCount(@NotNull RefJavaElement refElement) {
final int problemCount = super.getElementProblemCount(refElement);
if (problemCount > -1) return problemCount;
return refElement.isReferenced() ? 0 : 1;
}
}
@Override
public boolean queryExternalUsagesRequests(@NotNull InspectionManager manager,
@NotNull GlobalInspectionContext globalContext,
@NotNull ProblemDescriptionsProcessor problemDescriptionsProcessor) {
checkForReachables(globalContext);
final RefFilter filter = myPhase == 1 ? new StrictUnreferencedFilter(this, globalContext) :
new RefUnreachableFilter(this, globalContext);
final boolean[] requestAdded = {false};
globalContext.getRefManager().iterate(new RefJavaVisitor() {
@Override
public void visitElement(@NotNull 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(@NotNull final RefField refField) {
myProcessedSuspicious.add(refField);
PsiField psiField = refField.getElement();
if (psiField != null && isSerializationImplicitlyUsedField(psiField)) {
getEntryPointsManager().addEntryPoint(refField, false);
}
else {
getJavaContext().enqueueFieldUsagesProcessor(refField, new GlobalJavaInspectionContext.UsagesProcessor() {
@Override
public boolean process(PsiReference psiReference) {
getEntryPointsManager().addEntryPoint(refField, false);
return false;
}
});
requestAdded[0] = true;
}
}
@Override
public void visitMethod(@NotNull final RefMethod refMethod) {
myProcessedSuspicious.add(refMethod);
if (refMethod instanceof RefImplicitConstructor) {
visitClass(refMethod.getOwnerClass());
}
else {
PsiMethod psiMethod = (PsiMethod)refMethod.getElement();
if (psiMethod != null && 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(@NotNull final RefClass refClass) {
myProcessedSuspicious.add(refClass);
if (!refClass.isAnonymous()) {
getJavaContext().enqueueDerivedClassesProcessor(refClass, new GlobalJavaInspectionContext.DerivedClassesProcessor() {
@Override
public boolean process(PsiClass inheritor) {
getEntryPointsManager().addEntryPoint(refClass, false);
return false;
}
});
getJavaContext().enqueueClassUsagesProcessor(refClass, new GlobalJavaInspectionContext.UsagesProcessor() {
@Override
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(@NotNull PsiMethod psiMethod, RefClass refClass) {
return isReadObjectMethod(psiMethod, refClass) || isWriteObjectMethod(psiMethod, refClass) || isReadResolveMethod(psiMethod, refClass) ||
isWriteReplaceMethod(psiMethod, refClass) || isExternalizableNoParameterConstructor(psiMethod, refClass);
}
private void enqueueMethodUsages(final RefMethod refMethod) {
if (refMethod.getSuperMethods().isEmpty()) {
getJavaContext().enqueueMethodUsagesProcessor(refMethod, new GlobalJavaInspectionContext.UsagesProcessor() {
@Override
public boolean process(PsiReference psiReference) {
getEntryPointsManager().addEntryPoint(refMethod, false);
return false;
}
});
}
else {
for (RefMethod refSuper : refMethod.getSuperMethods()) {
enqueueMethodUsages(refSuper);
}
}
}
private GlobalJavaInspectionContext getJavaContext() {
return getContext().getExtension(GlobalJavaInspectionContext.CONTEXT);
}
@Nullable
@Override
public JobDescriptor[] getAdditionalJobs() {
return new JobDescriptor[]{getContext().getStdJobDescriptors().BUILD_GRAPH, getContext().getStdJobDescriptors().FIND_EXTERNAL_USAGES};
}
void checkForReachables(@NotNull final GlobalInspectionContext context) {
CodeScanner codeScanner = new CodeScanner();
// Cleanup previous reachability information.
context.getRefManager().iterate(new RefJavaVisitor() {
@Override
public void visitElement(@NotNull RefEntity refEntity) {
if (refEntity instanceof RefJavaElement) {
final RefJavaElementImpl refElement = (RefJavaElementImpl)refEntity;
if (!((GlobalInspectionContextBase)context).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(@NotNull 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(@NotNull 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(@NotNull 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);
}
}
}
}
}
}
@Override
public void initialize(@NotNull GlobalInspectionContext context) {
super.initialize(context);
myContext = context;
}
@Override
public void cleanup(Project project) {
super.cleanup(project);
myContext = null;
}
@Override
public boolean isGraphNeeded() {
return true;
}
}