Clover coverage report - clover
Coverage timestamp: Sat Oct 8 2005 22:54:17 EDT
file stats: LOC: 654   Methods: 24
NCLOC: 501   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
EventRecorder.java 67.9% 74.9% 75% 72.7%
coverage coverage
 1   
 package abbot.editor.recorder;
 2   
 
 3   
 import java.awt.*;
 4   
 import java.awt.dnd.*;
 5   
 import java.awt.event.*;
 6   
 import java.lang.reflect.*;
 7   
 import java.text.MessageFormat;
 8   
 import java.util.*;
 9   
 
 10   
 import javax.swing.*;
 11   
 
 12   
 import abbot.*;
 13   
 import abbot.i18n.Strings;
 14   
 import abbot.script.*;
 15   
 import abbot.script.Action;
 16   
 import abbot.script.Event;
 17   
 import abbot.script.Resolver;
 18   
 import abbot.tester.Robot;
 19   
 import abbot.util.AWT;
 20   
 
 21   
 /** 
 22   
  * Provides recording of raw AWT events and high-level semantic events.
 23   
  * This is the main controller for any SemanticRecorder objects.
 24   
  */
 25   
 
 26   
 // TODO: Are there other instances (cf JInternalFrame) where we'd like this
 27   
 // recorder to be the listener to other events?
 28   
 // TODO: add internal frame listener, re-post events when heard
 29   
 // TODO: discard events on LAF pieces of internal frames
 30   
 // TODO: extract filters as plugins
 31   
 // TODO: run all semantic recorder tests through this class, using canned
 32   
 // event streams instead of robot-generated events; keep the robot-generated
 33   
 // event streams, though, to test whether the stream has changed
 34   
 
 35   
 public class EventRecorder
 36   
     extends Recorder implements SemanticEvents {
 37   
     
 38   
     private static final String ANY_KEY = null;
 39   
     private static final int EITHER = 0;
 40   
     private static final int PRESS = 1;
 41   
     private static final int RELEASE = 2;
 42   
     private boolean captureMotion;
 43   
     private long lastStepTime;
 44   
     ArrayList steps = new ArrayList();
 45   
 
 46   
     /** Put all built-in recorder classes here.  Don't worry though, 'cause if
 47   
      * it doesn't get added here it'll get found dynamically.
 48   
      */
 49   
     private static final Class[] recorderClasses = {
 50   
         AbstractButton.class,
 51   
         Component.class,
 52   
         Container.class,
 53   
         Dialog.class,
 54   
         Frame.class,
 55   
         JComboBox.class,
 56   
         JComponent.class,
 57   
         JInternalFrame.class,
 58   
         JList.class,
 59   
         JMenuItem.class,
 60   
         JTabbedPane.class,
 61   
         JTable.class,
 62   
         JTree.class,
 63   
         Window.class,
 64   
     };
 65   
 
 66   
     /** Create a Recorder for use in capturing raw AWTEvents.  Indicate
 67   
      * whether mouse motion should be captured, and what semantic event type
 68   
      * to capture.
 69   
      */
 70  21
     public EventRecorder(Resolver resolver, boolean captureMotion) { 
 71  21
         super(resolver);
 72  21
         this.captureMotion = captureMotion;
 73   
         // Install existing semantic recorders
 74  21
         for (int i=0;i < recorderClasses.length;i++) {
 75  294
             getSemanticRecorder(recorderClasses[i]);
 76   
         }
 77   
     }
 78   
 
 79   
     /** Return the name of the type of GUI action to be recorded. */
 80  0
     public String toString() {
 81  0
         return captureMotion 
 82   
             ? Strings.get("actions.capture-all")
 83   
             : Strings.get("actions.capture");
 84   
     }
 85   
 
 86  0
     public void start() {
 87  0
         super.start();
 88  0
         steps.clear();
 89  0
         MessageFormat mf = new MessageFormat(Strings.get("RecordingX"));
 90  0
         setStatus(mf.format(new Object[] { toString() }));
 91  0
         lastStepTime = getLastEventTime();
 92   
     }
 93   
 
 94  353
     private boolean isKey(Step step, String code, int type) {
 95  353
         boolean match = false;
 96  353
         if (step instanceof Event) {
 97  148
             Event se = (Event)step;
 98  148
             match = "KeyEvent".equals(se.getType())
 99   
                 && (type == EITHER
 100   
                     || (type == PRESS
 101   
                         && "KEY_PRESSED".equals(se.getKind()))
 102   
                     || (type == RELEASE
 103   
                         && "KEY_RELEASED".equals(se.getKind())))
 104   
                 && (code == ANY_KEY
 105   
                     || code.equals(se.getAttribute(XMLConstants.TAG_KEYCODE)));
 106   
         }
 107  353
         return match;
 108   
     }
 109   
 
 110  116
     private boolean isKeyString(Step step) {
 111  116
         return (step instanceof Action)
 112   
             && ((Action)step).getMethodName().equals("actionKeyString");
 113   
     }
 114   
 
 115  62
     private boolean isKeyStroke(Step step, String keycode) {
 116  62
         if (step instanceof Action) {
 117  53
             Action action = (Action)step;
 118  53
             if (action.getMethodName().equals("actionKeyStroke")) {
 119  8
                 String[] args = action.getArguments();
 120  8
                 return (keycode == ANY_KEY
 121   
                         || (args.length > 1 && args[1].equals(keycode))
 122   
                         || (keycode.startsWith("VK_NUMPAD") 
 123   
                             && args[1].equals("VK_" + keycode.substring(9))));
 124   
             }
 125   
         }
 126  54
         return false;
 127   
     }
 128   
 
 129  17
     private void removeTerminalShift() {
 130   
         // Remove the terminal SHIFT keypress
 131  17
         if (steps.size() > 0) {
 132  17
             Step step = (Step)steps.get(steps.size()-1);
 133  17
             while (isKey(step, "VK_SHIFT", PRESS)) {
 134  0
                 steps.remove(step);
 135  0
                 if (steps.size() == 0)
 136  0
                     break;
 137  0
                 step = (Step)steps.get(steps.size()-1);
 138   
             }
 139   
         }
 140   
     }
 141   
 
 142   
     /** Eliminate redundant modifier keys surrounding keystrokes or
 143   
      * keystrings.
 144   
      */
 145  17
     private void removeExtraModifiers() {
 146  17
         setStatus("Removing extra modifiers");
 147  17
         for (int i=0;i < steps.size();i++) {
 148  67
             Step step = (Step)steps.get(i);
 149  67
             if (isKey(step, ANY_KEY, PRESS)) {
 150  10
                 Event se = (Event)step;
 151  10
                 String cs = se.getAttribute(XMLConstants.TAG_KEYCODE);
 152  10
                 int code = AWT.getKeyCode(cs);
 153  10
                 boolean remove = false;
 154  10
                 boolean foundKeyStroke = false;
 155  10
                 if (AWT.isModifier(code)) {
 156  21
                     for (int j=i+1;j < steps.size();j++) {
 157  21
                         Step next = (Step)steps.get(j);
 158  21
                         if (isKey(next, cs, RELEASE)) {
 159  6
                             if (foundKeyStroke) {
 160  5
                                 steps.remove(j);
 161  5
                                 remove = true;
 162   
                             }
 163  6
                             break;
 164   
                         }
 165  15
                         else if (isKeyStroke(next, ANY_KEY)
 166   
                                  || isKeyString(next)) {
 167  11
                             foundKeyStroke = true;
 168  11
                             remove = true;
 169   
                         }
 170  4
                         else if (!isKey(next, ANY_KEY, EITHER)) {
 171  4
                             break;
 172   
                         }
 173   
                     }
 174   
                 }
 175  10
                 if (remove) {
 176  5
                     steps.remove(i--);
 177   
                 }
 178   
             }
 179   
         }
 180   
     }
 181   
 
 182   
     /** Combine multiple keystroke actions into keystring actions. */
 183  17
     private void coalesceKeyStrings() {
 184  17
         setStatus("Coalescing key strings");
 185  17
         for (int i=0;i < steps.size();i++) {
 186  49
             Step step = (Step)steps.get(i);
 187  49
             if (isKeyString(step)) {
 188  7
                 int j = i;
 189  7
                 while (++j < steps.size()) {
 190  13
                     Step next = (Step)steps.get(j);
 191  13
                     if (isKeyString(next)) {
 192  13
                         Action action = (Action)step;
 193  13
                         String[] args1 = action.getArguments();
 194  13
                         String[] args2 = ((Action)next).getArguments();
 195  13
                         action.setArguments(new String[] {
 196   
                             args1[0], args1[1] + args2[1]
 197   
                         });
 198  13
                         setStatus("Joining '" + args1[1]
 199   
                                   + "' and '" + args2[1] + "'");
 200  13
                         steps.remove(j--);
 201   
                     }
 202   
                     else {
 203  0
                         setStatus("Next step is not a key string: " + next);
 204  0
                         break;
 205   
                     }
 206   
                 }
 207   
             }
 208   
         }
 209   
     }
 210   
 
 211   
     /** Eliminate redundant key press/release events surrounding a keytyped
 212   
      * event.  
 213   
      */
 214  17
     private void coalesceKeyEvents() {
 215  17
         setStatus("Coalescing key events");
 216  17
         for (int i=0;i < steps.size();i++) {
 217  95
             Step step = (Step)steps.get(i);
 218  95
             if (isKey(step, ANY_KEY, PRESS)) {
 219   
                 // In the case of modifiers, remove only if the presence of
 220   
                 // the key down/up is redundant.
 221  48
                 Event se = (Event)step;
 222  48
                 String cs = se.getAttribute(XMLConstants.TAG_KEYCODE);
 223  48
                 int code = AWT.getKeyCode(cs);
 224   
                 // OSX option modifier should be ignored, since it is used to
 225   
                 // generate input method events.
 226  48
                 boolean isOSXOption =
 227   
                     Platform.isOSX() && code == KeyEvent.VK_ALT;
 228  48
                 if (AWT.isModifier(code) && !isOSXOption)
 229  10
                     continue;
 230   
 
 231   
                 // In the case of non-modifier keys, walk the steps until we
 232   
                 // find the key release, then optionally replace the key press
 233   
                 // with a keystroke, or remove it if the keystroke was already
 234   
                 // recorded.  This sorts out jumbled key press/release events.
 235  38
                 boolean foundKeyStroke = false;
 236  38
                 boolean foundRelease = false;
 237  38
                 for (int j=i+1;j < steps.size();j++) {
 238  83
                     Step next = (Step)steps.get(j);
 239   
                     // If we find the release, remove it and this
 240  83
                     if (isKey(next, cs, RELEASE)) {
 241  36
                         foundRelease = true;
 242  36
                         String target = ((Event)next).getComponentID();
 243  36
                         steps.remove(j);
 244  36
                         steps.remove(i);
 245   
                         // Add a keystroke only if we didn't find any key
 246   
                         // input between press and release (except on OSX,
 247   
                         // where the option key generates input method events
 248   
                         // which aren't recorded). 
 249  36
                         if (!foundKeyStroke && !isOSXOption) {
 250  15
                             String mods = 
 251   
                                 se.getAttribute(XMLConstants.TAG_MODIFIERS);
 252  15
                             String[] args = 
 253  15
                                 (mods == null || "0".equals(mods)
 254   
                                  ? new String[] { target, cs }
 255   
                                  : new String[] { target, cs, mods});
 256  15
                             Step typed =
 257   
                                 new Action(getResolver(), 
 258   
                                            null, "actionKeyStroke", args);
 259  15
                             steps.add(i, typed);
 260  15
                             setStatus("Insert artifical " + typed);
 261   
                         }
 262   
                         else {
 263  21
                             setStatus("Removed redundant key events ("
 264   
                                       + cs + ")");
 265  21
                             --i;
 266   
                         }
 267  36
                         break;
 268   
                     }
 269  47
                     else if (isKeyStroke(next, ANY_KEY)
 270   
                              || isKeyString(next)) {
 271  38
                         foundKeyStroke = true;
 272   
                         // If it's a numpad keycode, use the numpad
 273   
                         // keycode instead of the resulting numeric character
 274   
                         // keystroke. 
 275  38
                         if (cs.startsWith("VK_NUMPAD")) {
 276  10
                             foundKeyStroke = false;
 277  10
                             steps.remove(j--);
 278   
                         }
 279   
                     }
 280   
                 }
 281   
                 // We don't like standalone key presses
 282  38
                 if (!foundRelease) {
 283  2
                     setStatus("Removed extraneous key press (" + cs + ")");
 284  2
                     steps.remove(i--);
 285   
                 }
 286   
             }
 287   
         }
 288   
     }
 289   
 
 290   
     // Required for OS X, remove modifier keys when they're only used to
 291   
     // invoke MB2/3
 292   
     private boolean pruneButtonModifier = false;
 293   
     private int lastButton = 0;
 294   
     /** Used only on Mac OS, to remove key modifiers that are used to simulate
 295   
      * mouse buttons 2 and 3.  Returns whether the event should be ignored.
 296   
      */
 297  0
     private boolean pruneClickModifiers(AWTEvent event) {
 298  0
         lastButton = 0;
 299  0
         boolean ignoreEvent = false;
 300  0
         if (event.getID() == MouseEvent.MOUSE_PRESSED) {
 301  0
             MouseEvent me = (MouseEvent)event;
 302  0
             int buttons = me.getModifiers() 
 303   
                 & (MouseEvent.BUTTON2_MASK|MouseEvent.BUTTON3_MASK);
 304  0
             pruneButtonModifier = buttons != 0;
 305  0
             lastButton = buttons;
 306   
         }
 307  0
         else if (event.getID() == KeyEvent.KEY_RELEASED
 308   
                  && pruneButtonModifier) {
 309  0
             pruneButtonModifier = false;
 310  0
             KeyEvent ke = (KeyEvent)event;
 311  0
             int code = ke.getKeyCode();
 312  0
             if ((code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_ALT
 313   
                  && (lastButton & MouseEvent.BUTTON2_MASK) != 0)
 314   
                 || (code == KeyEvent.VK_META 
 315   
                     && (lastButton & MouseEvent.BUTTON3_MASK) != 0)) {
 316  0
                 if (steps.size() > 1) {
 317  0
                     Step step = (Step)steps.get(steps.size()-2);
 318  0
                     if ((code == KeyEvent.VK_CONTROL 
 319   
                          && isKey(step, "VK_CONTROL", PRESS)
 320   
                         || (code == KeyEvent.VK_ALT
 321   
                             && isKey(step, "VK_ALT", PRESS)))
 322   
                         || (code == KeyEvent.VK_META
 323   
                             && isKey(step, "VK_META", PRESS))) {
 324   
                         // might be another one
 325  0
                         steps.remove(steps.size()-2);
 326  0
                         pruneButtonModifier = true;
 327  0
                         ignoreEvent = true;
 328   
                     }
 329   
                 }
 330   
             }
 331   
         }
 332  0
         return ignoreEvent;
 333   
     }
 334   
 
 335   
     /** Ignore any key presses at the end of the recording. */
 336  17
     private void removeTrailingKeyPresses() {
 337  17
         while (steps.size() > 0
 338   
                && isKey((Step)steps.get(steps.size()-1), ANY_KEY, PRESS)) {
 339  0
             steps.remove(steps.size()-1);
 340   
         }
 341   
     }
 342   
 
 343   
     /** Remove keypress events preceding and following ActionMap actions. */
 344  17
     private void removeShortcutModifierKeyPresses() {
 345  17
         int current = 0;
 346  17
         int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
 347  17
         String modifier = AWT.getKeyCode(AWT.maskToKeyCode(mask));
 348  17
         while (current < steps.size()) {
 349  45
             Step step = (Step)steps.get(current);
 350  45
             if (isKey(step, modifier, PRESS)) {
 351  4
                 Log.debug("Found possible extraneous modifier");
 352  4
                 int keyDown = current;
 353  4
                 Action action = null;
 354  10
                 while (++current < steps.size()) {
 355  10
                     step = (Step)steps.get(current);
 356  10
                     if (step instanceof Action) {
 357  6
                         if ("actionActionMap".
 358   
                             equals(((Action)step).getMethodName())) {
 359  6
                             action = (Action)step;
 360  6
                             continue;
 361   
                         }
 362   
                     }
 363  4
                     else if (isKey(step, modifier, RELEASE)) {
 364  4
                         if (action != null) {
 365  4
                             Log.debug("Removing extraneous shortcut modifier");
 366  4
                             steps.remove(current);
 367  4
                             steps.remove(keyDown);
 368  4
                             current = keyDown - 1;
 369   
                         }
 370   
                     }
 371  4
                     break;
 372   
                 }
 373   
             }
 374  45
             ++current;
 375   
         }
 376   
     }
 377   
 
 378   
     /** Insert an arbitrary script step into the currently recorded stream. */
 379