001 /* 002 File: OSXAdapter.java 003 004 Abstract: Hooks existing preferences/about/quit functionality from an 005 existing Java app into handlers for the Mac OS X application menu. 006 Uses a Proxy object to dynamically implement the 007 com.apple.eawt.ApplicationListener interface and register it with the 008 com.apple.eawt.Application object. This allows the complete project 009 to be both built and run on any platform without any stubs or 010 placeholders. Useful for developers looking to implement Mac OS X 011 features while supporting multiple platforms with minimal impact. 012 013 Version: 2.0 014 015 Disclaimer: IMPORTANT: This Apple software is supplied to you by 016 Apple Inc. ("Apple") in consideration of your agreement to the 017 following terms, and your use, installation, modification or 018 redistribution of this Apple software constitutes acceptance of these 019 terms. If you do not agree with these terms, please do not use, 020 install, modify or redistribute this Apple software. 021 022 In consideration of your agreement to abide by the following terms, and 023 subject to these terms, Apple grants you a personal, non-exclusive 024 license, under Apple's copyrights in this original Apple software (the 025 "Apple Software"), to use, reproduce, modify and redistribute the Apple 026 Software, with or without modifications, in source and/or binary forms; 027 provided that if you redistribute the Apple Software in its entirety and 028 without modifications, you must retain this notice and the following 029 text and disclaimers in all such redistributions of the Apple Software. 030 Neither the name, trademarks, service marks or logos of Apple Inc. 031 may be used to endorse or promote products derived from the Apple 032 Software without specific prior written permission from Apple. Except 033 as expressly stated in this notice, no other rights or licenses, express 034 or implied, are granted by Apple herein, including but not limited to 035 any patent rights that may be infringed by your derivative works or by 036 other works in which the Apple Software may be incorporated. 037 038 The Apple Software is provided by Apple on an "AS IS" basis. APPLE 039 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 040 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 041 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 042 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 043 044 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 045 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 046 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 047 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 048 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 049 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 050 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 051 POSSIBILITY OF SUCH DAMAGE. 052 053 Copyright 2003-2007 Apple, Inc., All Rights Reserved 054 055 */ 056 057 package edu.wisc.ssec.mcidasv; 058 059 import java.lang.reflect.*; 060 import java.util.HashMap; 061 062 063 public class OSXAdapter implements InvocationHandler { 064 065 protected Object targetObject; 066 protected Method targetMethod; 067 protected String proxySignature; 068 069 static Object macOSXApplication; 070 071 // Pass this method an Object and Method equipped to perform application shutdown logic 072 // The method passed should return a boolean stating whether or not the quit should occur 073 public static void setQuitHandler(Object target, Method quitHandler) { 074 setHandler(new OSXAdapter("handleQuit", target, quitHandler)); 075 } 076 077 // Pass this method an Object and Method equipped to display application info 078 // They will be called when the About menu item is selected from the application menu 079 public static void setAboutHandler(Object target, Method aboutHandler) { 080 boolean enableAboutMenu = (target != null && aboutHandler != null); 081 if (enableAboutMenu) { 082 setHandler(new OSXAdapter("handleAbout", target, aboutHandler)); 083 } 084 // If we're setting a handler, enable the About menu item by calling 085 // com.apple.eawt.Application reflectively 086 try { 087 Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { boolean.class }); 088 enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enableAboutMenu) }); 089 } catch (Exception ex) { 090 System.err.println("OSXAdapter could not access the About Menu"); 091 ex.printStackTrace(); 092 } 093 } 094 095 // Pass this method an Object and a Method equipped to display application options 096 // They will be called when the Preferences menu item is selected from the application menu 097 public static void setPreferencesHandler(Object target, Method prefsHandler) { 098 boolean enablePrefsMenu = (target != null && prefsHandler != null); 099 if (enablePrefsMenu) { 100 setHandler(new OSXAdapter("handlePreferences", target, prefsHandler)); 101 } 102 // If we're setting a handler, enable the Preferences menu item by calling 103 // com.apple.eawt.Application reflectively 104 try { 105 Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class }); 106 enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enablePrefsMenu) }); 107 } catch (Exception ex) { 108 System.err.println("OSXAdapter could not access the About Menu"); 109 ex.printStackTrace(); 110 } 111 } 112 113 // Pass this method an Object and a Method equipped to handle document events from the Finder 114 // Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the 115 // application bundle's Info.plist 116 public static void setFileHandler(Object target, Method fileHandler) { 117 setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) { 118 // Override OSXAdapter.callTarget to send information on the 119 // file to be opened 120 public boolean callTarget(Object appleEvent) { 121 if (appleEvent != null) { 122 try { 123 Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null); 124 String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[])null); 125 this.targetMethod.invoke(this.targetObject, new Object[] { filename }); 126 } catch (Exception ex) { 127 128 } 129 } 130 return true; 131 } 132 }); 133 } 134 135 // setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener 136 public static void setHandler(OSXAdapter adapter) { 137 try { 138 Class applicationClass = Class.forName("com.apple.eawt.Application"); 139 if (macOSXApplication == null) { 140 macOSXApplication = applicationClass.getConstructor((Class[])null).newInstance((Object[])null); 141 } 142 Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); 143 Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { applicationListenerClass }); 144 // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener 145 Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[] { applicationListenerClass }, adapter); 146 addListenerMethod.invoke(macOSXApplication, new Object[] { osxAdapterProxy }); 147 } catch (ClassNotFoundException cnfe) { 148 System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")"); 149 } catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods 150 System.err.println("Mac OS X Adapter could not talk to EAWT:"); 151 ex.printStackTrace(); 152 } 153 } 154 155 // Each OSXAdapter has the name of the EAWT method it intends to listen for (handleAbout, for example), 156 // the Object that will ultimately perform the task, and the Method to be called on that Object 157 protected OSXAdapter(String proxySignature, Object target, Method handler) { 158 this.proxySignature = proxySignature; 159 this.targetObject = target; 160 this.targetMethod = handler; 161 } 162 163 // Override this method to perform any operations on the event 164 // that comes with the various callbacks 165 // See setFileHandler above for an example 166 public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException { 167 Object result = targetMethod.invoke(targetObject, (Object[])null); 168 if (result == null) { 169 return true; 170 } 171 return Boolean.valueOf(result.toString()).booleanValue(); 172 } 173 174 // InvocationHandler implementation 175 // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked 176 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { 177 if (isCorrectMethod(method, args)) { 178 boolean handled = callTarget(args[0]); 179 setApplicationEventHandled(args[0], handled); 180 } 181 // All of the ApplicationListener methods are void; return null regardless of what happens 182 return null; 183 } 184 185 // Compare the method that was called to the intended method when the OSXAdapter instance was created 186 // (e.g. handleAbout, handleQuit, handleOpenFile, etc.) 187 protected boolean isCorrectMethod(Method method, Object[] args) { 188 return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1); 189 } 190 191 // It is important to mark the ApplicationEvent as handled and cancel the default behavior 192 // This method checks for a boolean result from the proxy method and sets the event accordingly 193 protected void setApplicationEventHandled(Object event, boolean handled) { 194 if (event != null) { 195 try { 196 Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class }); 197 // If the target method returns a boolean, use that as a hint 198 setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) }); 199 } catch (Exception ex) { 200 System.err.println("OSXAdapter was unable to handle an ApplicationEvent: " + event); 201 ex.printStackTrace(); 202 } 203 } 204 } 205 }