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

Last change on this file since 2249 was 2249, checked in by pharms, 7 years ago
  • solved some findbugs issues
File size: 34.2 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            String result = content + separator;
161
162            for (StructureNode child : children) {
163                result += child.toString();
164            }
165
166            if (content.endsWith("/>")) {
167                return result;
168            }
169            return result + "</StructureElement>" + separator;
170        }
171    }
172
173    /**
174     * <p>
175     * The time it takes for Jacareto to replay an event in ms.
176     * </p>
177     */
178    private static final int EVENT_DURATION = 150;
179
180    /**
181     * <p>
182     * The time it takes for Jacareto to replay each part of a double click event in ms.
183     * </p>
184     */
185    private static final int DOUBLE_CLICK_DURATION = 50;
186
187    /**
188     * <p>
189     * Application startup time in ms. The application needs to be fully initialized before Jacareto
190     * can start replaying.
191     * </p>
192     */
193    private static final int STARTUP_DELAY = 10000;
194
195    /**
196     * <p>
197     * The GUI element which is currently focused.
198     * </p>
199     */
200    private JFCGUIElement currentFocus;
201
202    /**
203     * <p>
204     * A tree of StructureNodes which represents the structure part inside a Jacareto XML file.
205     * </p>
206     */
207    private StructureNode structure;
208
209    /**
210     * <p>
211     * XML structure for key events modeled as StructureNodes.
212     * </p>
213     */
214    private StructureNode lastKeySequenceEvent;
215
216    /**
217     * <p>
218     * XML structure for key events modeled as StructureNodes.
219     * </p>
220     */
221    private StructureNode lastKeyTypedEvent;
222
223    /**
224     * <p>
225     * Bitmask which represents currently used key modifiers (such as shift etc).
226     * </p>
227     */
228    private int currentKeyModifiers;
229
230    /**
231     * <p>
232     * Maps VirtualKey objects for modifier keys back to AWT Event codes.
233     * </p>
234     */
235    private HashMap<VirtualKey, Integer> modifiers;
236
237    /**
238     * <p>
239     * XML structure for mouse events modeled as StructureNodes.
240     * </p>
241     */
242    private StructureNode lastMouseClickEvent;
243
244    /**
245     * <p>
246     * XML structure for focus events modeled as StructureNodes.
247     * </p>
248     */
249    private StructureNode lastFocusChangeEvent;
250
251    /**
252     * <p>
253     * XML structure for item and action events modeled as StructureNodes.
254     * </p>
255     */
256    private StructureNode lastItemActionEvent;
257
258    /**
259     * <p>
260     * The target of the last mouseDownEvent. It is necessary to save this because mouse down and up
261     * targets can differ.
262     * </p>
263     */
264    private IEventTarget lastMouseDownTarget;
265
266    /**
267     * <p>
268     * Associates the name of a menu element with its corresponding JFCGUIElement.
269     * </p>
270     */
271    private HashMap<String, JFCGUIElement> menuElements;
272
273    /**
274     * <p>
275     * The menu hierarchy.
276     * </p>
277     */
278    private List<String> menuList;
279
280    /**
281     * <p>
282     * Autoquest input sequence.
283     * </p>
284     */
285    private List<Event> sequence;
286
287    /**
288     * <p>
289     * XML output filename.
290     * </p>
291     */
292    private String filename;
293
294    /**
295     * <p>
296     * Jacareto replay execution classpath.
297     * </p>
298     */
299    private String classpath;
300
301    /**
302     * <p>
303     * Jacareto replay execution basepath.
304     * </p>
305     */
306    private String basepath;
307
308    /**
309     * <p>
310     * Jacareto replay execution classpathext.
311     * </p>
312     */
313    private String classpathext;
314
315    /**
316     * <p>
317     * Constructor.
318     * </p>
319     */
320    public JFCJacaretoReplayGenerator(List<Event> sequence,
321                                      String filename,
322                                      String classpath,
323                                      String basepath,
324                                      String classpathext,
325                                      String menu)
326    {
327        this.sequence = sequence;
328        this.filename = filename;
329        this.classpath = classpath;
330        this.basepath = basepath;
331        this.classpathext = classpathext;
332        currentFocus = null;
333        currentKeyModifiers = 0;
334        StructureNode.nextRef = 0;
335        lastFocusChangeEvent = null;
336        lastItemActionEvent = null;
337        lastKeySequenceEvent = null;
338        lastKeyTypedEvent = null;
339        lastMouseClickEvent = null;
340        lastMouseDownTarget = null;
341        menuElements = new HashMap<>();
342        modifiers = createModifierMap();
343
344        // try to parse in menu file, if available
345        if (!menu.isEmpty()) {
346            try {
347                menuList = Files.readAllLines(Paths.get(menu), Charset.defaultCharset());
348            }
349            catch (IOException e) {
350                Console.printerrln("Unable to open menu file");
351                Console.logException(e);
352            }
353        }
354    }
355
356    /**
357     * <p>
358     * Writes the Jacareto XML replay file.
359     * </p>
360     */
361    public void writeJacaretoXML() {
362        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
363
364        try {
365            writeJacaretoHead(writer, classpath, basepath, classpathext);
366            writeJacaretoEvents(writer, sequence);
367            writeJacaretoTail(writer);
368            writeLine(writer, "</JacaretoStructure>");
369
370            writer.flush();
371            writer.close();
372        }
373        catch (IOException e) {
374            Console.printerrln("Unable to write Jacareto replay file " + filename);
375        }
376    }
377
378    /**
379     * <p>
380     * Associates keyboard modifier keys with their AWT event codes.
381     * </p>
382     */
383    private HashMap<VirtualKey, Integer> createModifierMap() {
384        HashMap<VirtualKey, Integer> result = new HashMap<>();
385
386        result.put(VirtualKey.SHIFT, 1);
387        result.put(VirtualKey.CONTROL, 2);
388        result.put(VirtualKey.ALT, 8);
389        result.put(VirtualKey.ALT_GRAPH, 32);
390
391        return result;
392    }
393
394    /**
395     * <p>
396     * Writes a line and creates a new line.
397     * </p>
398     *
399     * @param writer
400     *            the BufferedWriter which writes the XML
401     * @param line
402     *            the line to write
403     *
404     */
405    private void writeLine(BufferedWriter writer, String line) throws IOException {
406        writer.write(line);
407        writer.newLine();
408    }
409
410    /**
411     * <p>
412     * Writes the Jacareto XML head part. This mainly contains information about the state of the
413     * system when the replay was captured.
414     *
415     * @param writer
416     *            the BufferedWriter which writes the XML
417     * @param classname
418     *            name of the main class of the program that will be replayed
419     * @param basepath
420     *            a basepath that is prepended to all paths specified in classpathext
421     * @param classpathext
422     *            additional required resources (e.g. jar files)
423     *
424     *            </p>
425     */
426    private void writeJacaretoHead(BufferedWriter writer,
427                                   String classname,
428                                   String basepath,
429                                   String classpathext) throws IOException
430    {
431        Calendar now = Calendar.getInstance();
432
433        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
434        writeLine(writer, "<JacaretoStructure>");
435        writeLine(writer, "<Record>");
436
437        //@formatter:off
438        writeLine(writer, "<Calendar "
439            + "procTime=\"0\" "
440            + "duration=\"0\" "
441            + "year=\"" + now.get(Calendar.YEAR) + "\" "
442            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
443            + "date=\"" + now.get(Calendar.DAY_OF_MONTH) + "\" "
444            + "hour=\"" + now.get(Calendar.HOUR_OF_DAY) + "\" "
445            + "min=\"" + now.get(Calendar.MINUTE) + "\" "
446            + "sec=\"" + now.get(Calendar.SECOND) + "\" "
447            + "uuid=\"" + UUID.randomUUID() + "\" />"
448        );
449        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
450        writeLine(writer, "<SystemInfo "
451            + "procTime=\"0\" "
452            + "duration=\"0\" "
453            + "screenWidth=\"" + gd.getDisplayMode().getWidth() + "\" "
454            + "screenHeight=\"" + gd.getDisplayMode().getHeight() + "\" "
455            + "javaVersion=\"" + System.getProperty("java.version") + "\" "
456            + "lookAndFeel=\"" + UIManager.getLookAndFeel().getClass().getName() + "\" "
457            + "uuid=\"" + UUID.randomUUID() + "\" />"
458        );
459        writeLine(writer,
460                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
461        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
462        writeLine(writer, "<ApplicationStarter "
463            + "procTime=\"" + STARTUP_DELAY + "\" "
464            + "duration=\"" + STARTUP_DELAY + "\" "
465            + "name=\"Autoquest Replay\" "
466            + "class=\"" + classname + "\" "
467            + "initclass=\"\" "
468            + "basepath=\"" + basepath + "\" "
469            + "classpathext=\"" + classpathext + "\" "
470            + "detectDuration=\"false\" "
471            + "captureparams=\"\" "
472            + "replayparams=\"\" "
473            + "uuid=\"" + UUID.randomUUID() + "\" />"
474        );
475        //@formatter:on
476    }
477
478    /**
479     * <p>
480     * Writes Jacareto XML code for all events within the Autoquest sequences.
481     * </p>
482     *
483     * @param writer
484     *            the BufferedWriter which writes the XML
485     * @param sequences
486     *            the Autoquest sequences
487     *
488     */
489    private void writeJacaretoEvents(BufferedWriter writer, List<Event> sequence)
490        throws IOException
491    {
492        structure = new StructureNode("RootElement");
493        // reference the elements that we included in the header
494        structure.addRecordable(); // Calendar
495        structure.addRecordable(); // SystemInfo
496        structure.addRecordable(); // KeyboardState
497        structure.addRecordable(); // ComponentMode
498        structure.addRecordable(); // ApplicationStarter
499
500        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
501            Event event = eventIter.next();
502
503            if (event.getType() instanceof MouseButtonDown) {
504                handleMouseButtonDown(writer, event);
505            }
506            else if (event.getType() instanceof MouseButtonUp) {
507                handleMouseButtonUp(writer, event);
508            }
509            else if (event.getType() instanceof MouseDoubleClick) {
510                handleMouseDoubleClick(writer, event);
511            }
512            else if (event.getType() instanceof MouseClick) {
513                if (event.getTarget() instanceof JFCMenuButton) {
514                    // if a menu file was provided, use the improved event
515                    // generation
516                    if (menuList != null) {
517                        if (menuElements.isEmpty()) {
518                            // parse the menu structure
519                            GUIModel model = ((IGUIElement) event.getTarget()).getGUIModel();
520                            getMenuElements(model.getRootElements(), model);
521                        }
522
523                        Stack<JFCGUIElement> hierarchy =
524                            findMenuItemHierarchy((JFCGUIElement) event.getTarget());
525
526                        while (!hierarchy.empty()) {
527                            generateFullClick(writer, event, hierarchy.pop());
528                        }
529                        continue;
530                    }
531                }
532
533                handleMouseClick(writer, event);
534            }
535            else if (event.getType() instanceof KeyboardFocusChange) {
536                handleKeyboardFocusChange(writer, event);
537            }
538            else if (event.getType() instanceof MouseDragAndDrop) {
539                handleMouseDragAndDrop(writer, event);
540            }
541            else if (event.getType() instanceof KeyPressed) {
542                handleKeyPressed(writer, event);
543            }
544            else if (event.getType() instanceof KeyReleased) {
545                handleKeyReleased(writer, event);
546            }
547            else if (event.getType() instanceof TextInput) {
548                handleTextInput(writer, event);
549            }
550            else {
551                Console.traceln(Level.WARNING, "No handler for event \"" + event + "\". Skipped.");
552            }
553        }
554    }
555
556    // EVENT HANDLERS
557
558    private void handleMouseClick(BufferedWriter writer, Event event) throws IOException {
559        lastKeySequenceEvent = null;
560
561        if (lastMouseClickEvent != null) {
562            if (lastMouseDownTarget == event.getTarget()) {
563                // this is the standard case:
564                // mouseDown, mouseUp and mouseClick sequence
565                // was triggered on this target
566
567                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
568                                     EVENT_DURATION, 500);
569                writeItemActionEvent(writer, event);
570
571                if (lastFocusChangeEvent == null) {
572                    // write structure sequentially
573                    structure.children.add(lastMouseClickEvent);
574                    structure.children.add(lastItemActionEvent);
575
576                    lastMouseClickEvent = null;
577                }
578                else {
579                    // with nested structure
580                    structure.children.add(lastItemActionEvent);
581                    lastItemActionEvent.children.add(0, lastFocusChangeEvent);
582                    lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
583
584                    lastFocusChangeEvent = null;
585                    lastMouseClickEvent = null;
586                }
587            }
588            else {
589                // target of mouseDown and mouseClick are different
590                // -> this is, for example, a click on a menu item
591                // within a condensed sequence
592                commitFocusEvent();
593
594                // finish the last click on the old target
595                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
596                                     EVENT_DURATION, 500);
597                structure.children.add(lastMouseClickEvent);
598
599                // and generate a new one
600                generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
601            }
602        }
603        else {
604            // a target was clicked repeatedly:
605            // the condensed sequence contains no mouseDowns or
606            // mouseUps anymore
607            // -> just generate another full click
608            generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
609        }
610
611    }
612
613    private void handleMouseDoubleClick(BufferedWriter writer, Event event) throws IOException {
614        StructureNode multiClick = structure.add("MultipleMouseClick");
615
616        // first click
617        lastMouseClickEvent = multiClick.add("MouseClick");
618        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
619                             DOUBLE_CLICK_DURATION, 501);
620        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
621                             DOUBLE_CLICK_DURATION, 502);
622        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
623                             DOUBLE_CLICK_DURATION, 500);
624        // second click
625        lastMouseClickEvent = multiClick.add("MouseClick");
626        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
627                             DOUBLE_CLICK_DURATION, 501);
628        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
629                             DOUBLE_CLICK_DURATION, 502);
630        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
631                             DOUBLE_CLICK_DURATION, 500);
632
633        lastMouseClickEvent = null;
634
635    }
636
637    private void handleKeyboardFocusChange(BufferedWriter writer, Event event) throws IOException {
638        lastKeySequenceEvent = null;
639        writeFocusChangeEvent(writer, event);
640    }
641
642    private void handleMouseButtonDown(BufferedWriter writer, Event event) throws IOException {
643        commitFocusEvent();
644        lastKeySequenceEvent = null;
645
646        lastMouseClickEvent = new StructureNode("MouseClick");
647        lastMouseDownTarget = event.getTarget();
648        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 501);
649    }
650
651    private void handleMouseButtonUp(BufferedWriter writer, Event event) throws IOException {
652        lastKeySequenceEvent = null;
653
654        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 502);
655    }
656
657    private void handleMouseDragAndDrop(BufferedWriter writer, Event event) throws IOException {
658        commitFocusEvent();
659
660        MouseDragAndDrop dragEvent = (MouseDragAndDrop) event.getType();
661        lastMouseClickEvent = new StructureNode("MouseDrag");
662        lastMouseDownTarget = null;
663
664        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
665                             dragEvent.getXStart(), dragEvent.getYStart(), 501);
666        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
667                             dragEvent.getX(), dragEvent.getY(), 506);
668
669        structure.children.add(lastMouseClickEvent);
670    }
671
672    private void handleKeyPressed(BufferedWriter writer, Event event) throws IOException {
673        commitFocusEvent();
674
675        if (lastKeySequenceEvent == null) {
676            lastKeySequenceEvent = structure.add("KeySequence");
677        }
678        lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
679
680        writeKeyEvent(writer, event, 401);
681    }
682
683    private void handleKeyReleased(BufferedWriter writer, Event event) throws IOException {
684        commitFocusEvent();
685
686        writeKeyEvent(writer, event, 402);
687    }
688
689    private void handleTextInput(BufferedWriter writer, Event event) throws IOException {
690        List<Event> textEvents = ((TextInput) event.getType()).getTextInputEvents();
691
692        // just split the text event into its key events again
693        for (Event textEvent : textEvents) {
694            if (textEvent.getType() instanceof KeyPressed) {
695                handleKeyPressed(writer, textEvent);
696            }
697            else if (textEvent.getType() instanceof KeyReleased) {
698                handleKeyReleased(writer, textEvent);
699            }
700        }
701    }
702
703    private void getMenuElements(List<IGUIElement> elements, GUIModel model) {
704        for (IGUIElement child : elements) {
705            if (child instanceof JFCMenuButton || child instanceof JFCMenu) {
706                menuElements.put(((JFCGUIElement) child).getName().replaceAll("^\"|\"$", ""),
707                                 (JFCGUIElement) child);
708            }
709            getMenuElements(model.getChildren(child), model);
710        }
711    }
712
713    private Stack<JFCGUIElement> findMenuItemHierarchy(JFCGUIElement item) {
714        Stack<JFCGUIElement> elements = new Stack<>();
715
716        // find line that contains this menu item name
717        int lineOfItem = -1;
718        for (int i = 0; i < menuList.size(); i++) {
719            String name = "\"" + menuList.get(i).trim().toLowerCase() + "\"";
720            if (name.equals(item.getName().trim().toLowerCase())) {
721                lineOfItem = i;
722            }
723        }
724
725        // now go backwards until the toplevel menu is found
726        int oldIndent = Integer.MAX_VALUE;
727        for (int j = lineOfItem; j >= 0; j--) {
728            String stripped = menuList.get(j).replaceFirst("^ *", "");
729            int indent = menuList.get(j).length() - stripped.length();
730
731            if (indent < oldIndent) {
732                // this is a parent submenu
733                elements.push(menuElements.get(stripped));
734                oldIndent = indent;
735            }
736        }
737
738        return elements;
739    }
740
741    private void commitFocusEvent() {
742        if (lastFocusChangeEvent != null) {
743            structure.children.add(lastFocusChangeEvent);
744            lastFocusChangeEvent = null;
745        }
746    }
747
748    private void generateFullClick(BufferedWriter writer, Event event, JFCGUIElement target)
749        throws IOException
750    {
751        lastMouseClickEvent = new StructureNode("MouseClick");
752        lastMouseDownTarget = event.getTarget();
753
754        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 501);
755        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 502);
756        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 500);
757        writeItemActionEvent(writer, event);
758
759        structure.children.add(lastMouseClickEvent);
760        structure.children.add(lastItemActionEvent);
761
762        lastMouseDownTarget = null;
763        lastMouseClickEvent = null;
764    }
765
766    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
767        writeLine(writer, "</Record>");
768
769        // write the recording's structure
770        writeLine(writer, "<Structure>");
771        writer.write(structure.toString());
772        // close root element
773        writeLine(writer, "</Structure>");
774    }
775
776    /**
777     * <p>
778     * Helper function that opens the replay file for writing.
779     * </p>
780     *
781     * @param filename
782     *            name and path of the replay file
783     * @param encoding
784     *            file encoding, empty string for platform default
785     * @return {@link OutputStreamWriter} that writes to the replay file
786     */
787    private OutputStreamWriter openReplayFile(String filename) {
788        File file = new File(filename);
789        boolean fileCreated;
790        try {
791            fileCreated = file.createNewFile();
792            if (!fileCreated) {
793                Console.traceln(Level.INFO, "Created logfile " + filename);
794            }
795            else {
796                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
797            }
798        }
799        catch (IOException e) {
800            Console.printerrln("Unable to create file " + filename);
801            Console.logException(e);
802        }
803        OutputStreamWriter writer = null;
804        try {
805            writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
806        }
807        catch (IOException e) {
808            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
809            Console.logException(e);
810        }
811        return writer;
812    }
813
814    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
815        JFCGUIElement target = (JFCGUIElement) event.getTarget();
816        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
817        // get rid of the quote symbols in the command because we want to
818        // escape the middle part for XML
819        String cmd = target.getName().substring(1, target.getName().length() - 1);
820
821        //@formatter:off
822        writeLine(writer,
823            "<ItemEvent "
824            + "procTime=\"0\" "
825            + "duration=\"0\" "
826            + "source=\"" + target.getJacaretoHierarchy() + "\" "
827            + "class=\"" + target.getSpecification().getType() + "\" "
828            + "uuid=\"" + UUID.randomUUID() + "\" "
829            + "ID=\"701\" "
830            + "item=\"\" "
831            + "stateChange=\"1\" />"
832        );
833        writeLine(writer,
834            "<ActionEvent "
835            + "procTime=\"0\" "
836            + "duration=\"0\" "
837            + "source=\"" + target.getJacaretoHierarchy() + "\" "
838            + "class=\"" + target.getSpecification().getType() + "\" "
839            + "uuid=\"" + UUID.randomUUID() + "\" "
840            + "ID=\"1001\" "
841            + "command=\"" + StringEscapeUtils.escapeXml(cmd) + "\" "
842            + "modifiers=\"" + getButtonModifier(info) + "\" />"
843        );
844        //@formatter:on
845        lastItemActionEvent = new StructureNode("ItemStateChange");
846        lastItemActionEvent.addRecordable();
847        lastItemActionEvent.addRecordable();
848    }
849
850    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
851        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
852        JFCGUIElement target = (JFCGUIElement) event.getTarget();
853
854        if (currentFocus != null) {
855            lastFocusChangeEvent = new StructureNode("FocusChange");
856
857            // focus lost on old target
858            writeFocusEvent(writer, info, currentFocus, 1005);
859            // focus gained on new target
860            writeFocusEvent(writer, info, target, 1004);
861        }
862        else {
863            // the first focus event but that is not the case in autoquest, skip
864        }
865
866        currentFocus = target;
867    }
868
869    private void writeFocusEvent(BufferedWriter writer,
870                                 KeyboardFocusChange info,
871                                 JFCGUIElement target,
872                                 int jacId) throws IOException
873    {
874        //@formatter:off
875        writeLine(writer,
876            "<FocusEvent "
877            + "procTime=\"0\" "
878            + "duration=\"0\" "
879            + "source=\"" + target.getJacaretoHierarchy() + "\" "
880            + "class=\"" + target.getSpecification().getType() + "\" "
881            + "uuid=\"" + UUID.randomUUID() + "\" "
882            + "ID=\"" + jacId + "\" "
883            + "component=\"null\" "
884            + "root=\"" + target.getJacaretoRoot() + "\" "
885            + "xPos=\"0\" "
886            + "yPos=\"0\" "
887            + "width=\"0\" "
888            + "height=\"0\" "
889            + "isTemporary=\"false\" />"
890        );
891        //@formatter:on
892        lastFocusChangeEvent.addRecordable();
893    }
894
895    private void writeMouseClickEvent(BufferedWriter writer,
896                                      Event event,
897                                      JFCGUIElement target,
898                                      int duration,
899                                      int jacId) throws IOException
900    {
901        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
902        writeMouseClickEvent(writer, event, target, duration, info.getX(), info.getY(), jacId);
903    }
904
905    private void writeMouseClickEvent(BufferedWriter writer,
906                                      Event event,
907                                      JFCGUIElement target,
908                                      int duration,
909                                      int x,
910                                      int y,
911                                      int jacId) throws IOException
912    {
913        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
914        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
915
916        //@formatter:off
917        writeLine(writer,
918            "<MouseEvent "
919            + "procTime=\"0\" "
920            + "duration=\"" + duration + "\" "
921            + "source=\"" + target.getJacaretoHierarchy() + "\" "
922            + "class=\"" + target.getSpecification().getType() + "\" "
923            + "uuid=\"" + UUID.randomUUID() + "\" "
924            + "ID=\"" + jacId + "\" "
925            + "component=\"null\" "
926            + "root=\"" + target.getJacaretoRoot() + "\" "
927            + "xPos=\"0\" "
928            + "yPos=\"0\" "
929            + "width=\"0\" "
930            + "height=\"0\" "
931            + "when=\"" + event.getTimestamp() + "\" "
932            + "isConsumed=\"false\">"
933        );
934        writeLine(writer,
935            "<MouseInfo "
936            + "xPosition=\"" + x + "\" "
937            + "yPosition=\"" + y + "\" "
938            + "rootX=\"0\" "
939            + "rootY=\"0\" "
940            + "clickCount=\"" + clickCount + "\" "
941            + "modifiers=\"" + getButtonModifier(info) + "\" "
942            + "isPopupTrigger=\"false\" />"
943        );
944        writeLine(writer, "</MouseEvent>");
945        //@formatter:on
946
947        lastMouseClickEvent.addRecordable();
948    }
949
950    private int getButtonModifier(MouseButtonInteraction info) {
951        switch (info.getButton())
952        {
953            case LEFT:
954                return 16;
955            case MIDDLE:
956                return 8;
957            case RIGHT:
958                return 4;
959            default:
960                Console.traceln(Level.WARNING, "Unknown mouse button pressed.");
961                return -1;
962        }
963    }
964
965    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
966        KeyInteraction info = (KeyInteraction) event.getType();
967        JFCGUIElement target = (JFCGUIElement) event.getTarget();
968        int keyCode = info.getKey().getVirtualKeyCode();
969
970        applyKeyModifier(info.getKey(), jacId == 401);
971
972        //@formatter:off
973        writeLine(writer,
974            "<KeyEvent "
975            + "procTime=\"0\" "
976            + "duration=\"" + EVENT_DURATION + "\" "
977            + "source=\"" + target.getJacaretoHierarchy() + "\" "
978            + "class=\"" + target.getSpecification().getType() + "\" "
979            + "uuid=\"" + UUID.randomUUID() + "\" "
980            + "ID=\"" + jacId + "\" "
981            + "component=\"null\" "
982            + "root=\"" + target.getJacaretoRoot() + "\" "
983            + "xPos=\"0\" "
984            + "yPos=\"0\" "
985            + "width=\"0\" "
986            + "height=\"0\" "
987            + "when=\"" + event.getTimestamp() + "\" "
988            + "isConsumed=\"false\">"
989        );
990        writeLine(writer,
991            "<KeyInfo "
992            + "keyCode=\"" + keyCode + "\" "
993            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
994            + "modifiers=\"" + currentKeyModifiers + "\" />"
995        );
996       
997        writeLine(writer, "</KeyEvent>");
998       
999        lastKeyTypedEvent.addRecordable();
1000    }
1001   
1002    private String getKeyChar (int keyCode) {
1003        if (keyCode >= 32 && keyCode < 127) {
1004            return String.valueOf((char)keyCode);
1005        }
1006        return "_NO_LEGAL_XML_CHAR";
1007    }
1008   
1009    private void applyKeyModifier (VirtualKey key, boolean set) {
1010        Integer modifier = modifiers.get(key);
1011        if (modifier != null) {
1012            if (set) {
1013                currentKeyModifiers |= modifier;
1014            }
1015            else {
1016                currentKeyModifiers &= ~modifier;
1017            }
1018        }
1019    }
1020}
Note: See TracBrowser for help on using the repository browser.