Saturday, December 27, 2008

Should we fork swing-layout project?

The swing-layout project was the initial effort to bring Swing a better layout system that would in particular take into account the specificities of the installed Look & Feel, alongside baseline alignment support.
Swing-layout works with Java5 (maybe Java1.4, I don't know, I have never checked).

This is where the new Java6 GroupLayout has been elaborated before integration into Java6.

Besides bringing a new LayoutManager that provides better layouts (at the expense of higher complexity in use, except if you use NetBeans Matisse designer), swing-layout also brought utilities available to other third-party layouts, in particular the aforementioned baseline support.

DesignGridLayout is one of those LayoutManagers that relies on swing-layout for baseline alignment. It is also one of my Open Source projects.

Swing-layout was available before Java 6 and, of course, the release of Java 6 has made it somewhat irrelevant (at least for the rare developers who could jump to Java 6 immediately).

Now it is strange to discover that Java 6 baseline support is better than swing-layout itself (when using Java 5). Indeed, the swing-layout project on java.net has been left in limbo for about 2 years, and it looks nobody is really responsible for it, several issues are still open and nobody cares!

So what should happen to this project? Should it definitly be buried in favor of Java 6?
Or, stated differently:
Do we necessarily need to upgrade to Java 6 to have good Swing layouts?

I don't think so! Java 5 is still mainstream nowadays, and it is planned to reach EOSL in one year (that's still some amout of time!). So I strongly believe Java 5 users should not be left behind.

Why do I talk about this topic here? As a matter of fact, I have hit swing-layout bugs in DesignGridLayout and I am facing a tough decision:

Should I leave DesignGridLayout Java 5 support behind and require Java 6 as a minimum?

Or should I try my best to keep Java 5 support -with no difference in the provided features- at least for one more year?

If so, how should I deal with swing-layout bugs?

One idea I had was to fork the swing-layout project and create my own, with the same license (LGPL). Although it seemed to me a good idea, the problems I got with this is that:
  • I don't have the facilities to test all cases (MacOSX, Linux GTK)
  • I don't have much time left for Open Source (I already have 4 OSS projects and I can't deal with them all, I always have to put one in top priority -currently DesignGridLayout- while the others have to wait, sometimes for several months)
  • I don't want to support GroupLayout which I don't use and which is a competitor of my own DesignGridLayout!
Indeed, yesterday I have refactored the whole baseline support in swing-layout (because currently everything is in one huge class with a lot of terrible code, very difficult to maintain and extend). I have fixed the two problems I have in DesignGridLayout (JScrollPane and JTableHeader baselines). This works but I don't know yet if I'll keep it (because it is LGPL and DesignGridLayout is Apache License 2) as part of DesignGridLayout source code.

Thus I think that, unfortunately, I'll have to throw away this code (is there someone interested out there?) and try to stick with the "official" swing-layout release and find some workaround that can be implemented directly in DesignGridLayout as a caller (no license incompatibility).

Of course, if someone is motivated and ready to take over the effort of forking swing-layout, then I would happily give back my work to that new project. Just drop me a note.

The rant

Now the status of this library, sponsored by Sun, reminds me of other "currently on-going" (:->) efforts such as: JSR-295 (beans-binding), JSR-296 (Swing Application Framework).
Once again, although Sun claims they don't leave Swing behind, they actually do, in favor of that half-baked JavaFX thingy, which is not even comparable to its competitors, which we may wonder if it deserves the "1.0" version number. Layout support in JavaFX made me laugh big times (it all boils down to HBox and VBox).

Maybe it's time to start forgetting Java (and Swing?) and learn something new (anything but JavaFX).

Sunday, December 07, 2008

Announce: DesignGridLayout 1.0 released!

One month after the third release candidate of DesignGridLayout 1.0 (1.0-rc3), no new bugs being received, I have decided to release the official 1.0 version of DesignGridLayout.

I trust this version to be stable and bug-free (however, if you do encounter a bug with it, do not hesitate to report it, I will be glad to provide a fix in a timely manner).

For me, this means I can now start seriously working on the next enhancement (for 1.1 version) that will allow users to define some components spanning several rows. You can already have an overview of the future API (up to the current state of my reflection) if you are interested.

If you have further questions about DesignGridLayout, please don't hesitate to ask in the corresponding project lists.

Sunday, November 16, 2008

My presentation on JSR-296 at Jazoon 08

I have just seen that Jazoon has removed all presentations files of Jazoon 08 talks.
That's the occasion for me to post the content of my talk on "JSR-296 Swing AppFramework from the trenches".

