IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Open source projects | Java technology
developerWorks
Using the Eclipse GUI outside the Eclipse Workbench, Part 3: Adding actions, menus, and toolbars
248KBe-mail it!
Contents:
Introduction
Menus
Actions
Adding a bar menu to an application window
Launching a program associated with a file
Using the system clipboard
Explorer instance diagram
Making actions context sensitive
Tool bars and pop-up menus
Conclusion
Resources
About the author
Rate this article
Related content:
Part 1 of this series
Part 2 of this series
Getting started with the Eclipse Platform
Developing Eclipse plug-ins
Plug a Swing-based development tool into Eclipse
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Completing the simple file explorer application

Level: Intermediate

Adrian Van Emmenis (mailto:van@vanemmenis.com?cc=&subject=Adding actions, menus, and toolbars)
Independent consultant
4 March 2003

In this third and final article in the series, A. O. Van Emmenis completes the file explorer example begun in Part 1 and Part 2 by adding actions, menu bars, pop-up menus, and toolbars. He shows how to set menu item properties, how to reuse actions in menus and toolbars, and how to make actions context sensitive by listening to events from viewers. The example actions use utilities to launch programs and access the system clipboard.

Introduction
Part 1 of this series began an example that subclassed the JFace application window and used a tree viewer and a table viewer to display folders and files. In Part 2 we did some tidying and added some icons using the JFace image registry.

This time, we'll look at actions and you can use them in menus and toolbars. We'll see the use of the Program class to launch programs and the Clipboard class to access the system clipboard. We have already used icons to display files and folders in viewers. We'll see how they can be used in menus and toolbars as well. Finally, we'll make actions listen to events from viewers in order to make them context sensitive.

Installation notes
If download the source code for the examples in this article, please note my system setup:

  • Windows 2000
  • Eclipse, stable build M3 (November 15, 2002)
  • Eclipse installed in C:\eclipse-2.1.0

I will leave you to do any swizzling of names and file separators in what follows, so that the programs work correctly on your system.

Build/run instructions
Ensure that these .jar files are on your class path:

C:\eclipse-2.1.0\plugins\org.eclipse.jface_2.1.0\jface.jar
C:\eclipse-2.1.0\plugins\org.eclipse.runtime_2.1.0\runtime.jar
C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\ws\win32\swt.jar
C:\eclipse-2.1.0\plugins\org.eclipse.ui.workbench_2.1.0\workbench.jar
C:\eclipse-2.1.0\plugins\org.eclipse.core.runtime_2.1.0\runtime.jar

Ensure that the Java VM picks up the correct shared libraries for the GUI you are using at runtime by running it with the following argument:

-Djava.library.path=C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\os\win32\x86\

Finally, so that the examples can find the .gif files containing the icons, run the programs from the folder that contains the icons folder.

Picking up the example from Part 2
At the end of the last article, our explorer application looked like Figure 1.

Figure 1. Explorer (version 8)
Figure 1. Explorer (version 8)

Folders are displayed in the left pane using a tree viewer. When you select a folder in the left pane, the files that it contains are shown in a table viewer in the right pane. We are sorting the items in the right pane so that folders appear first. We are using icons to represent files and folders in both viewers.

Let's add a simple bar menu to the window.

Menus
The JFace MenuManager simplifies the creation and updating of SWT menus. A menu manager can contain items, other menu managers (for sub-menus), and separators. Once you have created a menu manager, it can be represented by either a bar menu, a context menu (in other words, a pop-up menu), or a toolbar drop-down menu.

Just as with viewers, a menu manager is a helper object rather than a wrapper object, although you will generally not need to access the SWT menu itself. Before we discuss menus, let's see what can go into a menu manager.

Actions
You add actions to menu managers. Actually, you can add actions to buttons and toolbars, too. The idea is that you subclass Action, set the text that you want to appear in the menu/toolbar/button, and implement the run() method to make it do what you want.

Let's dive straight into an example: ExitAction shown in Listing 1:

Listing 1. ExitAction (version 1)

    
import org.eclipse.jface.action.*;
import org.eclipse.jface.window.*;

