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

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

a big wave of refactorings/documentation

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