Coverage Report - org.openpermis.editor.policy.MacAdapter
 
Classes in this File Line Coverage Branch Coverage Complexity
MacAdapter
0%
0/53
0%
0/20
2
MacAdapter$1
0%
0/6
N/A
2
MacAdapter$EventHandlerAdapter
0%
0/17
0%
0/10
2
MacAdapter$NopAction
0%
0/2
N/A
2
 
 1  
 /*
 2  
  * Copyright (c) 2009, Swiss Federal Department of Defence Civil Protection and Sport
 3  
  *                     (http://www.vbs.admin.ch)
 4  
  * Copyright (c) 2009, Ergon Informatik AG (http://www.ergon.ch)
 5  
  * All rights reserved.
 6  
  *
 7  
  * Licensed under the Open Permis License which accompanies this distribution,
 8  
  * and is available at http://www.openpermis.org/BSDlicenceKent.txt
 9  
  */
 10  
 package org.openpermis.editor.policy;
 11  
 
 12  
 import java.awt.Image;
 13  
 import java.awt.event.ActionEvent;
 14  
 import java.lang.reflect.InvocationHandler;
 15  
 import java.lang.reflect.Method;
 16  
 import java.lang.reflect.Proxy;
 17  
 
 18  
 import javax.swing.AbstractAction;
 19  
 import javax.swing.Action;
 20  
 
 21  
 import org.slf4j.Logger;
 22  
 import org.slf4j.LoggerFactory;
 23  
 
 24  
 /**
 25  
  * Adapter that maps Mac OS X specific system functions to Swing actions.
 26  
  * @since 0.1.0
 27  
  */
 28  0
 public final class MacAdapter {
 29  
 
 30  
         //---- Static
 31  
 
 32  
         /**
 33  
          * The logger object of this class.
 34  
          * @since 0.1.0
 35  
          */
 36  0
         private static final Logger LOGGER = 
 37  
                 LoggerFactory.getLogger(Application.class);
 38  
 
 39  
         /**
 40  
          * Checks if the application is running on Mac OS X.
 41  
          * @return <code>true</code> if the application is running on Mac OS X, 
 42  
          * <code>false</code> otherwise.
 43  
          * @since 0.1.0
 44  
          */
 45  
         public static boolean isMac () {
 46  0
                 final String osName = System.getProperty("os.name");
 47  0
                 return osName != null && osName.toLowerCase().startsWith("mac os x");
 48  
         }
 49  
 
 50  
         /**
 51  
          * Sets the Mac OS X application name and enables the application menu bar.
 52  
          * <p>Does nothing if the application is not running on Mac OS X.</p>
 53  
          * @note Has to be called prior to initializing AWT.
 54  
          * @param name the application name to set.
 55  
          * @since 0.1.0
 56  
          */
 57  
         public static void initialize (String name) {
 58  0
                 if (MacAdapter.isMac()) {
 59  0
                         System.setProperty("com.apple.mrj.application.apple.menu.about.name", name);
 60  0
                         System.setProperty("apple.laf.useScreenMenuBar", "true");
 61  
                 }
 62  0
         }
 63  
 
 64  
         //---- State
 65  
 
 66  
         /**
 67  
          * The EAWT application instance.
 68  
          * @see #createApplicationInstance()
 69  
          * @since 0.1.0
 70  
          */
 71  
         private final Object application;
 72  
 
 73  
         //---- Constructors
 74  
 
 75  
         /**
 76  
          * Creates a new mac adapter that is only active if running on a mac.
 77  
          * @since 0.1.0
 78  
          */
 79  0
         public MacAdapter () {
 80  0
                 this.application = isMac() ? createApplicationInstance() : null;
 81  0
         }
 82  
 
 83  
         //---- Methods
 84  
 
 85  
         /**
 86  
          * Creates an instance of an apple application object.
 87  
          * @return the application instance requested or <code>null</code> if we are not running
 88  
          * on Mac OS X or the current OS does not support Apple EAWT.
 89  
          * @since 0.1.0
 90  
          */
 91  
         private Object createApplicationInstance () {
 92  
                 try {
 93  0
                         final Class<?> applicationClass = Class.forName("com.apple.eawt.Application");
 94  0
                         return applicationClass.getConstructor((Class[]) null).newInstance((Object[]) null);
 95  0
                 } catch (Exception e) {
 96  0
                         LOGGER.warn("Could not initialize Apple EAWT.", e);
 97  0
                         return null;
 98  
                 }
 99  
         }
 100  
 
 101  
         /**
 102  
          * Checks if the adapter is active.
 103  
          * @return <code>true</code> if the adapter is active, <code>false</code> otherwise.
 104  
          * @since 0.1.0
 105  
          */
 106  
         private boolean isActive () {
 107  0
                 return this.application != null;
 108  
         }
 109  
 
 110  
         /**
 111  
          * Enables or disables an application feature.
 112  
          * @param method the method to call to enable or disable the feature.
 113  
          * @param enabled <code>true</code> to enable the feature, <code>false</code> to disable it.
 114  
          * @since 0.1.0
 115  
          */
 116  
         private void setEnabled (String method, boolean enabled) {
 117  
                 try {
 118  0
                         final Method enableAboutMethod = this.application.getClass().getDeclaredMethod(
 119  
                                 method, 
 120  
                                 new Class[] { boolean.class }
 121  
                         );
 122  0
                         enableAboutMethod.invoke(
 123  
                                 this.application, 
 124  
                                 new Object[] { Boolean.valueOf(enabled) }
 125  
                         );
 126  0
                 } catch (Exception e) {
 127  0
                         LOGGER.warn("Could not enable feature [" + method + "].", e);
 128  0
                 }
 129  0
         }
 130  
 
 131  
         /**
 132  
          * Registers an EAWT handler for the specified application method and action to execute.
 133  
          * @param adapter the adapter to register for EAWT events.
 134  
          * @since 0.1.0
 135  
          */
 136  
         private void register (EventHandlerAdapter adapter) {
 137  
                 try {
 138  0
                         final Class<?> applicationListenerClass = 
 139  
                                 Class.forName("com.apple.eawt.ApplicationListener");
 140  0
                         final Method addListenerMethod = this.application.getClass().getDeclaredMethod(
 141  
                                 "addApplicationListener", new Class[] { applicationListenerClass }
 142  
                         );
 143  0
                         final Object proxy = Proxy.newProxyInstance(
 144  
                                 getClass().getClassLoader(), 
 145  
                                 new Class[] { applicationListenerClass }, 
 146  
                                 adapter
 147  
 
 148  
                         );
 149  0
                         addListenerMethod.invoke(this.application, new Object[] { proxy });
 150  0
                         LOGGER.debug("Application listener for [{}] registered.", adapter.getSignature());
 151  0
                 } catch (Exception e) {
 152  0
                         LOGGER.warn("Could not register EAWT handler for [" + adapter.getSignature() + "].", e);
 153  0
                 }
 154  0
         }
 155  
 
 156  
         /**
 157  
          * Returns an action for use when registering handlers.
 158  
          * @param action the preferred action to execute.
 159  
          * @return the action to use for registering handlers, never <code>null</code>.
 160  
          * @since 0.1.0
 161  
          */
 162  
         private final Action getAction (Action action) {
 163  0
                 return action == null ? new NopAction() : action;
 164  
         }
 165  
 
 166  
         /**
 167  
          * Registers an action for the application quit command.
 168  
          * @param action the action to execute, <code>null</code> to disable handling.
 169  
          * @since 0.1.0
 170  
          */
 171  
         public void registerQuitAction (Action action) {
 172  0
                 if (!isActive()) {
 173  0
                         return;
 174  
                 }
 175  0
                 register(new EventHandlerAdapter("handleQuit", getAction(action)));
 176  0
         }
 177  
 
 178  
         /**
 179  
          * Registers an action for the application about menu item.
 180  
          * @param action the action to execute, <code>null</code> to disable handling.
 181  
          * @since 0.1.0
 182  
          */
 183  
         public void registerAboutAction (Action action) {
 184  0
                 if (!isActive()) {
 185  0
                         return;
 186  
                 }
 187  0
                 register(new EventHandlerAdapter("handleAbout", getAction(action)));
 188  0
                 setEnabled("setEnabledAboutMenu", action != null);
 189  0
         }
 190  
 
 191  
         /**
 192  
          * Registers an action for the application preferences menu item.
 193  
          * @param action the action to execute, <code>null</code> to disable handling.
 194  
          * @since 0.1.0
 195  
          */
 196  
         public void registerPreferencesAction (Action action) {
 197  0
                 register(new EventHandlerAdapter("handlePreferences", getAction(action)));
 198  0
                 setEnabled("setEnabledPreferencesMenu", action != null);
 199  0
         }
 200  
 
 201  
         /**
 202  
          * Registers an action for events that prompt the application to open a file.
 203  
          * <p>The file to be opened is passed in to the action handler as source of the
 204  
          * action event in form of a {@link String}.</p>
 205  
          * @param action the action to register.
 206  
          * @since 0.1.0
 207  
          */
 208  
         public void registerOpenFileAction (Action action) {
 209  0
                 register(
 210  
                         new EventHandlerAdapter("handleOpenFile", getAction(action)) {
 211  
                                 @Override
 212  0
                                 protected Object getEventSource (Object appleEvent) {
 213  
                                         try {
 214  0
                                                 final Method getFilenameMethod = 
 215  
                                                         appleEvent.getClass().getDeclaredMethod("getFilename", (Class[]) null);
 216  0
                                                 return getFilenameMethod.invoke(appleEvent, (Object[]) null);
 217  0
                                         } catch (Exception e) {
 218  0
                                                 LOGGER.warn("Could not retrieve file for event [" + appleEvent + "].", e);
 219  
                                         }
 220  0
                                         return super.getEventSource(appleEvent);
 221  
                                 }
 222  
                         }
 223  
                 );
 224  0
         }
 225  
 
 226  
         /**
 227  
          * Sets the image displayed on the dock.
 228  
          * @param image the image to use as dock image.
 229  
          * @since 0.1.0
 230  
          */
 231  
         public void setDockImage (Image image) {
 232  
                 try {
 233  0
                         final Method setDockIconImage = this.application.getClass().getDeclaredMethod(
 234  
                                 "setDockIconImage", 
 235  
                                 new Class[] { Image.class }
 236  
                         );
 237  0
                         setDockIconImage.invoke(
 238  
                                 this.application, 
 239  
                                 new Object[] { image }
 240  
                         );
 241  0
                 } catch (Exception e) {
 242  0
                         LOGGER.warn("Could not set dock icon image.", e);
 243  0
                 }
 244  
 
 245  0
         }
 246  
 
 247  
         //---- NopAction
 248  
 
 249  
         /**
 250  
          * Action that does nothing.
 251  
          * @since 0.1.0
 252  
          */
 253  0
         public static class NopAction
 254  
         extends AbstractAction
 255  
         {
 256  
 
 257  
                 //---- Static
 258  
 
 259  
                 /**
 260  
                  * @since 0.1.0
 261  
                  */
 262  
                 private static final long serialVersionUID = 4325289902247752534L;
 263  
 
 264  
                 //---- Action
 265  
 
 266  
                 /**
 267  
                  * @since 0.1.0
 268  
                  */
 269  
                 public void actionPerformed (ActionEvent e) {
 270  
                         // Nop.
 271  0
                 }
 272  
 
 273  
         }
 274  
 
 275  
         //---- EventHandlerAdapter
 276  
 
 277  
         /**
 278  
          * Adapter for EAWT event handlers.
 279  
          * @since 0.1.0
 280  
          */
 281  
         public static class EventHandlerAdapter
 282  
         implements InvocationHandler
 283  
         {
 284  
 
 285  
                 //---- State
 286  
 
 287  
                 /**
 288  
                  * The action to invoke.
 289  
                  * @since 0.1.0
 290  
                  */
 291  
                 private final Action action;
 292  
 
 293  
                 /**
 294  
                  * The signature of the method proxied.
 295  
                  * @since 0.1.0
 296  
                  */
 297  
                 private final String signature;
 298  
 
 299  
                 //---- Constructors
 300  
 
 301  
                 /**
 302  
                  * Creates an event handler adapter that calls an action on invocation.
 303  
                  * @param action the action to call if a method is invoked.
 304  
                  * @param signature the signature of the method invoked.
 305  
                  * @since 0.1.0
 306  
                  */
 307  0
                 public EventHandlerAdapter (String signature, Action action) {
 308  0
                         this.action = action;
 309  0
                         this.signature = signature;
 310  0
                 }
 311  
 
 312  
                 //---- Methods
 313  
 
 314  
                 /**
 315  
                  * Returns the signature of the event this proxy handles.
 316  
                  * @return the signature of the event this proxy handles.
 317  
                  * @since 0.1.0
 318  
                  */
 319  
                 public String getSignature () {
 320  0
                         return this.signature;
 321  
                 }
 322  
 
 323  
                 /**
 324  
                  * Checks if the invocation method and arguments match those of the event handler proxied.
 325  
                  * @param method the method to invoke.
 326  
                  * @param args the arguments of the invocation.
 327  
                  * @return <code>true</code> if the signature matches the event handler proxied,
 328  
                  * <code>false</code> otherwise.
 329  
                  * @since 0.1.0
 330  
                  */
 331  
                 private boolean checkSignature (Method method, Object[] args) {
 332  0
                         return 
 333  
                         this.signature != null &&
 334  
                         this.signature.equals(method.getName()) && 
 335  
                         args.length == 1;
 336  
                 }
 337  
 
 338  
                 //---- Method
 339  
 
 340  
                 /**
 341  
                  * Derives the action event source from the specified apple event.
 342  
                  * @param appleEvent the apple event object received at the handler.
 343  
                  * @return the action event source to use when invoking the target action.
 344  
                  * @since 0.1.0
 345  
                  */
 346  
                 protected Object getEventSource (Object appleEvent) {
 347  0
                         return appleEvent;
 348  
                 }
 349  
 
 350  
                 //---- InvocationHandler
 351  
 
 352  
                 /**
 353  
                  * Invokes the action that proxies this EAWT event handler and sets the event handled.
 354  
                  * @param proxy the proxy instance that the method was invoked on, ignored.
 355  
                  * @param method the method instance corresponding to the interface method invoked
 356  
                  * on the proxy instance, checked against the signature.
 357  
                  * @param args an array of objects containing the values of the arguments passed in the
 358  
                  * method invocation on the proxy instance, checked for a single event object.
 359  
                  * @return <code>null</code> always.
 360  
                  * @since 0.1.0
 361  
                  */
 362  
                 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
 363  0
                         if (checkSignature(method, args)) {
 364  0
                                 final Object appleEvent = args[0];
 365  0
                                 this.action.actionPerformed(
 366  
                                         new ActionEvent(
 367  
                                                 getEventSource(appleEvent), 
 368  
                                                 ActionEvent.ACTION_PERFORMED, 
 369  
                                                 this.signature
 370  
                                         )
 371  
                                 );
 372  0
                                 if (appleEvent != null) {
 373  
                                         try {
 374  0
                                                 final Method setHandledMethod = appleEvent.getClass().getDeclaredMethod(
 375  
                                                         "setHandled", new Class[] { boolean.class }
 376  
                                                 );
 377  0
                                                 setHandledMethod.invoke(appleEvent, new Object[] { Boolean.TRUE });
 378  0
                                         } catch (Exception e) {
 379  0
                                                 LOGGER.warn("Could not handle EAWT event [" + appleEvent + "].", e);
 380  0
                                         }
 381  
                                 }
 382  
                         }
 383  0
                         return null;
 384  
                 }
 385  
 
 386  
         }
 387  
 
 388  
 }