4

For my Swing project, I need to support both Java 5 and Java 6. I have defined a custom JComponent (call it Picture) and, after embedding it in a JScrollPane, I put it in a JPanel that uses DesignGridLayout manager.

DesignGridLayout supports baseline alignment thanks to swing-layout open source library (implements baseline support for Java 5 and provides compatibility with the new Java 6 baseline support).

My Picture class overrides public int getBaseline(int width, int height) so that I can define a correct baseline for it. Note that "override" is not completely correct: it overrides the method on Java6 but defines it in Java5.

When I run my sample app on Java5, everything is fine: the Picture baseline I have defined is correctly used.

However, when I use Java6, my Picture#getBaseline() method does not get called! And of course the baseline alignment of my picture is terrible (centered).

After checking in Java6 source, I have seen that, in BasicScrollPaneUI, getBaseline() calls first getBaselineResizeBehavior() on the viewport component (my Picture instance). And it will call getBaseline() only if getBaselineResizeBehavior() returns Component.BaselineResizeBehavior.CONSTANT_ASCENT.

Now my problem is that getBaselineResizeBehavior() is a Java6 method of JComponent that I cannot implement in Java5 because it returns an enum Component.BaselineResizeBehavior which does not exist in Java5.

So my question (finally) is: how can I implement (or simulate?) getBaselineResizeBehavior() so that my class can still compile and run in a Java5 environment?

jfpoilpret
  • 10,449
  • 2
  • 28
  • 32
  • Actually, what would be nice to have in javac is "conditional compilation" (like in C/C++). – jfpoilpret Jan 05 '09 at 00:28
  • Conditional compilation is not great - you then need two distributions. Reflection works much better for this (I have done it many times) - see my answer. – Lawrence Dol Jan 05 '09 at 00:41
  • Conditional compilation would be great if it was part of the language but that is another debate... Maybe AOP could help on that one (but I would like to avoid a heavy solution with heavy dependencies just for this "little" problem. – jfpoilpret Jan 05 '09 at 01:04

4 Answers4

2

how can I implement (or simulate?) getBaselineResizeBehavior() so that my class can still compile and run in a Java5 environment?

You cannot compile this method declaration with the Java 5 library because the type Component.BaselineResizeBehaviour does not exist:

public Component.BaselineResizeBehavior getBaselineResizeBehavior()

You must compile using Java 6. Your classes can still run on Java 5 if you compile to a 1.5 target, but you must take care that they handle absent types/methods gracefully. Add tests for these cases as you encounter them. Ensure developers attempt to run their code on Java 5 prior to check-in.

For example, this class...

public class MyPanel extends javax.swing.JPanel {

    public java.awt.Component.BaselineResizeBehavior getBaselineResizeBehavior() {
        return java.awt.Component.BaselineResizeBehavior.OTHER;
    }

    public static void main(String[] args) {
        new MyPanel();
        System.out.println("OK");
    }

}

...can be compiled and run as follows using the javac JDK compiler:

X:\fallback>javac -version
javac 1.6.0_05

X:\fallback>javac -target 1.5 MyPanel.java

X:\fallback>"C:\Program Files\Java\jre1.5.0_10\bin\java.exe" -cp . MyPanel
OK

All the popular IDEs offer options for generating older class versions. You can use reflection to test for the existence of methods/types at runtime when you need to make decisions about code paths.

Failure to set the target will result in errors like this:

Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version n
umber in .class file
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.security.SecureClassLoader.defineClass(Unknown Source)
    at java.net.URLClassLoader.defineClass(Unknown Source)
    at java.net.URLClassLoader.access$100(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClassInternal(Unknown Source)
McDowell
  • 107,573
  • 31
  • 204
  • 267
  • Interesting but it only partly answers the problem: I cannot compile it with Java5, I need Java6 with target 1.5. – jfpoilpret Jan 05 '09 at 13:27
  • Actually it doesn't work when using swing-layout.jar library: swing-layout uses reflection to find getBaseline() method, but to do so it loops through all methods of the class by calling Class.getMethods() which throws a NoClassDefFoundException due to the getBaselineResizeBehavior() method! – jfpoilpret Jan 05 '09 at 14:24
  • Unfortunate - combining this compile target approach with a Factory producer is probably the right way to go; unless you want to get into plugin mechanisms. – McDowell Jan 06 '09 at 11:34
2

I would make a subclass of Picture, perhaps called PictureJava6, which implemented the getBaselineResizeBehaviour(), and when creating instances of Picture, do:

public Component pictureFactory() {
    if(javaVersion > "1.6") {
        return new PictureJava6();
    } else {
        return new Picture();
    }
}
Rolf Rander
  • 3,221
  • 20
  • 21
  • Well although I don't like this method much, this is probably the only way to get what I want, although it requires me to compile with JDK6 (and target=1.5). I have just checked it it works. Thanks! – jfpoilpret Jan 05 '09 at 14:37
  • Well, the PictureJava6-class will obviously not compile without the java-6 library present. And it is reasonable (maybe not required?) that you need target=1.5 to be backward compatible, so I don't think you can get around this in any other way than making two separate versions compiled separately. – Rolf Rander Jan 06 '09 at 09:44
0

You can use reflection to try to get the CONSTANT_ASCENT return value by name. If it can't be reflected, you are J5, otherwise J6. This side-steps the explicit dependency and allows compilation to J5.

Foll. is an example of doing this for dialog modality:

try {
    Field  fld=Class.forName("java.awt.Dialog$ModalExclusionType").getField("TOOLKIT_EXCLUDE");
    Method mth=getClass().getMethod("setModalExclusionType",new Class[]{fld.getType()});
    mth.invoke(this,new Object[]{fld.get(null)});
    }
catch(Throwable thr) {
    log.errorln("Unable to configure window to be unaffected by modal dialogs - dialogs may need to be closed to operate help.");
    log.errorln("Use Java 6 or later to avoid modal dialogs conflicting with the help system.");
    log.errorln("Exception: "+thr);
    }

UPDATE: I originally posted code with the J5 code commented out; I've changed that because I realized it confuses the issue by implying that the J5 code would not work in J6 - it does.

Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
0

I think that the return type is not treated as part of the a method signature in resolving virtual functions and overloads; it might be that you can define your "overriding" method to return Object, and reflect out the return Enum per my first answer. Since you're compiling in J5 it won't be a compile time conflict, but the JVM should still choose your method to override... It might or it might throw a runtime exception. Still, it's worth a try.

For example:

public Object getBaselineResizeBehavior() {
    Object ret;
    // reflect out the return value
    return ret;
    }  

Any error handling can be System.out, purely for debugging, since this will not be invoked unless you are J6, so the reflection, correctly coded, should always work if invoked.

And I would, of course, comment this method to make it very clear what's going on.

Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
  • Thanks a lot, I'll give it a try tonight and report back here – jfpoilpret Jan 05 '09 at 02:50
  • Unfortunately this approach does not work, it compiles fine (with Java5) but when run under Java6, my method never gets called: it looks it is not recognized as overriding Component#getBaselineResizeBehavior(); the JVM probably checks the return type and thus calls the super-class method. – jfpoilpret Jan 05 '09 at 14:06
  • Ah well - that's too bad. No better idea has occurred to me, sorry this wasn't any help. – Lawrence Dol Jan 06 '09 at 01:50