PgsAction and Guice – a mix made in heaven

Close to 100 years ago I published an Open Source library called “PgsAction”. This library was targeted towards Swing developers who used Swings Action class. PgsAction supports to styles of usage. You can extend from the AbstractSystemAction class and gain transparent I18N support or you could use an Annotation-based API where you could specify that a method acts as an Action.

The Annotation-based API was introduced because the Swing Application Framework supported that style of usage. Personally, I hate it. It encourages code duplication, bad API-Design and is a source of frustation because of the use of strings as identifiers for actions. Thus I only use it in small projects or highly encapsulated environments (i.e. actions for a small dialog)

PgsAction thus promoted a different usage style: Each Action is a class, you should use inheritance when possible and Actions are identified through their Class.

In theory using Class-identifiers also enables a loser coupling between the Action and the ActionContainer/Swing component. Sadly I never made the effort to implement something like that. However, when I recently started a new Swing project (a special kind of PDF-Viewer for my new work as a math/english tutor) I really wanted to put some of my newly found wisdom on application design to use. For example I finally understood the concept of MVC-programming (at least I hope that – though I’ll probably understand that I know nothing in a few months again…) and I really appreciate dependency injection and test driven development.

Since I hate XML and wanted a lightweight solution (in this context I define “lightweight” as “something that I can add without having to write a hundred lines of code just to get it going”) I decided to use Google Guice as my dependency injection framework for the application. After I had finished the model- and service-layer part I was absolutly sure that I wanted to use Guice for the UI-layer as well. When I came to the point of having to implement actual actions I didn’t think twice before using PgsAction. Even after all those years and with the total stagnancy of development for PgsAction it still is the library that fits my style best. There was just one problem: I can’t use dependency injection with PgsAction! The ActionManager class that is used to recieve an instance of a concrete Action doesn’t provide any kind of intelligent lookup facility. It just uses reflection to create a new instance of an Action and returns that – making sure that there is exactly one instance of each Action.

While I prefer that to the string-based alternatives, I still saw a great potential in mixing in some Guice into PgsAction. You might really want more than a single instance of an Action, your Action implementation might need some dependencies (in SimpleEdit I used a ServiceLocator-API to handle dependencies) and you might want to test it or use a different implementation of a single Action based on the OS or (product-)license or whatever. Guice is great in all of this and thanks to the ActionManager class it is unbelievably easy for PgsAction to hook up with Guice. Below I’ll provide an ActionManager implementation for Guice and show some examples from my new application.
[cc lang=”java”]/*
* Copyright (c) 2010 Patrick Gotthardt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.pagosoft.myview.actions;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.pagosoft.action.ActionManager;

import javax.swing.*;

/**
* A simple class to connect PgsAction with Guice.
*/
public class GuiceActionManager extends ActionManager {
private Injector injector;

@Inject
public GuiceActionManager(Injector injector) {
this.injector = injector;
}

@Override
public K get(Class kClass) {
return injector.getInstance(kClass);
}
}[/cc]
As you can see we request an instance of the injector and delegate the construction of new Actions to it. This gives you amazing flexibility. You can now provide different implementations of an Action based on some conditions, you can specify whether or not an Action is supposed to be a singleton and most importantly it allows you to use dependency injection in your Action classes!

The next code snippet will show an Action implementation that requires a PrintService (for the actual action) and the EventBus (to adept its state). As you can see this is really just a little bit of code, but its a great way to decouple your classes.
[cc lang=”java”]/*
* Copyright (c) 2010 Patrick Gotthardt. All rights reserved.
*/

package com.pagosoft.myview.actions;

import com.google.inject.Inject;
import com.pagosoft.action.AbstractSystemAction;
import com.pagosoft.eventbus.ApplicationListener;
import com.pagosoft.eventbus.EventBus;
import com.pagosoft.myview.domain.Document;
import com.pagosoft.myview.events.DocumentChangedEvent;
import com.pagosoft.myview.print.DocumentPrintJob;
import com.pagosoft.myview.print.PrintService;

import java.awt.event.ActionEvent;

/**
* Prints a single document.
*/
public class PrintDocument extends AbstractSystemAction implements ApplicationListener {
private PrintService ps;
private Document doc;

@Inject
public PrintDocument(EventBus eventBus, PrintService ps) {
super(“print.document”);
this.ps = ps;
eventBus.add(this, DocumentChangedEvent.class);
setEnabled(false);
}

public void actionPerformed(ActionEvent e) {
ps.print(new DocumentPrintJob(doc));
}

public void handleEvent(DocumentChangedEvent event) {
doc = event.getSource();
setEnabled(doc != null);
}
}[/cc]
Now all that is left is to create a menubar or toolbar. Usually you should use the ActionContainer class of the PgsAction library, but in this case all I really need is a toolbar so I didn’t use ActionContainer.
[cc lang=”java”]
public class MyViewToolBar extends JToolBar {
@Inject
public MyViewToolBar(ActionManager am, ToolBarUI ui) {
setUI(ui);

PopupButton print = new PopupButton(am.get(PrintDocument.class));
JPopupMenu menu = print.getPopup();
menu.add(am.get(PrintDocument.class));
menu.add(am.get(PrintExercise.class));
menu.addSeparator();
menu.add(am.get(PrinterSettings.class));
add(print);

add(am.get(PreviousDocument.class));
add(am.get(RandomDocument.class));
add(am.get(NextDocument.class));
}
}[/cc]
As you can see we do not use Guices Injector class but instead rely on ActionManager to produce the actions. This further reduces the coupling between your classes since you could just pass an ActionManager Mock Object instead of the actual one.

While it is absolutly true that the application I wrote has a very basic UI I still think it is worth mentioning that I could easily provide a completely alternative (Swing) UI without having to rewrite anything but the UI layer.

Leave a comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.