1

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?

jobukkit
  • 2,490
  • 8
  • 26
  • 41
  • are you sure you don't have a trailing parenthesis or curly bracket somewhere in your code (that you may have not copied over here?). To me it really looks like your code should work. You may as well printout the result of `System.getProperty("os.name")`... it surely looks like a really stupid typo or mistake in your original code. – zmo Jun 05 '13 at 17:09

1 Answers1

4

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:

Java 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.

Community
  • 1
  • 1
CodeBlind
  • 4,519
  • 1
  • 24
  • 36
  • Can you provide an example of doing that with Reflection? – jobukkit Jun 05 '13 at 20:28
  • I tried it for the 4th line with `Class c = Class.forName("com.apple.eawt.Application"); Method m = c.getMethod("getApplication"); Object appInstance = m.invoke(null); m = appInstance.getClass().getMethod("addApplicationListener", MacAboutAndQuit.class); m.invoke(appInstance, new MacAboutAndQuit());` but then I got `Exception in thread "main" java.lang.NoSuchMethodException: com.apple.eawt.Application.addApplicationListener(me.com_boy.jotext2.main.MacAboutAndQuit) at java.lang.Class.getMethod(Class.java:1607) at me.com_boy.jotext2.main.JotextMain.main(JotextMain.java:36)` on run. – jobukkit Jun 07 '13 at 13:54
  • I suspect this is because `addApplicationListener` is written for some interface that `MacAboutAndQuit` implements, most likely something like `ApplicationListener`. Replace `MacAboutAndQuit.class` with whatever base-level `class` or `interface` expected by that method and you should be fine. – CodeBlind Jun 07 '13 at 17:02