Override JComponent.getBaselineResizeBehavior() but keep Java5 compatibility
Question
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?
Solution
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();
}
}
OTHER TIPS
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)
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.
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.