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