public class ExitAction extends Action
{
  ApplicationWindow window;

  public ExitAction(ApplicationWindow w)
  {
    window = w;
    setText("E&xit");
  }

  public void run()
  {
    window.close();
  }
}

All fairly simple. The & character before the x in Exit indicates that x is to be the keyboard navigation key (mnemonic) for this menu item. Note that this is different from a key accelerator (hotkey). We'll see these soon ...

Subclassing action
Don't be misled by the fact that Action defines the method getText(). You are not intended to override it. Instead, you are meant to use setText(String), and Action will store it and ensure that all the SWT controls that are currently using this action are updated with the new text.

This applies to all the other Action properties like the tool tip text, its enabled state, etc. -- we'll see these later.

Adding a bar menu to an application window
We need to configure the application window to have a bar menu using this method in ApplicationWindow:

protected void addMenuBar()

Remember that we must do this before the SWT shell is created. And this will call the application window method createMenuManager(), which returns the menu manager that it will use later to create the SWT Bar Menu. Here is our implementation in Listing 2:

Listing 2. Explorer - createMenuManager

    
protected MenuManager createMenuManager()
{
  MenuManager bar_menu = new MenuManager("");

  MenuManager file_menu = new MenuManager("&File");
  MenuManager edit_menu = new MenuManager("&Edit");
  MenuManager view_menu = new MenuManager("&View");

  bar_menu.add(file_menu);
  bar_menu.add(edit_menu);
  bar_menu.add(view_menu);

  file_menu.add(new ExitAction(this));

  return bar_menu;
}

Explorer now looks like Figure 2:

Figure 2. Explorer (Version 9)
Figure 2. Explorer (Version 9)

Note that the menus that are empty are disabled. Try closing the explorer application using the navigation keys ALT+Fx.

Let's improve the exit action a little, as shown in Listing 3:

Listing 3. ExitAction (version 2)

    
import org.eclipse.jface.action.*;
import org.eclipse.jface.resource.*;
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;

public class ExitAction extends Action
{
  ApplicationWindow window;

  public ExitAction(ApplicationWindow w)
  {
    window = w;
    setText("E&xit@Ctrl+W");
    setToolTipText("Exit the application");
    setImageDescriptor(
      ImageDescriptor.createFromURL(Util.newURL("file:icons/close.gif")));
  }

  public void run()
  {
    window.close();
  }
}

We've added an accelerator key (hotkey), a tool tip, and an image (the tool tip won't show up on menu items, but it will on tool bar items, which we'll see later). There is a method to set the accelerator key directly, but it's easier to specify it in the text after the @ character, since this way, it gets added into the text of the menu item, as shown in Figure 3:

Figure 3. Explorer (version 10)
Figure 3. Explorer (version 10)

The eagle-eyed reader may have spotted that we supplied an image descriptor directly to the action. What we would really like to do is get the image descriptor from the image registry. The problem is that the image registry only gives out Images -- you can't ask it for an ImageDescriptor. This is bug 23555 in the Eclipse bug database.

Launching a program associated with a file
Running the program associated with a file is a pretty useful thing to do. It's actually remarkably easy, too, using the Program class, as shown in Listing 4:

Listing 4. OpenAction (version 1)

    
import java.io.*;

import org.eclipse.jface.action.*;
import org.eclipse.jface.resource.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.program.*;

public class OpenAction extends Action
{
  Explorer window;

  public OpenAction(Explorer w)
  {
    window = w;
    setText("Run");
    setToolTipText("Run the associated program on a file");
    setImageDescriptor(
      ImageDescriptor.createFromURL(Util.newURL("file:icons/run.gif")));
  }

  public void run()
  {
    IStructuredSelection selection = window.getTableSelection();
    if (selection.size() != 1)
      return;

    File selected_file = (File) selection.getFirstElement();
    if (selected_file.isFile())
    {
      Program.launch(selected_file.getAbsolutePath());
    }
  }
}

