001    /*
002     * Created on Jul 19, 2008
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2008-2010 the original author or authors.
014     */
015    package org.fest.swing.core;
016    
017    import static java.awt.AWTEvent.KEY_EVENT_MASK;
018    import static java.awt.event.InputEvent.CTRL_MASK;
019    import static java.awt.event.InputEvent.SHIFT_MASK;
020    import static java.awt.event.KeyEvent.KEY_PRESSED;
021    import static java.awt.event.KeyEvent.VK_A;
022    import static org.fest.swing.core.InputModifiers.modifiersMatch;
023    import static org.fest.swing.core.InputModifiers.unify;
024    
025    import java.awt.AWTEvent;
026    import java.awt.Toolkit;
027    import java.awt.event.AWTEventListener;
028    import java.awt.event.KeyEvent;
029    
030    import org.fest.util.VisibleForTesting;
031    
032    /**
033     * Understands an escape valve for users to abort a running FEST-Swing test by pressing 'Ctrl + Shift + A'. The key
034     * combination to use to abort test is configurable through the method
035     * <code>{@link EmergencyAbortListener#keyCombination(KeyPressInfo)}</code>.
036     * <p>
037     * The following example shows to use this listener in a TestNG test:
038     * <pre>
039     * private EmergencyAbortListener listener;
040     *
041     *
042     * &#64;BeforeMethod public void setUp() {
043     *   // set up your test fixture.
044     *   listener = EmergencyAbortListener.registerInToolkit();
045     * }
046     *
047     * &#64;AfterMethod public void tearDown() {
048     *   // clean up resources.
049     *   listener.unregister();
050     * }
051     * </pre>
052     * </p>
053     * <p>
054     * Changing the default key combination for aborting test:
055     * <pre>
056     * listener = EmergencyAbortListener.registerInToolkit().{@link EmergencyAbortListener#keyCombination(KeyPressInfo) keyCombination}(key(VK_C).modifiers(SHIFT_MASK));
057     * </pre>
058     * </p>
059     *
060     * @author <a href="mailto:simeon.fitch@mseedsoft.com">Simeon H.K. Fitch</a>
061     * @author Alex Ruiz
062     */
063    public class EmergencyAbortListener implements AWTEventListener {
064    
065      private static final long EVENT_MASK = KEY_EVENT_MASK;
066    
067      private final Toolkit toolkit;
068      private final TestTerminator testTerminator;
069    
070      private int keyCode = VK_A;
071      private int modifiers = unify(CTRL_MASK, SHIFT_MASK);
072    
073      /**
074       * Attaches a new instance of <code>{@link EmergencyAbortListener}</code> in the given <code>{@link Toolkit}</code>.
075       * Any other instances of <code>EmergencyAbortListener</code> will be removed from the <code>Toolkit</code>.
076       * @return the created listener.
077       */
078      public static EmergencyAbortListener registerInToolkit() {
079        EmergencyAbortListener listener = new EmergencyAbortListener(Toolkit.getDefaultToolkit());
080        listener.register();
081        return listener;
082      }
083    
084      @VisibleForTesting
085      EmergencyAbortListener(Toolkit toolkit) {
086        this(toolkit, new TestTerminator());
087      }
088    
089      @VisibleForTesting
090      EmergencyAbortListener(Toolkit toolkit, TestTerminator testTerminator) {
091        this.testTerminator = testTerminator;
092        this.toolkit = toolkit;
093      }
094    
095      @VisibleForTesting
096      void register() {
097        removePrevious();
098        toolkit.addAWTEventListener(this, EVENT_MASK);
099      }
100    
101      private void removePrevious() {
102        AWTEventListener[] listeners = toolkit.getAWTEventListeners(EVENT_MASK);
103        for (AWTEventListener listener : listeners)
104          if (listener instanceof EmergencyAbortListener) toolkit.removeAWTEventListener(listener);
105      }
106    
107      /**
108       * Sets the key combination that will terminate any FEST-Swing test. The default combination is 'Ctrl + Shift + A'.
109       * @param keyPressInfo contains information about the key code and modifiers.
110       * @return this listener.
111       * @throws NullPointerException if the <code>KeyPressInfo</code> is <code>null</code>.
112       */
113      public EmergencyAbortListener keyCombination(KeyPressInfo keyPressInfo) {
114        if (keyPressInfo == null) throw new NullPointerException("KeyPressInfo should not be null");
115        keyCode = keyPressInfo.keyCode();
116        modifiers = unify(keyPressInfo.modifiers());
117        return this;
118      }
119    
120      /**
121       * Removes this listener from the <code>{@link Toolkit}</code> this listener is attached to.
122       */
123      public void unregister() {
124        toolkit.removeAWTEventListener(this);
125      }
126    
127      /**
128       * Inspects key events for the key combination that should terminate any running FEST-Swing tests.
129       * @param event the event to inspect.
130       * @see java.awt.event.AWTEventListener#eventDispatched(java.awt.AWTEvent)
131       */
132      public void eventDispatched(AWTEvent event) {
133        if (event.getID() != KEY_PRESSED) return;
134        if (!(event instanceof KeyEvent)) return;
135        KeyEvent e = (KeyEvent) event;
136        if (e.getKeyCode() != keyCode) return;
137        if (!modifiersMatch(e, modifiers)) return;
138        testTerminator.terminateTests();
139      }
140    
141      @VisibleForTesting
142      int keyCode() { return keyCode; }
143    
144      @VisibleForTesting
145      int modifiers() { return modifiers; }
146    }