source: trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/JFCJacaretoReplayGenerator.java @ 2250

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