During a recent bout of looking for leaks in my Eclipse Application, I ran into a limitation of my tool of choice, JProfiler, or at least, a limitation of my understanding of it. One problem I was having was the "No more Handles" error when repeatedly opening my editor. After some digging, I found a tool written by someone in the SWT team: Sleak. The "limitation" of JProfiler is that it works more from a generic Java memory point of view. I needed to find where handles were leaking - even if they were not using that much memory. Sleak solves this simply.
First, I'll go through getting it set up - it didn't work exactly for me as in the included instructions - but you'll need to refer to them to understand what I'm talking about from here. Just dropping it into my plugins dir as directed didn't work (not blaming the instructions here - this could've been my fault). I ended up with Class loader errors. I decided to repackage it myself, so I started by importing it into my workspace as an Eclipse project. Then, I exported it as a jar from the plugin overview screen. At this point, I had a Sleak jar in the specified output directory. I dropped this into my plugins directory, and followed the other instructions. The only other change I would recommend here is to just set the properties to true instead of adding lines since they are already in the .options file.
Now you're ready to find leaks. Launch Eclipse normally with the workspace containing all the code for your application. Open your favorite debug configuration and add "-debug -clean" to the end of your Program Arguments. Then, just launch the configuration. Once it loads, open the Sleak view by using the Window->Show View->Other->SWT Tools menu. The buttons you'll see are a little different from the instructions - you will see "Snap" and "Diff" which I interpret as "Take a Snapshot of the Handles in Use" and "Take Another One and Show the Differences from the Previous Snapshot". All you have to do is use the Snap button, preform the operation you think has leaks and then press Diff. You will get a nice list of the handles still hanging around. Clicking on them will often show you a preview in the window (this works for Colors, Images, and a few other visual handles). Clicking the stack box will even show you were in the code they were created.
I hope this helps. I really appreciate the work by the SWT team for releasing this tool for all us developers! If you wrote this, please leave a comment so you can take some credit.
Friday, March 21, 2008
Plugging SWT Leaks in Your Eclipse App
Posted by Adam Cabler at 3:10 PM 0 comments
Monday, December 17, 2007
Making Eclipse Newsgroups More Effective
As long as I've used Eclipse, I've found the newsgroups to be a great source of information and probably the best source of documentation outside the included help docs. However, I've always found it hard to find information in the platform newsgroup. The problem is that it has turned into a kind of catch-all for Eclipse users, plugin developers, jface developers, etc. This makes it really hard when you are searching for something on a topic from a particular point of view. A few months ago, I filed this bug to request a change. My proposal is to try to organize the information as follows:
platform.usersIn order to get some support for this change, I meant to post back then (really, I did). It was brought back to my attention by a comment on the bug, so I thought this would be a good time to see if any other in the community have the same feelings. If not, I'll just have to get better at searching;)
platform.pluginDev
platform.jface
...
Posted by Adam Cabler at 6:10 PM 0 comments
Labels: eclipse
Monday, November 26, 2007
When Display Decides to Wait...
Since its been a while since I've posted anything, I thought I would write about a weird problem I was having with one of my plugins. It initially presented as a failure to load certain files in my editor. The app simply seemed to hang forever. It only showed up when running a customer build, so I first needed to connect remotely. I quickly found out that it was a problem in my logger. Since there are many complicated plugins involved with the app, I was a little relieved to find this out since it is the smallest and easiest to follow plugin. My logger is setup to use log4j to direct log messages to the Eclipse console. The files that weren't loading were logging an enormous amount of data at load time. I was using MessageConsoleStream.println to log messages to the console. This filled a buffer in IOConsolePartitioner, which eventually called ArrayList.wait(). Since this was all happening in the display thread, Eclipse never finished loading.
The fix was pretty simple and only involved a few lines of code. I realized that I would need a reusable set of threads to do the work, so I first created a thread pool using the cool new Executors class introduced in Java 1.5 (it was really nice to have this handy and not have to implement it, again). I chose to create a single thread executor so my messages would be guaranteed in order (see Executors.newSingleThreadExecutor). Then I only needed to schedule the log messages by putting them in a runnable and passing it to the executor using the execute method. Besides fixing the hang problem, the performance of my app was increased since much of the logging work didn't need to be done in Display up until the string was shown on the console.
Posted by Adam Cabler at 7:16 AM 1 comments
Labels: eclipse
Tuesday, July 24, 2007
JFace TableViewer Sorting using LabelProvider
While designing some code for an Eclipse plugin, I decided that I wanted to be able to provide the normal sort mechanism for the tables that is present in so many other applications. I was actually surprised to find that there wasn't a simple API to just turn this on. I sure there are good reasons for this and there are many bug reports in this area. It even looks like there are some fixes in progress.
Anyway, while looking at some examples, I came across this snippet by Tom Schindl. It was really close to what I was looking for, but didn't quite work because it assumed a simple model and didn't exercise the LabelProvider to get the text to sort.
Since I usually create the columns in a loop, it was easy to modify the compare method to look up the index of the column using a label provider.
int i = 0;
for (String name : colNames) {
final int colIdx = i;
TableColumn column = new TableColumn(viewer.getTable(), SWT.NONE);
column.setWidth(200);
column.setText(name);
column.setMoveable(true);
TableColumnSorter cSorter = new TableColumnSorter(viewer, column) {
protected int doCompare(Viewer v, Object e1, Object e2) {
ITableLabelProvider lp = ((ITableLabelProvider) viewer
.getLabelProvider());
String t1 = lp.getColumnText(e1, colIdx);
String t2 = lp.getColumnText(e2, colIdx);
return t1.compareTo(t2);
}
};
cSorter.setSorter(cSorter, TableColumnSorter.ASC);
i++;
}
The full text of the example with my changes are below the split.
package snippet;
/*******************************************************************************
* TableViewerSorting Example
*
* Adam Cabler
*
* revised example by Tom Schindl
* http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.jface.snippets/Eclipse%20JFace%20Snippets/org/eclipse/jface/snippets/viewers/Snippet040TableViewerSorting.java?view=markup
*
* removed ColumnViewer references
* added ITableLabelProvider
* used label provider for text compare
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*
*******************************************************************************/
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
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.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableColumn;
/**
* Table sorting example using label provider
*
* @author cabler
*
*/
public class TableViewerSortingExample {
private class MyContentProvider implements IStructuredContentProvider {
public Object[] getElements(Object inputElement) {
return (Person[]) inputElement;
}
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
private class MyLabelProvider implements ITableLabelProvider {
@Override
public Image getColumnImage(Object element, int columnIndex) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getColumnText(Object element, int columnIndex) {
if (!(element instanceof Person)) {
return null;
}
Person p = (Person) element;
switch (columnIndex) {
case 0:
return p.givenname;
case 1:
return p.surname;
case 2:
return p.email;
}
return "Error";
}
@Override
public void addListener(ILabelProviderListener listener) {
// TODO Auto-generated method stub
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
@Override
public boolean isLabelProperty(Object element, String property) {
// TODO Auto-generated method stub
return false;
}
@Override
public void removeListener(ILabelProviderListener listener) {
// TODO Auto-generated method stub
}
}
public class Person {
public String givenname;
public String surname;
public String email;
public Person(String givenname, String surname, String email) {
this.givenname = givenname;
this.surname = surname;
this.email = email;
}
}
public TableViewerSortingExample(Shell shell) {
final TableViewer viewer = new TableViewer(shell, SWT.BORDER | SWT.FULL_SELECTION);
viewer.setContentProvider(new MyContentProvider());
viewer.setLabelProvider(new MyLabelProvider());
String[] colNames = new String[] { "Givenname", "Surname", "Email" };
int i = 0;
for (String name : colNames) {
final int colIdx = i;
TableColumn column = new TableColumn(viewer.getTable(), SWT.NONE);
column.setWidth(200);
column.setText(name);
column.setMoveable(true);
TableColumnSorter cSorter = new TableColumnSorter(viewer, column) {
protected int doCompare(Viewer v, Object e1, Object e2) {
ITableLabelProvider lp = ((ITableLabelProvider) viewer
.getLabelProvider());
String t1 = lp.getColumnText(e1, colIdx);
String t2 = lp.getColumnText(e2, colIdx);
return t1.compareTo(t2);
}
};
cSorter.setSorter(cSorter, TableColumnSorter.ASC);
i++;
}
Person[] model = createModel();
viewer.setInput(model);
viewer.getTable().setLinesVisible(true);
viewer.getTable().setHeaderVisible(true);
}
private Person[] createModel() {
Person[] elements = new Person[4];
elements[0] = new Person("Tom", "Schindl",
"tom.schindl@bestsolution.at");
elements[1] = new Person("Boris", "Bokowski",
"Boris_Bokowski@ca.ibm.com");
elements[2] = new Person("Tod", "Creasey", "Tod_Creasey@ca.ibm.com");
elements[3] = new Person("Wayne", "Beaton", "wayne@eclipse.org");
return elements;
}
private static abstract class TableColumnSorter extends ViewerComparator {
public static final int ASC = 1;
public static final int NONE = 0;
public static final int DESC = -1;
private int direction = 0;
private TableColumn column;
private TableViewer viewer;
public TableColumnSorter(TableViewer viewer, TableColumn column) {
this.column = column;
this.viewer = viewer;
this.column.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
if (TableColumnSorter.this.viewer.getComparator() != null) {
if (TableColumnSorter.this.viewer.getComparator() == TableColumnSorter.this) {
int tdirection = TableColumnSorter.this.direction;
if (tdirection == ASC) {
setSorter(TableColumnSorter.this, DESC);
} else if (tdirection == DESC) {
setSorter(TableColumnSorter.this, NONE);
}
} else {
setSorter(TableColumnSorter.this, ASC);
}
} else {
setSorter(TableColumnSorter.this, ASC);
}
}
});
}
public void setSorter(TableColumnSorter sorter, int direction) {
if (direction == NONE) {
column.getParent().setSortColumn(null);
column.getParent().setSortDirection(SWT.NONE);
viewer.setComparator(null);
} else {
column.getParent().setSortColumn(column);
sorter.direction = direction;
if (direction == ASC) {
column.getParent().setSortDirection(SWT.DOWN);
} else {
column.getParent().setSortDirection(SWT.UP);
}
if (viewer.getComparator() == sorter) {
viewer.refresh();
} else {
viewer.setComparator(sorter);
}
}
}
public int compare(Viewer viewer, Object e1, Object e2) {
return direction * doCompare(viewer, e1, e2);
}
protected abstract int doCompare(Viewer TableViewer, Object e1, Object e2);
}
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
new TableViewerSortingExample(shell);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
Posted by Adam Cabler at 1:40 PM 6 comments
Thursday, June 28, 2007
Cross Project Contributions
In my work with GEF I usually check out the newsgroups when looking for a solution to my current problem. I have noticed more than once that the solution exists in GMF, but wasn't contributed back to GEF and instead exists only for GMF users. I am really curious as to how this happens - especially when one of the stated goals of GMF is to contribute to GEF where possible. It seems that contributing fixes back to the source plugin would have several obvious benefits. Besides making these things available to a larger audience, this would make for more modular source trees and easier code maintenance.
I'm wondering if this exists in other projects as well and what can be done to help this problem (or if this isn't really a problem). I'm sure part of the issue has to do with the usual, time constraints and lack of resources, but here it would seem that it could actually be more efficient to back-contribute rather than trying to copy a chunk of code across projects and modify it to fix a bug or add a feature.
It is really not my intent to pick on specific projects - I only use GEF/GMF as an example because its one with which I'm familiar. And I really appreciate all the work that's been done there to date.
Posted by Adam Cabler at 10:47 AM 0 comments
Monday, June 4, 2007
Anti-Alias bug in 3.3 with GEF
I've been fighting a particularly nasty bug in Eclipse 3.3 lately and since I finally found the culprit, I wanted to post it in case other GEF developers encounter it before it's fixed.
Symptoms:
- Thumbnail in outline view won't show some edit parts
- Connection parts don't show until after editor is reopened in same session
graphics.setAntialias(SWT.ON)Commenting out this call fixed all the problems I was having. Now my edit parts don't look quite as good, but since I'm counting on being able to use 3.3, I'm just happy there is a workaround. I'm hopeful that this can be fixed by the final Europa release.
Posted by Adam Cabler at 3:10 PM 2 comments
Thursday, April 26, 2007
Synchronizing Views with a GEF Editor
View synchronizing is one of the fundamental features for most Eclipse workbench apps. When using a GEF editor, you can use additional views to provide more targeted information about the selection, or even allow editing. There is a lot of good information about this topic - both in the eclipse docs and in the newsgroups. However, I think I can augment that with a concise set of steps for getting this task done quickly.
In this post, I'll talk about how to easily allow views to respond to selection events in an editor.
- First, you will need to make sure events are being properly fired from your editor. Since a default selection provider is provided by the GEF Graphical Editor, this is handled for you if you are extending it.
- Next, you need to watch for these selection events in your views. This is done by simply adding a postSelectionListener to the page during createPartControl in the view:
- If all you wanted was to get selection events of your edit parts, you're done. However, in my designs, I usually like to hide the edit parts from view outside the editor. For this, you will need to use the Eclipse adapter mechanism. In your edit part getAdapter method, you have the opportunity to do any pre-processing.
Lets say that your edit parts are shapes and you want to find out the color, and size of them without exposing the edit parts to the view: - First, put an interface in a shared plugin called: IShapeInfo with getters for color and size
- In Shape.getAdapter, when IShapeInfo is passed in, return an implementation that provides size and color. (You can implement IShapeInfo in Shape, create an anonymous inner class, etc.)
- In your view's pageSelectionListener:
getSite().getPage().addPostSelectionListener(yourPageSelectionListener);
It is important to note here that you can't use the other add method that allows filtering by workbench part. This does not work for editors. I have filed an enhancement request to get this added, but it wouldn't be until 3.4 at the earliest since 3.3 is at API freeze.
//Get the selection as a list
StructuredSelection ss = (StructuredSelection) selection;
List sList = ss.toList();
/**
* Create a list of objects to show
*/
for (Iterator iterator = sList.iterator(); iterator.hasNext();) {
Object name = iterator.next();
//See if it implements IAdaptable
if (name instanceof IAdaptable) {
//See if the IShapeInfo is available as an adapter
Object o = ((IAdaptable)name).getAdapter(IShapeInfo.class);
if(o != null) {
IShapeInfo info = (IShapeInfo)o;
/** Now you can get size and color from the selected Object
}
}
}
Thats it. All you have to do is customize the interface and you can get custom information back from your edit parts - or any other selection - without exposing class details.
Posted by Adam Cabler at 11:01 AM 2 comments