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

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

fix a bug with XML escaping

  • Property svn:mime-type set to text/plain
File size: 34.9 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        // get rid of the quote symbols in the command because we want to
814        // escape the middle part for XML
815        String cmd = target.getName().substring(1, target.getName().length()-1);
816       
817        //@formatter:off
818        writeLine(writer,
819            "<ItemEvent "
820            + "procTime=\"0\" "
821            + "duration=\"0\" "
822            + "source=\"" + target.getJacaretoHierarchy() + "\" "
823            + "class=\"" + target.getSpecification().getType() + "\" "
824            + "uuid=\"" + UUID.randomUUID() + "\" "
825            + "ID=\"701\" "
826            + "item=\"\" "
827            + "stateChange=\"1\" />"
828        );
829        writeLine(writer,
830            "<ActionEvent "
831            + "procTime=\"0\" "
832            + "duration=\"0\" "
833            + "source=\"" + target.getJacaretoHierarchy() + "\" "
834            + "class=\"" + target.getSpecification().getType() + "\" "
835            + "uuid=\"" + UUID.randomUUID() + "\" "
836            + "ID=\"1001\" "
837            + "command=\"" + StringEscapeUtils.escapeXml(cmd) + "\" "
838            + "modifiers=\"" + getButtonModifier(info) + "\" />"
839        );
840        //@formatter:on
841        lastItemActionEvent = new StructureNode("ItemStateChange");
842        lastItemActionEvent.addRecordable();
843        lastItemActionEvent.addRecordable();
844    }
845
846    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
847        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
848        JFCGUIElement target = (JFCGUIElement) event.getTarget();
849
850        if (currentFocus != null) {
851            lastFocusChangeEvent = new StructureNode("FocusChange");
852
853            // focus lost on old target
854            writeFocusEvent(writer, info, currentFocus, 1005);
855            // focus gained on new target
856            writeFocusEvent(writer, info, target, 1004);
857        }
858        else {
859            // TODO: it seems like Jacareto wants a window activation before
860            // the first focus event but that is not the case in autoquest,
861            // skip for now
862        }
863
864        currentFocus = target;
865    }
866
867    private void writeFocusEvent(BufferedWriter writer,
868                                 KeyboardFocusChange info,
869                                 JFCGUIElement target,
870                                 int jacId) throws IOException
871    {
872        //@formatter:off
873        writeLine(writer,
874            "<FocusEvent "
875            + "procTime=\"0\" "
876            + "duration=\"0\" "
877            + "source=\"" + target.getJacaretoHierarchy() + "\" "
878            + "class=\"" + target.getSpecification().getType() + "\" "
879            + "uuid=\"" + UUID.randomUUID() + "\" "
880            + "ID=\"" + jacId + "\" "
881            + "component=\"null\" "
882            + "root=\"" + target.getJacaretoRoot() + "\" "
883            + "xPos=\"0\" "
884            + "yPos=\"0\" "
885            + "width=\"0\" "
886            + "height=\"0\" "
887            + "isTemporary=\"false\" />"
888        );
889        //@formatter:on
890        lastFocusChangeEvent.addRecordable();
891    }
892
893    private void writeMouseClickEvent(BufferedWriter writer,
894                                      Event event,
895                                      JFCGUIElement target,
896                                      int duration,
897                                      int jacId) throws IOException
898    {
899        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
900        writeMouseClickEvent(writer, event, target, duration, info.getX(), info.getY(), jacId);
901    }
902
903    private void writeMouseClickEvent(BufferedWriter writer,
904                                      Event event,
905                                      JFCGUIElement target,
906                                      int duration,
907                                      int x,
908                                      int y,
909                                      int jacId) throws IOException
910    {
911        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
912        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
913
914        //@formatter:off
915        writeLine(writer,
916            "<MouseEvent "
917            + "procTime=\"0\" "
918            + "duration=\"" + duration + "\" "
919            + "source=\"" + target.getJacaretoHierarchy() + "\" "
920            + "class=\"" + target.getSpecification().getType() + "\" "
921            + "uuid=\"" + UUID.randomUUID() + "\" "
922            + "ID=\"" + jacId + "\" "
923            + "component=\"null\" "
924            + "root=\"" + target.getJacaretoRoot() + "\" "
925            + "xPos=\"0\" "
926            + "yPos=\"0\" "
927            + "width=\"0\" "
928            + "height=\"0\" "
929            + "when=\"" + event.getTimestamp() + "\" "
930            + "isConsumed=\"false\">"
931        );
932        writeLine(writer,
933            "<MouseInfo "
934            + "xPosition=\"" + x + "\" "
935            + "yPosition=\"" + y + "\" "
936            + "rootX=\"0\" "
937            + "rootY=\"0\" "
938            + "clickCount=\"" + clickCount + "\" "
939            + "modifiers=\"" + getButtonModifier(info) + "\" "
940            + "isPopupTrigger=\"false\" />"
941        );
942        writeLine(writer, "</MouseEvent>");
943        //@formatter:on
944
945        lastMouseClickEvent.addRecordable();
946    }
947
948    private int getButtonModifier(MouseButtonInteraction info) {
949        switch (info.getButton())
950        {
951            case LEFT:
952                return 16;
953            case MIDDLE:
954                return 8;
955            case RIGHT:
956                return 4;
957            default:
958                // TODO: handle unknown mouse button
959                return -1;
960        }
961    }
962
963    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
964        KeyInteraction info = (KeyInteraction) event.getType();
965        JFCGUIElement target = (JFCGUIElement) event.getTarget();
966        int keyCode = info.getKey().getVirtualKeyCode();
967
968        applyKeyModifier(info.getKey(), jacId == 401);
969
970        //@formatter:off
971        writeLine(writer,
972            "<KeyEvent "
973            + "procTime=\"0\" "
974            + "duration=\"" + EVENT_DURATION + "\" "
975            + "source=\"" + target.getJacaretoHierarchy() + "\" "
976            + "class=\"" + target.getSpecification().getType() + "\" "
977            + "uuid=\"" + UUID.randomUUID() + "\" "
978            + "ID=\"" + jacId + "\" "
979            + "component=\"null\" "
980            + "root=\"" + target.getJacaretoRoot() + "\" "
981            + "xPos=\"0\" "
982            + "yPos=\"0\" "
983            + "width=\"0\" "
984            + "height=\"0\" "
985            + "when=\"" + event.getTimestamp() + "\" "
986            + "isConsumed=\"false\">"
987        );
988        writeLine(writer,
989            "<KeyInfo "
990            + "keyCode=\"" + keyCode + "\" "
991            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
992            + "modifiers=\"" + currentKeyModifiers + "\" />"
993        );
994       
995        writeLine(writer, "</KeyEvent>");
996       
997        lastKeyTypedEvent.addRecordable();
998    }
999   
1000    private String getKeyChar (int keyCode) {
1001        if (keyCode >= 32 && keyCode < 127) {
1002            return String.valueOf((char)keyCode);
1003        }
1004        return "_NO_LEGAL_XML_CHAR";
1005    }
1006   
1007    private void applyKeyModifier (VirtualKey key, boolean set) {
1008        Integer modifier = modifiers.get(key);
1009        if (modifier != null) {
1010            if (set) {
1011                currentKeyModifiers |= modifier;
1012            }
1013            else {
1014                currentKeyModifiers &= ~modifier;
1015            }
1016        }
1017    }
1018}
Note: See TracBrowser for help on using the repository browser.