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