Google Guice and plugins en

By Gerco on Wednesday 27 June 2012 19:00 - Comments are closed
Category: -, Views: 5.416

While refactoring an application to use Google Guice for dependency injection, I ran across some challenges on how to modularize my application with plugins. It turns out that these is a relatively simple solution: Guice's Multibinders.

Lets look at the problem first. Guice favors constructor injection and that often results in big, ugly constructors with dozens of arguments. In my experience this mostly happens when constructing GUI containers with a lot of "static" items (menus, tabs, lists of configuration pages, etc). These lists of items are not truly static because we would like to be able to extend them through plugins, for example.

When we follow a naive approach, a constructor for a JFrame that holds a bunch of tabs may look like this:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Inject
public MainFrame(
    Panel1 p1, Panel2 p2, Panel3 p3, Panel4 p4,
    Panel5 p5, Panel6 p6, Panel7 p7, Panel8 p8)
{
    addTab(p1);
    addTab(p2);
    addTab(p3);
    addTab(p4);
    addTab(p5);
    addTab(p6);
    addTab(p7);
    addTab(p8);
}



This has the advantage that it's perfectly clear which tabs this frame will host and in which order they will appear. Aside from that, it would be easy to add some code to listen to application events and enable and disable specific tabs when required. This code does lack a few things though: It's a lot of work to add tabs to it and it doesn't allow for adding more tabs after the application has been distributed (plugins).

To allow for adding tabs we need to solve several problems:
  • Allow for addition of tabs
  • Allow tab order to be determined by plugins or the user
  • Provide some way for tabs to enable/disable based on application state
Lets start one at a time and first make the list of tabs dynamic from the point of view of the frame that will host them:

Java:
1
2
3
4
5
6
7
8
@Inject
public MainFrame(Set<JComponent> panels)
{
    for(JComponent panel: panels)
    {
        addTab(panel);
    }
}



Now we are not injecting each tab separately, but we inject instead a Set of tabs. This removes the requirement for the frame to know exactly which tabs exist and makes the code somewhat simpler. We still have to specify which tabs go in this frame though. This is done in the Guice Module:


Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UIModule extends AbstractModule {

    @Override
    protected void configure() {
        Multibinder<JComponent> panels = Multibinder.newSetBinder(binder(), JComponent.class);
        panels.addBinding().to(Panel1.class);
        panels.addBinding().to(Panel2.class);
        panels.addBinding().to(Panel3.class);
        panels.addBinding().to(Panel4.class);
        panels.addBinding().to(Panel5.class);
        panels.addBinding().to(Panel6.class);
        panels.addBinding().to(Panel7.class);
        panels.addBinding().to(Panel8.class);
    }
}



So what has really changed? We moved the list of panels from one piece of Java code to another piece of Java code. It's still Java code and this didn't help one bit to implement a plugin architecture.

This is where the multi-module aspect of the Multibinder comes in. When a different Guice module defines a binder for the same type (in this case Set<JComponent>), they will be merged. At runtime, there will only be one Set<JComponent> and all tabs from all Modules will be presented in the frame.

Set<JComponent> is a little to generic of a type to be using for this purpose though. We want to do more with this. We need (at a minimum) to specify a name and a way to get a reference to the actual JComponent so lets start by defining an interface:

Java:
1
2
3
4
public interface UITab {
    public String getUITabName();
    public JComponent getUITabComponent();
}


This interface defines a few methods we'll need to display our tabs. "getUITabName" should return the display name for the tab and "getUITabComponent" is expected to return a reference to the actual UI component.

Each Panel class can now implement this interface, like so:

Java:
1
2
3
4
5
6
7
8
9
public class Panel1 extends JPanel implements UITab {
    public String getUITabName() {
        return "Panel 1";
    }

    public JComponent getUITabComponent() {
        return this;
    }
}



Each Panel can now be defined in its own module like this:

Java:
1
2
3
4
5
6
7
public class Panel1Module extends AbstractModule {
    @Override
    protected void configure() {
        Multibinder<UITab> panels = Multibinder.newSetBinder(binder(), UITab.class);
        panels.addBinding().to(Panel1.class);
    }
}



