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

Last change on this file since 1876 was 1872, checked in by dmay, 10 years ago

Redo some XML parts that were hardcoded before. Also escape XML strings when necessary. This should be enough to make borg calendar replays work without manual intervention.

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