source: trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/commands/CMDgenerateJacaretoReplay.java @ 1901

Last change on this file since 1901 was 1901, checked in by dmay, 9 years ago

always finish mouse clicks correctly

  • Property svn:mime-type set to text/plain
File size: 35.3 KB
RevLine 
[1671]1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
[1673]15package de.ugoe.cs.autoquest.plugin.jfc.commands;
[1671]16
[1872]17import org.apache.commons.lang.StringEscapeUtils;
18
19import java.awt.GraphicsDevice;
20import java.awt.GraphicsEnvironment;
[1671]21import java.io.BufferedWriter;
[1673]22import java.io.File;
23import java.io.FileOutputStream;
[1671]24import java.io.IOException;
[1673]25import java.io.OutputStreamWriter;
[1825]26import java.nio.charset.Charset;
27import java.nio.file.Files;
28import java.nio.file.Paths;
[1671]29import java.util.ArrayList;
[1715]30import java.util.Calendar;
[1671]31import java.util.Collection;
[1704]32import java.util.HashMap;
[1671]33import java.util.Iterator;
34import java.util.List;
[1825]35import java.util.Stack;
[1678]36import java.util.UUID;
[1673]37import java.util.logging.Level;
[1671]38
[1836]39import javax.swing.UIManager;
40
[1671]41import de.ugoe.cs.autoquest.CommandHelpers;
42import de.ugoe.cs.autoquest.SequenceInstanceOf;
[1673]43import de.ugoe.cs.util.console.Command;
[1671]44import de.ugoe.cs.autoquest.eventcore.Event;
[1809]45import de.ugoe.cs.autoquest.eventcore.IEventTarget;
[1685]46import de.ugoe.cs.autoquest.eventcore.gui.*;
[1825]47import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
48import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
[1691]49import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
[1673]50import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
[1825]51import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenu;
52import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenuButton;
[1671]53import de.ugoe.cs.util.console.Console;
54import de.ugoe.cs.util.console.GlobalDataContainer;
55
[1856]56/**
57 * <p>
58 * Command to create a Jacareto xml replay file from stored sessions.
59 * </p>
60 *
61 * @author Daniel May
62 * @version 1.0
63 */
64public class CMDgenerateJacaretoReplay implements Command {
[1689]65
[1856]66    /**
67     * <p>
68     * Helper class for the tree like structure part within a Jacareto file.
69     * </p>
70     */
71    private static class StructureNode {
[1688]72
[1856]73        /**
74         * <p>
75         * Keeps track of the next structure node id.
76         * </p>
77         */
78        public static int nextRef = 0;
[1688]79
[1856]80        /**
81         * <p>
82         * The node's type packaged into an XML string.
83         * </p>
84         */
85        public String content;
[1689]86
[1856]87        /**
88         * <p>
89         * This node's children.
90         * </p>
91         */
92        public ArrayList<StructureNode> children;
[1714]93
[1856]94        /**
95         * <p>
96         * Constructor. Creates a new StructureNode of a specified type and builds its Jacareto XML
97         * representation.
98         * </p>
99         *
100         * @param type
101         *            the type of this StructureNode, for example: 'MouseDownEvent'
102         */
103        public StructureNode(String type) {
104            setContent(type);
105            children = new ArrayList<StructureNode>();
106        }
[1688]107
[1856]108        /**
109         * <p>
110         * Constructor. Creates a StructureNode of type 'Recordable' with a valid id and builds its
111         * Jacareto XML representation.
112         * </p>
113         */
114        public StructureNode() {
115            content = "<Recordable ref=\"" + (nextRef++) + "\" />";
116            children = new ArrayList<StructureNode>();
117        }
[1689]118
[1856]119        /**
120         * <p>
121         * Builds the XML representation of a Jacareto structure type.
122         * </p>
123         *
124         * @param type
125         *            the type of this StructureNode, for example: 'MouseDownEvent'
126         */
127        public void setContent(String type) {
128            content = "<StructureElement class=\"jacareto.struct." + type + "\">";
129        }
[1688]130
[1856]131        /**
132         * <p>
133         * Adds a new StructureNode as a child of this node.
134         * </p>
135         *
136         * @param type
137         *            the type of the child node, for example: 'MouseDownEvent'
138         */
139        public StructureNode add(String type) {
140            StructureNode node = new StructureNode(type);
141            children.add(node);
142            return node;
[1688]143        }
144
[1856]145        /**
146         * <p>
147         * Builds the XML representation of a Jacareto structure type.
148         * </p>
149         *
150         * @param type
151         *            the type of this StructureNode, for example: 'MouseDownEvent'
152         */
153        public void addRecordable() {
154            children.add(new StructureNode());
[1688]155        }
[1856]156
157        /**
158         * <p>
159         * Returns a Jacareto XML representation of this StructureNode, includin all its children.
160         * </p>
161         */
162        @Override
163        public String toString() {
164            String separator = System.getProperty("line.separator");
165            String result = content + separator;
166
167            for (StructureNode child : children) {
168                result += child.toString();
169            }
170
171            if (content.endsWith("/>")) {
172                return result;
173            }
174            return result + "</StructureElement>" + separator;
175        }
[1688]176    }
177
[1856]178    /**
179     * <p>
180     * The time it takes for Jacareto to replay an event in ms.
181     * </p>
182     */
[1719]183    private static final int EVENT_DURATION = 150;
[1856]184
185    /**
186     * <p>
187     * The time it takes for Jacareto to replay each part of a double click event in ms.
188     * </p>
189     */
[1808]190    private static final int DOUBLE_CLICK_DURATION = 50;
[1856]191
192    /**
193     * <p>
194     * Application startup time in ms. The application needs to be fully initialized before Jacareto
195     * can start replaying.
196     * </p>
197     */
[1808]198    private static final int STARTUP_DELAY = 10000;
[1719]199
[1856]200    /**
201     * <p>
202     * The GUI element which is currently focused.
203     * </p>
204     */
[1686]205    private JFCGUIElement currentFocus;
[1856]206
207    /**
208     * <p>
209     * A tree of StructureNodes which represents the structure part inside a Jacareto XML file.
210     * </p>
211     */
[1688]212    private StructureNode structure;
[1704]213
[1856]214    /**
215     * <p>
216     * XML structure for key events modeled as StructureNodes.
217     * </p>
218     */
[1701]219    private StructureNode lastKeySequenceEvent;
[1856]220
221    /**
222     * <p>
223     * XML structure for key events modeled as StructureNodes.
224     * </p>
225     */
[1701]226    private StructureNode lastKeyTypedEvent;
[1856]227
228    /**
229     * <p>
230     * Bitmask which represents currently used key modifiers (such as shift etc).
231     * </p>
232     */
[1704]233    private int currentKeyModifiers;
234
[1856]235    /**
236     * <p>
237     * Maps VirtualKey objects for modifier keys back to AWT Event codes.
238     * </p>
239     */
[1704]240    private HashMap<VirtualKey, Integer> modifiers;
[1712]241
[1856]242    /**
243     * <p>
244     * XML structure for mouse events modeled as StructureNodes.
245     * </p>
246     */
[1688]247    private StructureNode lastMouseClickEvent;
[1856]248
249    /**
250     * <p>
251     * XML structure for focus events modeled as StructureNodes.
252     * </p>
253     */
[1688]254    private StructureNode lastFocusChangeEvent;
[1856]255
256    /**
257     * <p>
258     * XML structure for item and action events modeled as StructureNodes.
259     * </p>
260     */
[1688]261    private StructureNode lastItemActionEvent;
[1856]262
263    /**
264     * <p>
265     * The target of the last mouseDownEvent. It is necessary to save this because mouse down and up
266     * targets can differ.
267     * </p>
268     */
[1809]269    private IEventTarget lastMouseDownTarget;
[1856]270
271    /**
272     * <p>
273     * Associates the name of a menu element with its corresponding JFCGUIElement.
274     * </p>
275     */
[1825]276    private HashMap<String, JFCGUIElement> menuElements;
[1856]277
278    /**
279     * <p>
280     * The menu hierarchy.
281     * </p>
282     */
[1825]283    private List<String> menuList;
[1688]284
[1671]285    /*
286     * (non-Javadoc)
287     *
288     * @see de.ugoe.cs.util.console.Command#help()
289     */
290    @Override
291    public String help() {
[1825]292        return "generateJacaretoReplay <filename> <sequences> <class> <basepath> <classpathext> {<menufile>}";
[1671]293    }
294
295    /*
296     * (non-Javadoc)
297     *
298     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
299     */
300    @SuppressWarnings("unchecked")
301    @Override
302    public void run(List<Object> parameters) {
303        String filename;
304        String sequencesName;
[1712]305        String classpath;
306        String basepath;
307        String classpathext;
[1671]308        try {
309            filename = (String) parameters.get(0);
310            sequencesName = (String) parameters.get(1);
[1712]311            classpath = (String) parameters.get(2);
312            basepath = (String) parameters.get(3);
313            classpathext = (String) parameters.get(4);
[1671]314        }
315        catch (Exception e) {
316            throw new IllegalArgumentException();
317        }
318
[1712]319        if (parameters.size() > 5) {
[1825]320            try {
321                menuList =
322                    Files.readAllLines(Paths.get((String) parameters.get(5)),
323                                       Charset.defaultCharset());
324            }
325            catch (IOException e) {
326                Console.printerrln("Unable to open menu file");
327                Console.logException(e);
328            }
[1712]329        }
330
[1671]331        Collection<List<Event>> sequences = null;
332        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName);
333        if (dataObject == null) {
334            CommandHelpers.objectNotFoundMessage(sequencesName);
335            return;
336        }
337        if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
338            CommandHelpers.objectNotType(sequencesName, "Collection<List<Event<?>>>");
339            return;
340        }
341
342        sequences = (Collection<List<Event>>) dataObject;
[1825]343        menuElements = new HashMap<>();
[1856]344        modifiers = createModifierMap();
[1713]345
[1857]346        int index = 1;
[1872]347        for (List<Event> sequence : sequences) {
[1901]348            reinit();
[1872]349            writeJacaretoXML(sequence, filename + "_" + index, classpath, basepath, classpathext);
[1857]350            index++;
351        }
[1671]352    }
353
[1901]354    private void reinit() {
355        currentFocus = null;
356        currentKeyModifiers = 0;
357        StructureNode.nextRef = 0;
358        lastFocusChangeEvent = null;
359        lastItemActionEvent = null;
360        lastKeySequenceEvent = null;
361        lastKeyTypedEvent = null;
362        lastMouseClickEvent = null;
363        lastMouseDownTarget = null;
364    }
365
[1856]366    /**
367     * <p>
368     * Associates keyboard modifier keys with their AWT event codes.
369     * </p>
370     */
371    private HashMap<VirtualKey, Integer> createModifierMap() {
372        HashMap<VirtualKey, Integer> result = new HashMap<>();
373
374        result.put(VirtualKey.SHIFT, 1);
375        result.put(VirtualKey.CONTROL, 2);
376        result.put(VirtualKey.ALT, 8);
377        result.put(VirtualKey.ALT_GRAPH, 32);
378
379        return result;
380    }
381
382    /**
383     * <p>
384     * Writes a line and creates a new line.
385     * </p>
386     *
387     * @param writer
388     *            the BufferedWriter which writes the XML
389     * @param line
390     *            the line to write
391     *
392     */
[1671]393    private void writeLine(BufferedWriter writer, String line) throws IOException {
394        writer.write(line);
395        writer.newLine();
396    }
397
[1856]398    /**
399     * <p>
400     * Writes the Jacareto XML head part. This mainly contains information about the state of the
401     * system when the replay was captured.
402     *
403     * @param writer
404     *            the BufferedWriter which writes the XML
405     * @param classname
406     *            name of the main class of the program that will be replayed
407     * @param basepath
408     *            a basepath that is prepended to all paths specified in classpathext
409     * @param classpathext
410     *            additional required resources (e.g. jar files)
411     *
412     *            </p>
413     */
[1712]414    private void writeJacaretoHead(BufferedWriter writer,
415                                   String classname,
416                                   String basepath,
417                                   String classpathext) throws IOException
418    {
[1715]419        Calendar now = Calendar.getInstance();
420
[1671]421        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
422        writeLine(writer, "<JacaretoStructure>");
423        writeLine(writer, "<Record>");
424
[1715]425        //@formatter:off
[1716]426        writeLine(writer, "<Calendar "
427            + "procTime=\"0\" "
[1715]428            + "duration=\"0\" "
429            + "year=\"" + now.get(Calendar.YEAR) + "\" "
[1716]430            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
[1715]431            + "date=\"" + now.get(Calendar.DAY_OF_MONTH) + "\" "
432            + "hour=\"" + now.get(Calendar.HOUR_OF_DAY) + "\" "
433            + "min=\"" + now.get(Calendar.MINUTE) + "\" "
434            + "sec=\"" + now.get(Calendar.SECOND) + "\" "
435            + "uuid=\"" + UUID.randomUUID() + "\" />"
436        );
[1872]437        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
438        writeLine(writer, "<SystemInfo "
439            + "procTime=\"0\" "
440            + "duration=\"0\" "
441            + "screenWidth=\"" + gd.getDisplayMode().getWidth() + "\" "
442            + "screenHeight=\"" + gd.getDisplayMode().getHeight() + "\" "
443            + "javaVersion=\"" + System.getProperty("java.version") + "\" "
444            + "lookAndFeel=\"" + UIManager.getLookAndFeel().getClass().getName() + "\" "
445            + "uuid=\"" + UUID.randomUUID() + "\" />"
446        );
[1671]447        writeLine(writer,
448                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
[1678]449        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
[1712]450        writeLine(writer, "<ApplicationStarter "
[1808]451            + "procTime=\"" + STARTUP_DELAY + "\" "
452            + "duration=\"" + STARTUP_DELAY + "\" "
[1712]453            + "name=\"Autoquest Replay\" "
454            + "class=\"" + classname + "\" "
[1825]455            + "initclass=\"\" "
[1712]456            + "basepath=\"" + basepath + "\" "
457            + "classpathext=\"" + classpathext + "\" "
458            + "detectDuration=\"false\" "
459            + "captureparams=\"\" "
460            + "replayparams=\"\" "
[1715]461            + "uuid=\"" + UUID.randomUUID() + "\" />"
[1712]462        );
463        //@formatter:on
[1671]464    }
465
[1856]466    /**
467     * <p>
468     * Writes Jacareto XML code for all events within the Autoquest sequences.
469     * </p>
470     *
471     * @param writer
472     *            the BufferedWriter which writes the XML
473     * @param sequences
474     *            the Autoquest sequences
475     *
476     */
[1857]477    private void writeJacaretoEvents(BufferedWriter writer, List<Event> sequence)
[1671]478        throws IOException
479    {
[1689]480        structure = new StructureNode("RootElement");
[1671]481        // reference the elements that we included in the header
[1689]482        structure.addRecordable(); // Calendar
483        structure.addRecordable(); // SystemInfo
484        structure.addRecordable(); // KeyboardState
485        structure.addRecordable(); // ComponentMode
486        structure.addRecordable(); // ApplicationStarter
[1671]487
[1857]488        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
489            Event event = eventIter.next();
[1671]490
[1857]491            if (event.getType() instanceof MouseButtonDown) {
492                handleMouseButtonDown(writer, event);
493            }
494            else if (event.getType() instanceof MouseButtonUp) {
495                handleMouseButtonUp(writer, event);
496            }
497            else if (event.getType() instanceof MouseDoubleClick) {
498                handleMouseDoubleClick(writer, event);
499            }
500            else if (event.getType() instanceof MouseClick) {
501                if (event.getTarget() instanceof JFCMenuButton) {
502                    // if a menu file was provided, use the improved event
503                    // generation
504                    if (menuList != null) {
505                        if (menuElements.isEmpty()) {
506                            // parse the menu structure
507                            GUIModel model = ((IGUIElement) event.getTarget()).getGUIModel();
508                            getMenuElements(model.getRootElements(), model);
509                        }
[1825]510
[1857]511                        Stack<JFCGUIElement> hierarchy =
512                            findMenuItemHierarchy((JFCGUIElement) event.getTarget());
[1825]513
[1857]514                        while (!hierarchy.empty()) {
515                            generateFullClick(writer, event, hierarchy.pop());
[1825]516                        }
[1857]517                        continue;
[1825]518                    }
[1857]519                }
[1825]520
[1857]521                handleMouseClick(writer, event);
[1671]522            }
[1857]523            else if (event.getType() instanceof KeyboardFocusChange) {
524                handleKeyboardFocusChange(writer, event);
525            }
526            else if (event.getType() instanceof MouseDragAndDrop) {
527                handleMouseDragAndDrop(writer, event);
528            }
529            else if (event.getType() instanceof KeyPressed) {
530                handleKeyPressed(writer, event);
531            }
532            else if (event.getType() instanceof KeyReleased) {
533                handleKeyReleased(writer, event);
534            }
535            else if (event.getType() instanceof TextInput) {
536                handleTextInput(writer, event);
537            }
538            else {
[1872]539                Console.traceln(Level.WARNING, "No handler for event \"" + event + "\". Skipped.");
[1857]540            }
[1671]541        }
542    }
543
[1856]544    // EVENT HANDLERS
545
546    private void handleMouseClick(BufferedWriter writer, Event event) throws IOException {
547        lastKeySequenceEvent = null;
548
549        if (lastMouseClickEvent != null) {
550            if (lastMouseDownTarget == event.getTarget()) {
551                // this is the standard case:
552                // mouseDown, mouseUp and mouseClick sequence
553                // was triggered on this target
554
555                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
556                                     EVENT_DURATION, 500);
557                writeItemActionEvent(writer, event);
558
559                if (lastFocusChangeEvent == null) {
560                    // write structure sequentially
561                    structure.children.add(lastMouseClickEvent);
562                    structure.children.add(lastItemActionEvent);
[1901]563
564                    lastMouseClickEvent = null;
[1856]565                }
566                else {
567                    // with nested structure
568                    structure.children.add(lastItemActionEvent);
569                    lastItemActionEvent.children.add(0, lastFocusChangeEvent);
570                    lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
571
572                    lastFocusChangeEvent = null;
573                    lastMouseClickEvent = null;
574                }
575            }
576            else {
577                // target of mouseDown and mouseClick are different
578                // -> this is, for example, a click on a menu item
579                // within a condensed sequence
580                commitFocusEvent();
581
582                // finish the last click on the old target
583                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
584                                     EVENT_DURATION, 500);
585                structure.children.add(lastMouseClickEvent);
586
587                // and generate a new one
588                generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
589            }
590        }
591        else {
592            // a target was clicked repeatedly:
593            // the condensed sequence contains no mouseDowns or
594            // mouseUps anymore
595            // -> just generate another full click
596            generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
597        }
598
599    }
600
601    private void handleMouseDoubleClick(BufferedWriter writer, Event event) throws IOException {
602        StructureNode multiClick = structure.add("MultipleMouseClick");
603
604        // first click
605        lastMouseClickEvent = multiClick.add("MouseClick");
606        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
607                             DOUBLE_CLICK_DURATION, 501);
608        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
609                             DOUBLE_CLICK_DURATION, 502);
610        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
611                             DOUBLE_CLICK_DURATION, 500);
612        // second click
613        lastMouseClickEvent = multiClick.add("MouseClick");
614        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
615                             DOUBLE_CLICK_DURATION, 501);
616        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
617                             DOUBLE_CLICK_DURATION, 502);
618        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
619                             DOUBLE_CLICK_DURATION, 500);
620
621        lastMouseClickEvent = null;
622
623    }
624
625    private void handleKeyboardFocusChange(BufferedWriter writer, Event event) throws IOException {
626        lastKeySequenceEvent = null;
627        writeFocusChangeEvent(writer, event);
628    }
629
630    private void handleMouseButtonDown(BufferedWriter writer, Event event) throws IOException {
[1838]631        commitFocusEvent();
632        lastKeySequenceEvent = null;
633
[1856]634        lastMouseClickEvent = new StructureNode("MouseClick");
[1838]635        lastMouseDownTarget = event.getTarget();
636        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 501);
637    }
638
[1856]639    private void handleMouseButtonUp(BufferedWriter writer, Event event) throws IOException {
[1838]640        lastKeySequenceEvent = null;
641
642        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 502);
643    }
644
645    private void handleMouseDragAndDrop(BufferedWriter writer, Event event) throws IOException {
646        commitFocusEvent();
647
648        MouseDragAndDrop dragEvent = (MouseDragAndDrop) event.getType();
649        lastMouseClickEvent = new StructureNode("MouseDrag");
650        lastMouseDownTarget = null;
651
652        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
653                             dragEvent.getXStart(), dragEvent.getYStart(), 501);
654        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
655                             dragEvent.getX(), dragEvent.getY(), 506);
656
657        structure.children.add(lastMouseClickEvent);
658    }
659
[1836]660    private void handleKeyPressed(BufferedWriter writer, Event event) throws IOException {
661        commitFocusEvent();
662
663        if (lastKeySequenceEvent == null) {
664            lastKeySequenceEvent = structure.add("KeySequence");
665        }
666        lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
667
668        writeKeyEvent(writer, event, 401);
669    }
670
671    private void handleKeyReleased(BufferedWriter writer, Event event) throws IOException {
672        commitFocusEvent();
673
674        writeKeyEvent(writer, event, 402);
675    }
676
677    private void handleTextInput(BufferedWriter writer, Event event) throws IOException {
678        List<Event> textEvents = ((TextInput) event.getType()).getTextInputEvents();
679
680        // just split the text event into its key events again
681        for (Event textEvent : textEvents) {
682            if (textEvent.getType() instanceof KeyPressed) {
683                handleKeyPressed(writer, textEvent);
684            }
685            else if (textEvent.getType() instanceof KeyReleased) {
686                handleKeyReleased(writer, textEvent);
687            }
688        }
689    }
690
[1825]691    private void getMenuElements(List<IGUIElement> elements, GUIModel model) {
692        for (IGUIElement child : elements) {
693            if (child instanceof JFCMenuButton || child instanceof JFCMenu) {
694                menuElements.put(((JFCGUIElement) child).getName().replaceAll("^\"|\"$", ""),
695                                 (JFCGUIElement) child);
696            }
697            getMenuElements(model.getChildren(child), model);
698        }
699    }
700
701    private Stack<JFCGUIElement> findMenuItemHierarchy(JFCGUIElement item) {
702        Stack<JFCGUIElement> elements = new Stack<>();
703
704        // find line that contains this menu item name
705        int lineOfItem = -1;
706        for (int i = 0; i < menuList.size(); i++) {
707            String name = "\"" + menuList.get(i).trim().toLowerCase() + "\"";
708            if (name.equals(item.getName().trim().toLowerCase())) {
709                lineOfItem = i;
710            }
711        }
712
713        // now go backwards until the toplevel menu is found
714        int oldIndent = Integer.MAX_VALUE;
715        for (int j = lineOfItem; j >= 0; j--) {
716            String stripped = menuList.get(j).replaceFirst("^ *", "");
717            int indent = menuList.get(j).length() - stripped.length();
718
719            if (indent < oldIndent) {
720                // this is a parent submenu
[1834]721                elements.push(menuElements.get(stripped));
[1825]722                oldIndent = indent;
723            }
724        }
725
726        return elements;
727    }
728
[1808]729    private void commitFocusEvent() {
730        if (lastFocusChangeEvent != null) {
731            structure.children.add(lastFocusChangeEvent);
732            lastFocusChangeEvent = null;
733        }
734    }
735
[1825]736    private void generateFullClick(BufferedWriter writer, Event event, JFCGUIElement target)
737        throws IOException
738    {
[1809]739        lastMouseClickEvent = new StructureNode("MouseClick");
740        lastMouseDownTarget = event.getTarget();
741
[1825]742        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 501);
743        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 502);
744        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 500);
[1809]745        writeItemActionEvent(writer, event);
746
747        structure.children.add(lastMouseClickEvent);
748        structure.children.add(lastItemActionEvent);
749
750        lastMouseDownTarget = null;
[1901]751        lastMouseClickEvent = null;
[1714]752    }
753
[1688]754    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
[1671]755        writeLine(writer, "</Record>");
756
757        // write the recording's structure
758        writeLine(writer, "<Structure>");
[1688]759        writer.write(structure.toString());
[1678]760        // close root element
[1671]761        writeLine(writer, "</Structure>");
762    }
763
[1857]764    private void writeJacaretoXML(List<Event> sequence,
[1712]765                                  String filename,
766                                  String classpath,
767                                  String basepath,
768                                  String classpathext)
769    {
[1671]770        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
771
772        try {
[1825]773            writeJacaretoHead(writer, classpath, basepath, classpathext);
[1857]774            writeJacaretoEvents(writer, sequence);
[1688]775            writeJacaretoTail(writer);
[1678]776            writeLine(writer, "</JacaretoStructure>");
[1671]777
778            writer.flush();
779            writer.close();
780        }
781        catch (IOException e) {
782            Console.printerrln("Unable to write Jacareto replay file " + filename);
783        }
784    }
785
[1673]786    /**
787     * <p>
788     * Helper function that opens the replay file for writing.
789     * </p>
790     *
791     * @param filename
792     *            name and path of the replay file
793     * @param encoding
794     *            file encoding, empty string for platform default
795     * @return {@link OutputStreamWriter} that writes to the replay file
796     */
797    private OutputStreamWriter openReplayFile(String filename) {
798        File file = new File(filename);
799        boolean fileCreated;
800        try {
801            fileCreated = file.createNewFile();
802            if (!fileCreated) {
803                Console.traceln(Level.INFO, "Created logfile " + filename);
804            }
805            else {
806                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
807            }
808        }
809        catch (IOException e) {
810            Console.printerrln("Unable to create file " + filename);
811            Console.logException(e);
812        }
813        OutputStreamWriter writer = null;
814        try {
815            writer = new OutputStreamWriter(new FileOutputStream(file));
816        }
817        catch (IOException e) {
818            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
819            Console.logException(e);
820        }
821        return writer;
822    }
[1683]823
[1688]824    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
[1685]825        JFCGUIElement target = (JFCGUIElement) event.getTarget();
826        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
[1883]827        // get rid of the quote symbols in the command because we want to
828        // escape the middle part for XML
[1901]829        String cmd = target.getName().substring(1, target.getName().length() - 1);
830
[1684]831        //@formatter:off
832        writeLine(writer,
833            "<ItemEvent "
834            + "procTime=\"0\" "
835            + "duration=\"0\" "
836            + "source=\"" + target.getJacaretoHierarchy() + "\" "
837            + "class=\"" + target.getSpecification().getType() + "\" "
838            + "uuid=\"" + UUID.randomUUID() + "\" "
839            + "ID=\"701\" "
840            + "item=\"\" "
841            + "stateChange=\"1\" />"
842        );
843        writeLine(writer,
844            "<ActionEvent "
845            + "procTime=\"0\" "
846            + "duration=\"0\" "
847            + "source=\"" + target.getJacaretoHierarchy() + "\" "
848            + "class=\"" + target.getSpecification().getType() + "\" "
849            + "uuid=\"" + UUID.randomUUID() + "\" "
850            + "ID=\"1001\" "
[1883]851            + "command=\"" + StringEscapeUtils.escapeXml(cmd) + "\" "
[1685]852            + "modifiers=\"" + getButtonModifier(info) + "\" />"
[1684]853        );
854        //@formatter:on
[1689]855        lastItemActionEvent = new StructureNode("ItemStateChange");
856        lastItemActionEvent.addRecordable();
857        lastItemActionEvent.addRecordable();
[1684]858    }
859
[1688]860    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
[1686]861        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
862        JFCGUIElement target = (JFCGUIElement) event.getTarget();
863
864        if (currentFocus != null) {
[1689]865            lastFocusChangeEvent = new StructureNode("FocusChange");
[1688]866
[1686]867            // focus lost on old target
[1688]868            writeFocusEvent(writer, info, currentFocus, 1005);
[1686]869            // focus gained on new target
[1688]870            writeFocusEvent(writer, info, target, 1004);
[1686]871        }
872        else {
873            // TODO: it seems like Jacareto wants a window activation before
874            // the first focus event but that is not the case in autoquest,
875            // skip for now
876        }
877
878        currentFocus = target;
879    }
880
881    private void writeFocusEvent(BufferedWriter writer,
882                                 KeyboardFocusChange info,
883                                 JFCGUIElement target,
884                                 int jacId) throws IOException
885    {
886        //@formatter:off
887        writeLine(writer,
888            "<FocusEvent "
889            + "procTime=\"0\" "
890            + "duration=\"0\" "
891            + "source=\"" + target.getJacaretoHierarchy() + "\" "
892            + "class=\"" + target.getSpecification().getType() + "\" "
893            + "uuid=\"" + UUID.randomUUID() + "\" "
894            + "ID=\"" + jacId + "\" "
895            + "component=\"null\" "
896            + "root=\"" + target.getJacaretoRoot() + "\" "
897            + "xPos=\"0\" "
898            + "yPos=\"0\" "
899            + "width=\"0\" "
900            + "height=\"0\" "
901            + "isTemporary=\"false\" />"
902        );
903        //@formatter:on
[1689]904        lastFocusChangeEvent.addRecordable();
[1686]905    }
906
[1825]907    private void writeMouseClickEvent(BufferedWriter writer,
908                                      Event event,
909                                      JFCGUIElement target,
910                                      int duration,
911                                      int jacId) throws IOException
[1683]912    {
[1685]913        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
[1838]914        writeMouseClickEvent(writer, event, target, duration, info.getX(), info.getY(), jacId);
915    }
916
917    private void writeMouseClickEvent(BufferedWriter writer,
918                                      Event event,
919                                      JFCGUIElement target,
920                                      int duration,
921                                      int x,
922                                      int y,
923                                      int jacId) throws IOException
924    {
925        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
[1685]926        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
927
[1683]928        //@formatter:off
929        writeLine(writer,
930            "<MouseEvent "
931            + "procTime=\"0\" "
[1808]932            + "duration=\"" + duration + "\" "
[1683]933            + "source=\"" + target.getJacaretoHierarchy() + "\" "
934            + "class=\"" + target.getSpecification().getType() + "\" "
935            + "uuid=\"" + UUID.randomUUID() + "\" "
936            + "ID=\"" + jacId + "\" "
937            + "component=\"null\" "
938            + "root=\"" + target.getJacaretoRoot() + "\" "
939            + "xPos=\"0\" "
940            + "yPos=\"0\" "
941            + "width=\"0\" "
942            + "height=\"0\" "
[1685]943            + "when=\"" + event.getTimestamp() + "\" "
[1683]944            + "isConsumed=\"false\">"
945        );
946        writeLine(writer,
947            "<MouseInfo "
[1838]948            + "xPosition=\"" + x + "\" "
949            + "yPosition=\"" + y + "\" "
[1683]950            + "rootX=\"0\" "
951            + "rootY=\"0\" "
[1685]952            + "clickCount=\"" + clickCount + "\" "
953            + "modifiers=\"" + getButtonModifier(info) + "\" "
[1683]954            + "isPopupTrigger=\"false\" />"
955        );
956        writeLine(writer, "</MouseEvent>");
957        //@formatter:on
958
[1689]959        lastMouseClickEvent.addRecordable();
[1683]960    }
[1685]961
962    private int getButtonModifier(MouseButtonInteraction info) {
963        switch (info.getButton())
964        {
965            case LEFT:
966                return 16;
967            case MIDDLE:
968                return 8;
969            case RIGHT:
970                return 4;
971            default:
972                // TODO: handle unknown mouse button
973                return -1;
974        }
975    }
[1691]976
977    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
978        KeyInteraction info = (KeyInteraction) event.getType();
979        JFCGUIElement target = (JFCGUIElement) event.getTarget();
[1704]980        int keyCode = info.getKey().getVirtualKeyCode();
[1691]981
[1704]982        applyKeyModifier(info.getKey(), jacId == 401);
[1712]983
[1691]984        //@formatter:off
985        writeLine(writer,
986            "<KeyEvent "
987            + "procTime=\"0\" "
[1719]988            + "duration=\"" + EVENT_DURATION + "\" "
[1691]989            + "source=\"" + target.getJacaretoHierarchy() + "\" "
990            + "class=\"" + target.getSpecification().getType() + "\" "
991            + "uuid=\"" + UUID.randomUUID() + "\" "
992            + "ID=\"" + jacId + "\" "
993            + "component=\"null\" "
994            + "root=\"" + target.getJacaretoRoot() + "\" "
995            + "xPos=\"0\" "
996            + "yPos=\"0\" "
997            + "width=\"0\" "
998            + "height=\"0\" "
999            + "when=\"" + event.getTimestamp() + "\" "
1000            + "isConsumed=\"false\">"
1001        );
1002        writeLine(writer,
1003            "<KeyInfo "
[1704]1004            + "keyCode=\"" + keyCode + "\" "
1005            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
1006            + "modifiers=\"" + currentKeyModifiers + "\" />"
[1691]1007        );
1008       
1009        writeLine(writer, "</KeyEvent>");
1010       
[1701]1011        lastKeyTypedEvent.addRecordable();
[1691]1012    }
1013   
[1701]1014    private String getKeyChar (int keyCode) {
1015        if (keyCode >= 32 && keyCode < 127) {
1016            return String.valueOf((char)keyCode);
1017        }
[1712]1018        return "_NO_LEGAL_XML_CHAR";
[1691]1019    }
[1704]1020   
1021    private void applyKeyModifier (VirtualKey key, boolean set) {
1022        Integer modifier = modifiers.get(key);
1023        if (modifier != null) {
1024            if (set) {
1025                currentKeyModifiers |= modifier;
1026            }
1027            else {
1028                currentKeyModifiers &= ~modifier;
1029            }
1030        }
1031    }
[1671]1032}
Note: See TracBrowser for help on using the repository browser.