Reputation: 2670
I'm using the following code for integrating my Java app with Mac OS X:
if (System.getProperty("os.name").equals("Mac OS X"))
{
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText"); // Program name
com.apple.eawt.Application.getApplication().setDockIconImage(new javax.swing.ImageIcon( JotextMain.class.getResource("icons/Icon.png") ).getImage()); // Dock icon
System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
com.apple.eawt.Application.getApplication().addApplicationListener(new MacAboutAndQuit()); //Make "Quit" and "About" options work
}
On Mac OS X, it works fine. On Ubuntu however:
Exception in thread "main" java.lang.NoClassDefFoundError: com/apple/eawt/ApplicationListener
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2451)
at java.lang.Class.getMethod0(Class.java:2694)
at java.lang.Class.getMethod(Class.java:1622)
at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:494)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:486)
Caused by: java.lang.ClassNotFoundException: com.apple.eawt.ApplicationListener
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
... 6 more
When I comment out that code, it launches fine on Ubuntu. Strange, because it checks for Mac OS X first before executing Mac OS X specific operations. I don't have Windows, but friend of me using Windows also reported my .jar didn't launch and gives a "A Java Exception has occured" message from the Java Virtual Machine Launcher instead. On a Windows PC at school, same problem.
What's causing this?
Upvotes: 1
Views: 2158
Reputation: 4569
The second line of your if
block is the problem (as well as the fourth). Even though your code doesn't ever call that function, the JVM still has to try to load the com.apple.eawt.Application
class at initialization of whatever class contains that if
statement, simply because of the reference to it. When you're not on Mac, that class doesn't exist, hence the error.
A workaround would be to use Reflection to make that function call. This should prevent the class loader from trying to load that class until the call is actually made.
Example
javax.swing.ImageIcon icon = ...
Class c = Class.forName("com.apple.eawt.Application");
Method m = c.getMethod("getApplication");
Object applicationInstance = m.invoke(null);
m = applicationInstance.getClass().getMethod("setDockIconImage", javax.swing.ImageIcon.class);
m.invoke(applicationInstance,icon);
This is the Reflection equivalent of that second line in your if
block. There's a lot about this that flies in the face of good development practice - you won't get compiler warnings if class/method names change, but it does allow you to make calls on specific types without directly referring to them in your source code. This prevents the class loader from loading those OS-specific classes until you're actually inside the if
statement.
To clean this up a bit, you could make a special class that handles all of the Mac-OS specific stuff, wrap up that functionality in a single method, then call that method with Reflection like this example - this will save you one of the Method m = ...
-styled calls.
Reflection Caveats
Aside from the fact that this code is ugly and not easy to maintain, Reflection is VERY slow. It's best used in cases like this where you might be initializing something once for the duration of your program. Typically, Reflection is something you want to stay away from for performance reasons, if not for the maintainability issue.
Read this for more info on Reflection performance:
A more-elegant solution
Rather than replace everything inside that if
block with Reflection code, I would create an interface, like the following:
interface SpecificPlatform{
void initialize(javax.swing.ImageIcon icon);
}
Then, I'd implement it for Mac:
class MacOSX implements SpecificPlatform{
@Override
public void initialize(javax.swing.ImageIcon icon){
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText");
Application.getApplication().setDockIconImage(new javax.swing.ImageIcon(
JotextMain.class.getResource("icons/Icon.png") ).getImage()); // Dock icon
System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
Application.getApplication().addApplicationListener(new MacAboutAndQuit());
}
}
Now... change your original code to something like this:
javax.swing.ImageIcon icon = ...
SpecificPlatform sp = null;
if (System.getProperty("os.name").equals("Mac OS X"))
sp = (SpecificPlatform)(Class.forName("pkg.name.MacOSX").getConstructor().
newInstance());
}
//Check for other OS-specific classes here ...
if(sp != null) sp.initialize(icon);
Now you only have one ugly Reflection call per OS, instead of making a whole bunch of ugly Reflection calls for every OS-specific method call you have to make.
Upvotes: 4