Index: trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/JFCJacaretoReplayGenerator.java
===================================================================
--- trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/JFCJacaretoReplayGenerator.java	(revision 2035)
+++ trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/JFCJacaretoReplayGenerator.java	(revision 2035)
@@ -0,0 +1,1020 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.plugin.jfc;
+
+import org.apache.commons.lang.StringEscapeUtils;
+
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+import java.util.UUID;
+import java.util.logging.Level;
+
+import javax.swing.UIManager;
+
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.autoquest.eventcore.IEventTarget;
+import de.ugoe.cs.autoquest.eventcore.gui.*;
+import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
+import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
+import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
+import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
+import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenu;
+import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenuButton;
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * This class generated XML replays for a replaying tool called 'Jacareto' from a sequence.
+ * </p>
+ * 
+ * @author Daniel May
+ * @version 1.0
+ */
+public class JFCJacaretoReplayGenerator {
+
+    /**
+     * <p>
+     * Helper class for the tree like structure part within a Jacareto file.
+     * </p>
+     */
+    private static class StructureNode {
+
+        /**
+         * <p>
+         * Keeps track of the next structure node id.
+         * </p>
+         */
+        public static int nextRef = 0;
+
+        /**
+         * <p>
+         * The node's type packaged into an XML string.
+         * </p>
+         */
+        public String content;
+
+        /**
+         * <p>
+         * This node's children.
+         * </p>
+         */
+        public ArrayList<StructureNode> children;
+
+        /**
+         * <p>
+         * Constructor. Creates a new StructureNode of a specified type and builds its Jacareto XML
+         * representation.
+         * </p>
+         * 
+         * @param type
+         *            the type of this StructureNode, for example: 'MouseDownEvent'
+         */
+        public StructureNode(String type) {
+            setContent(type);
+            children = new ArrayList<StructureNode>();
+        }
+
+        /**
+         * <p>
+         * Constructor. Creates a StructureNode of type 'Recordable' with a valid id and builds its
+         * Jacareto XML representation.
+         * </p>
+         */
+        public StructureNode() {
+            content = "<Recordable ref=\"" + (nextRef++) + "\" />";
+            children = new ArrayList<StructureNode>();
+        }
+
+        /**
+         * <p>
+         * Builds the XML representation of a Jacareto structure type.
+         * </p>
+         * 
+         * @param type
+         *            the type of this StructureNode, for example: 'MouseDownEvent'
+         */
+        public void setContent(String type) {
+            content = "<StructureElement class=\"jacareto.struct." + type + "\">";
+        }
+
+        /**
+         * <p>
+         * Adds a new StructureNode as a child of this node.
+         * </p>
+         * 
+         * @param type
+         *            the type of the child node, for example: 'MouseDownEvent'
+         */
+        public StructureNode add(String type) {
+            StructureNode node = new StructureNode(type);
+            children.add(node);
+            return node;
+        }
+
+        /**
+         * <p>
+         * Builds the XML representation of a Jacareto structure type.
+         * </p>
+         * 
+         * @param type
+         *            the type of this StructureNode, for example: 'MouseDownEvent'
+         */
+        public void addRecordable() {
+            children.add(new StructureNode());
+        }
+
+        /**
+         * <p>
+         * Returns a Jacareto XML representation of this StructureNode, includin all its children.
+         * </p>
+         */
+        @Override
+        public String toString() {
+            String separator = System.getProperty("line.separator");
+            String result = content + separator;
+
+            for (StructureNode child : children) {
+                result += child.toString();
+            }
+
+            if (content.endsWith("/>")) {
+                return result;
+            }
+            return result + "</StructureElement>" + separator;
+        }
+    }
+
+    /**
+     * <p>
+     * The time it takes for Jacareto to replay an event in ms.
+     * </p>
+     */
+    private static final int EVENT_DURATION = 150;
+
+    /**
+     * <p>
+     * The time it takes for Jacareto to replay each part of a double click event in ms.
+     * </p>
+     */
+    private static final int DOUBLE_CLICK_DURATION = 50;
+
+    /**
+     * <p>
+     * Application startup time in ms. The application needs to be fully initialized before Jacareto
+     * can start replaying.
+     * </p>
+     */
+    private static final int STARTUP_DELAY = 10000;
+
+    /**
+     * <p>
+     * The GUI element which is currently focused.
+     * </p>
+     */
+    private JFCGUIElement currentFocus;
+
+    /**
+     * <p>
+     * A tree of StructureNodes which represents the structure part inside a Jacareto XML file.
+     * </p>
+     */
+    private StructureNode structure;
+
+    /**
+     * <p>
+     * XML structure for key events modeled as StructureNodes.
+     * </p>
+     */
+    private StructureNode lastKeySequenceEvent;
+
+    /**
+     * <p>
+     * XML structure for key events modeled as StructureNodes.
+     * </p>
+     */
+    private StructureNode lastKeyTypedEvent;
+
+    /**
+     * <p>
+     * Bitmask which represents currently used key modifiers (such as shift etc).
+     * </p>
+     */
+    private int currentKeyModifiers;
+
+    /**
+     * <p>
+     * Maps VirtualKey objects for modifier keys back to AWT Event codes.
+     * </p>
+     */
+    private HashMap<VirtualKey, Integer> modifiers;
+
+    /**
+     * <p>
+     * XML structure for mouse events modeled as StructureNodes.
+     * </p>
+     */
+    private StructureNode lastMouseClickEvent;
+
+    /**
+     * <p>
+     * XML structure for focus events modeled as StructureNodes.
+     * </p>
+     */
+    private StructureNode lastFocusChangeEvent;
+
+    /**
+     * <p>
+     * XML structure for item and action events modeled as StructureNodes.
+     * </p>
+     */
+    private StructureNode lastItemActionEvent;
+
+    /**
+     * <p>
+     * The target of the last mouseDownEvent. It is necessary to save this because mouse down and up
+     * targets can differ.
+     * </p>
+     */
+    private IEventTarget lastMouseDownTarget;
+
+    /**
+     * <p>
+     * Associates the name of a menu element with its corresponding JFCGUIElement.
+     * </p>
+     */
+    private HashMap<String, JFCGUIElement> menuElements;
+
+    /**
+     * <p>
+     * The menu hierarchy.
+     * </p>
+     */
+    private List<String> menuList;
+
+    /**
+     * <p>
+     * Autoquest input sequence.
+     * </p>
+     */
+    private List<Event> sequence;
+
+    /**
+     * <p>
+     * XML output filename.
+     * </p>
+     */
+    private String filename;
+
+    /**
+     * <p>
+     * Jacareto replay execution classpath.
+     * </p>
+     */
+    private String classpath;
+
+    /**
+     * <p>
+     * Jacareto replay execution basepath.
+     * </p>
+     */
+    private String basepath;
+
+    /**
+     * <p>
+     * Jacareto replay execution classpathext.
+     * </p>
+     */
+    private String classpathext;
+
+    /**
+     * <p>
+     * Constructor.
+     * </p>
+     */
+    public JFCJacaretoReplayGenerator(List<Event> sequence,
+                                      String filename,
+                                      String classpath,
+                                      String basepath,
+                                      String classpathext,
+                                      String menu)
+    {
+        this.sequence = sequence;
+        this.filename = filename;
+        this.classpath = classpath;
+        this.basepath = basepath;
+        this.classpathext = classpathext;
+        currentFocus = null;
+        currentKeyModifiers = 0;
+        StructureNode.nextRef = 0;
+        lastFocusChangeEvent = null;
+        lastItemActionEvent = null;
+        lastKeySequenceEvent = null;
+        lastKeyTypedEvent = null;
+        lastMouseClickEvent = null;
+        lastMouseDownTarget = null;
+        menuElements = new HashMap<>();
+        modifiers = createModifierMap();
+
+        // try to parse in menu file, if available
+        if (!menu.isEmpty()) {
+            try {
+                menuList = Files.readAllLines(Paths.get(menu), Charset.defaultCharset());
+            }
+            catch (IOException e) {
+                Console.printerrln("Unable to open menu file");
+                Console.logException(e);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Writes the Jacareto XML replay file.
+     * </p>
+     */
+    public void writeJacaretoXML() {
+        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
+
+        try {
+            writeJacaretoHead(writer, classpath, basepath, classpathext);
+            writeJacaretoEvents(writer, sequence);
+            writeJacaretoTail(writer);
+            writeLine(writer, "</JacaretoStructure>");
+
+            writer.flush();
+            writer.close();
+        }
+        catch (IOException e) {
+            Console.printerrln("Unable to write Jacareto replay file " + filename);
+        }
+    }
+
+    /**
+     * <p>
+     * Associates keyboard modifier keys with their AWT event codes.
+     * </p>
+     */
+    private HashMap<VirtualKey, Integer> createModifierMap() {
+        HashMap<VirtualKey, Integer> result = new HashMap<>();
+
+        result.put(VirtualKey.SHIFT, 1);
+        result.put(VirtualKey.CONTROL, 2);
+        result.put(VirtualKey.ALT, 8);
+        result.put(VirtualKey.ALT_GRAPH, 32);
+
+        return result;
+    }
+
+    /**
+     * <p>
+     * Writes a line and creates a new line.
+     * </p>
+     * 
+     * @param writer
+     *            the BufferedWriter which writes the XML
+     * @param line
+     *            the line to write
+     * 
+     */
+    private void writeLine(BufferedWriter writer, String line) throws IOException {
+        writer.write(line);
+        writer.newLine();
+    }
+
+    /**
+     * <p>
+     * Writes the Jacareto XML head part. This mainly contains information about the state of the
+     * system when the replay was captured.
+     * 
+     * @param writer
+     *            the BufferedWriter which writes the XML
+     * @param classname
+     *            name of the main class of the program that will be replayed
+     * @param basepath
+     *            a basepath that is prepended to all paths specified in classpathext
+     * @param classpathext
+     *            additional required resources (e.g. jar files)
+     * 
+     *            </p>
+     */
+    private void writeJacaretoHead(BufferedWriter writer,
+                                   String classname,
+                                   String basepath,
+                                   String classpathext) throws IOException
+    {
+        Calendar now = Calendar.getInstance();
+
+        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
+        writeLine(writer, "<JacaretoStructure>");
+        writeLine(writer, "<Record>");
+
+        //@formatter:off
+        writeLine(writer, "<Calendar "
+            + "procTime=\"0\" "
+            + "duration=\"0\" "
+            + "year=\"" + now.get(Calendar.YEAR) + "\" "
+            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
+            + "date=\"" + now.get(Calendar.DAY_OF_MONTH) + "\" "
+            + "hour=\"" + now.get(Calendar.HOUR_OF_DAY) + "\" "
+            + "min=\"" + now.get(Calendar.MINUTE) + "\" "
+            + "sec=\"" + now.get(Calendar.SECOND) + "\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" />"
+        );
+        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
+        writeLine(writer, "<SystemInfo "
+            + "procTime=\"0\" "
+            + "duration=\"0\" " 
+            + "screenWidth=\"" + gd.getDisplayMode().getWidth() + "\" "
+            + "screenHeight=\"" + gd.getDisplayMode().getHeight() + "\" "
+            + "javaVersion=\"" + System.getProperty("java.version") + "\" "
+            + "lookAndFeel=\"" + UIManager.getLookAndFeel().getClass().getName() + "\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" />"
+        );
+        writeLine(writer,
+                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
+        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
+        writeLine(writer, "<ApplicationStarter "
+            + "procTime=\"" + STARTUP_DELAY + "\" "
+            + "duration=\"" + STARTUP_DELAY + "\" "
+            + "name=\"Autoquest Replay\" "
+            + "class=\"" + classname + "\" "
+            + "initclass=\"\" " 
+            + "basepath=\"" + basepath + "\" "
+            + "classpathext=\"" + classpathext + "\" "
+            + "detectDuration=\"false\" "
+            + "captureparams=\"\" "
+            + "replayparams=\"\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" />"
+        );
+        //@formatter:on
+    }
+
+    /**
+     * <p>
+     * Writes Jacareto XML code for all events within the Autoquest sequences.
+     * </p>
+     * 
+     * @param writer
+     *            the BufferedWriter which writes the XML
+     * @param sequences
+     *            the Autoquest sequences
+     * 
+     */
+    private void writeJacaretoEvents(BufferedWriter writer, List<Event> sequence)
+        throws IOException
+    {
+        structure = new StructureNode("RootElement");
+        // reference the elements that we included in the header
+        structure.addRecordable(); // Calendar
+        structure.addRecordable(); // SystemInfo
+        structure.addRecordable(); // KeyboardState
+        structure.addRecordable(); // ComponentMode
+        structure.addRecordable(); // ApplicationStarter
+
+        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
+            Event event = eventIter.next();
+
+            if (event.getType() instanceof MouseButtonDown) {
+                handleMouseButtonDown(writer, event);
+            }
+            else if (event.getType() instanceof MouseButtonUp) {
+                handleMouseButtonUp(writer, event);
+            }
+            else if (event.getType() instanceof MouseDoubleClick) {
+                handleMouseDoubleClick(writer, event);
+            }
+            else if (event.getType() instanceof MouseClick) {
+                if (event.getTarget() instanceof JFCMenuButton) {
+                    // if a menu file was provided, use the improved event
+                    // generation
+                    if (menuList != null) {
+                        if (menuElements.isEmpty()) {
+                            // parse the menu structure
+                            GUIModel model = ((IGUIElement) event.getTarget()).getGUIModel();
+                            getMenuElements(model.getRootElements(), model);
+                        }
+
+                        Stack<JFCGUIElement> hierarchy =
+                            findMenuItemHierarchy((JFCGUIElement) event.getTarget());
+
+                        while (!hierarchy.empty()) {
+                            generateFullClick(writer, event, hierarchy.pop());
+                        }
+                        continue;
+                    }
+                }
+
+                handleMouseClick(writer, event);
+            }
+            else if (event.getType() instanceof KeyboardFocusChange) {
+                handleKeyboardFocusChange(writer, event);
+            }
+            else if (event.getType() instanceof MouseDragAndDrop) {
+                handleMouseDragAndDrop(writer, event);
+            }
+            else if (event.getType() instanceof KeyPressed) {
+                handleKeyPressed(writer, event);
+            }
+            else if (event.getType() instanceof KeyReleased) {
+                handleKeyReleased(writer, event);
+            }
+            else if (event.getType() instanceof TextInput) {
+                handleTextInput(writer, event);
+            }
+            else {
+                Console.traceln(Level.WARNING, "No handler for event \"" + event + "\". Skipped.");
+            }
+        }
+    }
+
+    // EVENT HANDLERS
+
+    private void handleMouseClick(BufferedWriter writer, Event event) throws IOException {
+        lastKeySequenceEvent = null;
+
+        if (lastMouseClickEvent != null) {
+            if (lastMouseDownTarget == event.getTarget()) {
+                // this is the standard case:
+                // mouseDown, mouseUp and mouseClick sequence
+                // was triggered on this target
+
+                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                                     EVENT_DURATION, 500);
+                writeItemActionEvent(writer, event);
+
+                if (lastFocusChangeEvent == null) {
+                    // write structure sequentially
+                    structure.children.add(lastMouseClickEvent);
+                    structure.children.add(lastItemActionEvent);
+
+                    lastMouseClickEvent = null;
+                }
+                else {
+                    // with nested structure
+                    structure.children.add(lastItemActionEvent);
+                    lastItemActionEvent.children.add(0, lastFocusChangeEvent);
+                    lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
+
+                    lastFocusChangeEvent = null;
+                    lastMouseClickEvent = null;
+                }
+            }
+            else {
+                // target of mouseDown and mouseClick are different
+                // -> this is, for example, a click on a menu item
+                // within a condensed sequence
+                commitFocusEvent();
+
+                // finish the last click on the old target
+                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                                     EVENT_DURATION, 500);
+                structure.children.add(lastMouseClickEvent);
+
+                // and generate a new one
+                generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
+            }
+        }
+        else {
+            // a target was clicked repeatedly:
+            // the condensed sequence contains no mouseDowns or
+            // mouseUps anymore
+            // -> just generate another full click
+            generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
+        }
+
+    }
+
+    private void handleMouseDoubleClick(BufferedWriter writer, Event event) throws IOException {
+        StructureNode multiClick = structure.add("MultipleMouseClick");
+
+        // first click
+        lastMouseClickEvent = multiClick.add("MouseClick");
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                             DOUBLE_CLICK_DURATION, 501);
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                             DOUBLE_CLICK_DURATION, 502);
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                             DOUBLE_CLICK_DURATION, 500);
+        // second click
+        lastMouseClickEvent = multiClick.add("MouseClick");
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                             DOUBLE_CLICK_DURATION, 501);
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                             DOUBLE_CLICK_DURATION, 502);
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
+                             DOUBLE_CLICK_DURATION, 500);
+
+        lastMouseClickEvent = null;
+
+    }
+
+    private void handleKeyboardFocusChange(BufferedWriter writer, Event event) throws IOException {
+        lastKeySequenceEvent = null;
+        writeFocusChangeEvent(writer, event);
+    }
+
+    private void handleMouseButtonDown(BufferedWriter writer, Event event) throws IOException {
+        commitFocusEvent();
+        lastKeySequenceEvent = null;
+
+        lastMouseClickEvent = new StructureNode("MouseClick");
+        lastMouseDownTarget = event.getTarget();
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 501);
+    }
+
+    private void handleMouseButtonUp(BufferedWriter writer, Event event) throws IOException {
+        lastKeySequenceEvent = null;
+
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 502);
+    }
+
+    private void handleMouseDragAndDrop(BufferedWriter writer, Event event) throws IOException {
+        commitFocusEvent();
+
+        MouseDragAndDrop dragEvent = (MouseDragAndDrop) event.getType();
+        lastMouseClickEvent = new StructureNode("MouseDrag");
+        lastMouseDownTarget = null;
+
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
+                             dragEvent.getXStart(), dragEvent.getYStart(), 501);
+        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
+                             dragEvent.getX(), dragEvent.getY(), 506);
+
+        structure.children.add(lastMouseClickEvent);
+    }
+
+    private void handleKeyPressed(BufferedWriter writer, Event event) throws IOException {
+        commitFocusEvent();
+
+        if (lastKeySequenceEvent == null) {
+            lastKeySequenceEvent = structure.add("KeySequence");
+        }
+        lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
+
+        writeKeyEvent(writer, event, 401);
+    }
+
+    private void handleKeyReleased(BufferedWriter writer, Event event) throws IOException {
+        commitFocusEvent();
+
+        writeKeyEvent(writer, event, 402);
+    }
+
+    private void handleTextInput(BufferedWriter writer, Event event) throws IOException {
+        List<Event> textEvents = ((TextInput) event.getType()).getTextInputEvents();
+
+        // just split the text event into its key events again
+        for (Event textEvent : textEvents) {
+            if (textEvent.getType() instanceof KeyPressed) {
+                handleKeyPressed(writer, textEvent);
+            }
+            else if (textEvent.getType() instanceof KeyReleased) {
+                handleKeyReleased(writer, textEvent);
+            }
+        }
+    }
+
+    private void getMenuElements(List<IGUIElement> elements, GUIModel model) {
+        for (IGUIElement child : elements) {
+            if (child instanceof JFCMenuButton || child instanceof JFCMenu) {
+                menuElements.put(((JFCGUIElement) child).getName().replaceAll("^\"|\"$", ""),
+                                 (JFCGUIElement) child);
+            }
+            getMenuElements(model.getChildren(child), model);
+        }
+    }
+
+    private Stack<JFCGUIElement> findMenuItemHierarchy(JFCGUIElement item) {
+        Stack<JFCGUIElement> elements = new Stack<>();
+
+        // find line that contains this menu item name
+        int lineOfItem = -1;
+        for (int i = 0; i < menuList.size(); i++) {
+            String name = "\"" + menuList.get(i).trim().toLowerCase() + "\"";
+            if (name.equals(item.getName().trim().toLowerCase())) {
+                lineOfItem = i;
+            }
+        }
+
+        // now go backwards until the toplevel menu is found
+        int oldIndent = Integer.MAX_VALUE;
+        for (int j = lineOfItem; j >= 0; j--) {
+            String stripped = menuList.get(j).replaceFirst("^ *", "");
+            int indent = menuList.get(j).length() - stripped.length();
+
+            if (indent < oldIndent) {
+                // this is a parent submenu
+                elements.push(menuElements.get(stripped));
+                oldIndent = indent;
+            }
+        }
+
+        return elements;
+    }
+
+    private void commitFocusEvent() {
+        if (lastFocusChangeEvent != null) {
+            structure.children.add(lastFocusChangeEvent);
+            lastFocusChangeEvent = null;
+        }
+    }
+
+    private void generateFullClick(BufferedWriter writer, Event event, JFCGUIElement target)
+        throws IOException
+    {
+        lastMouseClickEvent = new StructureNode("MouseClick");
+        lastMouseDownTarget = event.getTarget();
+
+        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 501);
+        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 502);
+        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 500);
+        writeItemActionEvent(writer, event);
+
+        structure.children.add(lastMouseClickEvent);
+        structure.children.add(lastItemActionEvent);
+
+        lastMouseDownTarget = null;
+        lastMouseClickEvent = null;
+    }
+
+    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
+        writeLine(writer, "</Record>");
+
+        // write the recording's structure
+        writeLine(writer, "<Structure>");
+        writer.write(structure.toString());
+        // close root element
+        writeLine(writer, "</Structure>");
+    }
+
+    /**
+     * <p>
+     * Helper function that opens the replay file for writing.
+     * </p>
+     * 
+     * @param filename
+     *            name and path of the replay file
+     * @param encoding
+     *            file encoding, empty string for platform default
+     * @return {@link OutputStreamWriter} that writes to the replay file
+     */
+    private OutputStreamWriter openReplayFile(String filename) {
+        File file = new File(filename);
+        boolean fileCreated;
+        try {
+            fileCreated = file.createNewFile();
+            if (!fileCreated) {
+                Console.traceln(Level.INFO, "Created logfile " + filename);
+            }
+            else {
+                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
+            }
+        }
+        catch (IOException e) {
+            Console.printerrln("Unable to create file " + filename);
+            Console.logException(e);
+        }
+        OutputStreamWriter writer = null;
+        try {
+            writer = new OutputStreamWriter(new FileOutputStream(file));
+        }
+        catch (IOException e) {
+            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
+            Console.logException(e);
+        }
+        return writer;
+    }
+
+    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
+        JFCGUIElement target = (JFCGUIElement) event.getTarget();
+        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
+        // get rid of the quote symbols in the command because we want to
+        // escape the middle part for XML
+        String cmd = target.getName().substring(1, target.getName().length() - 1);
+
+        //@formatter:off
+        writeLine(writer,
+            "<ItemEvent "
+            + "procTime=\"0\" "
+            + "duration=\"0\" "
+            + "source=\"" + target.getJacaretoHierarchy() + "\" "
+            + "class=\"" + target.getSpecification().getType() + "\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" "
+            + "ID=\"701\" "
+            + "item=\"\" "
+            + "stateChange=\"1\" />"
+        );
+        writeLine(writer,
+            "<ActionEvent "
+            + "procTime=\"0\" "
+            + "duration=\"0\" "
+            + "source=\"" + target.getJacaretoHierarchy() + "\" "
+            + "class=\"" + target.getSpecification().getType() + "\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" "
+            + "ID=\"1001\" "
+            + "command=\"" + StringEscapeUtils.escapeXml(cmd) + "\" "
+            + "modifiers=\"" + getButtonModifier(info) + "\" />"
+        );
+        //@formatter:on
+        lastItemActionEvent = new StructureNode("ItemStateChange");
+        lastItemActionEvent.addRecordable();
+        lastItemActionEvent.addRecordable();
+    }
+
+    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
+        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
+        JFCGUIElement target = (JFCGUIElement) event.getTarget();
+
+        if (currentFocus != null) {
+            lastFocusChangeEvent = new StructureNode("FocusChange");
+
+            // focus lost on old target
+            writeFocusEvent(writer, info, currentFocus, 1005);
+            // focus gained on new target
+            writeFocusEvent(writer, info, target, 1004);
+        }
+        else {
+            // the first focus event but that is not the case in autoquest, skip
+        }
+
+        currentFocus = target;
+    }
+
+    private void writeFocusEvent(BufferedWriter writer,
+                                 KeyboardFocusChange info,
+                                 JFCGUIElement target,
+                                 int jacId) throws IOException
+    {
+        //@formatter:off
+        writeLine(writer,
+            "<FocusEvent "
+            + "procTime=\"0\" "
+            + "duration=\"0\" "
+            + "source=\"" + target.getJacaretoHierarchy() + "\" "
+            + "class=\"" + target.getSpecification().getType() + "\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" "
+            + "ID=\"" + jacId + "\" "
+            + "component=\"null\" "
+            + "root=\"" + target.getJacaretoRoot() + "\" "
+            + "xPos=\"0\" "
+            + "yPos=\"0\" "
+            + "width=\"0\" "
+            + "height=\"0\" "
+            + "isTemporary=\"false\" />" 
+        );
+        //@formatter:on
+        lastFocusChangeEvent.addRecordable();
+    }
+
+    private void writeMouseClickEvent(BufferedWriter writer,
+                                      Event event,
+                                      JFCGUIElement target,
+                                      int duration,
+                                      int jacId) throws IOException
+    {
+        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
+        writeMouseClickEvent(writer, event, target, duration, info.getX(), info.getY(), jacId);
+    }
+
+    private void writeMouseClickEvent(BufferedWriter writer,
+                                      Event event,
+                                      JFCGUIElement target,
+                                      int duration,
+                                      int x,
+                                      int y,
+                                      int jacId) throws IOException
+    {
+        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
+        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
+
+        //@formatter:off
+        writeLine(writer,
+            "<MouseEvent "
+            + "procTime=\"0\" "
+            + "duration=\"" + duration + "\" "
+            + "source=\"" + target.getJacaretoHierarchy() + "\" "
+            + "class=\"" + target.getSpecification().getType() + "\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" "
+            + "ID=\"" + jacId + "\" "
+            + "component=\"null\" "
+            + "root=\"" + target.getJacaretoRoot() + "\" "
+            + "xPos=\"0\" "
+            + "yPos=\"0\" "
+            + "width=\"0\" "
+            + "height=\"0\" "
+            + "when=\"" + event.getTimestamp() + "\" " 
+            + "isConsumed=\"false\">" 
+        );
+        writeLine(writer,
+            "<MouseInfo "
+            + "xPosition=\"" + x + "\" "
+            + "yPosition=\"" + y + "\" "
+            + "rootX=\"0\" "
+            + "rootY=\"0\" "
+            + "clickCount=\"" + clickCount + "\" "
+            + "modifiers=\"" + getButtonModifier(info) + "\" "
+            + "isPopupTrigger=\"false\" />"
+        );
+        writeLine(writer, "</MouseEvent>");
+        //@formatter:on
+
+        lastMouseClickEvent.addRecordable();
+    }
+
+    private int getButtonModifier(MouseButtonInteraction info) {
+        switch (info.getButton())
+        {
+            case LEFT:
+                return 16;
+            case MIDDLE:
+                return 8;
+            case RIGHT:
+                return 4;
+            default:
+                Console.traceln(Level.WARNING, "Unknown mouse button pressed.");
+                return -1;
+        }
+    }
+
+    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
+        KeyInteraction info = (KeyInteraction) event.getType();
+        JFCGUIElement target = (JFCGUIElement) event.getTarget();
+        int keyCode = info.getKey().getVirtualKeyCode();
+
+        applyKeyModifier(info.getKey(), jacId == 401);
+
+        //@formatter:off
+        writeLine(writer,
+            "<KeyEvent "
+            + "procTime=\"0\" "
+            + "duration=\"" + EVENT_DURATION + "\" "
+            + "source=\"" + target.getJacaretoHierarchy() + "\" "
+            + "class=\"" + target.getSpecification().getType() + "\" "
+            + "uuid=\"" + UUID.randomUUID() + "\" "
+            + "ID=\"" + jacId + "\" "
+            + "component=\"null\" "
+            + "root=\"" + target.getJacaretoRoot() + "\" "
+            + "xPos=\"0\" "
+            + "yPos=\"0\" "
+            + "width=\"0\" "
+            + "height=\"0\" "
+            + "when=\"" + event.getTimestamp() + "\" " 
+            + "isConsumed=\"false\">" 
+        );
+        writeLine(writer,
+            "<KeyInfo "
+            + "keyCode=\"" + keyCode + "\" "
+            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
+            + "modifiers=\"" + currentKeyModifiers + "\" />"
+        );
+        
+        writeLine(writer, "</KeyEvent>");
+        
+        lastKeyTypedEvent.addRecordable();
+    }
+    
+    private String getKeyChar (int keyCode) {
+        if (keyCode >= 32 && keyCode < 127) {
+            return String.valueOf((char)keyCode);
+        }
+        return "_NO_LEGAL_XML_CHAR";
+    }
+    
+    private void applyKeyModifier (VirtualKey key, boolean set) {
+        Integer modifier = modifiers.get(key);
+        if (modifier != null) {
+            if (set) {
+                currentKeyModifiers |= modifier;
+            }
+            else {
+                currentKeyModifiers &= ~modifier;
+            }
+        }
+    }
+}
Index: trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateAltIndex.java
===================================================================
--- trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateAltIndex.java	(revision 1956)
+++ trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateAltIndex.java	(revision 2035)
@@ -22,4 +22,5 @@
 import java.util.Iterator;
 import java.util.List;