We get hold of the selected elements from the table using getTableSelection() (we'll see that method in a moment) and then check that there is exactly one element selected -- remember that the table is now a multi-select style table -- and then get the element itself, check it is really a file rather than a folder, and launch it.

The Program launch() method does all the work looking up the associated program based on the file extension, and then it runs the appropriate executable, supplying the absolute file name as the argument.

Before we try this out, let's do one last action.

Using the system clipboard
Here's a neat little use of the system clipboard. The action CopyFileNamesToClipboardAction copies the absolute file names of all the selected files to the clipboard.

We transfer text to the system clipboard using the Clipboard object.

First make the Util class lazily create a clipboard object, as shown in Listing 5:

Listing 5. Util (version 2)

    
import java.net.*;

import org.eclipse.jface.resource.*;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.widgets.*;

public class Util
{
  private static ImageRegistry image_registry;
  private static Clipboard clipboard;

  public static URL newURL(String url_name)
  {
    try
    {
      return new URL(url_name);
    }
    catch (MalformedURLException e)
    {
      throw new RuntimeException("Malformed URL " + url_name, e);
    }
  }

  public static ImageRegistry getImageRegistry()
  {
    if (image_registry == null)
    {
      image_registry = new ImageRegistry();
      image_registry.put(
        "folder",
        ImageDescriptor.createFromURL(newURL("file:icons/folder.gif")));
      image_registry.put(
        "file",
        ImageDescriptor.createFromURL(newURL("file:icons/file.gif")));
    }
    return image_registry;
  }

  public static Clipboard getClipboard()
  {
    if (clipboard == null)
    {
      clipboard = new Clipboard(Display.getCurrent());
    }

    return clipboard;
  }
}

To put text into the clipboard, we use this method on Clipboard:

public void setContents(Object[] data, Transfer[] dataTypes)

Each array slot in data is associated with a transfer object that tells the clipboard what type of data is in data. In our case we'll use a TextTransfer object, which tells the clipboard that we are transferring plain text (rather than, say, RTF).

The arguments to public void setContents(Object[] data, Transfer[] dataTypes) are arrays, so that you can transfer data in several formats at once. For example, a word processing application might want to transfer text in RTF and plain text.

In the code that follows in Listing 6, we do this:

  • Get the selection
  • Check it's not empty
  • Iterate though it, adding the absolute file names to a string buffer
  • Transfer the string to the clipboard with a Text Transfer object
  • Put the string into the status line to give us some feedback

Listing 6. CopyFileNamesToClipboardAction (version 1)

    
import java.io.*;
import java.util.*;

import org.eclipse.jface.action.*;
import org.eclipse.jface.resource.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.*;
import org.eclipse.swt.dnd.*;

public class CopyFileNamesToClipboardAction extends Action
{
  Explorer window;

  public CopyFileNamesToClipboardAction(Explorer w)
  {
    window = w;
    setToolTipText("Copy absolute file names of selected files to the clipboard");
    setText("Copy File &Names@Ctrl+Shift+C");
    setImageDescriptor(
      ImageDescriptor.createFromURL(Util.newURL("file:icons/copy.gif")));
  }

  public void run()
  {
    Clipboard clipboard = Util.getClipboard();
    TextTransfer text_transfer = TextTransfer.getInstance();
    
    IStructuredSelection selection = window.getTableSelection();
    if (selection.isEmpty())
    {
      return;
    }

    StringBuffer string_buffer = new StringBuffer();
    for (Iterator i = selection.iterator(); i.hasNext();)
    {
      File file = (File) i.next();
      string_buffer.append(" ");
      string_buffer.append(file.getAbsolutePath());
    }

    clipboard.setContents(
      new Object[] { string_buffer.toString()},
      new Transfer[] { text_transfer });
  }
}

And finally, here are the changes to Explorer to add the getTableSelection() method, along with the code to add the two new actions to the bar menu (Listing 7):

Listing 7. Explorer (version 10)

    
import java.io.*;

import org.eclipse.jface.action.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.widgets.*;

public class Explorer extends ApplicationWindow
{
  private TableViewer tbv;
  ...
  public static void main(String[] args)
  {
    Explorer w = new Explorer();
    w.setBlockOnOpen(true);
    w.open();
    Display.getCurrent().dispose();
    Util.getClipboard().dispose();
  }

  protected MenuManager createMenuManager()
  {
    MenuManager bar_menu = new MenuManager("");

    MenuManager file_menu = new MenuManager("&File");
    MenuManager edit_menu = new MenuManager("&Edit");
    MenuManager view_menu = new MenuManager("&View");

    bar_menu.add(file_menu);
    bar_menu.add(edit_menu);
    bar_menu.add(view_menu);

    file_menu.add(new ExitAction(this));
    edit_menu.add(new CopyFileNamesToClipboardAction(this));
    edit_menu.add(new OpenAction(this));

    return bar_menu;
  }

  public IStructuredSelection getTableSelection()
  {
    return (IStructuredSelection) (tbv.getSelection());
  }
}

Explorer instance diagram
So, we now have 3 actions and 8 other classes. Let's review the instance diagram (Figure 4) for Explorer (I've shown the classes that we implemented in a darker gray).

