Thursday, May 13, 2010

Extracting something from a selection

That's a somewhat tedious task in JFace programming: you receive an object that represents the selection on a tree or table viewer; the selection object is of type ISelection (a JFace interface), but that means it could be either an IStructuredSelection (one or more elements from a tree, table or list) or an ITextSelection (some text marked in a text editor); thus you need to check the subtype of ISelection first, then you have to check whether the selection is empty, then you can get the first element (for it might be a multi-selection, too), then you have to cast that into the correct type. All in all, you often end up with something long-winded like this:

selectionService.addPostSelectionListener( new ISelectionListener() {
public void selectionChanged( IWorkbenchPart part, ISelection selection ) {
if( selection instanceof IStructuredSelection && !selection.isEmpty() ) {
IStructuredSelection strusel = (IStructuredSelection)selection;
Object element = strusel.getFirstElement();
if( element instanceof MyCoolType ) {
MyCoolType coolThing = (MyCoolType)element;
// ... and now we finally can do something with the cool thing
}
}
}
} );
And you think: oh well, shouldn't this be a little easier to do?

You are right to ask that question. Here's one way to do it a little more elegantly:
public void selectionChanged( IWorkbenchPart part, ISelection selection ) {
MyCoolType coolThing = new ElementFrom( selection ).as( MyCoolType.class );
// ... now do something with coolThing already
}
And this would be the implementation of the ElementFrom utility:


/** Extract the first element (if any) from a JFace selection, in a type-safe
* manner.
*/
public class ElementFrom {

private final ISelection selection;

/** constructs a new extraction operation.
*
* @param selection a JFace selection object from which to extract the
* selected element. Can be null, in which case this extraction
* operation yields null.
*/
public ElementFrom( ISelection selection ) {
this.selection = selection;
}

/** retrieves the extracted element typed as specified.
*
* @param cls the class representing the expected type of the extracted element.
* Must not be null.
* @param the expected type of the extracted element.
* @return the element under the expected type, or null
*/
public T as( Class cls ) {
return extractElement( cls );
}

private T extractElement( Class cls ) {
T result = null;
if( selectionIsGood() ) {
IStructuredSelection strusel = (IStructuredSelection)selection;
Object element = strusel.getFirstElement();
if( elementTypeIsGood( element, cls ) ) {
result = cls.cast( element );
}
}
return result;
}

private boolean elementTypeIsGood( Object element, Class cls ) {
return cls.isAssignableFrom( element.getClass() );
}

private boolean selectionIsGood() {
return selection instanceof IStructuredSelection && !selection.isEmpty();
}
}
Have fun :-)

6 comments:

  1. Strange things are going on in this universe, btw: I haven't got the faintest idea where these </t> things at the end of the post come from. They're not in my post. Hmmm...

    ReplyDelete
  2. Love this idea, thanks Leif for sharing!

    ReplyDelete
  3. yep, a nice way to get selections in strict type manner. Sorry to mention that, but the idea's pretty common in jface land: I've used and found it several times already: have a look at the rcp company utilities that are around for quite some time already:

    http://code.google.com/p/rcp-company-utilities/source/browse/trunk/com.rcpcompany.utils.selection/src/com/rcpcompany/utils/selection/SelectionUtils.java

    Greets
    André

    ReplyDelete
  4. André, thanks for the pointer :-)

    Didn't know of this particular lib, but I expect that many have similar utilities, it really is a common pattern.

    ReplyDelete
  5. completely agree! IMHO this stuff (and further common tasks) should make it into the official code

    ReplyDelete
  6. cls.isAssignableFrom( element.getClass() )

    should be written as

    cls.isInstance( element )

    ReplyDelete