In this episode of Eclipse Papercuts we will look at how we can analyse our own code to find “dead” code in your projects. We will write a plug-in to find methods which are not called (within the workspace).

Of course if you are a framework developer all your exported public methods are API and you can hardly change it. But in a lot of cases the development team has all code in one workspace. In this case it should be easy to identify dead code easily. Currently you have to select each method and select “Open Call Hierachy”.
This calls of course for a simpler solution, lets solve a papercut.
Create a Plug-in Project “de.vogella.jdt.codeanalysis” .
Define the following model class which will store the results of the calucation.
package de.vogella.jdt.infoview.model;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMethod;
public class MethodInformation {
private final String methodName;
private final int numberOfCalls;
private final ICompilationUnit cu;
private final IMethod method;
public MethodInformation(String methodName, int numberOfCalls,
ICompilationUnit cu, IMethod method) {
this.methodName = methodName;
this.numberOfCalls = numberOfCalls;
this.cu = cu;
this.method = method;
}
/**
* @return the method
*/
public String getMethodName() {
return methodName;
}
/**
* @return the numberOfCalls
*/
public int getNumberOfCalls() {
return numberOfCalls;
}
/**
* @return the cu
*/
public ICompilationUnit getResource() {
return cu;
}
/**
* @return the method
*/
public IMethod getMethod() {
return method;
}
}
Define a Eclipse command “de.vogella.jdt.codeanalysis.calculateUsage” with the default handler “de.vogella.jdt.codeanalysis.handler.CalculateUsage”.
Create these two helper classes with will search through a given Java project. The usage of the JDT functionality is explained in Eclipse JDT.
package de.vogella.jdt.codeanalysis.analysis;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import de.vogella.jdt.codeanalysis.model.MethodInformation;
public class CodeAnalysis {
public static List<MethodInformation> calculate(IJavaProject project) {
List<MethodInformation> list = new ArrayList<MethodInformation>();
try {
if (project.isOpen()) {
IPackageFragment[] packages = project
.getPackageFragments();
// parse(JavaCore.create(project));
for (IPackageFragment mypackage : packages) {
if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
for (ICompilationUnit unit : mypackage
.getCompilationUnits()) {
IType[] types = unit.getTypes();
for (int i = 0; i < types.length; i++) {
IType type = types[i];
IMethod[] methods = type.getMethods();
for (int j = 0; j < methods.length; j++) {
IMethod method = methods[j];
if (!method.isMainMethod()) {
int number = performIMethodSearch(method);
if (number == 0) {
MethodInformation metric = new MethodInformation(
method.getElementName(),
number, unit, method);
list.add(metric);
}
}
}
}
}
}
}
}
} catch (CoreException e) {
e.printStackTrace();
}
return list;
}
private static int performIMethodSearch(IMethod method)
throws CoreException {
SearchPattern pattern = SearchPattern.createPattern(method,
IJavaSearchConstants.REFERENCES);
IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
MySearchRequestor requestor = new MySearchRequestor();
SearchEngine searchEngine = new SearchEngine();
searchEngine.search(pattern, new SearchParticipant[] { SearchEngine
.getDefaultSearchParticipant() }, scope, requestor, null);
return requestor.getNumberOfCalls();
}
}
package de.vogella.jdt.codeanalysis.analysis;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.search.MethodReferenceMatch;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchRequestor;
public class MySearchRequestor extends SearchRequestor {
private int numberOfCalls = 0;
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
if (match instanceof MethodReferenceMatch) {
// MethodReferenceMatch methodMatch = (MethodReferenceMatch) match;
// Object element = methodMatch.getElement();
numberOfCalls++;
}
}
/**
* @return the numberOfCall
*/
public int getNumberOfCalls() {
return numberOfCalls;
}
}
We create a little View with a Table (and its content and label provider)
package de.vogella.jdt.codeanalysis.views;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.part.ViewPart;
import de.vogella.jdt.codeanalysis.model.MethodInformation;
public class ResultView extends ViewPart {
public static final String ID = "de.vogella.jdt.codeanalysis.ResultView";
private TableViewer viewer;
public void setInput(List<MethodInformation> list) {
viewer.setInput(list);
}
@Override
public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
buildTableColumns(viewer);
viewer.setLabelProvider(new AnalysisLabelProvider());
viewer.setContentProvider(new AnalysisContentProvider());
viewer.getTable().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
Object data = e.item.getData();
if (!(data instanceof MethodInformation))
return;
MethodInformation info = (MethodInformation) data;
IMethod method = info.getMethod();
ICompilationUnit cu = info.getResource();
if (cu == null)
return;
try {
IEditorPart part = JavaUI.openInEditor(cu);
JavaUI.revealInEditor(part, (IJavaElement) method);
} catch (CoreException ex) {
// error handling
}
}
});
}
@Override
public void setFocus() {
}
private void buildTableColumns(TableViewer viewer) {
String[] titles = { "File", "Method", "Number of Calls" };
int[] bounds = { 100, 100, 100 };
for (int i = 0; i < titles.length; i++) {
TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
column.getColumn().setText(titles[i]);
column.getColumn().setWidth(bounds[i]);
column.getColumn().setResizable(true);
column.getColumn().setMoveable(true);
}
Table table = viewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
}
public class AnalysisLabelProvider extends LabelProvider implements
ITableLabelProvider {
@Override
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
@Override
public String getColumnText(Object element, int columnIndex) {
MethodInformation metric = (MethodInformation) element;
switch (columnIndex) {
case 0:
return metric.getResource().getElementName();
case 1:
return metric.getMethodName();
default:
return String.valueOf(metric.getNumberOfCalls());
}
}
}
public class AnalysisContentProvider implements IStructuredContentProvider {
@Override
public Object[] getElements(Object inputElement) {
List<MethodInformation> list = (List<MethodInformation>) inputElement;
return list.toArray();
}
@Override
public void dispose() {
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
}
Finally we can create the handler:
package de.vogella.jdt.codeanalysis.handler;
import java.util.List;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.handlers.HandlerUtil;
import de.vogella.jdt.codeanalysis.analysis.CodeAnalysis;
import de.vogella.jdt.codeanalysis.model.MethodInformation;
import de.vogella.jdt.codeanalysis.views.ResultView;
public class CalculateUsage extends AbstractHandler {
@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
IStructuredSelection selection = (IStructuredSelection) HandlerUtil
.getActiveMenuSelection(event);
if (selection == null || selection.getFirstElement() == null) {
// Nothing selected, do nothing
MessageDialog.openInformation(HandlerUtil.getActiveShell(event),
"Information", "Please select a project");
return null;
}
final Object firstElement = selection.getFirstElement();
if (!(firstElement instanceof IJavaProject)) {
return null;
}
final IJavaProject project = (IJavaProject) firstElement;
try {
if (!project.isOpen()
|| !(project.getProject()
.hasNature("org.eclipse.jdt.core.javanature"))) {
MessageDialog.openInformation(
HandlerUtil.getActiveShell(event), "Information",
"Only works for open Java Projects");
return null;
}
} catch (CoreException e1) {
return null;
}
Job job = new Job("Calculate Usage of methods") {
@Override
protected IStatus run(IProgressMonitor monitor) {
final List<MethodInformation> calculate = CodeAnalysis
.calculate(project);
// Open view in the UI thread
Display.getDefault().asyncExec(new Runnable() {
public void run() {
try {
final ResultView findView = (ResultView) HandlerUtil
.getActiveWorkbenchWindow(event)
.getActivePage().showView(ResultView.ID);
findView.setInput(calculate);
} catch (PartInitException e) {
e.printStackTrace();
}
}
});
return Status.OK_STATUS;
}
};
job.setUser(true);
job.schedule();
return null;
}
}
As a last step add your command to the menu of the package explorer. This is also described Eclipse Plugin Development you results in the following plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.ui.views">
<view
class="de.vogella.jdt.codeanalysis.views.ResultView"
id="de.vogella.jdt.codeanalysis.ResultView"
name="Analysis Result"
restorable="true">
</view>
</extension>
<extension
point="org.eclipse.ui.commands">
<command
defaultHandler="de.vogella.jdt.codeanalysis.handler.CalculateUsage"
id="de.vogella.jdt.codeanalysis.calculateUsage"
name="Calculate method usage">
</command>
</extension>
<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="popup:org.eclipse.jdt.ui.PackageExplorer">
<command
commandId="de.vogella.jdt.codeanalysis.calculateUsage"
label="Calculate method usage"
style="push">
</command>
</menuContribution>
</extension>
</plugin>
If you now export your plugin into your Eclipse IDE you will have a new menu entry which allows you to start the usage calculation of your methods. Once finished your View should be opened / updated and the methods displayed which are not called. By double-clicking on them you can jump to the related method.
Note: project.isOpen() returns false, if you select an open project only with the right mouse click. If you click the project with the left mouse you should be fine.
Of course you can easily think of possible extensions to this approach:
- Calculate the usage of all methods and show then in the table
- Make is work for several projects
- Display in the table if a method has private, protected, default or public access
- Introduce filter
- Create marker in the editor for the identified methods. See Eclipse Plugin Development – Resource Markers
And here is the project for download.
de.vogella.jdt.codeanalysis.source_1.0.0.200907151800
Last but not least if would be nice if the Eclipse API Tools could help us with finding “dead” code. If have therefore opened Bug 283574.
In this episode of Eclipse Papercuts we will look at how we can analyse our own code to find "dead" code in your projects. We will write a plug-in to find methods which are not called (within the workspace).
Of course if you are a framework developer all your exported public methods are API and you can hardly change it. But in a lot of cases the development team has all code in one workspace. In this case it should be easy to identify dead code easily. Currently you have to select each method and select "Open Call Hierachy".
This calls of course for a simpler solution, lets solve a papercut.
Create a Plug-in Project "de.vogella.jdt.codeanalysis" .
Define the following model class which will store the results of the calucation.
[sourcecode language="java"]
package de.vogella.jdt.infoview.model;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMethod;
public class MethodInformation {
private final String methodName;
private final int numberOfCalls;
p