Figure 4. Explorer (version 10), with its associated objects
Figure 4. Explorer (version 10), with its associated objects

Let's run this version and see the actions (Figure 5):

Figure 5. Explorer (version 10)
Figure 5. Explorer (version 10)

Avoiding spaghetti in GUI instance hierarchies
In my experience, when designing GUI objects, you tend to end up building fairly large instance hierarchies. Objects at low levels in different hierarchies need to know about each other, so there is a temptation to create ad-hoc cross references between them, which can lead to code resembling that famous pasta.

Even in our small example, we can see this just beginning to happen. The action objects need to know about the currently selected item in the table viewer. The question is: what object does the action store? Is it the table viewer, the SWT table itself, the Window?

Figure 6. Windows, widgets, and actions
Figure 6. Windows, widgets and actions

One solution is to funnel all the access to widgets/selection objects through the window. Have each action store the window that created it, and then it can use the accessor methods on that window to get to the objects it needs.

If you want to share actions between different windows where the widgets are accessed using different method names, then you can wrap the access to the widgets from the action inside a method, which could be reimplemented in subclasses of the action to fetch the correct widget.

For example, to share an action that needed to access a selection, the method getSelection()in the superclass could be implemented as:

window.getTableSelection()

in one subclass and (say)

window.getThirdListViewerSelection()

in another subclass.

Making actions context sensitive
Another thing we can do with actions is to make them aware of what is happening in the rest of the window and adapt themselves accordingly. We're going to make OpenAction listen for any changes to the current selection in the table viewer. When it notices a change, it can look at the new selection and change its text, tool tip, and enabled state to reflect that.

OpenAction launches the "associated" program on the currently selected file. If nothing is selected then, rather than letting it run and report an error, or just mysteriously do nothing, why don't we disable the action?

While we're thinking about being nice to the user, I often get confused when I see menu items that are disabled and I don't understand why they are disabled. So, here's an idea: How about changing the tool tip so that we even tell the user why the action is disabled?

We also have to decide what to do if the selection includes several files. Well, we could just run them all, one after another, but, since I have painful memories of selecting 300 files and choosing the Open option from the Windows File Explorer, let's just disable it for now.

Finally, we'll check if the selected item is a folder and disable the open action in this case as well.

We need to make OpenAction a listener for the SelectionChanged event from the table viewer, so we'll make it implement ISelectionChangedListener (Listing 8).

We'll do this in selectionChanged():

  • Set the text and tool tip text to the default
  • Check the selection
  • If we don't have exactly one item selected, we disable the action, adjust the tool tip to say why it is disabled, and return.
  • If the selection is a file (rather than a folder), then we adjust the text and tool tip to reflect that file name and enable it.

Listing 8. OpenAction (version 2)

    
import java.io.*;

import org.eclipse.jface.action.*;
import org.eclipse.jface.resource.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.program.*;

