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

Last change on this file since 1857 was 1857, checked in by sherbold, 9 years ago
  • added command parseJFCDirwithJacaretoIndicies
  • modified command generateJacaretoReplay such that it now can handle multiple sequences at once
  • added new GUI mappings
  • Property svn:mime-type set to text/plain
File size: 34.3 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.plugin.jfc.commands;
16
17import 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        int index = 1;
345        for( List<Event> sequence : sequences ) {
346            writeJacaretoXML(sequence, filename+"_"+index, classpath, basepath, classpathext);
347            index++;
348        }
349    }
350
351    /**
352     * <p>
353     * Associates keyboard modifier keys with their AWT event codes.
354     * </p>
355     */
356    private HashMap<VirtualKey, Integer> createModifierMap() {
357        HashMap<VirtualKey, Integer> result = new HashMap<>();
358
359        result.put(VirtualKey.SHIFT, 1);
360        result.put(VirtualKey.CONTROL, 2);
361        result.put(VirtualKey.ALT, 8);
362        result.put(VirtualKey.ALT_GRAPH, 32);
363
364        return result;
365    }
366
367    /**
368     * <p>
369     * Writes a line and creates a new line.
370     * </p>
371     *
372     * @param writer
373     *            the BufferedWriter which writes the XML
374     * @param line
375     *            the line to write
376     *
377     */
378    private void writeLine(BufferedWriter writer, String line) throws IOException {
379        writer.write(line);
380        writer.newLine();
381    }
382
383    /**
384     * <p>
385     * Writes the Jacareto XML head part. This mainly contains information about the state of the
386     * system when the replay was captured.
387     *
388     * @param writer
389     *            the BufferedWriter which writes the XML
390     * @param classname
391     *            name of the main class of the program that will be replayed
392     * @param basepath
393     *            a basepath that is prepended to all paths specified in classpathext
394     * @param classpathext
395     *            additional required resources (e.g. jar files)
396     *
397     *            </p>
398     */
399    private void writeJacaretoHead(BufferedWriter writer,
400                                   String classname,
401                                   String basepath,
402                                   String classpathext) throws IOException
403    {
404        Calendar now = Calendar.getInstance();
405
406        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
407        writeLine(writer, "<JacaretoStructure>");
408        writeLine(writer, "<Record>");
409
410        //@formatter:off
411        writeLine(writer, "<Calendar "
412            + "procTime=\"0\" "
413            + "duration=\"0\" "
414            + "year=\"" + now.get(Calendar.YEAR) + "\" "
415            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
416            + "date=\"" + now.get(Calendar.DAY_OF_MONTH) + "\" "
417            + "hour=\"" + now.get(Calendar.HOUR_OF_DAY) + "\" "
418            + "min=\"" + now.get(Calendar.MINUTE) + "\" "
419            + "sec=\"" + now.get(Calendar.SECOND) + "\" "
420            + "uuid=\"" + UUID.randomUUID() + "\" />"
421        );
422        writeLine(writer,
423                  "<SystemInfo procTime=\"0\" duration=\"0\" screenWidth=\"2646\" screenHeight=\"1024\" javaVersion=\"1.7.0_65\" "
424                  + "lookAndFeel=\"" + UIManager.getLookAndFeel().getClass().getName() + "\" "
425                  + "uuid=\"720f430f-52cf-4d8b-9fbe-58434f766efe\" />");
426        writeLine(writer,
427                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
428        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
429        writeLine(writer, "<ApplicationStarter "
430            + "procTime=\"" + STARTUP_DELAY + "\" "
431            + "duration=\"" + STARTUP_DELAY + "\" "
432            + "name=\"Autoquest Replay\" "
433            + "class=\"" + classname + "\" "
434            + "initclass=\"\" "
435            + "basepath=\"" + basepath + "\" "
436            + "classpathext=\"" + classpathext + "\" "
437            + "detectDuration=\"false\" "
438            + "captureparams=\"\" "
439            + "replayparams=\"\" "
440            + "uuid=\"" + UUID.randomUUID() + "\" />"
441        );
442        //@formatter:on
443    }
444
445    /**
446     * <p>
447     * Writes Jacareto XML code for all events within the Autoquest sequences.
448     * </p>
449     *
450     * @param writer
451     *            the BufferedWriter which writes the XML
452     * @param sequences
453     *            the Autoquest sequences
454     *
455     */
456    private void writeJacaretoEvents(BufferedWriter writer, List<Event> sequence)
457        throws IOException
458    {
459        structure = new StructureNode("RootElement");
460        // reference the elements that we included in the header
461        structure.addRecordable(); // Calendar
462        structure.addRecordable(); // SystemInfo
463        structure.addRecordable(); // KeyboardState
464        structure.addRecordable(); // ComponentMode
465        structure.addRecordable(); // ApplicationStarter
466
467        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
468            Event event = eventIter.next();
469
470            if (event.getType() instanceof MouseButtonDown) {
471                handleMouseButtonDown(writer, event);
472            }
473            else if (event.getType() instanceof MouseButtonUp) {
474                handleMouseButtonUp(writer, event);
475            }
476            else if (event.getType() instanceof MouseDoubleClick) {
477                handleMouseDoubleClick(writer, event);
478            }
479            else if (event.getType() instanceof MouseClick) {
480                if (event.getTarget() instanceof JFCMenuButton) {
481                    // if a menu file was provided, use the improved event
482                    // generation
483                    if (menuList != null) {
484                        if (menuElements.isEmpty()) {
485                            // parse the menu structure
486                            GUIModel model = ((IGUIElement) event.getTarget()).getGUIModel();
487                            getMenuElements(model.getRootElements(), model);
488                        }
489
490                        Stack<JFCGUIElement> hierarchy =
491                            findMenuItemHierarchy((JFCGUIElement) event.getTarget());
492
493                        while (!hierarchy.empty()) {
494                            generateFullClick(writer, event, hierarchy.pop());
495                        }
496                        continue;
497                    }
498                }
499
500                handleMouseClick(writer, event);
501            }
502            else if (event.getType() instanceof KeyboardFocusChange) {
503                handleKeyboardFocusChange(writer, event);
504            }
505            else if (event.getType() instanceof MouseDragAndDrop) {
506                handleMouseDragAndDrop(writer, event);
507            }
508            else if (event.getType() instanceof KeyPressed) {
509                handleKeyPressed(writer, event);
510            }
511            else if (event.getType() instanceof KeyReleased) {
512                handleKeyReleased(writer, event);
513            }
514            else if (event.getType() instanceof TextInput) {
515                handleTextInput(writer, event);
516            }
517            else {
518                Console.traceln(Level.WARNING, "No handler for event \"" + event +
519                    "\". Skipped.");
520            }
521        }
522    }
523
524    // EVENT HANDLERS
525
526    private void handleMouseClick(BufferedWriter writer, Event event) throws IOException {
527        lastKeySequenceEvent = null;
528
529        if (lastMouseClickEvent != null) {
530            if (lastMouseDownTarget == event.getTarget()) {
531                // this is the standard case:
532                // mouseDown, mouseUp and mouseClick sequence
533                // was triggered on this target
534
535                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
536                                     EVENT_DURATION, 500);
537                writeItemActionEvent(writer, event);
538
539                if (lastFocusChangeEvent == null) {
540                    // write structure sequentially
541                    structure.children.add(lastMouseClickEvent);
542                    structure.children.add(lastItemActionEvent);
543                }
544                else {
545                    // with nested structure
546                    structure.children.add(lastItemActionEvent);
547                    lastItemActionEvent.children.add(0, lastFocusChangeEvent);
548                    lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
549
550                    lastFocusChangeEvent = null;
551                    lastMouseClickEvent = null;
552                }
553            }
554            else {
555                // target of mouseDown and mouseClick are different
556                // -> this is, for example, a click on a menu item
557                // within a condensed sequence
558                commitFocusEvent();
559
560                // finish the last click on the old target
561                writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
562                                     EVENT_DURATION, 500);
563                structure.children.add(lastMouseClickEvent);
564
565                // and generate a new one
566                generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
567            }
568        }
569        else {
570            // a target was clicked repeatedly:
571            // the condensed sequence contains no mouseDowns or
572            // mouseUps anymore
573            // -> just generate another full click
574            generateFullClick(writer, event, (JFCGUIElement) event.getTarget());
575        }
576
577    }
578
579    private void handleMouseDoubleClick(BufferedWriter writer, Event event) throws IOException {
580        StructureNode multiClick = structure.add("MultipleMouseClick");
581
582        // first click
583        lastMouseClickEvent = multiClick.add("MouseClick");
584        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
585                             DOUBLE_CLICK_DURATION, 501);
586        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
587                             DOUBLE_CLICK_DURATION, 502);
588        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
589                             DOUBLE_CLICK_DURATION, 500);
590        // second click
591        lastMouseClickEvent = multiClick.add("MouseClick");
592        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
593                             DOUBLE_CLICK_DURATION, 501);
594        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
595                             DOUBLE_CLICK_DURATION, 502);
596        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(),
597                             DOUBLE_CLICK_DURATION, 500);
598
599        lastMouseClickEvent = null;
600
601    }
602
603    private void handleKeyboardFocusChange(BufferedWriter writer, Event event) throws IOException {
604        lastKeySequenceEvent = null;
605        writeFocusChangeEvent(writer, event);
606    }
607
608    private void handleMouseButtonDown(BufferedWriter writer, Event event) throws IOException {
609        commitFocusEvent();
610        lastKeySequenceEvent = null;
611
612        lastMouseClickEvent = new StructureNode("MouseClick");
613        lastMouseDownTarget = event.getTarget();
614        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 501);
615    }
616
617    private void handleMouseButtonUp(BufferedWriter writer, Event event) throws IOException {
618        lastKeySequenceEvent = null;
619
620        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 502);
621    }
622
623    private void handleMouseDragAndDrop(BufferedWriter writer, Event event) throws IOException {
624        commitFocusEvent();
625
626        MouseDragAndDrop dragEvent = (MouseDragAndDrop) event.getType();
627        lastMouseClickEvent = new StructureNode("MouseDrag");
628        lastMouseDownTarget = null;
629
630        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
631                             dragEvent.getXStart(), dragEvent.getYStart(), 501);
632        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION,
633                             dragEvent.getX(), dragEvent.getY(), 506);
634
635        structure.children.add(lastMouseClickEvent);
636    }
637
638    private void handleKeyPressed(BufferedWriter writer, Event event) throws IOException {
639        commitFocusEvent();
640
641        if (lastKeySequenceEvent == null) {
642            lastKeySequenceEvent = structure.add("KeySequence");
643        }
644        lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
645
646        writeKeyEvent(writer, event, 401);
647    }
648
649    private void handleKeyReleased(BufferedWriter writer, Event event) throws IOException {
650        commitFocusEvent();
651
652        writeKeyEvent(writer, event, 402);
653    }
654
655    private void handleTextInput(BufferedWriter writer, Event event) throws IOException {
656        List<Event> textEvents = ((TextInput) event.getType()).getTextInputEvents();
657
658        // just split the text event into its key events again
659        for (Event textEvent : textEvents) {
660            if (textEvent.getType() instanceof KeyPressed) {
661                handleKeyPressed(writer, textEvent);
662            }
663            else if (textEvent.getType() instanceof KeyReleased) {
664                handleKeyReleased(writer, textEvent);
665            }
666        }
667    }
668
669    private void getMenuElements(List<IGUIElement> elements, GUIModel model) {
670        for (IGUIElement child : elements) {
671            if (child instanceof JFCMenuButton || child instanceof JFCMenu) {
672                menuElements.put(((JFCGUIElement) child).getName().replaceAll("^\"|\"$", ""),
673                                 (JFCGUIElement) child);
674            }
675            getMenuElements(model.getChildren(child), model);
676        }
677    }
678
679    private Stack<JFCGUIElement> findMenuItemHierarchy(JFCGUIElement item) {
680        Stack<JFCGUIElement> elements = new Stack<>();
681
682        // find line that contains this menu item name
683        int lineOfItem = -1;
684        for (int i = 0; i < menuList.size(); i++) {
685            String name = "\"" + menuList.get(i).trim().toLowerCase() + "\"";
686            if (name.equals(item.getName().trim().toLowerCase())) {
687                lineOfItem = i;
688            }
689        }
690
691        // now go backwards until the toplevel menu is found
692        int oldIndent = Integer.MAX_VALUE;
693        for (int j = lineOfItem; j >= 0; j--) {
694            String stripped = menuList.get(j).replaceFirst("^ *", "");
695            int indent = menuList.get(j).length() - stripped.length();
696
697            if (indent < oldIndent) {
698                // this is a parent submenu
699                elements.push(menuElements.get(stripped));
700                oldIndent = indent;
701            }
702        }
703
704        return elements;
705    }
706
707    private void commitFocusEvent() {
708        if (lastFocusChangeEvent != null) {
709            structure.children.add(lastFocusChangeEvent);
710            lastFocusChangeEvent = null;
711        }
712    }
713
714    private void generateFullClick(BufferedWriter writer, Event event, JFCGUIElement target)
715        throws IOException
716    {
717        lastMouseClickEvent = new StructureNode("MouseClick");
718        lastMouseDownTarget = event.getTarget();
719
720        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 501);
721        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 502);
722        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 500);
723        writeItemActionEvent(writer, event);
724
725        structure.children.add(lastMouseClickEvent);
726        structure.children.add(lastItemActionEvent);
727
728        lastMouseDownTarget = null;
729    }
730
731    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
732        writeLine(writer, "</Record>");
733
734        // write the recording's structure
735        writeLine(writer, "<Structure>");
736        writer.write(structure.toString());
737        // close root element
738        writeLine(writer, "</Structure>");
739    }
740
741    private void writeJacaretoXML(List<Event> sequence,
742                                  String filename,
743                                  String classpath,
744                                  String basepath,
745                                  String classpathext)
746    {
747        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
748
749        try {
750            writeJacaretoHead(writer, classpath, basepath, classpathext);
751            writeJacaretoEvents(writer, sequence);
752            writeJacaretoTail(writer);
753            writeLine(writer, "</JacaretoStructure>");
754
755            writer.flush();
756            writer.close();
757        }
758        catch (IOException e) {
759            Console.printerrln("Unable to write Jacareto replay file " + filename);
760        }
761    }
762
763    /**
764     * <p>
765     * Helper function that opens the replay file for writing.
766     * </p>
767     *
768     * @param filename
769     *            name and path of the replay file
770     * @param encoding
771     *            file encoding, empty string for platform default
772     * @return {@link OutputStreamWriter} that writes to the replay file
773     */
774    private OutputStreamWriter openReplayFile(String filename) {
775        File file = new File(filename);
776        boolean fileCreated;
777        try {
778            fileCreated = file.createNewFile();
779            if (!fileCreated) {
780                Console.traceln(Level.INFO, "Created logfile " + filename);
781            }
782            else {
783                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
784            }
785        }
786        catch (IOException e) {
787            Console.printerrln("Unable to create file " + filename);
788            Console.logException(e);
789        }
790        OutputStreamWriter writer = null;
791        try {
792            writer = new OutputStreamWriter(new FileOutputStream(file));
793        }
794        catch (IOException e) {
795            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
796            Console.logException(e);
797        }
798        return writer;
799    }
800
801    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
802        JFCGUIElement target = (JFCGUIElement) event.getTarget();
803        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
804
805        //@formatter:off
806        writeLine(writer,
807            "<ItemEvent "
808            + "procTime=\"0\" "
809            + "duration=\"0\" "
810            + "source=\"" + target.getJacaretoHierarchy() + "\" "
811            + "class=\"" + target.getSpecification().getType() + "\" "
812            + "uuid=\"" + UUID.randomUUID() + "\" "
813            + "ID=\"701\" "
814            + "item=\"\" "
815            + "stateChange=\"1\" />"
816        );
817        writeLine(writer,
818            "<ActionEvent "
819            + "procTime=\"0\" "
820            + "duration=\"0\" "
821            + "source=\"" + target.getJacaretoHierarchy() + "\" "
822            + "class=\"" + target.getSpecification().getType() + "\" "
823            + "uuid=\"" + UUID.randomUUID() + "\" "
824            + "ID=\"1001\" "
825            + "command=" + target.getName() + " "
826            + "modifiers=\"" + getButtonModifier(info) + "\" />"
827        );
828        //@formatter:on
829        lastItemActionEvent = new StructureNode("ItemStateChange");
830        lastItemActionEvent.addRecordable();
831        lastItemActionEvent.addRecordable();
832    }
833
834    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
835        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
836        JFCGUIElement target = (JFCGUIElement) event.getTarget();
837
838        if (currentFocus != null) {
839            lastFocusChangeEvent = new StructureNode("FocusChange");
840
841            // focus lost on old target
842            writeFocusEvent(writer, info, currentFocus, 1005);
843            // focus gained on new target
844            writeFocusEvent(writer, info, target, 1004);
845        }
846        else {
847            // TODO: it seems like Jacareto wants a window activation before
848            // the first focus event but that is not the case in autoquest,
849            // skip for now
850        }
851
852        currentFocus = target;
853    }
854
855    private void writeFocusEvent(BufferedWriter writer,
856                                 KeyboardFocusChange info,
857                                 JFCGUIElement target,
858                                 int jacId) throws IOException
859    {
860        //@formatter:off
861        writeLine(writer,
862            "<FocusEvent "
863            + "procTime=\"0\" "
864            + "duration=\"0\" "
865            + "source=\"" + target.getJacaretoHierarchy() + "\" "
866            + "class=\"" + target.getSpecification().getType() + "\" "
867            + "uuid=\"" + UUID.randomUUID() + "\" "
868            + "ID=\"" + jacId + "\" "
869            + "component=\"null\" "
870            + "root=\"" + target.getJacaretoRoot() + "\" "
871            + "xPos=\"0\" "
872            + "yPos=\"0\" "
873            + "width=\"0\" "
874            + "height=\"0\" "
875            + "isTemporary=\"false\" />"
876        );
877        //@formatter:on
878        lastFocusChangeEvent.addRecordable();
879    }
880
881    private void writeMouseClickEvent(BufferedWriter writer,
882                                      Event event,
883                                      JFCGUIElement target,
884                                      int duration,
885                                      int jacId) throws IOException
886    {
887        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
888        writeMouseClickEvent(writer, event, target, duration, info.getX(), info.getY(), jacId);
889    }
890
891    private void writeMouseClickEvent(BufferedWriter writer,
892                                      Event event,
893                                      JFCGUIElement target,
894                                      int duration,
895                                      int x,
896                                      int y,
897                                      int jacId) throws IOException
898    {
899        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
900        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
901
902        //@formatter:off
903        writeLine(writer,
904            "<MouseEvent "
905            + "procTime=\"0\" "
906            + "duration=\"" + duration + "\" "
907            + "source=\"" + target.getJacaretoHierarchy() + "\" "
908            + "class=\"" + target.getSpecification().getType() + "\" "
909            + "uuid=\"" + UUID.randomUUID() + "\" "
910            + "ID=\"" + jacId + "\" "
911            + "component=\"null\" "
912            + "root=\"" + target.getJacaretoRoot() + "\" "
913            + "xPos=\"0\" "
914            + "yPos=\"0\" "
915            + "width=\"0\" "
916            + "height=\"0\" "
917            + "when=\"" + event.getTimestamp() + "\" "
918            + "isConsumed=\"false\">"
919        );
920        writeLine(writer,
921            "<MouseInfo "
922            + "xPosition=\"" + x + "\" "
923            + "yPosition=\"" + y + "\" "
924            + "rootX=\"0\" "
925            + "rootY=\"0\" "
926            + "clickCount=\"" + clickCount + "\" "
927            + "modifiers=\"" + getButtonModifier(info) + "\" "
928            + "isPopupTrigger=\"false\" />"
929        );
930        writeLine(writer, "</MouseEvent>");
931        //@formatter:on
932
933        lastMouseClickEvent.addRecordable();
934    }
935
936    private int getButtonModifier(MouseButtonInteraction info) {
937        switch (info.getButton())
938        {
939            case LEFT:
940                return 16;
941            case MIDDLE:
942                return 8;
943            case RIGHT:
944                return 4;
945            default:
946                // TODO: handle unknown mouse button
947                return -1;
948        }
949    }
950
951    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
952        KeyInteraction info = (KeyInteraction) event.getType();
953        JFCGUIElement target = (JFCGUIElement) event.getTarget();
954        int keyCode = info.getKey().getVirtualKeyCode();
955
956        applyKeyModifier(info.getKey(), jacId == 401);
957
958        //@formatter:off
959        writeLine(writer,
960            "<KeyEvent "
961            + "procTime=\"0\" "
962            + "duration=\"" + EVENT_DURATION + "\" "
963            + "source=\"" + target.getJacaretoHierarchy() + "\" "
964            + "class=\"" + target.getSpecification().getType() + "\" "
965            + "uuid=\"" + UUID.randomUUID() + "\" "
966            + "ID=\"" + jacId + "\" "
967            + "component=\"null\" "
968            + "root=\"" + target.getJacaretoRoot() + "\" "
969            + "xPos=\"0\" "
970            + "yPos=\"0\" "
971            + "width=\"0\" "
972            + "height=\"0\" "
973            + "when=\"" + event.getTimestamp() + "\" "
974            + "isConsumed=\"false\">"
975        );
976        writeLine(writer,
977            "<KeyInfo "
978            + "keyCode=\"" + keyCode + "\" "
979            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
980            + "modifiers=\"" + currentKeyModifiers + "\" />"
981        );
982       
983        writeLine(writer, "</KeyEvent>");
984       
985        lastKeyTypedEvent.addRecordable();
986    }
987   
988    private String getKeyChar (int keyCode) {
989        if (keyCode >= 32 && keyCode < 127) {
990            return String.valueOf((char)keyCode);
991        }
992        return "_NO_LEGAL_XML_CHAR";
993    }
994   
995    private void applyKeyModifier (VirtualKey key, boolean set) {
996        Integer modifier = modifiers.get(key);
997        if (modifier != null) {
998            if (set) {
999                currentKeyModifiers |= modifier;
1000            }
1001            else {
1002                currentKeyModifiers &= ~modifier;
1003            }
1004        }
1005    }
1006}
Note: See TracBrowser for help on using the repository browser.