Now that each of the panels is defined in its own module, we can split them out into separate projects (plugins). Each Panel can get its own jar file. All we still need is a way for the main application to detect the available plugins and load them. The ServiceLoader class is a good possibility. Some classpath detection of an appliction specific plugin descriptor or an XML file listing all installed plugins are also possible.

Programmers: What we do

Door Gerco op dinsdag 14 februari 2012 02:06 - Reacties (4)
Categorie: -, Views: 7.156

Er is momenteel een "what x thinks I do" meme gaande dus ik dacht ook maar eens wat bij te dragen:

Programmer

Altitude developers add offline mode en

By Gerco on Sunday 13 June 2010 12:21 - Comments are closed
Category: -, Views: 2.796

In a previous blog post I ranted about the required connection to the login server before being able to play Altitude (cartoony planes, mayhem, etc). The connection requirement is still there, but the developers have added an offline mode to be able to play Altitude against bots when you don't have an internet connection available.

While the whole universe of Altitude players are still kicked out of their games whenever the central login server fails (including LAN players), this hasn't happened since the Steam free weekend is over so the servers seem to be capable of handling the load at the moment.

I'd like to thank the Altitude team for this update, great way to listen to your players!

Little brother

Door Gerco op zaterdag 29 mei 2010 13:20 - Reacties (13)
Categorie: -, Views: 2.909

Met de verkiezingen voor de deur was ik wat aan het rondgooglen over anti-privacy, OV-chipkaarten en andere enge maatregelen en kwam ik gisteren het boek "Little Brother" van Cory Doctorow tegen.

Cory biedt dit boek gratis aan als eBook in diverse formaten (onder Creative Commons) en het is ook als hardcopy te koop. De ongeduldige Nederlander in mij heeft de ePub versie gedownload en het boek in 1 ruk uitgelezen. Het is 04.30 geworden vannacht, maar het was het waard. De hardcopy is inmiddels onderweg naar mijn brievenbus.

Little Brother beschrijft een toekomst die niet al te ver weg is. Sterker nog, deze staat al voor de deur en staat op het punt om aan te bellen. Na een terroristische aanslag in San Fransico veranderd de stad in een politiestaat. Chipkaarten als de FasTrak (lees OV-chipkaart) worden door de Department of Homeland Security (DHS) gebruikt om alle verplaatsingen (zowel per OV als auto) te registreren en plukken iedereen van de straat die niet aan het "normale" patroon voldoet. Overal zijn camera's, zogenaamd voor de veiligheid, maar niemand kan uitleggen hoe die camera's de burger dan precies veiliger maken.

Het boek heeft een sterke technologische ondertoon en de hoofdpersoon is een echte tweaker. Na onterecht gearresteerd te worden en in een geheime gevangenis aan een dagenlange ondervraging blootgesteld te zijn besluit hij zelf de DHS aan te pakken. Het verhaal is fictie, maar de technologie die erin voorkomt is dat (bijna) niet. Het is een eye-opener voor iedereen die dacht dat hij niets te verbergen had.

Download gratis als eBook of voor mensen die niet graag van een scherm lezen kun je de hardcopy kopen bij bol.com.

When one bad decision ruins a perfectly good game en

By Gerco on Sunday 23 May 2010 21:15 - Comments (16)
Category: -, Views: 3.645

The past week-end, Altitude has been free to play via Steam. Altitude is a fun little game where cartoony airplanes battle each other in a frantic meelee of death and destruction.

I have enjoyed playing Altitude so much that I decided to purchase it (it wasn't even 5 euros, great deal!). After playing for a while I found that this game cannot be played at all without being connected to the so-called "Login server". If the login server were to fail during play (as it has done a few times in the past hour), everyone world-wide is kicked out of their game and needs to restart it (assuming that the login server is up again).

When the login server is down, the game can't even be played offline against bots and you cannot play the flight training levels (which are 100% local) without being connected to this login server. Since the servers have been offline for several hours (total) in the past few days, almost all players must have had problems with it at some point in the past few days.

There are so many things wrong with this model that I cannot even count them anymore. Requiring every single player in the world to be connected to your servers while playing is something even behemoths like EA can't get right, let alone an indy developer like Nimbly Games. Don't even try!

This is a surefire way of pissing off your players, I wonder what the developers were thinking when they implemented this DRM "functionality".