public class OpenAction
  extends Action
  implements ISelectionChangedListener
{
  Explorer window;

  public OpenAction(Explorer w)
  {
    window = w;
    setText("Run");
    setToolTipText("Run the associated program on a file");
    setImageDescriptor(
      ImageDescriptor.createFromURL(Util.newURL("file:icons/run.gif")));
  }

  public void run()
  {
    IStructuredSelection selection = window.getTableSelection();
    if (selection.size() != 1)
    {
      return;
    }

    File selected_file = (File) selection.getFirstElement();
    if (selected_file.isFile())
    {
      Program.launch(selected_file.getAbsolutePath());
    }
  }

  public void selectionChanged(SelectionChangedEvent event)
  {
    setText("Run");
    setToolTipText("Run the associated program on a file");

    IStructuredSelection selection = window.getTableSelection();
    if (selection.size() != 1)
    {
      setEnabled(false);
      setToolTipText(
        getToolTipText() + " (Only enabled when exactly one item is selected)");
      return;
    }

    File file = (File) selection.getFirstElement();
    if (file.isFile())
    {
      setEnabled(true);
      setText("Run the associated program on " + file.getName());
      setToolTipText(
        "Run the program associated with "
          + file.getName()
          + " with this file as the argument");
    }
  }
}

And now, to make the open action listen to the table viewer for the SelectionChanged event (Listing 9).

Listing 9. Explorer (version 11); some code that hasn't changed has been omitted

    
...
public class Explorer extends ApplicationWindow
{
  private TableViewer tbv;
  private OpenAction open_action;
  ...
  protected Control createContents(Composite parent)
  {
    ...
    tbv.addSelectionChangedListener(open_action);

    return sash_form;
  }
  ...
  protected MenuManager createMenuManager()
  {
    MenuManager bar_menu = new MenuManager("");

    MenuManager file_menu = new MenuManager("&File");
    MenuManager edit_menu = new MenuManager("&Edit");
    MenuManager view_menu = new MenuManager("&View");

    bar_menu.add(file_menu);
    bar_menu.add(edit_menu);
    bar_menu.add(view_menu);

    file_menu.add(new ExitAction(this));
    edit_menu.add(new CopyFileNamesToClipboardAction(this));
    open_action = new OpenAction(this);
    edit_menu.add(open_action);

    return bar_menu;
  }
...
}

Figure 7. Explorer (version 11), showing Edit menu with a single file selected
Figure 7. Explorer (version 11), showing Edit menu with a single file selected

Now let's see how the number of selected files changes the open action (Figure 8):

Figure 8. Explorer (version 11), showing Edit menu with no file selected
Figure 8. Explorer (version 11), showing Edit menu with no file selected

Tool bars and pop-up menus
Finally we'll add a toolbar and pop-up (context) menu. Happily, there isn't really much to do since we've done all the hard work already in our actions. The toolbar and the pop-up menu are merely going to share those actions.

Just as with the status line and the bar menu, we configure the window to have a tool bar, and we implement the createToolBarManager() method to create it.

It's slightly different for the pop-up menu. We create it in the createContents() method and add it directly to the table widget.

We also refactor the code to have all three actions as fields (rather than local variables) so that we can access them from three methods. Let's see the final version of Explorer (Listing 10):

Listing 10. Explorer (version 12)

    
import java.io.*;

import org.eclipse.jface.action.*;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.widgets.*;

public class Explorer extends ApplicationWindow
{
  private TableViewer tbv;
  private TreeViewer tv;
  private OpenAction open_action;
  private ExitAction exit_action;
  private CopyFileNamesToClipboardAction copy_action;

  public Explorer()
  {
    super(null);

    exit_action = new ExitAction(this);
    copy_action = new CopyFileNamesToClipboardAction(this);
    open_action = new OpenAction(this);

    addStatusLine();
    addMenuBar();
    addToolBar(SWT.FLAT | SWT.WRAP);
  }

  protected Control createContents(Composite parent)
  {
    getShell().setText("JFace File Explorer");
    SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL);

    tv = new TreeViewer(sash_form);
    tv.setContentProvider(new FileTreeContentProvider());
    tv.setLabelProvider(new FileTreeLabelProvider());
    tv.setInput(new File("C:\\"));
    tv.addFilter(new AllowOnlyFoldersFilter());

