Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
MacAdapter |
|
| 2.0;2 | ||||
MacAdapter$1 |
|
| 2.0;2 | ||||
MacAdapter$EventHandlerAdapter |
|
| 2.0;2 | ||||
MacAdapter$NopAction |
|
| 2.0;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 | } |