+import java.util.logging.Level;
 
 import de.ugoe.cs.autoquest.CommandHelpers;
@@ -33,9 +34,11 @@
 import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenuButton;
 import de.ugoe.cs.util.console.Command;
+import de.ugoe.cs.util.console.Console;
 import de.ugoe.cs.util.console.GlobalDataContainer;
 
 /**
  * <p>
- * TODO comment
+ * Command to add an alternative index to JFCGuiElementSpecs. This index counts child type
+ * separately.
  * </p>
  * 
@@ -59,7 +62,6 @@
             sequencesName = (String) parameters.get(0);
             if (parameters.size() >= 2) {
-                menuList =
-                    Files.readAllLines(Paths.get((String) parameters.get(1)),
-                                       Charset.defaultCharset());
+                menuList = Files.readAllLines(Paths.get((String) parameters.get(1)),
+                                              Charset.defaultCharset());
             }
         }
@@ -128,12 +130,9 @@
     @Override
     public String help() {
-        // TODO Auto-generated method stub
         System.out.println("parseJFCDirwithJacaretoIndices <path> {<sequencesName>} {<menuFile>}");
         return null;
     }
 
-    // duplicates to parseJFCwithJacaretoIndices
     private int findPopupMenuIndex(IGUIElement item, GUIModel model) {
-        // TODO: refactor
         int index = -1;
         List<IGUIElement> children = model.getChildren(item);
@@ -151,5 +150,5 @@
         if (menuChild == null) {
             // this popup menu cannot be identified
-            // TODO: exception, logging etc
+            Console.traceln(Level.WARNING, "Index for popup menu item could not be found.");
             return -1;
         }
@@ -169,5 +168,5 @@
         if (lineOfItem == -1) {
             // failed to find this item in the menu file
-            // TODO: exception, logging etc
+            Console.traceln(Level.WARNING, "Menu file seems to be missing an item.");
             return -1;
         }
Index: trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateJacaretoReplay.java
===================================================================
--- trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateJacaretoReplay.java	(revision 1956)
+++ trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateJacaretoReplay.java	(revision 2035)
@@ -15,27 +15,6 @@
 package de.ugoe.cs.autoquest.plugin.jfc.commands;
 
-import org.apache.commons.lang.StringEscapeUtils;
-
-import java.awt.GraphicsDevice;
-import java.awt.GraphicsEnvironment;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Stack;
-import java.util.UUID;
-import java.util.logging.Level;
-
-import javax.swing.UIManager;
 
 import de.ugoe.cs.autoquest.CommandHelpers;
@@ -43,13 +22,5 @@
 import de.ugoe.cs.util.console.Command;
 import de.ugoe.cs.autoquest.eventcore.Event;
-import de.ugoe.cs.autoquest.eventcore.IEventTarget;
-import de.ugoe.cs.autoquest.eventcore.gui.*;
-import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
-import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
-import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
-import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
-import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenu;
-import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenuButton;
-import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.autoquest.plugin.jfc.JFCJacaretoReplayGenerator;
 import de.ugoe.cs.util.console.GlobalDataContainer;
 
@@ -64,228 +35,4 @@
 public class CMDgenerateJacaretoReplay implements Command {
 
-    /**
-     * <p>
-     * Helper class for the tree like structure part within a Jacareto file.
-     * </p>
-     */
-    private static class StructureNode {
-
-        /**
-         * <p>
-         * Keeps track of the next structure node id.
-         * </p>
-         */
-        public static int nextRef = 0;
-
-        /**
-         * <p>
-         * The node's type packaged into an XML string.
-         * </p>
-         */
-        public String content;
-
-        /**
-         * <p>
-         * This node's children.
-         * </p>
-         */
-        public ArrayList<StructureNode> children;
-
-        /**
-         * <p>
-         * Constructor. Creates a new StructureNode of a specified type and builds its Jacareto XML
-         * representation.
-         * </p>
-         * 
-         * @param type
-         *            the type of this StructureNode, for example: 'MouseDownEvent'
-         */
-        public StructureNode(String type) {
-            setContent(type);
-            children = new ArrayList<StructureNode>();
-        }
-
-        /**
-         * <p>
-         * Constructor. Creates a StructureNode of type 'Recordable' with a valid id and builds its
-         * Jacareto XML representation.
-         * </p>
-         */
-        public StructureNode() {
-            content = "<Recordable ref=\"" + (nextRef++) + "\" />";
-            children = new ArrayList<StructureNode>();
-        }
-
-        /**
-         * <p>
-         * Builds the XML representation of a Jacareto structure type.
-         * </p>
-         * 
-         * @param type
-         *            the type of this StructureNode, for example: 'MouseDownEvent'
-         */
-        public void setContent(String type) {
-            content = "<StructureElement class=\"jacareto.struct." + type + "\">";
-        }
-
-        /**
-         * <p>
-         * Adds a new StructureNode as a child of this node.
-         * </p>
-         * 
-         * @param type
-         *            the type of the child node, for example: 'MouseDownEvent'
-         */
-        public StructureNode add(String type) {
-            StructureNode node = new StructureNode(type);
-            children.add(node);
-            return node;
-        }
-
-        /**
-         * <p>
-         * Builds the XML representation of a Jacareto structure type.
-         * </p>
-         * 
-         * @param type
-         *            the type of this StructureNode, for example: 'MouseDownEvent'
-         */
-        public void addRecordable() {
-            children.add(new StructureNode());
-        }
-
-        /**
-         * <p>
-         * Returns a Jacareto XML representation of this StructureNode, includin all its children.
-         * </p>
-         */
-        @Override
-        public String toString() {
-            String separator = System.getProperty("line.separator");
-            String result = content + separator;
-
-            for (StructureNode child : children) {
-                result += child.toString();
-            }
-
-            if (content.endsWith("/>")) {
-                return result;
-            }
-            return result + "</StructureElement>" + separator;
-        }
-    }
-
-    /**
-     * <p>
-     * The time it takes for Jacareto to replay an event in ms.
-     * </p>
-     */
-    private static final int EVENT_DURATION = 150;
-
-    /**
-     * <p>
-     * The time it takes for Jacareto to replay each part of a double click event in ms.
-     * </p>
-     */
-    private static final int DOUBLE_CLICK_DURATION = 50;
-
-    /**
-     * <p>
-     * Application startup time in ms. The application needs to be fully initialized before Jacareto
-     * can start replaying.
-     * </p>
-     */
-    private static final int STARTUP_DELAY = 10000;
-
-    /**
-     * <p>
-     * The GUI element which is currently focused.
-     * </p>
-     */
-    private JFCGUIElement currentFocus;
-
-    /**
-     * <p>
-     * A tree of StructureNodes which represents the structure part inside a Jacareto XML file.
-     * </p>
-     */
-    private StructureNode structure;
-
-    /**
-     * <p>
-     * XML structure for key events modeled as StructureNodes.
-     * </p>
-     */
-    private StructureNode lastKeySequenceEvent;
-
-    /**
-     * <p>
-     * XML structure for key events modeled as StructureNodes.
-     * </p>
-     */
-    private StructureNode lastKeyTypedEvent;
-
-    /**
-     * <p>
-     * Bitmask which represents currently used key modifiers (such as shift etc).
-     * </p>
-     */
-    private int currentKeyModifiers;
-
-    /**
-     * <p>
-     * Maps VirtualKey objects for modifier keys back to AWT Event codes.
-     * </p>
-     */
-    private HashMap<VirtualKey, Integer> modifiers;
-
-    /**
-     * <p>
-     * XML structure for mouse events modeled as StructureNodes.
-     * </p>
-     */
-    private StructureNode lastMouseClickEvent;
-
-    /**
-     * <p>
-     * XML structure for focus events modeled as StructureNodes.
-     * </p>
-     */
-    private StructureNode lastFocusChangeEvent;
-
-    /**
-     * <p>
-     * XML structure for item and action events modeled as StructureNodes.
-     * </p>
-     */
-    private StructureNode lastItemActionEvent;
-
-    /**
-     * <p>
-     * The target of the last mouseDownEvent. It is necessary to save this because mouse down and up
-     * targets can differ.
-     * </p>
-     */
-    private IEventTarget lastMouseDownTarget;
-
-    /**
-     * <p>
-     * Associates the name of a menu element with its corresponding JFCGUIElement.
-     * </p>
-     */
-    private HashMap<String, JFCGUIElement> menuElements;
-
-    /**
-     * <p>
-     * The menu hierarchy.
-     * </p>
-     */
-    private List<String> menuList;
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see de.ugoe.cs.util.console.Command#help()
-     */
     @Override
     public String help() {
@@ -306,4 +53,5 @@
         String basepath;
         String classpathext;
+        String menu = "";
         try {
             filename = (String) parameters.get(0);
@@ -318,13 +66,5 @@
 
         if (parameters.size() > 5) {
-            try {
-                menuList =
-                    Files.readAllLines(Paths.get((String) parameters.get(5)),
-                                       Charset.defaultCharset());
-            }
-            catch (IOException e) {
-                Console.printerrln("Unable to open menu file");
-                Console.logException(e);
-            }
+            menu = (String) parameters.get(5);
         }
 
@@ -341,692 +81,13 @@
 
         sequences = (Collection<List<Event>>) dataObject;
-        menuElements = new HashMap<>();
-        modifiers = createModifierMap();
 
         int index = 1;
         for (List<Event> sequence : sequences) {
-            reinit();
-            writeJacaretoXML(sequence, filename + "_" + index, classpath, basepath, classpathext);
+            JFCJacaretoReplayGenerator generator =
+                new JFCJacaretoReplayGenerator(sequence, filename + "_" + index, classpath,
+                                               basepath, classpathext, menu);
+            generator.writeJacaretoXML();
             index++;
         }
     }
-
-    private void reinit() {
-        currentFocus = null;
-        currentKeyModifiers = 0;
-        StructureNode.nextRef = 0;
-        lastFocusChangeEvent = null;
-        lastItemActionEvent = null;
-        lastKeySequenceEvent = null;
-        lastKeyTypedEvent = null;
-        lastMouseClickEvent = null;
-        lastMouseDownTarget = null;
-    }
-
-    /**
-     * <p>
-     * Associates keyboard modifier keys with their AWT event codes.
-     * </p>
-     */
-    private HashMap<VirtualKey, Integer> createModifierMap() {
-        HashMap<VirtualKey, Integer> result = new HashMap<>();
-
-        result.put(VirtualKey.SHIFT, 1);
-        result.put(VirtualKey.CONTROL, 2);
-        result.put(VirtualKey.ALT, 8);
-        result.put(VirtualKey.ALT_GRAPH, 32);
-
-        return result;
-    }
-
-    /**
-     * <p>
-     * Writes a line and creates a new line.
-     * </p>
-     * 
-     * @param writer
-     *            the BufferedWriter which writes the XML
-     * @param line
-     *            the line to write
-     * 
-     */
-    private void writeLine(BufferedWriter writer, String line) throws IOException {
-        writer.write(line);
-        writer.newLine();
-    }
-
-    /**
-     * <p>
-     * Writes the Jacareto XML head part. This mainly contains information about the state of the
-     * system when the replay was captured.
-     * 
-     * @param writer
-     *            the BufferedWriter which writes the XML
-     * @param classname
-     *            name of the main class of the program that will be replayed
-     * @param basepath
-     *            a basepath that is prepended to all paths specified in classpathext
-     * @param classpathext
-     *            additional required resources (e.g. jar files)
-     * 
-     *            </p>
-     */
-    private void writeJacaretoHead(BufferedWriter writer,
-                                   String classname,
-                                   String basepath,
-                                   String classpathext) throws IOException
-    {
-        Calendar now = Calendar.getInstance();
-
-        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
-        writeLine(writer, "<JacaretoStructure>");
-        writeLine(writer, "<Record>");
-
-        //@formatter:off
-        writeLine(writer, "<Calendar "
-            + "procTime=\"0\" "
-            + "duration=\"0\" "
-            + "year=\"" + now.get(Calendar.YEAR) + "\" "
-            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
-            + "date=\"" + now.get(Calendar.DAY_OF_MONTH) + "\" "
-            + "hour=\"" + now.get(Calendar.HOUR_OF_DAY) + "\" "
-            + "min=\"" + now.get(Calendar.MINUTE) + "\" "
-            + "sec=\"" + now.get(Calendar.SECOND) + "\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" />"
-        );
-        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
-        writeLine(writer, "<SystemInfo "
-            + "procTime=\"0\" "
-            + "duration=\"0\" " 
-            + "screenWidth=\"" + gd.getDisplayMode().getWidth() + "\" "
-            + "screenHeight=\"" + gd.getDisplayMode().getHeight() + "\" "
-            + "javaVersion=\"" + System.getProperty("java.version") + "\" "
-            + "lookAndFeel=\"" + UIManager.getLookAndFeel().getClass().getName() + "\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" />"
-        );
-        writeLine(writer,
-                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
-        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
-        writeLine(writer, "<ApplicationStarter "
-            + "procTime=\"" + STARTUP_DELAY + "\" "
-            + "duration=\"" + STARTUP_DELAY + "\" "
-            + "name=\"Autoquest Replay\" "
-            + "class=\"" + classname + "\" "
-            + "initclass=\"\" " 
-            + "basepath=\"" + basepath + "\" "
-            + "classpathext=\"" + classpathext + "\" "
-            + "detectDuration=\"false\" "
-            + "captureparams=\"\" "
-            + "replayparams=\"\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" />"
-        );
-        //@formatter:on
-    }
-
-    /**
-     * <p>
-     * Writes Jacareto XML code for all events within the Autoquest sequences.
-     * </p>
-     * 
-     * @param writer
-     *            the BufferedWriter which writes the XML
-     * @param sequences
-     *            the Autoquest sequences
-     * 
-     */
-    private void writeJacaretoEvents(BufferedWriter writer, List<Event> sequence)
-        throws IOException
-    {
-        structure = new StructureNode("RootElement");
-        // reference the elements that we included in the header
-        structure.addRecordable(); // Calendar
-        structure.addRecordable(); // SystemInfo
-        structure.addRecordable(); // KeyboardState
-        structure.addRecordable(); // ComponentMode
-        structure.addRecordable(); // ApplicationStarter
-
-        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
-            Event event = eventIter.next();
-
-            if (event.getType() instanceof MouseButtonDown) {
-                handleMouseButtonDown(writer, event);
-            }
-            else if (event.getType() instanceof MouseButtonUp) {
-                handleMouseButtonUp(writer, event);
-            }
-            else if (event.getType() instanceof MouseDoubleClick) {
-                handleMouseDoubleClick(writer, event);
-            }
-            else if (event.getType() instanceof MouseClick) {
-                if (event.getTarget() instanceof JFCMenuButton) {
-                    // if a menu file was provided, use the improved event
-                    // generation
-                    if (menuList != null) {
-                        if (menuElements.isEmpty()) {
-                            // parse the menu structure
-                            GUIModel model = ((IGUIElement) event.getTarget()).getGUIModel();
-                            getMenuElements(model.getRootElements(), model);
-                        }
-
-                        Stack<JFCGUIElement> hierarchy =
-                            findMenuItemHierarchy((JFCGUIElement) event.getTarget());
-
-                        while (!hierarchy.empty()) {
-                            generateFullClick(writer, event, hierarchy.pop());
-                        }
-                        continue;
-                    }
-                }
-
-                handleMouseClick(writer, event);
-            }
-            else if (event.getType() instanceof KeyboardFocusChange) {
-                handleKeyboardFocusChange(writer, event);
-            }
-            else if (event.getType() instanceof MouseDragAndDrop) {
-                handleMouseDragAndDrop(writer, event);
-            }
-            else if (event.getType() instanceof KeyPressed) {
-                handleKeyPressed(writer, event);
-            }
-            else if (event.getType() instanceof KeyReleased) {
-                handleKeyReleased(writer, event);
-            }
-            else if (event.getType() instanceof TextInput) {
-                handleTextInput(writer, event);
-            }
-            else {
-                Console.traceln(Level.WARNING, "No handler for event \"" + event + "\". Skipped.");
-            }
-        }
-    }
-
-    // EVENT HANDLERS
-
-    private void handleMouseClick(BufferedWriter writer, Event event) throws IOException {
-        lastKeySequenceEvent = null;
-
-        if (lastMouseClickEvent != null) {
-            if (lastMouseDownTarget == event.getTarget()) {
-                // this is the standard case:
-                // mouseDown, mouseUp and mouseClick sequence
-                // was triggered on this target
-
-                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                                     EVENT_DURATION, 500);
-                writeItemActionEvent(writer, event);
-
-                if (lastFocusChangeEvent == null) {
-                    // write structure sequentially
-                    structure.children.add(lastMouseClickEvent);
-                    structure.children.add(lastItemActionEvent);
-
-                    lastMouseClickEvent = null;
-                }
-                else {
-                    // with nested structure
-                    structure.children.add(lastItemActionEvent);
-                    lastItemActionEvent.children.add(0, lastFocusChangeEvent);
-                    lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
-
-                    lastFocusChangeEvent = null;
-                    lastMouseClickEvent = null;
-                }
-            }
-            else {
-                // target of mouseDown and mouseClick are different
-                // -> this is, for example, a click on a menu item
-                // within a condensed sequence
-                commitFocusEvent();
-
-                // finish the last click on the old target
-                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                                     EVENT_DURATION, 500);
-                structure.children.add(lastMouseClickEvent);
-
-                // and generate a new one
-                generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
-            }
-        }
-        else {
-            // a target was clicked repeatedly:
-            // the condensed sequence contains no mouseDowns or
-            // mouseUps anymore
-            // -> just generate another full click
-            generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
-        }
-
-    }
-
-    private void handleMouseDoubleClick(BufferedWriter writer, Event event) throws IOException {
-        StructureNode multiClick = structure.add("MultipleMouseClick");
-
-        // first click
-        lastMouseClickEvent = multiClick.add("MouseClick");
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                             DOUBLE_CLICK_DURATION, 501);
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                             DOUBLE_CLICK_DURATION, 502);
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                             DOUBLE_CLICK_DURATION, 500);
-        // second click
-        lastMouseClickEvent = multiClick.add("MouseClick");
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                             DOUBLE_CLICK_DURATION, 501);
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                             DOUBLE_CLICK_DURATION, 502);
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
-                             DOUBLE_CLICK_DURATION, 500);
-
-        lastMouseClickEvent = null;
-
-    }
-
-    private void handleKeyboardFocusChange(BufferedWriter writer, Event event) throws IOException {
-        lastKeySequenceEvent = null;
-        writeFocusChangeEvent(writer, event);
-    }
-
-    private void handleMouseButtonDown(BufferedWriter writer, Event event) throws IOException {
-        commitFocusEvent();
-        lastKeySequenceEvent = null;
-
-        lastMouseClickEvent = new StructureNode("MouseClick");
-        lastMouseDownTarget = event.getTarget();
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 501);
-    }
-
-    private void handleMouseButtonUp(BufferedWriter writer, Event event) throws IOException {
-        lastKeySequenceEvent = null;
-
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 502);
-    }
-
-    private void handleMouseDragAndDrop(BufferedWriter writer, Event event) throws IOException {
-        commitFocusEvent();
-
-        MouseDragAndDrop dragEvent = (MouseDragAndDrop) event.getType();
-        lastMouseClickEvent = new StructureNode("MouseDrag");
-        lastMouseDownTarget = null;
-
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
-                             dragEvent.getXStart(), dragEvent.getYStart(), 501);
-        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
-                             dragEvent.getX(), dragEvent.getY(), 506);
-
-        structure.children.add(lastMouseClickEvent);
-    }
-
-    private void handleKeyPressed(BufferedWriter writer, Event event) throws IOException {
-        commitFocusEvent();
-
-        if (lastKeySequenceEvent == null) {
-            lastKeySequenceEvent = structure.add("KeySequence");
-        }
-        lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
-
-        writeKeyEvent(writer, event, 401);
-    }
-
-    private void handleKeyReleased(BufferedWriter writer, Event event) throws IOException {
-        commitFocusEvent();
-
-        writeKeyEvent(writer, event, 402);
-    }
-
-    private void handleTextInput(BufferedWriter writer, Event event) throws IOException {
-        List<Event> textEvents = ((TextInput) event.getType()).getTextInputEvents();
-
-        // just split the text event into its key events again
-        for (Event textEvent : textEvents) {
-            if (textEvent.getType() instanceof KeyPressed) {
-                handleKeyPressed(writer, textEvent);
-            }
-            else if (textEvent.getType() instanceof KeyReleased) {
-                handleKeyReleased(writer, textEvent);
-            }
-        }
-    }
-
-    private void getMenuElements(List<IGUIElement> elements, GUIModel model) {
-        for (IGUIElement child : elements) {
-            if (child instanceof JFCMenuButton || child instanceof JFCMenu) {
-                menuElements.put(((JFCGUIElement) child).getName().replaceAll("^\"|\"$", ""),
-                                 (JFCGUIElement) child);
-            }
-            getMenuElements(model.getChildren(child), model);
-        }
-    }
-
-    private Stack<JFCGUIElement> findMenuItemHierarchy(JFCGUIElement item) {
-        Stack<JFCGUIElement> elements = new Stack<>();
-
-        // find line that contains this menu item name
-        int lineOfItem = -1;
-        for (int i = 0; i < menuList.size(); i++) {
-            String name = "\"" + menuList.get(i).trim().toLowerCase() + "\"";
-            if (name.equals(item.getName().trim().toLowerCase())) {
-                lineOfItem = i;
-            }
-        }
-
-        // now go backwards until the toplevel menu is found
-        int oldIndent = Integer.MAX_VALUE;
-        for (int j = lineOfItem; j >= 0; j--) {
-            String stripped = menuList.get(j).replaceFirst("^ *", "");
-            int indent = menuList.get(j).length() - stripped.length();
-
-            if (indent < oldIndent) {
-                // this is a parent submenu
-                elements.push(menuElements.get(stripped));
-                oldIndent = indent;
-            }
-        }
-
-        return elements;
-    }
-
-    private void commitFocusEvent() {
-        if (lastFocusChangeEvent != null) {
-            structure.children.add(lastFocusChangeEvent);
-            lastFocusChangeEvent = null;
-        }
-    }
-
-    private void generateFullClick(BufferedWriter writer, Event event, JFCGUIElement target)
-        throws IOException
-    {
-        lastMouseClickEvent = new StructureNode("MouseClick");
-        lastMouseDownTarget = event.getTarget();
-
-        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 501);
-        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 502);
-        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 500);
-        writeItemActionEvent(writer, event);
-
-        structure.children.add(lastMouseClickEvent);
-        structure.children.add(lastItemActionEvent);
-
-        lastMouseDownTarget = null;
-        lastMouseClickEvent = null;
-    }
-
-    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
-        writeLine(writer, "</Record>");
-
-        // write the recording's structure
-        writeLine(writer, "<Structure>");
-        writer.write(structure.toString());
-        // close root element
-        writeLine(writer, "</Structure>");
-    }
-
-    private void writeJacaretoXML(List<Event> sequence,
-                                  String filename,
-                                  String classpath,
-                                  String basepath,
-                                  String classpathext)
-    {
-        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
-
-        try {
-            writeJacaretoHead(writer, classpath, basepath, classpathext);
-            writeJacaretoEvents(writer, sequence);
-            writeJacaretoTail(writer);
-            writeLine(writer, "</JacaretoStructure>");
-
-            writer.flush();
-            writer.close();
-        }
-        catch (IOException e) {
-            Console.printerrln("Unable to write Jacareto replay file " + filename);
-        }
-    }
-
-    /**
-     * <p>
-     * Helper function that opens the replay file for writing.
-     * </p>
-     * 
-     * @param filename
-     *            name and path of the replay file
-     * @param encoding
-     *            file encoding, empty string for platform default
-     * @return {@link OutputStreamWriter} that writes to the replay file
-     */
-    private OutputStreamWriter openReplayFile(String filename) {
-        File file = new File(filename);
-        boolean fileCreated;
-        try {
-            fileCreated = file.createNewFile();
-            if (!fileCreated) {
-                Console.traceln(Level.INFO, "Created logfile " + filename);
-            }
-            else {
-                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
-            }
-        }
-        catch (IOException e) {
-            Console.printerrln("Unable to create file " + filename);
-            Console.logException(e);
-        }
-        OutputStreamWriter writer = null;
-        try {
-            writer = new OutputStreamWriter(new FileOutputStream(file));
-        }
-        catch (IOException e) {
-            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
-            Console.logException(e);
-        }
-        return writer;
-    }
-
-    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
-        JFCGUIElement target = (JFCGUIElement) event.getTarget();
-        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
-        // get rid of the quote symbols in the command because we want to
-        // escape the middle part for XML
-        String cmd = target.getName().substring(1, target.getName().length() - 1);
-
-        //@formatter:off
-        writeLine(writer,
-            "<ItemEvent "
-            + "procTime=\"0\" "
-            + "duration=\"0\" "
-            + "source=\"" + target.getJacaretoHierarchy() + "\" "
-            + "class=\"" + target.getSpecification().getType() + "\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" "
-            + "ID=\"701\" "
-            + "item=\"\" "
-            + "stateChange=\"1\" />"
-        );
-        writeLine(writer,
-            "<ActionEvent "
-            + "procTime=\"0\" "
-            + "duration=\"0\" "
-            + "source=\"" + target.getJacaretoHierarchy() + "\" "
-            + "class=\"" + target.getSpecification().getType() + "\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" "
-            + "ID=\"1001\" "
-            + "command=\"" + StringEscapeUtils.escapeXml(cmd) + "\" "
-            + "modifiers=\"" + getButtonModifier(info) + "\" />"
-        );
-        //@formatter:on
-        lastItemActionEvent = new StructureNode("ItemStateChange");
-        lastItemActionEvent.addRecordable();
-        lastItemActionEvent.addRecordable();
-    }
-
-    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
-        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
-        JFCGUIElement target = (JFCGUIElement) event.getTarget();
-
-        if (currentFocus != null) {
-            lastFocusChangeEvent = new StructureNode("FocusChange");
-
-            // focus lost on old target
-            writeFocusEvent(writer, info, currentFocus, 1005);
-            // focus gained on new target
-            writeFocusEvent(writer, info, target, 1004);
-        }
-        else {
-            // TODO: it seems like Jacareto wants a window activation before
-            // the first focus event but that is not the case in autoquest,
-            // skip for now
-        }
-
-        currentFocus = target;
-    }
-
-    private void writeFocusEvent(BufferedWriter writer,
-                                 KeyboardFocusChange info,
-                                 JFCGUIElement target,
-                                 int jacId) throws IOException
-    {
-        //@formatter:off
-        writeLine(writer,
-            "<FocusEvent "
-            + "procTime=\"0\" "
-            + "duration=\"0\" "
-            + "source=\"" + target.getJacaretoHierarchy() + "\" "
-            + "class=\"" + target.getSpecification().getType() + "\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" "
-            + "ID=\"" + jacId + "\" "
-            + "component=\"null\" "
-            + "root=\"" + target.getJacaretoRoot() + "\" "
-            + "xPos=\"0\" "
-            + "yPos=\"0\" "
-            + "width=\"0\" "
-            + "height=\"0\" "
-            + "isTemporary=\"false\" />" 
-        );
-        //@formatter:on
-        lastFocusChangeEvent.addRecordable();
-    }
-
-    private void writeMouseClickEvent(BufferedWriter writer,
-                                      Event event,
-                                      JFCGUIElement target,
-                                      int duration,
-                                      int jacId) throws IOException
-    {
-        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
-        writeMouseClickEvent(writer, event, target, duration, info.getX(), info.getY(), jacId);
-    }
-
-    private void writeMouseClickEvent(BufferedWriter writer,
-                                      Event event,
-                                      JFCGUIElement target,
-                                      int duration,
-                                      int x,
-                                      int y,
-                                      int jacId) throws IOException
-    {
-        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
-        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
-
-        //@formatter:off
-        writeLine(writer,
-            "<MouseEvent "
-            + "procTime=\"0\" "
-            + "duration=\"" + duration + "\" "
-            + "source=\"" + target.getJacaretoHierarchy() + "\" "
-            + "class=\"" + target.getSpecification().getType() + "\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" "
-            + "ID=\"" + jacId + "\" "
-            + "component=\"null\" "
-            + "root=\"" + target.getJacaretoRoot() + "\" "
-            + "xPos=\"0\" "
-            + "yPos=\"0\" "
-            + "width=\"0\" "
-            + "height=\"0\" "
-            + "when=\"" + event.getTimestamp() + "\" " 
-            + "isConsumed=\"false\">" 
-        );
-        writeLine(writer,
-            "<MouseInfo "
-            + "xPosition=\"" + x + "\" "
-            + "yPosition=\"" + y + "\" "
-            + "rootX=\"0\" "
-            + "rootY=\"0\" "
-            + "clickCount=\"" + clickCount + "\" "
-            + "modifiers=\"" + getButtonModifier(info) + "\" "
-            + "isPopupTrigger=\"false\" />"
-        );
-        writeLine(writer, "</MouseEvent>");
-        //@formatter:on
-
-        lastMouseClickEvent.addRecordable();
-    }
-
-    private int getButtonModifier(MouseButtonInteraction info) {
-        switch (info.getButton())
-        {
-            case LEFT:
-                return 16;
-            case MIDDLE:
-                return 8;
-            case RIGHT:
-                return 4;
-            default:
-                // TODO: handle unknown mouse button
-                return -1;
-        }
-    }
-
-    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
-        KeyInteraction info = (KeyInteraction) event.getType();
-        JFCGUIElement target = (JFCGUIElement) event.getTarget();
-        int keyCode = info.getKey().getVirtualKeyCode();
-
-        applyKeyModifier(info.getKey(), jacId == 401);
-
-        //@formatter:off
-        writeLine(writer,
-            "<KeyEvent "
-            + "procTime=\"0\" "
-            + "duration=\"" + EVENT_DURATION + "\" "
-            + "source=\"" + target.getJacaretoHierarchy() + "\" "
-            + "class=\"" + target.getSpecification().getType() + "\" "
-            + "uuid=\"" + UUID.randomUUID() + "\" "
-            + "ID=\"" + jacId + "\" "
-            + "component=\"null\" "
-            + "root=\"" + target.getJacaretoRoot() + "\" "
-            + "xPos=\"0\" "
-            + "yPos=\"0\" "
-            + "width=\"0\" "
-            + "height=\"0\" "
-            + "when=\"" + event.getTimestamp() + "\" " 
-            + "isConsumed=\"false\">" 
-        );
-        writeLine(writer,
-            "<KeyInfo "
-            + "keyCode=\"" + keyCode + "\" "
-            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
-            + "modifiers=\"" + currentKeyModifiers + "\" />"
-        );
-        
-        writeLine(writer, "</KeyEvent>");
-        
-        lastKeyTypedEvent.addRecordable();
-    }
-    
-    private String getKeyChar (int keyCode) {
-        if (keyCode >= 32 && keyCode < 127) {
-            return String.valueOf((char)keyCode);
-        }
-        return "_NO_LEGAL_XML_CHAR";
-    }
-    
-    private void applyKeyModifier (VirtualKey key, boolean set) {
-        Integer modifier = modifiers.get(key);
-        if (modifier != null) {
-            if (set) {
-                currentKeyModifiers |= modifier;
-            }
-            else {
-                currentKeyModifiers &= ~modifier;
-            }
-        }
-    }
 }
