for (int i=0;i < steps.size();i++) {
Step step = (Step)steps.get(i);
if (isKey(step, ANY_KEY, PRESS)) {
// In the case of modifiers, remove only if the presence of
// the key down/up is redundant.
Event se = (Event)step;
String cs = se.getAttribute(XMLConstants.TAG_KEYCODE);
int code = Robot.getKeyCode(cs);
// OSX option modifier should be ignored, since it is used to
// generate input method events.
boolean isOSXOption =
Platform.isOSX() && code == KeyEvent.VK_ALT;
if (Robot.isModifier(code) && !isOSXOption)
continue;
// In the case of non-modifier keys, walk the steps until we
// find the key release, then optionally replace the key press
// with a keystroke, or remove it if the keystroke was already
// recorded. This sorts out jumbled key press/release events.
boolean foundKeyStroke = false;
boolean foundRelease = false;
for (int j=i+1;j < steps.size();j++) {
Step next = (Step)steps.get(j);
// If we find the release, remove it and this
if (isKey(next, cs, RELEASE)) {
foundRelease = true;
String target = ((Event)next).getComponentID();
steps.remove(j);
steps.remove(i);
// Add a keystroke only if we didn't find any key
// input between press and release (except on OSX,
// where the option key generates input method events
// which aren't recorded).
if (!foundKeyStroke && !isOSXOption) {
String mods =
se.getAttribute(XMLConstants.TAG_MODIFIERS);
String[] args =
(mods == null || "0".equals(mods)
? new String[] { target, cs }
: new String[] { target, cs, mods});
Step typed =