001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2017 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 http://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 089public class OSXAdapter implements InvocationHandler { 090 091 protected Object targetObject; 092 protected Method targetMethod; 093 protected String proxySignature; 094 095 static Object macOSXApplication; 096 097 // Pass this method an Object and Method equipped to perform application shutdown logic 098 // The method passed should return a boolean stating whether or not the quit should occur 099 public static void setQuitHandler(Object target, Method quitHandler) { 100 setHandler(new OSXAdapter("handleQuit", target, quitHandler)); 101 } 102 103 // Pass this method an Object and Method equipped to display application info 104 // They will be called when the About menu item is selected from the application menu 105 public static void setAboutHandler(Object target, Method aboutHandler) { 106 boolean enableAboutMenu = (target != null && aboutHandler != null); 107 if (enableAboutMenu) { 108 setHandler(new OSXAdapter("handleAbout", target, aboutHandler)); 109 } 110 // If we're setting a handler, enable the About menu item by calling 111 // com.apple.eawt.Application reflectively 112 try { 113 Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { boolean.class }); 114 enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enableAboutMenu) }); 115 } catch (Exception ex) { 116 System.err.println("OSXAdapter could not access the About Menu"); 117 ex.printStackTrace(); 118 } 119 } 120 121 // Pass this method an Object and a Method equipped to display application options 122 // They will be called when the Preferences menu item is selected from the application menu 123 public static void setPreferencesHandler(Object target, Method prefsHandler) { 124 boolean enablePrefsMenu = (target != null && prefsHandler != null); 125 if (enablePrefsMenu) { 126 setHandler(new OSXAdapter("handlePreferences", target, prefsHandler)); 127 } 128 // If we're setting a handler, enable the Preferences menu item by calling 129 // com.apple.eawt.Application reflectively 130 try { 131 Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class }); 132 enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enablePrefsMenu) }); 133 } catch (Exception ex) { 134 System.err.println("OSXAdapter could not access the About Menu"); 135 ex.printStackTrace(); 136 } 137 } 138 139 // Pass this method an Object and a Method equipped to handle document events from the Finder 140 // Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the 141 // application bundle's Info.plist 142 public static void setFileHandler(Object target, Method fileHandler) { 143 setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) { 144 // Override OSXAdapter.callTarget to send information on the 145 // file to be opened 146 public boolean callTarget(Object appleEvent) { 147 if (appleEvent != null) { 148 try { 149 Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null); 150 String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[])null); 151 this.targetMethod.invoke(this.targetObject, new Object[] { filename }); 152 } catch (Exception ex) { 153 154 } 155 } 156 return true; 157 } 158 }); 159 } 160 161 // setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener 162 public static void setHandler(OSXAdapter adapter) { 163 try { 164 Class applicationClass = Class.forName("com.apple.eawt.Application"); 165 if (macOSXApplication == null) { 166 macOSXApplication = applicationClass.getConstructor((Class[])null).newInstance((Object[])null); 167 } 168 Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener"); 169 Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { applicationListenerClass }); 170 // Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener 171 Object osxAdapterProxy = Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[] { applicationListenerClass }, adapter); 172 addListenerMethod.invoke(macOSXApplication, new Object[] { osxAdapterProxy }); 173 } catch (ClassNotFoundException cnfe) { 174 System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")"); 175 } catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods 176 System.err.println("Mac OS X Adapter could not talk to EAWT:"); 177 ex.printStackTrace(); 178 } 179 } 180 181 // Each OSXAdapter has the name of the EAWT method it intends to listen for (handleAbout, for example), 182 // the Object that will ultimately perform the task, and the Method to be called on that Object 183 protected OSXAdapter(String proxySignature, Object target, Method handler) { 184 this.proxySignature = proxySignature; 185 this.targetObject = target; 186 this.targetMethod = handler; 187 } 188 189 // Override this method to perform any operations on the event 190 // that comes with the various callbacks 191 // See setFileHandler above for an example 192 public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException { 193 Object result = targetMethod.invoke(targetObject, (Object[])null); 194 if (result == null) { 195 return true; 196 } 197 return Boolean.valueOf(result.toString()).booleanValue(); 198 } 199 200 // InvocationHandler implementation 201 // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked 202 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { 203 if (isCorrectMethod(method, args)) { 204 boolean handled = callTarget(args[0]); 205 setApplicationEventHandled(args[0], handled); 206 } 207 // All of the ApplicationListener methods are void; return null regardless of what happens 208 return null; 209 } 210 211 // Compare the method that was called to the intended method when the OSXAdapter instance was created 212 // (e.g. handleAbout, handleQuit, handleOpenFile, etc.) 213 protected boolean isCorrectMethod(Method method, Object[] args) { 214 return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1); 215 } 216 217 // It is important to mark the ApplicationEvent as handled and cancel the default behavior 218 // This method checks for a boolean result from the proxy method and sets the event accordingly 219 protected void setApplicationEventHandled(Object event, boolean handled) { 220 if (event != null) { 221 try { 222 Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class }); 223 // If the target method returns a boolean, use that as a hint 224 setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) }); 225 } catch (Exception ex) { 226 System.err.println("OSXAdapter was unable to handle an ApplicationEvent: " + event); 227 ex.printStackTrace(); 228 } 229 } 230 } 231}