Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I call an OS X-specific method for my cross-platform Jar?

I have a compiled executable JAR file that fails on Windows platforms.

The reason for this is because I want to properly integrate certain OS X-specific properties — such as the About window.

Even though I specifically cordoned off the code using a conditional, the JAR is still crashing with a NoClassDefFoundError on the very first line of execution.

if (isOSX()) {
    com.apple.eawt.Application application = com.apple.eawt.Application.getApplication();
    application.setAboutHandler(new com.apple.eawt.AboutHandler() {
        @Override
        public void handleAbout(com.apple.eawt.AppEvent.AboutEvent ae) {
            new AboutWindow();
        }
    });
    application.setDefaultMenuBar(MenuSystem.createMenu());
}

Is it possible to include this code in my JAR file so I can have one consistent codebase?

like image 516
Redandwhite Avatar asked Nov 15 '12 15:11

Redandwhite


People also ask

Are JAR files cross platform?

JAR (Java Archive) is a cross-platform file format based on the ZIP format, which can be used in Windows, Mac OSX, Linux, Android OS and more.

Is Java really cross platform?

Java is cross platform because a program's source code is compiled into an intermediate "bytecode" language. The bytecode is then executed by a Java Virtual Machine (Java interpreter) that was written for that particular hardware platform.


2 Answers

Using Brian's answer as a base, I was able to use the following code to set the Mac dock icon image using OS X specific methods. Unlike the other answer, this answer includes a complete example and does not have the problem of trying to cast the object to a OS X specific class object.

What the code does:

  1. Instead of importing the OS X specific library, this code uses Class.forName(String) to get the Class object associated with the OS X specific class. In this example, the com.apple.eawt.Application library is used to set the dock icon image.
  2. Reflection is then used to invoke the methods from the OS X specific class. getClass() is used to get the Application object, and getMethod().invoke() is used to call the getApplication() and setDockIconImage() methods.

I've included comments showing the code that would be normally used in a OS X exclusive program, to make it clear what the reflection code is replacing. (If you are creating a program that only needs to run on Mac, see How do you change the Dock Icon of a Java program? for the code needed to set the dock icon image.)

// Retrieve the Image object from the locally stored image file
// "frame" is the name of my JFrame variable, and "filename" is the name of the image file
Image image = Toolkit.getDefaultToolkit().getImage(frame.getClass().getResource(fileName));

try {
    // Replace: import com.apple.eawt.Application
    String className = "com.apple.eawt.Application";
    Class<?> cls = Class.forName(className);

    // Replace: Application application = Application.getApplication();
    Object application = cls.newInstance().getClass().getMethod("getApplication")
        .invoke(null);

    // Replace: application.setDockIconImage(image);
    application.getClass().getMethod("setDockIconImage", java.awt.Image.class)
        .invoke(application, image);
}
catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException
        | InvocationTargetException | NoSuchMethodException | SecurityException
        | InstantiationException e) {
    e.printStackTrace();
}

Although this code works, this method still seems to be a messy workaround, and it would be great if anyone has any other suggestions on how to include OS X specific code in a program used on Mac and Windows.

like image 121
Tot Zam Avatar answered Oct 20 '22 00:10

Tot Zam


Have you tried loading the surrounding class dynamically using Class.forName ?

Class.forName("com.myproject.ClassContainingApple");

This way you can refer to all your Apple-specific classes inside one class, and dynamically load it in your isOSX() branch. You have to do it this way since you're not able to load a class that refers to other unavailable classes - you'll have to determine if you're in OSX, and only then load up anything referring to OSX-only classes.

An extensible way to do this if you have more OS-specific requirements is to name your class after the OS and then load a classname based upon the detected OS. e.g. call your classes WindowsExtensions, OSXExtensions, LinuxExtensions etc. (you will have to look up the appropriate names - I'm just providing examples)

e.g. here's an example of usage:

String className = ""java.util.ArrayList";
Class cls = Class.forName(className);
List list = (List)cls.newInstance();
like image 44
Brian Agnew Avatar answered Oct 20 '22 01:10

Brian Agnew