    tbv =
      new TableViewer(sash_form, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
    tbv.setContentProvider(new FileTableContentProvider());
    tbv.setLabelProvider(new FileTableLabelProvider());
    tbv.setSorter(new FileSorter());

    TableColumn column = new TableColumn(tbv.getTable(), SWT.LEFT);
    column.setText("Name");
    column.setWidth(200);

    column = new TableColumn(tbv.getTable(), SWT.RIGHT);
    column.setText("Size");
    column.setWidth(100);

    tbv.getTable().setHeaderVisible(true);

    tv.addSelectionChangedListener(new ISelectionChangedListener()
    {
      public void selectionChanged(SelectionChangedEvent event)
      {
        IStructuredSelection selection =
          (IStructuredSelection) event.getSelection();

        Object selected_file = selection.getFirstElement();
        tbv.setInput(selected_file);
      }
    });

    tbv.addSelectionChangedListener(new ISelectionChangedListener()
    {
      public void selectionChanged(SelectionChangedEvent event)
      {
        IStructuredSelection selection =
          (IStructuredSelection) event.getSelection();

        setStatus("Number of items selected is " + selection.size());
      }
    });

    tbv.addSelectionChangedListener(open_action);

    MenuManager menu_manager = new MenuManager();
    tbv.getTable().setMenu(menu_manager.createContextMenu(tbv.getTable()));

    menu_manager.add(exit_action);
    menu_manager.add(copy_action);
    menu_manager.add(open_action);

    return sash_form;
  }

  public static void main(String[] args)
  {
    Explorer w = new Explorer();
    w.setBlockOnOpen(true);
    w.open();
    Display.getCurrent().dispose();
    Util.getClipboard().dispose();
  }

  protected MenuManager createMenuManager()
  {
    MenuManager bar_menu = new MenuManager("");

    MenuManager file_menu = new MenuManager("&File");
    MenuManager edit_menu = new MenuManager("&Edit");
    MenuManager view_menu = new MenuManager("&View");

    bar_menu.add(file_menu);
    bar_menu.add(edit_menu);
    bar_menu.add(view_menu);

    file_menu.add(exit_action);
    edit_menu.add(copy_action);
    edit_menu.add(open_action);

    return bar_menu;
  }

  public IStructuredSelection getTableSelection()
  {
    return (IStructuredSelection) (tbv.getSelection());
  }

  public void openFolder(File folder)
  {
    tv.setExpandedState(folder, true);
    tv.setSelection(new StructuredSelection(folder), false);
  }
  
  protected ToolBarManager createToolBarManager(int style)
  {

    ToolBarManager tool_bar_manager = new ToolBarManager(style);

    tool_bar_manager.add(exit_action);
    tool_bar_manager.add(copy_action);
    tool_bar_manager.add(open_action);

    return tool_bar_manager;
  }
}

And now, for the last time, let's fire up Explorer and see the tool bar and the pop-up menu in operation (Figures 9 and 10).

Figure 9. Explorer (version 12) showing pop-up menu with one file selected
Figure 9. Explorer (version 12) showing popup menu with one file selected

Figure 10. Explorer (version 12) showing tool tip with two files selected
Figure 10. Explorer (version 12) showing tool tip with two files selected

Conclusion
We've covered quite a lot of JFace in these three articles. We've seen how the pluggable JFace window, viewer, and menu framework can generate great-looking user interfaces using relatively small amounts of code.

I hope that you have learned:

  • How to subclass application windows
  • How to use the pluggable viewers and content providers
  • How to add icons using images and the image registry
  • How to use the system clipboard
  • How to launch programs
  • How to use menus and actions
  • How actions can work with different menu containers and listeners to produce context-sensitive applications

But, of course, there's more. Check out the Resources for more information.

Resources

About the author
Photo of authorA. O. Van Emmenis is an independent consultant, specializing in Java/J2EE training and consulting, based in Cambridge, UK. Van has worked in the software industry for 20 years or so. He first started working with objects using Smalltalk in the CAD industry and now works mainly in Java. He is particularly interested in Agile methods and GUI design. You can contact Van at van@vanemmenis.com.


248KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Open source projects | Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact