The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov

Kirill Grouchnikov's Blog

Porting small library from Java 5.0 to Java 1.4 - could it be any harder?

Posted by kirillcool on July 15, 2005 at 11:45 AM | Comments (4)

And so, last evening I set out to provide 1.4-compatible version of my Substance look-and-feel library. What promised to be an easy task (a couple of changes per file, given not too excessive use of generics), turned out to be a frustrating and valuable experience not only with Swing classes, but with Java 5.0 features in general.

First off, I should say that at work we write for JDK 1.4.2. For me, last year that amounted to roughly 2600 hours spent on writing 1.4.2-compliant code. At home (for a few projects here on java.net) it's been 5.0 from the beginning. Last year that amounted to roughly 700 hours spent on using 5.0 features. Not claiming to know my way through all new features in 5.0 (there were way too many hands raised on Joshua Bloch's "Collections Connection" when he asked to see who knows everything in 5.0), I feel comfortable with all new language features. Contrary to what some claim (he looks suspiciously familiar), generics in the "light" version (no wildcards, no "super" or "extends" etc.) make the code look better and more robust. As a collection guru, you may think that nobody can forget that exact value type stored in some map, but what about your average programmer who left last week and whose bugs you need to fix? Your best hope would be that he documented the correct types of keys and values in javadoc, otherwise you would either hunt all the puts, or debug until your fingers are blue and ready to fall off.

And so, i thought. 77 files, 440KB of code. How hard can it be? It shouldn't even matter how many files or how many lines of code. Just take Retroweaver, add ant task and lean back. The "lean back" part ended abruptly, seeing that i have used quite a handful of methods that were new in 5.0 (which i used). Here lies the main deception of Retroweaver - its abilities are very limited. It can handle generics, enhanced for loops, enums, iterables, but that's about it. A class that's new in 5.0, or a new method on existing class - you're on your own. And that certainly discourages the use of Retroweaver - I moved to 5.0 not only because of the language features. I want to use new stuff like Formatter, Scanner, concurrency, ExecutorService and even String.replace that gets two CharSequences as parameters. And so, the first big disappointment was - if you want to port your library back to 1.4, you can't use any 5.0 new stuff. That sounds obvious, but not so if you start writing a new library straight in 5.0.

The next step was to see the functions that were not present in 1.4 and replace them with the corresponding counterparts from 1.4. That sounds easy, but when you extend functionality of the existing Java classes, it's not. For look-and-feel, you extend the existing UI delegates and plug-in your stuff. The stuff you plug should involve only drawing, so that couldn't change much between different JDK versions, right? Right and wrong. It doesn't change much, but a lot of stuff gets exposed in later JDK versions that was private in the previous ones. One of the best examples here would be createScrollButton in TabbedPaneUI. When you set your tabbed pane to work in SCROLL_TAB_LAYOUT mode, the tabs that overflow the available space are not visible, and can be "scrolled to" using two small arrow buttons. The default implementation draws the button on its own, ignoring the installed ButtonUI, so you would want to override this behaviour to provide consistent look and feel to all the buttons. In 5.0 it's very easy - you are override createScrollButton() function, it's called and then Swing plugs in all the listeners on those buttons. In 1.4 - big surprise. Pretty much everything in BaseTabbedPaneUI is private, the buttons, the listener, the layout. And everything is not only wired, but also hardcoded for the class names. And you can't make your button inherit from the inner button - its class is private. What you can do - the good old "copy paste". That's what is done in JGoodies - its TabbedPaneUI is a complete rip-off (with a few tweaks to plug its own buttons), spanning 3092 lines. My original 5.0 version of TabbedPaneUI is 235 lines. And that brought the second disappointment - there are very good reasons to use new functions. That sounds obvious too, but yet again if you start out with new functionality and then force yourself to go back, you'll have to implement it yourself.

At this point, the next step was obvious - the code must be rewritten. But should it still be in 5.0? That's a tough question. You can integrate Retroweaver in your ant script and run it everytime you change something. You can pay close attention to javadoc comments of every single function that you intend to use. You can set your compiler to 1.4 settings, but it will not catch use of new 5.0 functions. Or, you can just take your code and backport it to 1.4 yourself. After all, you are not using any new stuff (apart from generics and enums), and you are not burdened with all the bookkeeping.

A few clicks in Eclipse, and the project was configured to 1.4. Thirty seconds later, the build is over, and on 77 files I had 856 errors. About 30% were on generics (both parametrized collections and missing casts on retrieving objects), 30% were on @Override (you use that a lot writing look-and-feel under 5.0), and 35% were on enums. The most unexpected part was for enums - you just get used to having name(), values() and using them in switch statements. The harsh 1.4 reality sets in, and even when you use Bloch's enum pattern, you still need to provide name(), values() and rewrite your switch statements. Couple of hours later - all the errors were gone (except the functions that were not available in 1.4), and the code was uglier than before, especially when you need to store Longs in a Map (autoboxing is nice), or iterating over entries in Set or array. The third disappointment set in during the two hours frantic coding - removing 5.0 language features takes more that it seems. Less obvious (maybe because it's sold as "syntactic change" only), but time-consuming nonetheless.

When the last errors were fixed, I run the test application that tests all component UIs. Instantly i was greated with the long despised "use getRootPane().add()" message. Quick fix, the application runs, but:
  • background of menu and menu items is dark gray instead of nice light gray. The same for combobox.
  • default button behaviour is transferred to the clicked button.
  • the buttons are not "rollover-enabled"
  • dialog root pane is not opaque
  • selected items in list are not highlighted
  • sliders have background for ticks
  • progress bar is off by 1 pixel in both width and height
  • root pane header is off by 1 pixel in width
All these were either fixed or corrected for 5.0. Now i have to introduce "bug fixes" myself. That's when the last disappointment sets in - each new JDK version fixes existing bugs in older versions. Obvious as well, but quite unexpected when all you want is to run your library in older JDK and just want it to show the same behaviour.

With all of the above, twenty four hours later, the library has not been ported back, but a few lessons have been learned.

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Another informative blog. Thanks Kirill.
    When I saw your kb vs number of files I just had to work the numbers, I estimated about 300 lines of code on average. :D
    Are they pretty much evenly spread (ie some small, some big, some in the middle), or do you have a more skewed distribution? Eg lots of small classes and lots of big ones?
    I ask because when I'm 'in the OO zone' I find I tend to create lots of smallish 'helper' classes and tag interfaces. Which I think makes my average class quite small. But then if you're building a reasonably complex GUI that can take heaps of lines of code just declaring and laying the components out, so the swing classes tend to be the heavyweights.
    Do you use annotations much? If so, how hard is it to convert back?

    Posted by: rickcarson on July 18, 2005 at 06:39 PM

  • Rick,
    All the files are under 9Kb (and spread evenly), and then i have 5 heavyweights (4 UI classes that Swing doesn't allow to override properly, so you pretty much have to write everything from the scratch, and one image creator class that takes care of all images). The "heavyweights" are 12, 21, 33, 33 and 79 KB.
    Last thing abou the file size distibution. I think it doesn't really matter if you have a bunch of small files or few big files. It only depends on how many 5.0 features you use.

    Posted by: kirillcool on July 18, 2005 at 11:13 PM

  • Hi Kirill,

    I'm the author of this "complete rip-off" in JGoodies. The buttons was a nice add-on. The main reason for it was to solve the painting problems in SCROLL_TAB_LAYOUT mode. The current implementation of BasicTabbedPaneUI is not able to handle properly the overlapped tabs like in JGoodies.

    Best regards,
    Andrej Golovnin

    Posted by: golovnin on July 19, 2005 at 12:56 AM

  • Andrej,
    The same happened to me in RootPane UI delegate. They do not expose the same functions as in InternalFrame UI (createNorthPane), so i basically had to take Swing implementation and change a lot of stuff to create my own header panel (with real buttons that provide all rollover effects, unlike what you get in setDecorated).

    Posted by: kirillcool on July 19, 2005 at 01:00 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds