5

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?

Tot Zam
  • 8,406
  • 10
  • 51
  • 76
Redandwhite
  • 2,529
  • 4
  • 25
  • 43

3 Answers3

9

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.

Community
  • 1
  • 1
Tot Zam
  • 8,406
  • 10
  • 51
  • 76
  • 1
    The answer works fine. The problem is that with JDK 9, reflection is discouraged. I receive this : **WARNING: An illegal reflective access operation has occurred** I solved with this: [https://stackoverflow.com/a/29329228/6081475] – emish89 Oct 23 '17 at 09:43
5

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();
Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • 1
    Hi, Reflection is path I want to take. I have gained an instance of `application` by calling: `Object application = Class.forName("com.apple.eawt.Application").getMethod("getApplication").invoke(null);` Now, how can I call a method on the object? – Redandwhite Nov 15 '12 at 15:55
  • Just create an instance of Application via Class.newInstance() and call it as normal. You don't really need reflection here – Brian Agnew Nov 15 '12 at 16:13
  • one also must avoid to `import` OSX-specific classes if one wants to **build** the application on other platforms. – Andre Holzner Jan 18 '13 at 10:16
  • @BrianAgnew Using `Class.newInstance()` doesn't just work for classes that you can't import because you can't just cast the `cls.newInstance()` to a `com.apple.eawt.Application`. `(List)cls.newInstance();` only works because you are able to import the `List` class. The method stated in your answer, therefore does not just work. – Tot Zam May 05 '17 at 01:33
  • Note that you do this in a conditional branch and the class you load is precompiled on OSX. This does work. I've done this before – Brian Agnew May 05 '17 at 08:05
0

This is not possible the way you do it because java is loading all classes that a class (may) use during the instantiation of the class, so the named class is not available.

You could try to put the code in another class in your project and instantiate this class only if your are running on OSX.

If this does not work either you have to use reflection because then the VM only can detect this class when you load it via reflection.

HTH

Uwe Plonus
  • 9,803
  • 4
  • 41
  • 48
  • I'd like to try do it by Reflection. I have managed to get an Object named application that is of type `com.apple.eawt.Application`. How can I call a method on this Object, which is really an instance? – Redandwhite Nov 15 '12 at 15:51
  • Reflection only works once the class is actually loaded. You need to solve the classloading issue first. – rancidfishbreath Nov 15 '12 at 15:51
  • @rancidfishbreath `Class.forName()` IS reflection. I've said that reflection could be a way. – Uwe Plonus Nov 15 '12 at 15:52
  • @Redandwhite you can cast the object to the given type after creating it. – Uwe Plonus Nov 15 '12 at 15:54
  • @UwePlonus I stand corrected. Here is a [reference](http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html) for others showing he is correct. – rancidfishbreath Nov 15 '12 at 15:59
  • I am getting close, but I don't know how to do the override as I am doing in my code above. – Redandwhite Nov 15 '12 at 16:05
  • Try the other solution I described. Create a class, put the code in it and then instanciate this class only if you're running on OSX. Perhaps instanciate this class via reflection. – Uwe Plonus Nov 15 '12 at 17:14