The full content (in PPT format) is here (I have just removed the Jazoon template stuff). If you don't have PowerPoint, here is a PDF version (unfortunately without the original animations). Unfortunately I could find no way to convert it to ODP format (for OpenOffice users) without big losses in animations but also in diagrams (most of them got terribly ugly after conversion). I am afraid I'll have to give up on that one (if anyone can convert the original PPT to ODP please email me the result and I'll post it here).

If you want to have an idea of the content before download, here is the agenda:
  • Introduction
    • why do we need a Swing framework
    • what is JSR-296? what does it bring (in a nutshell)?
    • about the presented applications
  • Application Lifecycle
    • standard application lifecycle
    • adding dependency injection (Spring, Hivemind, Guice)
    • adding docking capability (MyDoggy)
    • tips & pitfalls
  • Resources & I18N
    • resource injection principles
    • what about JTable?
    • tips & pitfalls
  • Actions & Tasks
    • a better, more responsive, javax.swing.Action
    • where should I put my @Action?
    • when should I use a Task?
    • what about Exception handling?
    • tips & pitfalls
  • Persistent Session State
    • state restoration (or users' happiness)
    • what is stored? what is not? where?
    • tips & pitfalls
  • What is missing for a complete GUI framework?
    • GUI framework architecture
    • Example: EL4J Swing stack
  • Future
    • JSR-296 standpoint
    • what's likely to change?
Please note that, considering the recent events about Sun in general, and Swing in particular, the "Future" section of the presentation is probably largely obsolete:-(
Still, I believe that Swing is a better platform for GUI development than many others (not just talking about Java here), and developers will still use it for a couple of years more (maybe not in pure Java, though, other options exist: Groovy, Scala). Wait and see...

Enjoy!

Wednesday, November 05, 2008

Announce: DesignGridLayout 1.0-rc1 released!

I am particulaly glad to announce the first release candidate of DesignGridLayout V1.0.

DesignGridLayout is a Swing LayoutManager, revolutionary by its API, simple but powerful. Its main advantages are:
  • Good looking forms (alignment, spacing, sizing, visual balance): this is entirely taken over by DesignGridLayout itself without any special hint from the developer
  • Reduced learning curve for developers, thanks to its fluent API which is simple, effective, compile-safe and IDE code-completion friendly
  • No graphics designer needed: the API is the graphical designer
  • Readability and maintainability: you can literally "visualize" the layout by browsing the code that sets it up; inserting a new row of components is done by simply inserting a new line of code in the layout setup code...
  • Free: the project is open source and released under Apache License 2.0
Version 1.0-rc1 is available here or through the java net maven 2 repository (for more info, you can check my previous post and replace "0.9" with "1.0-rc1").
This version brings the following improvements:
  • #13: support for multiple groups of fields, each with its own label column
  • #5: smart vertical resize: DesignGridLayout automatically determines which rows should grow vertically and also make sure that components height is suitable to display an entire line of information (useful for JList, JTable, JTextArea)
  • #18: smarter horizontal resize behavior: now DesignGridLayout won't resize components under their minimum size
  • #9: automatic support of right-to-left text orientation based on Locale
  • #16: smarter gaps for empty rows
  • #15: resolution independence
  • #20: fixed ugly layout problem when container has a border
  • #12: now setLayout() is automatically called by DesignGridLayout constructor
Please note that V1.0 required API changes that I could unfortunately not keep compatible with previous 0.9 release. This should be the last time that happens (V1.1 should only extend the current API).

I consider the current version ready for production as the current test suite of DesignGridLayout is quite comprehensive and covers all its features.
However, I have decided to prepare a release candidate to give myself a chance to fix any problems that users may find but that I could not discover by myself (in particular, problems related to platforms that I don't have: MacOS-X, Linux, Solaris).

If needed, I will create further release candidates. I will wait about one month after a rc until I cut a final release.

What's next?
  • first of all, I'll get some rest;-)
  • then I'll spend some weeks on my other open source project, guice-gui
  • finally I'll start working on DesignGridLayout V1.1, which should include just one improvement (issue #10: support for components spanning several rows) which should be released as a Christmas present;-)
Enjoy it and don't hesitate to report any problems or enhancements!

Monday, November 03, 2008

In search of the perfect API (final part)

This is the last post of my series on DesignGridLayout API evolutions. As mentioned before, although I use DesignGridLayout to illustrate it, this series might be useful to any developer of libraries.

In the first, second, third and fourth posts, I have described the evolution of DesignGridLayout API from its original release to the latest -soon released now- V1.0.

This last post is more a reflection on the next probable evolution of this API. This means that anything shown in this post has not been implemented (and might be difficult to implement), but thinking in terms of API first rather than implementation should be your mantra when writing a library with a large users base (I'm dreaming aloud;-))

Immediately after releasing V1.0 (in a few days), I'll start working on V1.1, which main enhancement will be issue #10 (add support for components spanning several rows).

This is not an easy problem, API-wise; indeed, you have to keep in mind the following principles that DesignGridLayout project adheres to:
  • the feature (API) must be easy to understand and use
  • the code visually represents the actual layout
  • developer mistakes must be avoided or can be found (and eradicated) at compile-time
I would also add an important point here:
  • don't break the current API (and the existing code that uses DesignGridLayout library V1.0)!
With this in mind, let's try to sketch what we would like the user to write (in terms of code), starting from a layout that is possible in V1.0:
layout.row().grid(lbl1).add(table1).add(field2);
layout.row().grid() .empty() .add(field3);
layout.row().grid(lbl4).add(field4);
Note that table1 represents a JList in a JScrollPane with a preferred size of a few rows (let's say 2 for this example).

Here is a snapshot of the displayed layout for this snippet:


Here we can see that with DesignGridLayout V1.0, table1 is displayed at its preferred height but this fixes the height of its whole row. Hence a lot of empty space is lost on the second column of the layout. Instead, wee would like to have table1 span on the second row as well (that's why we have already put an empty() column in the same column in row 2).

Now how can we let the user indicate that table1 should span on 2nd row?

One option I have in mind is something like this:
layout.row().grid(lbl1).add(table1).add(field2);
layout.row().grid() .spanRow() .add(field3);
layout.row().grid(lbl4).add(field4);
Using "spanRow()" on the second row would have the following meaning:
  1. the component on the previous row at the same column position in the same sub-grid will span this row
  2. the horizontal span of this component will be the same as the column span defined for that same component on the previous row
  3. the sub-grid in which spanRow() appears will have its gridspan forced to the same value as the sub-grid in the same position of the previous row
  4. if the matching component in the previous row is empty() or empty(n), then spanRow() is also empty() or empty(n)
  5. if this is the first row in the layout, then spanRow() is equivalent to empty()
This definition solves some problematic cases when using column spans in a grid:
layout.row().grid(lbl1).add(table1, 2).add(field2);
layout.row().grid() .spanRow() .add(field3);
layout.row().grid(lbl4).add(field4);
Or even when using multiple sub-grids:
layout.row().grid(lbl1, 2).add(table1).add(field2);
layout.row().grid(1) .spanRow().add(field3);
layout.row().grid(lbl4) .add(field4) .grid(lbl5).add(field5);
However, there is a problem in this latter example: in second row, we specify a gridspan of 1, but the actual gridspan will be 2 (the same as in the first row)! This looks counter-intuitive.

One approach to solving this problem would be to prevent the possible call to grid(int gridspan) or grid(JLabel label, int gridspan).

But the only way to prevent it would be to explicitly indicate that, when we create a new sub-grid in a row, this row will include row-spanning components, and in this case we can restrict the interface to prevent setting explicitly gridspans.

For this we could simply change the signature of grid methods in ISubGridStarter interface:
public interface ISubGridStarter {
public ISpannableGridRow grid(JLabel);
public ISpannableGridRow grid();
public IGridRow grid(JLabel, int gridspan);
public IGridRow grid(int gridspan);
}
Here we introduce a new interface ISpannableGridRow which is defined as follows:
public interface ISpannableGridRow extends IGridRow {
public ISpannableGridRow spanRow();
}
This is just an extension of the normal IGridRow with one extra method that allows spanning of components.

Now there is another problem (unsolved yet, else this issue would be fixed in V1.0 already;-)), related to vertical resize feature.
DesignGridLayout has the notion of rows that can vary in height or not (based on the components they hold); in addition, it allows you to define a vertical grow weight factor for those rows with variable height (see DesignGridLayout.row(double weight) method).
Now that components can possibly span several rows, how should we address the way individual rows will be shared vertical space?
Or is this really a problem? Can't the current algorithm work as well? Only a prototype for issue #10 will tell us...

For the definitive API of DesignGridLayout V1.1, well, I guess we'll have to wait until official release;-) maybe as a Christmas present, or sooner if I can manage!

This post closes my long discussion on searching the best possible API for a library. Maybe, in a later post, I'll show implementation details for this API (in particular, how return co-variance (Java 5+) has helped me much for that).

Hope you found this series interesting. Don't hesitate to comment! Enjoy.

Sunday, November 02, 2008

In search of the perfect API (part 4)

This is the fourth post of my series about how the API of DesignGridLayout library has been improved. This series can be useful for anyone developing libraries (DesignGridLayout just illustrates improvements).

The first post discussed the original V0.1 API and its related issues.

The second post has shown how to solve most V0.1 issues.

The third post has dealt with API improvements driven by new features. In particular, one interesting change to the API has been to introduce a new interface ISubGridStarter to reduce the list of available methods in a context, these methods then returning the "full" interface IGridRow.

In this installment, I will talk about the latest changes made to the API before V1.0 is released (which should happen very soon now).

Latest 1.0-SNAPSHOT: back to 0.1?

First of all, I was not very happy with the new methods I introduced in DesignGridLayout class in one of both previous snapshots:
public class DesignGridLayout {
public INonGridRow leftRow();
public INonGridRow leftRow(double weight);
public INonGridRow rightRow();
public INonGridRow rightRow(double weight);
public INonGridRow centerRow();
public INonGridRow centerRow(double weight);
public IGridRow row();
public IGridRow row(double weight);
}
There is a lot of repeat here. In addition, it is not clear why "row()" is not "gridRow()" (lack of consistency).
In addition, the ISubGridStarter interface uses methods all named "label()" to actually do 2 things:
  1. Start a new canonical sub-grid
  2. Set a label to this new sub-grid (or no label at all)
So the name is not representative of the method behavior. In particular, it is extremely weird to call label() with no argument to indicate we start a new sub-grid with no label!

Actually, I prefer something like that (in terms of usage):
layout.row().left().add(...);
layout.row(1.0).grid(lbl1).add(...).grid(lbl2).add(...);
layout.row(0.5).grid() .add(...).grid(lbl3).add(...);
Several changes have occurred to make this kind of code possible.

First I introduced (once again) a new interface:
public interface IRowCreator extends ISubGridStarter {
public INonGridRow left();
public INonGridRow right();
public INonGridRow center();
}
I also modified ISubGridStarter (only the method names):
public interface ISubGridStarter {
public IGridRow gridJLabel);
public IGridRow grid();
}
You have to remember that IGridRow extends ISubGridStarter (which allows to later start new sub-grids in a row).

Finally, I simplified DesignGridLayout API:
public class DesignGridLayout implements LayoutManager {
...
public IRowCreator row();
public IRowCreator row(double weight);
public void emptyRow();
}
Now we have only 2 general methods to create a new row (whatever its type), one allows you to specify the vertical growth weight factor (the no-arg row() will automatically determine if the actual row should have variable height and, if so, assign it a default weight of 1.0).

Also note that we still keep emptyRow(), but it has lost its "int height" argument, this is due to an improvement of the behavior (issue #16: make emptyRow smarter).

Now we can code our layouts like that:
layout.row().grid(lbl1).add(field1)             .grid(lbl2) .add(field2).add(field3);
layout.row().grid() .add(field4).add(field5) .grid(lbl3) .add(field6);
layout.row().grid() .grid(lbl4) .add(field7).add(field8).add(field9);
layout.emptyRow();
layout.row().center().add(ok).add(cancel);
The snippet above will exhibit the following layout:


The source code reflects the way the container will be laid out.
Code completion is helpful in that it does not authorize useless, or erroneous, method calls.

In fact, we have just built a simple "DSL" for laying out a Swing container!

To summarize, here is what we have gained in consistency (compared with previous 1.0-SNAPSHOT):
  • you add a new row to your form with one of two methods, named the same (row())
  • once a new row has been created, you can only choose its type in a clear way (left(), right(), center() or grid())
  • on a grid row, you can start a new sub-grid at any time with grid(), this is consistent with the way you started the grid row (because a grid row starts with a sub-grid)
In addition to previous improvements:
  • it is never possible for users to call a meaningless API (e.g. add(JComponent child, int span) in a non-grid row)
  • thus the number of options (callable methods) to the user in a context is restricted to its minimum (easing the search of methods through code completion in your IDE)
Please note that throughout this series, the API was simplified a little bit (there are a few more methods to some interfaces, but not many) in order to make it easier to follow.

This API should now be considered stabilized and there should be no change until the first official release candidate 1.0-rc1 of DesignGridLayout (which should be in a few days now).

In the next (and last) installment, I will talk about the future extensions of DesignGridLayout API (due for V1.1), necessary to implement the issue #10 (Support for components spanning several rows).

Saturday, November 01, 2008

In search of the perfect API (part 3)

This is the third installment of the short series discussing how to enhance a library API to make it better for its users. The series is based on DesignGridLayout as a practical example.

The first post discussed the original API of DesignGridLayout V0.1, focusing on its weak points for the end user.

The second post introduced the API of V0.9, showing how most of V0.1 pain points were solved (in particular by using interfaces and making public only what has to be public).

This third post will describe two evolutions (performed in two distinct steps) made in the current development trunk (which I call 1.0-SNAPSHOT), originated in new features requested by some users.

First evolution to 1.0-SNAPSHOT

When implementing issue #5 (vertical resize behavior), it had to be possible to add a "vertical grow weight" factor to any kind of row (except empty rows), in the same manner as you can assign "weighty" in Swing GridBagLayout.

For that, I only modified DesignGridLayout class:
public class DesignGridLayout {
...
public INonGridRow leftRow();
public INonGridRow leftRow(double weight);
public INonGridRow rightRow();
public INonGridRow rightRow(double weight);
public INonGridRow centerRow();
public INonGridRow centerRow(double weight);
public IGridRow row();
public IGridRow row(double weight);
public void emptyRow(int height);
}
That was really straightforward and it worked well for the library user. Note that we still did not solve the API issue (existing from V0.1) allowing multiple calls to label() on grid rows, but where only one call is effective.

Second evolution of 1.0-SNAPSHOT


Issue #13 (grids with multiple labels) was an important request from DesignGridLayout users so I had first to find the right API before implementing this feature.

The first idea that came to my mind was an API that would allow the following snippet:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row() .add(field3).label(lbl4).add(field4);
So I need multiple calls to label(JLabel) (which was easy because the current API already enabled that, that was even its main remaining flaw).

Please note the absence of a label at the beginning of the second row, which means that there will be no label at this position in the displayed form (however empty space will be added, so that field1 and field3 are vertically aligned with each other). But this possibility required to have the same possibility for the second (and next) label in a row, I needed something like that:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row() .add(field3).label() .add(field4);
Note the new no-arg label() method. Here is the new IGridRow interface (excerpt):
public interface IGridRow {
...
IGridRow label(JLabel);
IGridRow label();
}
Adding this method solves the problem but leads to inconsistency in the API, that we see in the snippet above, you now have two ways to obtain the same result:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row() .add(field3).label() .add(field4);
or
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row().label() .add(field3).label() .add(field4);
The second way should actually be the only way to get this result! There are two reasons why I want that:
  1. API Consistency (as mentioned before)
  2. It allows unclear code when creating specific layouts where one would want to completely skip the first label AND the first field (and define only the second label and field)
For example, what layout would be produced by the following snippet:
layout.row().label(lbl1).add(field1).label(lbl2).add(field2);
layout.row().label(lbl4).add(field4);
Should lbl4 belong to the first label column or the second one? If we want to avoid such inconsistencies we have to make it mandatory to call one of both label() methods when starting a grid row.

Concretely this requires adding a new interface returned by DesignGridLayout#row():
public class DesignGridLayout {
...
public ISubGridStarter row();
public ISubGridStarter row(double weight);
}

public interface ISubGridStarter {
public IGridRow label(JLabel label);
public IGridRow label();
}
I also refactored IGridRow interface to extend ISubGridStarter:
public interface IGridRow extends ISubGridStarter {
...
}
This way, we have one unique, consistent way to define our layout.

In next installment, I will explain some last minute changes in the 1.0-SNAPSHOT API, in order to make it definitely better (and easier to use).

Friday, October 31, 2008

In search of the perfect API (part 2)

This is the second installment of a sort series about the evolution of the API of DesignGridLayout library.
Although DesignGridLayout is used as an example, this series is more general and its goal is to show how you can improve the API of any library, and what pitfalls you should avoid.

In the first installment of this short series, we have visited the initial API of DesignGridLayout (version 0.1) and seen several drawbacks it exhibited.

Release 0.9 (heavy refactoring of both API and implementation)

When I took over the DesignGridLayout project, a few months ago, I had decided to completely refactor the library source code and make the API has good as possible; in particular, I wanted to remove all drawbacks of version 0.1.

First of all, I have added interfaces in order to isolate API from the implementation details (too many public classes and methods were exposed in version 0.1).

Since, in DesignGridLayout, the accessible features are different if you use:
  • a grid row
  • a non-grid row (center, left or right)
  • an empty row (which sole purpose is to provide some empty vertical space)
I have first decided to make different methods in DesignGridLayout class to create these various kinds of rows; one of my goals was to be able to provide a specific API according to each kind of row; in addition, deciding the type of the row at the time you create it from DesignGridLayout would remove another problem of V0.1 related to multiple changes of the row type (which for the end user is non-sense).
public class DesignGridLayout {
...
public INonGridRow leftRow();
public INonGridRow rightRow();
public INonGridRow centerRow();
public IGridRow row();
public void emptyRow(int height);
}
All names starting with I are interfaces (I borrowed this convention from .NET, although arguable at the beginning, I found it quite interesting in my situation).

Here are the interfaces (simplified, I put only the main methods for clarity):
public interface INonGridRow {
public INonGridRow add(JComponent... children);
}

public interface IGridRow {
public IGridRow label(JLabel label);
public IGridRow add(JComponent... children);
public IGridRow add(JComponent child, int span);
public IGridRow empty();
public IGridRow empty(int span);
}
Now we clearly set the list of methods available to each kind of row, excluding any unsupported method (which we didn't do in V0.1).

We have also replaced the constant EMPTY (previously defined in Row) and replaced it by 2 empty() methods that are more explicit and help the developer (when using IDE code completion facility).

With this API, we have solved most problems existing with V0.1 with one exception: one can still call label() several times in a grid row although it is not supported by the library.

Now the example code shown in the first installment can be rewritten
layout.row().label(lbl1).add(field1).add(field2).add(field3);
layout.row().label(lbl2).add(field4).add(field5);
layout.row() .add(field6).add(field7);
layout.emptyRow(18);
layout.centerRow().add(ok).add(cancel);
to be compared with the previous snippet:
layout.row().label(lbl1).add(field1).add(field2).add(field3);
layout.row().label(lbl2).add(field4).add(field5);
layout.row() .add(field6).add(field7);
layout.row().height(18);
layout.row().center().add(ok).add(cancel);
The difference may not be remarkable here, but believe me you will see the difference when using your preferred IDE (code completion will not show you a plethora of irrelevant methods for the current context).

That's it for the API of V0.9. In next installment, I will describe 2 evolutions (due to adding new features) made to the API in 2 snapshots of the future V1.0.

That's it for this time. Enjoy!

Thursday, October 30, 2008

DesignGridLayout: in search of the perfect API (part 1)

This is the first installment of a short series (4 or 5 posts) telling the story of DesignGridLayout API.

From the first beginning, DesignGridLayout was meant to be easy to use in order to build Swing forms; its API was meant to make the use of any graphical designer totally useless. It was also meant to give code maintainers the possibility to "see" the UI while only browsing the source code.

For that, it has used a fluent API (some may use the term "DSL" or "Domain Specific Language" in this context) so that you could, in one line of code, define one row of your UI, eg:
layout.row().label(lbl1).add(field1).add(field2).add(field3);
layout.row().label(lbl2).add(field4).add(field5);
layout.row() .add(field6).add(field7);
layout.row().height(18);
layout.row().center().add(ok).add(cancel);

The code above would give the following result in a dialog:


As mentioned above, this series is about the evolution of that API. Prior to giving details on API history and changes motivation, I must thank Joshua Bloch for his excellent book "Effective Java, 2nd Edition" (I read it this summer, I had never read the first edition before) which I recommend to every Java programmer and particularly to library developers; this book has helped me a lot in thinking about providing a better API to DesignGridLayout users.

Initial release 0.1

Version 0.1 could be seen as a good prototype of implementing canonical grids in a Swing layout with a rather good API.

The library was then made of 4 classes only (all public)!

public class DesignGridLayout implements LayoutManager {
...
public Row row() {...}
}

public class Row {
public static final JComponent EMPTY = ...;
public Row label(JLabel label) {...}
public Row add(JComponent child) {...}
public Row add(JComponent... children) {...}
public Row add(JComponent child, int span) {...}
public Row left() {...}
public Row center() {...}
public Row right() {...}
public Row grid() {...}
public Row height(int height) {...}
...
}

public enum RowAlign {...}

public class RowItem {...}

Quite straightforward, wasn't it? However, this API suffered from several problems (don't forget that was a kind of prototype at that time):
  1. Some public classes (RowItem, RowAlign) should have been hidden because they were implementation details. By exposing these classes, you make them part of the API, preventing potential future changes in the implementation (don't forget that when designing an API, you should strive to make it as good as can be, but also as narrow as possible, because it will be hard to change afterwards -without breaking compatibility).
  2. Many public methods from DesignGridLayout and Row classes should have been hidden because they served no purpose to the user of the library, once again they were implementation details that were unintentionally added to the API, leading to a crippled API (in the sense that all those useless methods appear and are proposed by IDE code-completion feature)
  3. This API made it possible to change the type of a row as many times as you want (by calling e.g. layout.row().grid().left().right().center().add(...);)
  4. You could add a label to a non-grid row (which was not supported by the implementation) so this could give wrong impressions to users of the library
  5. You could add a component with a column span > 1 in a non-grid row (not supported)
  6. You could specify the height of any row (which is not advised for a proper look). Usage of the height(int) method was intended as in layout.row().height(18) (no component added to the row).
To summarize the main issues with this API:
  • many methods or classes should not be public (they should be either private or package-private)
  • the API allows code that is not supported at runtime and may give the code writer/reader a false idea of how the UI looks like
In the next installment, I will talk about the API changes in version 0.9 (current official version of DesignGridLayout).

That's all for today!

Sunday, October 26, 2008

DesignGridLayout: final 1.0 soon released! Or not?

Today, I have finished fixing the last of the high priority issues of DesignGridLayout.
All fixes are already available on Subversion trunk, and also as snapshots there (note: it is not available through maven).

The past 3 weeks were quite tough in terms of issues fixing, with a lot of refactoring, improvements, and clean-up.

Now I am quite happy with the current standpoint about new features:
  • "smart vertical resize" (already discussed in this post)
  • automatic support of right-to-left orientation Locales (discussed there)
  • smarter -and simpler- API for empty rows (no need to specify a number of pixels: that's one step toward resolution independence)
  • better minimum size management (previously, DesignGridLayout would calculate a minimum size equal to the preferred size of the Container, but during very narrow resize, it would shrink each component to ridiculously small widths)
  • automatic call to setLayout() from DesignGridLayout constructor
  • last, but not least, smart support for forms with multiple label columns (this required some extension of the "spirit" of canonical grids, rising to the concept of "canonical sub-grids")
The concept of "multi-label grids" (or "canonical sub-grids" as I often name them) still needs to be introduced on the project web site (or on this blog) but that should not be a very heavy task.

So I consider it is soon time for an official 1.0 release!

Or is it?

I mean, there are still several open issues (enhancements only, not bugs), some of which might be worth having fixed in the 1.0 version. Of course, willing to fix these issues would then mean postponing the official 1.0 release (might be end November -or later, depending on how many issues we want solved- instead of early November).

So I ask the question here (I have also asked it there actually): what do you, DesignGridLayout users, prefer?
  1. Version 1.0 in about one week, all current issues open solved only in later versions?
  2. Version 1.0 with more issues closed (which ones?), but released later (between mid November and end December, depending on which issues to solve)
If you don't know, then I'll choose on my own (but I have to admit that I am not sure about what I prefer).

For the time being, even if you don't want to answer this question, you can always check the latest snapshot and report problems if you find any. Also, some of the new API method names I have chosen may be argued (I lacked inspiration for some), so don't hesitate to comment also on these (but please suggest a replacement for the new methods which name you don't like).

Enjoy!

Thursday, October 09, 2008

DesignGridLayout: real-time resizing of JScrollPane

Hi,

In my quest to solve the issue #5 of DesignGridLayout (namely: "Layout does not allow additional height usage after resize"), I have created a special dialog for testing my fixes for this issue. You can see a snapshot below (at default -ie preferred- size).


In this sample, I have put several rows, containing various kinds of components, of which some should be given extra height when the user resizes the dialog (eg JTable), and some should never grow taller than their preferred size (eg JTextField).

I could find out that there are only 2 categories of components that want extra height when it becomes available:
  • any Component that is set as the view of a JScrollPane (in particular JTextArea, JTable, JList)
  • any JSlider using JSlider.VERTICAL policy
Besides these, I did not find any component that should grow height when its embedding dialog is resized.

Based on these observations, I have implemented a simple internal mechanism into DesignGridLayout for distinguishing these 2 kinds of components:
interface HeightGrowPolicy
{
/**
* Checks if a {@link Component} can grow in height.
* @param component the component to test
* @return {@code true} if {@code component} has a variable height;
* {@code false} if {@code component} has a fixed height.
*/
public boolean canGrowHeight(Component component);
}
This interface is implemented by several classes:
  • HeightGrowPolicyMapper (allows to map different Component classes to their own specific HeightGrowPolicy),
  • JScrollPaneHeightGrowPolicy (special implementation for JScrollPane),
  • JSliderHeightGrowPolicy (special implementation for JSlider)
The mechanism itself is easily extensible because I can easily add further policies for other kinds of Components.

My first working prototype for issue #5 was roughly that simple (of course I also had to make some little changes in a few existing classes of DesignGridLayout library).

But I was not fully satisfied with it. The main reason for this was that when you extend the height of the dialog, all resizable components get a few pixels more for their height, which is absolutely ugly for a JTable, a JList or a JTextArea, because these components would then start to show, on the bottom side, some "incomplete" row or line of text.

Whenever I see this happening in a GUI application (be it made in Java or any other language) I generally get angry and immediately classify it in the category of "non professional" software.

So I inferred on some way to solve this: that is really the responsibility of a LayoutManager to make sure that whenever you resize a Container, all resized Components keep good-looking.

First I have extended the interface HeightGrowPolicy above:
interface HeightGrowPolicy
{
public boolean canGrowHeight(Component component);

/**
* Computes the maximum amount of extra height that a {@link Component} can
* use.
* @param component the component to test
* @param extraHeight the amount of available extra height
* @return the maximum amount of extra height that {@code component} can use
* without exceeding {@code extraHeight}
*/
public int computeExtraHeight(Component component, int extraHeight);
}
to give a chance to let DesignGridLayout know what amount of extra height a given Component will accept to keep its good look. This amount can be anything between 0 and extraHeight.

Here is the implementation for JScrollPane components:
class JScrollPaneHeightGrowPolicy implements HeightGrowPolicy
{
public boolean canGrowHeight(Component component)
{
return true;
}

public int computeExtraHeight(Component component, int extraHeight)
{
JScrollPane scroller = (JScrollPane) component;
int unit = scroller.getVerticalScrollBar().getUnitIncrement(+1);
if (unit <= 0)
{
return extraHeight;
}
else
{
// Return an integral number of units pixels
return (extraHeight / unit) * unit;
}
}
}

Simple isn't it? It makes use (behind the scenes) of the Scrollable interface that is most often implemented by components that are supposed to be used in JScrollPane (namely JList, JTable, JTextArea and JTree).

Provided that the preferred size of these components of your dialog is already good looking, then DesignGridLayout will ensure that they always stay this way. If you want to have proper preferred size for JTable, JList or JTextArea, you can use specific API of these components in your own code as in the following snippet:
JTextArea area = new JTextArea();
JTable table = new JTable();
JList list = new JList();

// area has preferred height to show exactly 3 lines of text
area.setRows(3);

// table has preferred height to show exactly 4 rows
int height = 4 * table.getRowHeight();
table.setPreferredScrollableViewportSize(new Dimension(PREF_WIDTH, height));

// list has preferred height to show exactly 2 items (ie 2 rows)
list.setVisibleRowCount(2);

You can try this on the Java Web Start enabled example.

Not bad!

But that's not the end of the story yet. If you play a bit with the example above, you'll see that between two actual resize of e.g. the first JTable component, the spacing between that row and the next is increasing, which is in contradiction with DesignGridLayout philosophy which promises to use the "ideal" inter-components spacing (according to the LAF/platform in use).

So I have made a second attempt, that you can experiment through this JWS example.

In this attempt, inter-components spacing is always preserved. However, to my viewpoint, resizing does not have a very good behavior (in real-time I mean):
  • first of all, the user does not "feel" that something is going on during the first pixels of his resizing action: the layout does not change at all! The user may just stop here and think that resize does not work!
  • second, a weird behavior is observed when the exact number of extra pixels is obtained during resize: all components below the first JTable seem to "hop" to a new position several dozens of pixels away from their previous one!
Hence I believe I will stick with the first solution and completely remove the second one. I could make it an option for the library users to choose, but I would not feel satisfied of giving such a possibility to create layouts with such a bad feel during resize.

What do you think?

If you want to look at DesignGridLayout code which snippets have been used in this post, you should check out the latest trunk from subversion.

Have fun!

Tuesday, September 30, 2008

Flash news: RTL support for DesignGridLayout

Tonight, I have just committed into Subversion the support of right-to-left orientation for DesignGridLayout. as a fix for issue #9.

Implementation was easier than foreseen, except for the test part itself (see below).

First of all, here are 2 screenshots with the same layout, but one uses LTR, the other uses RTL.





DesignGridLayout automatically discovers the orientation to be used for the container it is in charge of laying out, and simply inverts x coordinates if container is RTL-based.

Determining component orientation is based on the Component#getComponentOrientation() method which returns a ComponentOrientation instance. This instance determines the orientation of the text, it is not limited to horizontal LTR (eg English) and RTL (eg Arabic) languages, but also defines languages that are written vertically (all from top to bottom) and which "lines" (should we say "columns"?) are written from right to left (eg Chinese, Japanese) or left to right (eg Mongolian).

However, as far as I know, Swing components LAFs do not support components for vertical languages (I have never seen a vertical JLabel or JTextField), hence for these languages, we have to revert to the "usual" horizontal LTR orientation. For this, DesignGridLayout needs a simple trick to determine text orientation:
ComponentOrientation orientation = parent.getComponentOrientation();
boolean rtl = orientation.isHorizontal() && !orientation.isLeftToRight();

Just using orientation.isLeftToRight() is not sufficient because it would render eg Japanese horizontally from right to left, which I doubt any Japanese person can read (just imagine reading English from right to left!).

As mentioned above, implementing this enhancement was quite easy, but writing test cases for it was much more difficult than I expected.

My original idea for tests was just to set the default Locale to one of English, Arabic, Japanese or Mongolian (in order to cover the four possible situations defined in ComponentOrientation). But it turned out that just creating a new Locale through new Locale("JA") will work only if you have this Locale installed with your JRE, else a new Locale will be instantiated but it will be unusable; in particular, ComponentOrientation.getOrientation(Locale locale) will not return the expected value.

Since my JRE does not include Arabic, Hebrew, Japanese, Chinese and Mongolian, I had to find another way for testing. I chose to directly create a ComponentOrientation instance, but this is not possible since it has no public constructor and it is declared final! The only way to directly use ComponentOrientation is to use one of the 2 provided static instances LEFT_TO_RIGHT and RIGHT_TO_LEFT, which is what I did. But it prevented me from testing the new DesignGridLayout RTL support with vertical orientations.

Hence my call to DesignGridLayout users in Japan or China (for vertical right to left) and Mongolia (for vertical left to right) for testing the latest DesignGridLayout (in subversion trunk) with their respective Locale and send me a screenshot of their results. Thanks in advance!

That'll be all for today.

Sunday, September 28, 2008

Using DesignGridLayout 0.9 in Maven projects

Yesterday, I have released the latest official version of DesignGridLayout, almost 2 years after the previous official release (!), and 4 months after being handed over the project (Jason is still project owner but he is much too busy currently).

As mentioned in my previous post, lots of things have changed between these 2 versions.

Maybe one exciting news is that now, Maven users don't have to struggle to use DesignGridLayout in their projects. Indeed, version 0.9 is now uploaded to a maven repository accessible to anyone (no more need to manually install the jar into your local repository).

However, since I didn't use Maven "central" repository (the conditions for uploading an artifact there are too strict: all your dependencies must be present in central repo too), I wanted to briefly show how to use DesignGridLayout in your maven-based project.

DesignGridLayout artifacts have been uploaded to java.net maven repository. Hence you have to add this repository to your pom.xml (you may also add it to settings.xml if you prefer):
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2/</url>
<layout>default</layout>
</repository>
</repositories>

Now you just have to declare the dependency as any other artifact used by your project:
<dependencies>
<dependency>
<groupId>net.java.dev.designgridlayout</groupId>
<artifactId>designgridlayout</artifactId>
<version>0.9</version>
</dependency>
</dependencies>

And voila! You are now ready to use DesignGridLayout for your GUI project!

In addition to DesignGridLayout jar artifact, the repository also contains javadoc and source artifacts that you can automatically download, eg for your IDE (for eclipse, you can follow the instructions there).

Note that swing-layout.jar, the only dependency of DesignGridLayout, will be automatically downloaded to your local repository.

That's it for today, enjoy!

Thursday, September 25, 2008

DesignGridLayout news

Two months ago, I had announced my participation to the OSS project DesignGridLayout.

As a brief reminder, DesignGridLayout is a LayoutManager for Java Swing GUI, which main advantages are:
  • Good looking dialogs (alignment, spacing, sizing, visual balance): this is taken over by DesignGridLayout itself without any special hint from the developer
  • Reduced learning curve for developers, thanks to the fluent API which is, at the same time, simple, effective, compile-safe (no cryptic strings to express the layout) and IDE code-completion friendly
  • No graphics designer needed: the API is the graphical designer
  • Readability and maintainability: you can literally "visualize" the layout by browsing the code that sets it up; inserting a new row of components is done by inserting a new line of code in your layout setup code...
During the past 2 months, after struggling with the switch from CVS to SVN, I have finally finished taking the project over.

In the past few days, I have checked in the latest source code into SVN, and updated the web site.

Several things have changed in this project (as compared with previous 0.1.1 version):
  • License: the original GPL has been changed to ASL 2.0, much more open
  • Build: now the project uses maven 2 for the build and the site generation
  • Package: the old "zappini.designgridlayout" has been changed to a more standard "net.java.dev.designgridlayout"
  • Source code: it has been refactored to improve the API and ease future evolutions
  • Issues: all known bugs have been fixed
  • API: it has been improved on several points (more on this below) such as its narrowing (in order to prevent calls that have no effect, hence potentially pollute source code using DesignGridLayout), as well as the implementation of a few enhancements
  • Javadoc: has been completely rewritten and completed for all public API, along with examples in the package description
Improvements on the API consist essentially in using different interfaces for the different "rows" created by DesignGridLayout:
  • IRow
  • IGridRow extends IRow
  • INonGridRow extends IRow
These interfaces define the exact methods available to each kind of row. Once a row has been created, you cannot change its type (e.g. from Grid to Center) as you could before, which was useless, required more calls and led to some flaws in the API (e.g. what should happen if you set the row type to Grid, then back to Center; and why would you do that?).

Moreover, in DesignGridLayout, methods that create rows have been specialized to determine upfront which kind of row is to be created:
  • DesignGridLayout#row() creates a grid row (IGridRow)
  • DesignGridLayout#centerRow() creates a non grid row, with centered components (INonGridRow)
  • DesignGridLayout#leftRow() creates a non grid row, with left-aligned components (INonGridRow)
  • DesignGridLayout#rightRow() creates a non grid row, with right-aligned components (INonGridRow)
  • DesignGridLayout#emptyRow(int height) creates an empty row, with no component at all (used for introducing vertical spacing between rows)
Other changes in the API are:
  • Removal of IGridRow#label(String) to keep only IGridRow#label(JLabel): this was motivated by the fact a LayoutManager should not create components by itself (arguable opinion, I admit); in addition, this reduces one's options for GUI i18n (one option is to use Component#getName()) to set its text, which is impossible here, since the end-developer code can not get hold of the created JLabel)
  • Row.EMPTY "component" is replaced by IGridRow#empty() methods
  • New INonGridRow#fill() added to allow extreme components to take all remaining space in the row. This is particularly useful to split groups of rows with a label and a separator (as in Karsten Lentzsch FormLayout)
  • New IRow#addMulti() method to add several components in only one grid column, which is useful when you have components that should always "stick together" (eg a JSpinner and a JLabel indicating a unit of measure)
With all these changes, all constants and enum have been removed from the previous version because they serve no purpose now.

You can find the current snapshot (named "0.9-SNAPSHOT") of this version there.

So a further question is "when will the official 0.9 version be released?". That should be short now, we should expect an official release by mid October, including uploaded artifacts to some maven repository (for developers using maven).

What's the roadmap for 1.0?

There are a couple of enhancements requests submitted here.

The main enhancement planned for 1.0 will be the support for variable height rows that would get extra height during resize; this is particularly useful for rows that include components such as JList, JTable, or more generally any component wrapped in a JScrollPane.

Another interesting feature I would like in 1.0 release is components spanning several rows. This is a particularly useful feature and I know several users have been expecting it. The difficult part here will be to define the right API for that, in order to keep this feature easy to use, easy to visualize and safe (reducing potential errors at runtime by catching them at compile-time).

Finally, 1.0 release may include some attempts at right-to-left languages support. This will depend on several factors.

In any case, if you are a DesignGridLayout user or consider it for your next Swing GUI, please take your chance and participate in the discussions on the 3 issues above, so that DesignGridLayout can keep its spirit while bringing important features common in daily GUI design work.

Of course you can also suggest other enhancements and report bugs if you find any.

You can find more details on DesignGridLayout here.

Enjoy GUI design with DesignGridLayout!

Sunday, August 03, 2008

Dynamically loading plugins in Java (without OSGi)

In a previous post, I have discussed a simple solution to find dynamically find Guice Modules located in jar files, placed in a given directory but not initially part of an application classpath.

After some discussions, on the Guice mailing list, linked to that topic, I found that there had been some similar efforts to do the same (or almost the same); in particular, Java 6 brought the java.util.ServiceLoader class that does the same, except that the dynamic jar files discovery is left to you (by creating your own ClassLoader to include these jars).

Based on the discussions above, I have reworked my prototype to make it generic, thus independent of Guice (but usable with Gucie Modules of course).

This is what I'll present in this post today. Some code (or even entire classes) from my previous post have been reused.

The basic API for "service discovery" lies in the PluginDiscoveryManager interface:
public interface PluginDiscoveryManager
{
public <T> Iterable<T> getPluggedServices(Class<T> service);
}
Usage is quite straightforward once you got an implementation of that interface, you just need to call getPluggedServices() passing it the type of services you want to dynamically load:
PluginDiscoveryManager manager = ...;
for (IMyService service: manager.getPluggedServices(IMyService.class))
{
// Do something with service
service.doSomething();
}
My prototype has currently two implementations for PluginDiscoveryManager: ClassPathPluginDiscoveryManager and DirectoryPluginDiscoveryManager.

ClassPathPluginDiscoveryManager looks for services in jars that are already in the classpath, whereas DirectoryPluginDiscoveryManager will dynamically add all jars present in a given directory to the classpath before searching for services.

Let's take a look at ClassPathPluginDiscoveryManager first (I removed all exception handling code for clarity):
public class ClassPathPluginDiscoveryManager implements PluginDiscoveryManager
{
static private final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
static private final String MANIFEST_PLUGIN_ATTRIBUTE = "Plugins";

public ClassPathPluginDiscoveryManager(ClassLoader loader)
{
_loader = loader;
}

public <T> Iterable<T> getPluggedServices(Class<T> clazz)
{
List<T> services = new ArrayList<T>();
// Get all MANIFEST files in the classpath
Enumeration<URL> manifests = _loader.getResources(MANIFEST_PATH);
while (manifests.hasMoreElements())
{
addOnePluginServices(manifests.nextElement(), clazz, services);
}
return services;
}

private <T> void addOnePluginServices(
URL manifestUrl, Class<T> clazz, List<T> services)
{
InputStream input = null;
input = manifestUrl.openStream();
Manifest manifest = new Manifest(input);
String implementations =
manifest.getMainAttributes().getValue(MANIFEST_PLUGIN_ATTRIBUTE);
if (implementations != null)
{
for (String impl: implementations.split("[ \t]+"))
{
addOneService(impl, clazz, services);
}
}
}

private <T> void addOneService(
String implementation, Class<T> clazz, List<T> services)
{
Class<?> service = Class.forName(implementation, false, _loader);
// Check is service implements clazz
if (clazz.isAssignableFrom(service))
{
services.add(clazz.cast(service.newInstance()));
}
}

private final ClassLoader _loader;
}
As in my previous prototype with Guice, service implementation classes are discovered by checking a special attribute in the MANIFEST file:
Plugins: package1.MyService1Impl package2.MyService2Impl
What's new here is that you can mix different types of services in the attribute (with Guice, they all had to be Guice Modules). ClassPathPluginDiscoveryManager.getPluggedServices() will retain only implementations for the required service class.

Now DirectoryPluginDiscoveryManager is simply based on ClassPathPluginDiscoveryManager but creates a special ClassLoader to dynamically inspect the required directories:
public class DirectoryPluginDiscoveryManager extends ClassPathPluginDiscoveryManager
{
public DirectoryPluginDiscoveryManager(
boolean includeSubDirs, File... directories)
{
this(Thread.currentThread().getContextClassLoader(), includeSubDirs,
directories);
}

public DirectoryPluginDiscoveryManager(
ClassLoader parent, boolean includeSubDirs, File... directories)
{
super(ClassLoaderHelper.buildClassLoader(includeSubDirs, parent, directories));
}
}
All ClassLoader support is provided by the ClassLoaderHelper class, already presented in my previous post.

The main advantage of the solution presented here is its simplicity: PluginDiscoveryManager implementations are not complex, writing a supported plugin is very simple, just a few lines in your ant or maven configuration file, e.g.
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Plugins>package1.MyService1Impl package2.MyService2Impl</Plugins>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
in the maven pom.xml of one actual plugin project.

Additionally, this solution works with Java 5 or higher, no need to wait for JSR-277!

You can find the full code of this proto, along with javadoc, tests and maven configuration in this zip archive.

Thursday, July 31, 2008

Dynamically loading Guice modules as external plugins

Hi,

today I will present some experiments I am currently working on with Guice.

Nowadays, it is more and more common to see applications that users can customize simply by dropping "plugins" in some defined directory. A well-known example is Eclipse, but there are many such examples.

One way to realize this is to use an "OSGi container" and write plugins that obey OSGi specifications. But OSGi is quite a complex stuff (in particular if you intend to write an OSGi container).

While working with Guice, I felt the need to have the possibility to plugin new functionality to a Guice-based application by dropping jars containing classes implementing the Guice Module interface. But I did not want to depend on OSGi for this -quite simple- stuff.

So I have started some work on my own. The main problem for this kind of system is to discover all Modules that can be used to create a Guice Injector.

First of all, in order to permit several implementations, I have defined an interface for Module discovery:
public interface ModulesDiscoveryManager
{
public Iterable<Module> getModules();
}
In this post, I will show two implementations of this interface.

The first implementation, ClassPathModulesDiscoveryManager, is quite straight-forward but does not fulfill completely the initial requirements (but we will reuse this class for the other implementation class).
public class ClassPathModulesDiscoveryManager implements ModulesDiscoveryManager
{
static private final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
static private final String MANIFEST_GUICE_NAME = "Guice-Modules";

public ClassPathModulesDiscoveryManager()
{
this(Thread.currentThread().getContextClassLoader());
}

public ClassPathModulesDiscoveryManager(ClassLoader loader)
{
_logger.log(Level.INFO, "Loading all dynamic Guice Module");
try
{
// Get all MANIFEST files in the classpath
Enumeration<URL> manifests = loader.getResources(MANIFEST_PATH);
while (manifests.hasMoreElements())
{
addModule(loader, manifests.nextElement());
}
}
catch (IOException e)
{
// Should not happen
_logger.log(Level.SEVERE, "Could not add all dynamic Modules", e);
}
}

private void addModule(ClassLoader loader, URL manifestUrl)
{
InputStream input = null;
try
{
input = manifestUrl.openStream();
Manifest manifest = new Manifest(input);
String modules = manifest.getMainAttributes().getValue(MANIFEST_GUICE_NAME);
if (modules != null)
{
for (String module: modules.split("[ \t]+"))
{
addModule(loader, module);
}
}
}
catch (IOException e)
{
_logger.log(Level.SEVERE, "addModule problem with URL: " + manifestUrl, e);
}
finally
{
if (input != null)
{
close(input);
}
}
}

private void addModule(ClassLoader loader, String module)
{
try
{
Class<?> clazz = Class.forName(module, true, loader);
_modules.add((Module) clazz.newInstance());
_logger.log(Level.INFO, "Guice Module %s dynamically added.", module);
}
catch (Exception e)
{
_logger.log(Level.SEVERE, "addModule problem with: " + module, e);
}
}

public Iterable<Module> getModules()
{
return _modules;
}

static final private Logger _logger =
Logger.getLogger(ClassPathModulesDiscoveryManager.class.getName());
private final List<Module> _modules = new ArrayList<Module>();
}
Note that the snippet above does not include the obvious close() method.
The main idea for discovering classes implementing Module is to require some little help from the plugins developers: add a special attribute in the "META-INF/MANIFEST.MF" file of every jar to define the Module class names (full name, including package) included in that jar:
Guice-Modules: package1.MyModule1 package2.MyModule2
ClassPathModulesDiscoveryManager implementation is quite straightforward, given a ClassLoader, the constructor searches for all META-INF/MANIFEST.MF resources, then looks for a "Guice-Modules" attribute, extracts each Module class (separated by spaces) and finally loads each class with the provided ClassLoader and instantiates it (it must have a public no-arg constructor).

Now comes the second implementation of the ClassPathModulesDiscoveryManager interface, DirectoryModulesDiscoveryManager; this one is a subclass of ClassPathModulesDiscoveryManager.
public class DirectoryModulesDiscoveryManager extends ClassPathModulesDiscoveryManager
{
public DirectoryModulesDiscoveryManager(
boolean includeSubDirs, File... directories)
{
this(Thread.currentThread().getContextClassLoader(), includeSubDirs,
directories);
}

public DirectoryModulesDiscoveryManager(
ClassLoader parent, boolean includeSubDirs, File... directories)
{
super(ClassLoaderHelper.buildClassLoader(
Arrays.asList(directories), includeSubDirs, parent));
}
}
This class is quite simple, but most of its work is performed by another helper class. That helper is actually in charge of creating a new ClassLoader that will add all jars found in a given set of directories to the classpath:
final class ClassLoaderHelper
{
static ClassLoader buildClassLoader(
List<File> directories, boolean includeSubDirs)
{
return buildClassLoader(
directories, includeSubDirs, Thread.currentThread().getContextClassLoader());
}

static ClassLoader buildClassLoader(
List<File> directories, boolean includeSubDirs, ClassLoader parent)
{
List<URL> allJars = new ArrayList<URL>();
// Find all Jars in each directory
for (File dir: directories)
{
fillJarsList(allJars, dir, includeSubDirs);
}
return new URLClassLoader(allJars.toArray(new URL[allJars.size()]), parent);
}

static private void fillJarsList(List<URL> jars, File dir, boolean includeSubDirs)
{
try
{
for (File jar: dir.listFiles(_jarsFilter))
{
jars.add(jar.toURI().toURL());
}

if (includeSubDirs)
{
for (File subdir: dir.listFiles(_dirsFilter))
{
fillJarsList(jars, subdir, true);
}
}
}
catch (Exception e)
{
// Should not happen
}
}

static final private FileFilter _jarsFilter = new FileFilter() {...};
static final private FileFilter _dirsFilter = new FileFilter() {...};
}
The snippet above doesn't show the obvious FileFilters used to traverse directories and search for jar files.

The main bits are in:
  • fillJarsList(): add all jar files to a List (after converting these files into URLs)
  • buildClassLoader(): new URLClassLoader(allJars.toArray(new URL[allJars.size()]), parent);
That's all there is to it!

Now you can build Guice-based applications with Guice-friendly plugins! Just do that in your main entry point method:
ModulesDiscoveryManager manager = new DirectoryModulesDiscoveryManager(
true, new File("SomePluginDirectory"));
Guice.createInjector(manager.getModules());
You can find a complete maven project with tests in this zip. The tests need 4 simple plugins that are built by 4 sub-projects.
To use the source code, just unzip the file somewhere on your disk, it will create a "plugins" directory that contains a maven pom.xml. Type mvn install there and it will create all plugins used by the tests, then the main "manager" project that contains the source code presented in this post.

Eventually, this code may be part of my future Guice-GUI framework which shall be released by the end of August.

Enjoy!

Sunday, July 27, 2008

New OSS project responsibility!

About one month ago, I have been assigned owner of the DesignGridLayout project.
I was very happy for that because:
  1. I like this project a lot (since the first time I knew it, in November 2007)
  2. But it seemed to have been dormant for more than one year
Most of the few issues open on the project were posted by me, hence I had to fix them by myself in the past.

Recently, I thought about completely refactoring DesignGridLayout source code, which I did, in order to improve its API furthermore and make it easier to enhance later on.

Since I have several improvement ideas, I have then contacted the curren project owner to ask him if he was interested in my work. His answer was that unfortunately he is much too busy to keep the project living and thus he suggested that I could become owner of the project and keep it alive! That's how it all happened!

From then on, I have started to take hold of the project and suggest important changes:
  • more open license (GPL currently)
  • upload to maven2 repository
  • switching from CVS to Subversion
  • API improvements
I truly believe that DesignGridLayout is a revolutionary LayoutManager for Swing applications but unfortunately, there was not much advertising about it so far.

If you are fed up of standard Swing layouts (GridBagLayout in particular;-)) and you find 3rd party OSS layouts a bit steep to learn, then you should definately take a look at DesignGridLayout, it is worth your (little) time!

I'll blog more about DesignGridLayout soon after I have released a new version including the whole refactored source code.

How to intercept Guice instance creation?

In this first post, I'll talk about some of my experiments with Google Guice Dependency Injection library.

I am using Guice 1.0 although 2.0 is planned for release this summer (but I prefer to wait and see...) Anyway, the problem I'm going to talk about seems not to be addressed in the future Guice 2.0 either (although the solution might be simpler later in 2.0, we'll see).

Guice is a very good library for Dependency Injection, it is heavily depending on Java 5 Generics thus ensuring very high confidence in your code robustness at compile-time. It is also using annotations a lot.

Guice API is quite simple for the end user, however it may be considered too simple, considering a few missing features that look fundamental.

As an example, I have a use case where I need to process annotations (not Guice annotations, but other unrelated annotations) to any class instance created and injected by Guice. How can I do that? Guice 1.0 (and 2.0 as far as I understand) provides no "hook" system that would enable me to be notified whenever a new instance has been created and injected by Guice Injector. But that's exactly what one needs to be able to add the necessary annotation processing code for all new instances created by Guice!

So how can we do that?

After several experiments, I think I have found one way, not perfect (I'll explain why later), but still acceptable, that works fine with Guice 1.0 without any hack.

I call this way "Guice creation hooks through Scope interception". Wait a minute! Guice does not provide any way to intercept Scope methods! Correct, in Guice, Scopes are not first-class citizen and cannot be injected, intercepted... That seems quite natural however (just imagine a Scope that can be scoped!).

So how do we intercept Scope methods then? Well, there are not too many possibilities: you have to create your own Scopes, and have them delegate to real Scopes (like Scopes.SINGLETON which is one of the few standard Scopes that Guice brings out of the box).
Creating a new Scope means creating a class implementing the Scope interface, and defining an Annotation for it. Since we are creating Scopes that wrap other Scopes, we'll have to define one new Annotation for each Scope we want to wrap (that's the most boring part).

In my example, I'll define only 2 annotations used to define 2 scopes that respectively wrap Singleton and Guice default scope (which has no specific name):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ScopeAnnotation
public @interface HookedSingleton
{
}
And exactly the same for the "no scope" annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ScopeAnnotation
public @interface HookedDefaultScope
{
}
Unfortunately I couldn't find better names for these annotations:-(

Now let's see the implementation of the class implementing Scope:
public class HookedScope implements Scope
{
private final Scope _wrappedScope;
private final List<CreationHook> _hooks = new ArrayList<CreationHook>();

public HookedScope(Scope scope)
{
_wrappedScope = scope;
}

public void addHook(CreationHook hook)
{
_hooks.add(hook);
}

public <T> Provider<T> scope(Key<T> key, final Provider<T> creator)
{
Provider<T> hookedCreator = new Provider<T>()
{
public T get()
{
// Actually create a new T instance
T instance = creator.get();
// Add processing to newly created instance
for (CreationHook hook: _hooks)
{
hook.process(instance);
}
return instance;
}
};
// Make sure that we are called back whenever an instance is actually
// created by unscoped
if (_wrappedScope != null)
{
return _wrappedScope.scope(key, hookedCreator);
}
else
{
return hookedCreator;
}
}
}
This class has to be instantiated once for every scope used in the application (you want to hook ALL objects instantiation, don't you?)

This class works as follows:
First it stores the actual Scope that it is wrapping.
Then, when its scope() method is called, it is passed (creator, 2nd argument) a special Provider that is the one Provider that unconditionnally instantiates a T. Thus if we just decorate creator, and pass it along to the real Scope that we wrap, the decorated Provider will be called every time a new creation is about to happen. Our decorated Provider (named hookedCreator) first simply calls the real Guice creator, then it passes the newly created instance to any hook that is registered to it (through its addHook method).

Hooks just have to implement the CreationHook interface below:
public interface CreationHook
{
public <T> void process(T instance);
}
Now the next step is to actually instantiate our HookedScopes and bind them to their
respective annotations, this is done in a Guice Module:
public class ScopeModule extends AbstractModule
{
static HookedScope SINGLETON = new HookedScope(Scopes.SINGLETON);
static HookedScope NO_SCOPE = new HookedScope(null);

@Override protected void configure()
{
bindScope(HookedSingleton.class, SINGLETON);
bindScope(HookedDefaultScope.class, NO_SCOPE);
}
}
And you pass this module when you create the Guice Injector:
    Injector injector = Guice.createInjector(new ScopeModule(), ...);
Note the static SINGLETON and NO_SCOPE defined above, they will be needed so that you can
add your hooks when you need:
    CreationHook hook = ...;
ScopeModule.SINGLETON.addHook(hook);
ScopeModule.NO_SCOPE.addHook(hook);
As you can see, this leads to a new problem: you will have to add your hooks to each individual
HookedScope that you have created; in the example above that's only 2 scopes, but you may have more in your application!

Thus, a better way is to implement the whole logic of creating the HookedScopes and adding hooks in one single helper class:
final public class GuiceHookHelper
{
static private final Map<Scope, Scope> _allScopes =
new HashMap<Scope, Scope>();
static private final List<CreationHook> _hooks =
new ArrayList<CreationHook>();

// Use the following improved scopes in bind()...in(scope) instead
// of Scoped.SINGLETON or no scope at all.
static final public Scope SINGLETON = hook(Scopes.SINGLETON);
static final public Scope NO_SCOPE = hook(null);

static public Scope hook(Scope wrappedScope)
{
Scope result = _allScopes.get(wrappedScope);
if (result == null)
{
result = new HookedScope(wrappedScope);
_allScopes.put(wrappedScope, result);
}
return result;
}

static public void addHook(CreationHook hook)
{
_hooks.add(hook);
}

static public void removeHook(CreationHook hook)
{
_hooks.remove(hook);
}

static private class HookedScope implements Scope
{
private final Scope _wrappedScope;

private HookedScope(Scope scope)
{
_wrappedScope = scope;
}

public <T> Provider<T> scope(Key<T> key, final Provider<T> creator)
{
Provider<T> hookedCreator = new Provider<T>()
{
public T get()
{
// Actually create a new T instance
T instance = creator.get();
// Add processing to newly created instance
for (CreationHook hook: _hooks)
{
hook.process(instance);
}
return instance;
}
};
// Make sure that we are called back whenever an instance is actually
// created by unscoped
if (_wrappedScope != null)
{
return _wrappedScope.scope(key, hookedCreator);
}
else
{
return hookedCreator;
}
}
}
}
Now this class centralizes all HookedScopes and all hooks, hiding all internals from the end user.
With this class, it is now easy to wrap other scopes and add hooks.

The main drawback of this approach is that you cannot use standard Guice scope annotations (@Singleton or no annotation at all) for scoping your classes. You have to use new annotations, even for the default scope! Compared with what it brings, I think this drawback is not that big.

These classes will be part of my future Guice-GUI framework (to be released this summer on SourceForge) where adding annotation processing to Guice instantiated classes makes sense when you want to use EventBus with its annotations for instance (common in GUI development).

For the time being, you can find the complete source code for the experiments presented in this post, a maven pom.xml and eclipse project settings here. Just unzip it and either import it into eclipse or install it with maven. The project includes a couple of unit tests (with TestNG).

Enjoy!