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 }