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
Line 
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
15package de.ugoe.cs.autoquest.plugin.jfc.commands;
16
17import org.apache.commons.lang.StringEscapeUtils;
18
19import java.awt.GraphicsDevice;
20import java.awt.GraphicsEnvironment;
21import java.io.BufferedWriter;
22import java.io.File;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.OutputStreamWriter;
26import java.nio.charset.Charset;
27import java.nio.file.Files;
28import java.nio.file.Paths;
29import java.util.ArrayList;
30import java.util.Calendar;
31import java.util.Collection;
32import java.util.HashMap;
33import java.util.Iterator;
34import java.util.List;
35import java.util.Stack;
36import java.util.UUID;
37import java.util.logging.Level;
38
39import javax.swing.UIManager;
40
41import de.ugoe.cs.autoquest.CommandHelpers;
42import de.ugoe.cs.autoquest.SequenceInstanceOf;
43import de.ugoe.cs.util.console.Command;
44import de.ugoe.cs.autoquest.eventcore.Event;
45import de.ugoe.cs.autoquest.eventcore.IEventTarget;
46import de.ugoe.cs.autoquest.eventcore.gui.*;
47import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
48import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
49import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
50import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
51import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenu;
52import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenuButton;
53import de.ugoe.cs.util.console.Console;
54import de.ugoe.cs.util.console.GlobalDataContainer;
55
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 {
65
66    /**
67     * <p>
68     * Helper class for the tree like structure part within a Jacareto file.
69     * </p>
70     */
71    private static class StructureNode {
72
73        /**
74         * <p>
75         * Keeps track of the next structure node id.
76         * </p>
77         */
78        public static int nextRef = 0;
79
80        /**
81         * <p>
82         * The node's type packaged into an XML string.
83         * </p>
84         */
85        public String content;
86
87        /**
88         * <p>
89         * This node's children.
90         * </p>
91         */
92        public ArrayList<StructureNode> children;
93
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        }
107
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        }
118
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        }
130
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;
143        }
144
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());
155        }
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        }
176    }
177
178    /**
179     * <p>
180     * The time it takes for Jacareto to replay an event in ms.
181     * </p>
182     */
183    private static final int EVENT_DURATION = 150;
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     */
190    private static final int DOUBLE_CLICK_DURATION = 50;
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     */
198    private static final int STARTUP_DELAY = 10000;
199
200    /**
201     * <p>
202     * The GUI element which is currently focused.
203     * </p>
204     */
205    private JFCGUIElement currentFocus;
206
207    /**
208     * <p>
209     * A tree of StructureNodes which represents the structure part inside a Jacareto XML file.
210     * </p>
211     */
212    private StructureNode structure;
213
214    /**
215     * <p>
216     * XML structure for key events modeled as StructureNodes.
217     * </p>
218     */
219    private StructureNode lastKeySequenceEvent;
220
221    /**
222     * <p>
223     * XML structure for key events modeled as StructureNodes.
224     * </p>
225     */
226    private StructureNode lastKeyTypedEvent;
227
228    /**
229     * <p>
230     * Bitmask which represents currently used key modifiers (such as shift etc).
231     * </p>
232     */
233    private int currentKeyModifiers;
234
235    /**
236     * <p>
237     * Maps VirtualKey objects for modifier keys back to AWT Event codes.
238     * </p>
239     */
240    private HashMap<VirtualKey, Integer> modifiers;
241
242    /**
243     * <p>
244     * XML structure for mouse events modeled as StructureNodes.
245     * </p>
246     */
247    private StructureNode lastMouseClickEvent;
248
249    /**
250     * <p>
251     * XML structure for focus events modeled as StructureNodes.
252     * </p>
253     */
254    private StructureNode lastFocusChangeEvent;
255
256    /**
257     * <p>
258     * XML structure for item and action events modeled as StructureNodes.
259     * </p>
260     */
261    private StructureNode lastItemActionEvent;
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     */
269    private IEventTarget lastMouseDownTarget;
270
271    /**
272     * <p>
273     * Associates the name of a menu element with its corresponding JFCGUIElement.
274     * </p>
275     */
276    private HashMap<String, JFCGUIElement> menuElements;
277
278    /**
279     * <p>
280     * The menu hierarchy.
281     * </p>
282     */
283    private List<String> menuList;
284
285    /*
286     * (non-Javadoc)
287     *
288     * @see de.ugoe.cs.util.console.Command#help()
289     */
290    @Override
291    public String help() {
292        return "generateJacaretoReplay <filename> <sequences> <class> <basepath> <classpathext> {<menufile>}";
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;
305        String classpath;
306        String basepath;
307        String classpathext;
308        try {
309            filename = (String) parameters.get(0);
310            sequencesName = (String) parameters.get(1);
311            classpath = (String) parameters.get(2);
312            basepath = (String) parameters.get(3);
313            classpathext = (String) parameters.get(4);
314        }
315        catch (Exception e) {
316            throw new IllegalArgumentException();
317        }
318
319        if (parameters.size() > 5) {
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            }
329        }
330
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;
343        menuElements = new HashMap<>();
344        modifiers = createModifierMap();
345
346        int index = 1;
347        for (List<Event> sequence : sequences) {
348            reinit();
349            writeJacaretoXML(sequence, filename + "_" + index, classpath, basepath, classpathext);
350            index++;
351        }
352    }
353
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
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     */
393    private void writeLine(BufferedWriter writer, String line) throws IOException {
394        writer.write(line);
395        writer.newLine();
396    }
397
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     */
414    private void writeJacaretoHead(BufferedWriter writer,
415                                   String classname,
416                                   String basepath,
417                                   String classpathext) throws IOException
418    {
419        Calendar now = Calendar.getInstance();
420
421        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
422        writeLine(writer, "<JacaretoStructure>");
423        writeLine(writer, "<Record>");
424
425        //@formatter:off
426        writeLine(writer, "<Calendar "
427            + "procTime=\"0\" "
428            + "duration=\"0\" "
429            + "year=\"" + now.get(Calendar.YEAR) + "\" "
430            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
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        );
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        );
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\" />");
449        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
450        writeLine(writer, "<ApplicationStarter "
451            + "procTime=\"" + STARTUP_DELAY + "\" "
452            + "duration=\"" + STARTUP_DELAY + "\" "
453            + "name=\"Autoquest Replay\" "
454            + "class=\"" + classname + "\" "
455            + "initclass=\"\" "
456            + "basepath=\"" + basepath + "\" "
457            + "classpathext=\"" + classpathext + "\" "
458            + "detectDuration=\"false\" "
459            + "captureparams=\"\" "
460            + "replayparams=\"\" "
461            + "uuid=\"" + UUID.randomUUID() + "\" />"
462        );
463        //@formatter:on
464    }
465
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     */
477    private void writeJacaretoEvents(BufferedWriter writer, List<Event> sequence)
478        throws IOException
479    {
480        structure = new StructureNode("RootElement");
481        // reference the elements that we included in the header
482        structure.addRecordable(); // Calendar
483        structure.addRecordable(); // SystemInfo
484        structure.addRecordable(); // KeyboardState
485        structure.addRecordable(); // ComponentMode
486        structure.addRecordable(); // ApplicationStarter
487
488        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
489            Event event = eventIter.next();
490
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                        }
510
511                        Stack<JFCGUIElement> hierarchy =
512                            findMenuItemHierarchy((JFCGUIElement) event.getTarget());
513
514                        while (!hierarchy.empty()) {
515                            generateFullClick(writer, event, hierarchy.pop());
516                        }
517                        continue;
518                    }
519                }
520
521                handleMouseClick(writer, event);
522            }
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 {
539                Console.traceln(Level.WARNING, "No handler for event \"" + event + "\". Skipped.");
540            }
541        }
542    }
543
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);
563
564                    lastMouseClickEvent = null;
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 {
631        commitFocusEvent();
632        lastKeySequenceEvent = null;
633
634        lastMouseClickEvent = new StructureNode("MouseClick");
635        lastMouseDownTarget = event.getTarget();
636        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 501);
637    }
638
639    private void handleMouseButtonUp(BufferedWriter writer, Event event) throws IOException {
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
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
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
721                elements.push(menuElements.get(stripped));
722                oldIndent = indent;
723            }
724        }
725
726        return elements;
727    }
728
729    private void commitFocusEvent() {
730        if (lastFocusChangeEvent != null) {
731            structure.children.add(lastFocusChangeEvent);
732            lastFocusChangeEvent = null;
733        }
734    }
735
736    private void generateFullClick(BufferedWriter writer, Event event, JFCGUIElement target)
737        throws IOException
738    {
739        lastMouseClickEvent = new StructureNode("MouseClick");
740        lastMouseDownTarget = event.getTarget();
741
742        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 501);
743        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 502);
744        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 500);
745        writeItemActionEvent(writer, event);
746
747        structure.children.add(lastMouseClickEvent);
748        structure.children.add(lastItemActionEvent);
749
750        lastMouseDownTarget = null;
751        lastMouseClickEvent = null;
752    }
753
754    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
755        writeLine(writer, "</Record>");
756
757        // write the recording's structure
758        writeLine(writer, "<Structure>");
759        writer.write(structure.toString());
760        // close root element
761        writeLine(writer, "</Structure>");
762    }
763
764    private void writeJacaretoXML(List<Event> sequence,
765                                  String filename,
766                                  String classpath,
767                                  String basepath,
768                                  String classpathext)
769    {
770        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
771
772        try {
773            writeJacaretoHead(writer, classpath, basepath, classpathext);
774            writeJacaretoEvents(writer, sequence);
775            writeJacaretoTail(writer);
776            writeLine(writer, "</JacaretoStructure>");
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
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    }
823
824    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
825        JFCGUIElement target = (JFCGUIElement) event.getTarget();
826        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
827        // get rid of the quote symbols in the command because we want to
828        // escape the middle part for XML
829        String cmd = target.getName().substring(1, target.getName().length() - 1);
830
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\" "
851            + "command=\"" + StringEscapeUtils.escapeXml(cmd) + "\" "
852            + "modifiers=\"" + getButtonModifier(info) + "\" />"
853        );
854        //@formatter:on
855        lastItemActionEvent = new StructureNode("ItemStateChange");
856        lastItemActionEvent.addRecordable();
857        lastItemActionEvent.addRecordable();
858    }
859
860    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
861        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
862        JFCGUIElement target = (JFCGUIElement) event.getTarget();
863
864        if (currentFocus != null) {
865            lastFocusChangeEvent = new StructureNode("FocusChange");
866
867            // focus lost on old target
868            writeFocusEvent(writer, info, currentFocus, 1005);
869            // focus gained on new target
870            writeFocusEvent(writer, info, target, 1004);
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
904        lastFocusChangeEvent.addRecordable();
905    }
906
907    private void writeMouseClickEvent(BufferedWriter writer,
908                                      Event event,
909                                      JFCGUIElement target,
910                                      int duration,
911                                      int jacId) throws IOException
912    {
913        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
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();
926        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
927
928        //@formatter:off
929        writeLine(writer,
930            "<MouseEvent "
931            + "procTime=\"0\" "
932            + "duration=\"" + duration + "\" "
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\" "
943            + "when=\"" + event.getTimestamp() + "\" "
944            + "isConsumed=\"false\">"
945        );
946        writeLine(writer,
947            "<MouseInfo "
948            + "xPosition=\"" + x + "\" "
949            + "yPosition=\"" + y + "\" "
950            + "rootX=\"0\" "
951            + "rootY=\"0\" "
952            + "clickCount=\"" + clickCount + "\" "
953            + "modifiers=\"" + getButtonModifier(info) + "\" "
954            + "isPopupTrigger=\"false\" />"
955        );
956        writeLine(writer, "</MouseEvent>");
957        //@formatter:on
958
959        lastMouseClickEvent.addRecordable();
960    }
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    }
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();
980        int keyCode = info.getKey().getVirtualKeyCode();
981
982        applyKeyModifier(info.getKey(), jacId == 401);
983
984        //@formatter:off
985        writeLine(writer,
986            "<KeyEvent "
987            + "procTime=\"0\" "
988            + "duration=\"" + EVENT_DURATION + "\" "
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 "
1004            + "keyCode=\"" + keyCode + "\" "
1005            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
1006            + "modifiers=\"" + currentKeyModifiers + "\" />"
1007        );
1008       
1009        writeLine(writer, "</KeyEvent>");
1010       
1011        lastKeyTypedEvent.addRecordable();
1012    }
1013   
1014    private String getKeyChar (int keyCode) {
1015        if (keyCode >= 32 && keyCode < 127) {
1016            return String.valueOf((char)keyCode);
1017        }
1018        return "_NO_LEGAL_XML_CHAR";
1019    }
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    }
1032}
Note: See TracBrowser for help on using the repository browser.