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    }