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

Last change on this file since 1872 was 1872, checked in by dmay, 9 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
RevLine 
[1671]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
[1673]15package de.ugoe.cs.autoquest.plugin.jfc.commands;
[1671]16
[1872]17import org.apache.commons.lang.StringEscapeUtils;
18
19import java.awt.GraphicsDevice;
20import java.awt.GraphicsEnvironment;
[1671]21import java.io.BufferedWriter;
[1673]22import java.io.File;
23import java.io.FileOutputStream;
[1671]24import java.io.IOException;
[1673]25import java.io.OutputStreamWriter;
[1825]26import java.nio.charset.Charset;
27import java.nio.file.Files;
28import java.nio.file.Paths;
[1671]29import java.util.ArrayList;
[1715]30import java.util.Calendar;
[1671]31import java.util.Collection;
[1704]32import java.util.HashMap;
[1671]33import java.util.Iterator;
34import java.util.List;
[1825]35import java.util.Stack;
[1678]36import java.util.UUID;
[1673]37import java.util.logging.Level;
[1671]38
[1836]39import javax.swing.UIManager;
40
[1671]41import de.ugoe.cs.autoquest.CommandHelpers;
42import de.ugoe.cs.autoquest.SequenceInstanceOf;
[1673]43import de.ugoe.cs.util.console.Command;
[1671]44import de.ugoe.cs.autoquest.eventcore.Event;
[1809]45import de.ugoe.cs.autoquest.eventcore.IEventTarget;
[1685]46import de.ugoe.cs.autoquest.eventcore.gui.*;
[1825]47import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
48import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
[1691]49import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
[1673]50import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
[1825]51import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenu;
52import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCMenuButton;
[1671]53import de.ugoe.cs.util.console.Console;
54import de.ugoe.cs.util.console.GlobalDataContainer;
55
[1856]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 {
[1689]65
[1856]66    /**
67     * <p>
68     * Helper class for the tree like structure part within a Jacareto file.
69     * </p>
70     */
71    private static class StructureNode {
[1688]72
[1856]73        /**
74         * <p>
75         * Keeps track of the next structure node id.
76         * </p>
77         */
78        public static int nextRef = 0;
[1688]79
[1856]80        /**
81         * <p>
82         * The node's type packaged into an XML string.
83         * </p>
84         */
85        public String content;
[1689]86
[1856]87        /**
88         * <p>
89         * This node's children.
90         * </p>
91         */
92        public ArrayList<StructureNode> children;
[1714]93
[1856]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        }
[1688]107
[1856]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        }
[1689]118
[1856]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        }
[1688]130
[1856]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;
[1688]143        }
144
[1856]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());
[1688]155        }
[1856]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        }
[1688]176    }
177
[1856]178    /**
179     * <p>
180     * The time it takes for Jacareto to replay an event in ms.
181     * </p>
182     */
[1719]183    private static final int EVENT_DURATION = 150;
[1856]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     */
[1808]190    private static final int DOUBLE_CLICK_DURATION = 50;
[1856]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     */
[1808]198    private static final int STARTUP_DELAY = 10000;
[1719]199
[1856]200    /**
201     * <p>
202     * The GUI element which is currently focused.
203     * </p>
204     */
[1686]205    private JFCGUIElement currentFocus;
[1856]206
207    /**
208     * <p>
209     * A tree of StructureNodes which represents the structure part inside a Jacareto XML file.
210     * </p>
211     */
[1688]212    private StructureNode structure;
[1704]213
[1856]214    /**
215     * <p>
216     * XML structure for key events modeled as StructureNodes.
217     * </p>
218     */
[1701]219    private StructureNode lastKeySequenceEvent;
[1856]220
221    /**
222     * <p>
223     * XML structure for key events modeled as StructureNodes.
224     * </p>
225     */
[1701]226    private StructureNode lastKeyTypedEvent;
[1856]227
228    /**
229     * <p>
230     * Bitmask which represents currently used key modifiers (such as shift etc).
231     * </p>
232     */
[1704]233    private int currentKeyModifiers;
234
[1856]235    /**
236     * <p>
237     * Maps VirtualKey objects for modifier keys back to AWT Event codes.
238     * </p>
239     */
[1704]240    private HashMap<VirtualKey, Integer> modifiers;
[1712]241
[1856]242    /**
243     * <p>
244     * XML structure for mouse events modeled as StructureNodes.
245     * </p>
246     */
[1688]247    private StructureNode lastMouseClickEvent;
[1856]248
249    /**
250     * <p>
251     * XML structure for focus events modeled as StructureNodes.
252     * </p>
253     */
[1688]254    private StructureNode lastFocusChangeEvent;
[1856]255
256    /**
257     * <p>
258     * XML structure for item and action events modeled as StructureNodes.
259     * </p>
260     */
[1688]261    private StructureNode lastItemActionEvent;
[1856]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     */
[1809]269    private IEventTarget lastMouseDownTarget;
[1856]270
271    /**
272     * <p>
273     * Associates the name of a menu element with its corresponding JFCGUIElement.
274     * </p>
275     */
[1825]276    private HashMap<String, JFCGUIElement> menuElements;
[1856]277
278    /**
279     * <p>
280     * The menu hierarchy.
281     * </p>
282     */
[1825]283    private List<String> menuList;
[1688]284
[1671]285    /*
286     * (non-Javadoc)
287     *
288     * @see de.ugoe.cs.util.console.Command#help()
289     */
290    @Override
291    public String help() {
[1825]292        return "generateJacaretoReplay <filename> <sequences> <class> <basepath> <classpathext> {<menufile>}";
[1671]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;
[1712]305        String classpath;
306        String basepath;
307        String classpathext;
[1671]308        try {
309            filename = (String) parameters.get(0);
310            sequencesName = (String) parameters.get(1);
[1712]311            classpath = (String) parameters.get(2);
312            basepath = (String) parameters.get(3);
313            classpathext = (String) parameters.get(4);
[1671]314        }
315        catch (Exception e) {
316            throw new IllegalArgumentException();
317        }
318
[1712]319        if (parameters.size() > 5) {
[1825]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            }
[1712]329        }
330
[1671]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;
[1825]343        menuElements = new HashMap<>();
[1856]344        modifiers = createModifierMap();
[1704]345        currentKeyModifiers = 0;
[1713]346        StructureNode.nextRef = 0;
347
[1857]348        int index = 1;
[1872]349        for (List<Event> sequence : sequences) {
350            writeJacaretoXML(sequence, filename + "_" + index, classpath, basepath, classpathext);
[1857]351            index++;
352        }
[1671]353    }
354
[1856]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     */
[1671]382    private void writeLine(BufferedWriter writer, String line) throws IOException {
383        writer.write(line);
384        writer.newLine();
385    }
386
[1856]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     */
[1712]403    private void writeJacaretoHead(BufferedWriter writer,
404                                   String classname,
405                                   String basepath,
406                                   String classpathext) throws IOException
407    {
[1715]408        Calendar now = Calendar.getInstance();
409
[1671]410        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
411        writeLine(writer, "<JacaretoStructure>");
412        writeLine(writer, "<Record>");
413
[1715]414        //@formatter:off
[1716]415        writeLine(writer, "<Calendar "
416            + "procTime=\"0\" "
[1715]417            + "duration=\"0\" "
418            + "year=\"" + now.get(Calendar.YEAR) + "\" "
[1716]419            + "month=\"" + (now.get(Calendar.MONTH) + 1) + "\" "
[1715]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        );
[1872]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        );
[1671]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\" />");
[1678]438        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
[1712]439        writeLine(writer, "<ApplicationStarter "
[1808]440            + "procTime=\"" + STARTUP_DELAY + "\" "
441            + "duration=\"" + STARTUP_DELAY + "\" "
[1712]442            + "name=\"Autoquest Replay\" "
443            + "class=\"" + classname + "\" "
[1825]444            + "initclass=\"\" "
[1712]445            + "basepath=\"" + basepath + "\" "
446            + "classpathext=\"" + classpathext + "\" "
447            + "detectDuration=\"false\" "
448            + "captureparams=\"\" "
449            + "replayparams=\"\" "
[1715]450            + "uuid=\"" + UUID.randomUUID() + "\" />"
[1712]451        );
452        //@formatter:on
[1671]453    }
454
[1856]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     */
[1857]466    private void writeJacaretoEvents(BufferedWriter writer, List<Event> sequence)
[1671]467        throws IOException
468    {
[1689]469        structure = new StructureNode("RootElement");
[1671]470        // reference the elements that we included in the header
[1689]471        structure.addRecordable(); // Calendar
472        structure.addRecordable(); // SystemInfo
473        structure.addRecordable(); // KeyboardState
474        structure.addRecordable(); // ComponentMode
475        structure.addRecordable(); // ApplicationStarter
[1671]476
[1857]477        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
478            Event event = eventIter.next();
[1671]479
[1857]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                        }
[1825]499
[1857]500                        Stack<JFCGUIElement> hierarchy =
501                            findMenuItemHierarchy((JFCGUIElement) event.getTarget());
[1825]502
[1857]503                        while (!hierarchy.empty()) {
504                            generateFullClick(writer, event, hierarchy.pop());
[1825]505                        }
[1857]506                        continue;
[1825]507                    }
[1857]508                }
[1825]509
[1857]510                handleMouseClick(writer, event);
[1671]511            }
[1857]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 {
[1872]528                Console.traceln(Level.WARNING, "No handler for event \"" + event + "\". Skipped.");
[1857]529            }
[1671]530        }
531    }
532
[1856]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 {
[1838]618        commitFocusEvent();
619        lastKeySequenceEvent = null;
620
[1856]621        lastMouseClickEvent = new StructureNode("MouseClick");
[1838]622        lastMouseDownTarget = event.getTarget();
623        writeMouseClickEvent(writer, event, (JFCGUIElement) event.getTarget(), EVENT_DURATION, 501);
624    }
625
[1856]626    private void handleMouseButtonUp(BufferedWriter writer, Event event) throws IOException {
[1838]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
[1836]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
[1825]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
[1834]708                elements.push(menuElements.get(stripped));
[1825]709                oldIndent = indent;
710            }
711        }
712
713        return elements;
714    }
715
[1808]716    private void commitFocusEvent() {
717        if (lastFocusChangeEvent != null) {
718            structure.children.add(lastFocusChangeEvent);
719            lastFocusChangeEvent = null;
720        }
721    }
722
[1825]723    private void generateFullClick(BufferedWriter writer, Event event, JFCGUIElement target)
724        throws IOException
725    {
[1809]726        lastMouseClickEvent = new StructureNode("MouseClick");
727        lastMouseDownTarget = event.getTarget();
728
[1825]729        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 501);
730        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 502);
731        writeMouseClickEvent(writer, event, target, EVENT_DURATION, 500);
[1809]732        writeItemActionEvent(writer, event);
733
734        structure.children.add(lastMouseClickEvent);
735        structure.children.add(lastItemActionEvent);
736
737        lastMouseDownTarget = null;
[1714]738    }
739
[1688]740    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
[1671]741        writeLine(writer, "</Record>");
742
743        // write the recording's structure
744        writeLine(writer, "<Structure>");
[1688]745        writer.write(structure.toString());
[1678]746        // close root element
[1671]747        writeLine(writer, "</Structure>");
748    }
749
[1857]750    private void writeJacaretoXML(List<Event> sequence,
[1712]751                                  String filename,
752                                  String classpath,
753                                  String basepath,
754                                  String classpathext)
755    {
[1671]756        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
757
758        try {
[1825]759            writeJacaretoHead(writer, classpath, basepath, classpathext);
[1857]760            writeJacaretoEvents(writer, sequence);
[1688]761            writeJacaretoTail(writer);
[1678]762            writeLine(writer, "</JacaretoStructure>");
[1671]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
[1673]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    }
[1683]809
[1688]810    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
[1685]811        JFCGUIElement target = (JFCGUIElement) event.getTarget();
812        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
813
[1684]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\" "
[1872]834            + "command=" + StringEscapeUtils.escapeXml(target.getName()) + " "
[1685]835            + "modifiers=\"" + getButtonModifier(info) + "\" />"
[1684]836        );
837        //@formatter:on
[1689]838        lastItemActionEvent = new StructureNode("ItemStateChange");
839        lastItemActionEvent.addRecordable();
840        lastItemActionEvent.addRecordable();
[1684]841    }
842
[1688]843    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
[1686]844        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
845        JFCGUIElement target = (JFCGUIElement) event.getTarget();
846
847        if (currentFocus != null) {
[1689]848            lastFocusChangeEvent = new StructureNode("FocusChange");
[1688]849
[1686]850            // focus lost on old target
[1688]851            writeFocusEvent(writer, info, currentFocus, 1005);
[1686]852            // focus gained on new target
[1688]853            writeFocusEvent(writer, info, target, 1004);
[1686]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
[1689]887        lastFocusChangeEvent.addRecordable();
[1686]888    }
889
[1825]890    private void writeMouseClickEvent(BufferedWriter writer,
891                                      Event event,
892                                      JFCGUIElement target,
893                                      int duration,
894                                      int jacId) throws IOException
[1683]895    {
[1685]896        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
[1838]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();
[1685]909        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
910
[1683]911        //@formatter:off
912        writeLine(writer,
913            "<MouseEvent "
914            + "procTime=\"0\" "
[1808]915            + "duration=\"" + duration + "\" "
[1683]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\" "
[1685]926            + "when=\"" + event.getTimestamp() + "\" "
[1683]927            + "isConsumed=\"false\">"
928        );
929        writeLine(writer,
930            "<MouseInfo "
[1838]931            + "xPosition=\"" + x + "\" "
932            + "yPosition=\"" + y + "\" "
[1683]933            + "rootX=\"0\" "
934            + "rootY=\"0\" "
[1685]935            + "clickCount=\"" + clickCount + "\" "
936            + "modifiers=\"" + getButtonModifier(info) + "\" "
[1683]937            + "isPopupTrigger=\"false\" />"
938        );
939        writeLine(writer, "</MouseEvent>");
940        //@formatter:on
941
[1689]942        lastMouseClickEvent.addRecordable();
[1683]943    }
[1685]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    }
[1691]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();
[1704]963        int keyCode = info.getKey().getVirtualKeyCode();
[1691]964
[1704]965        applyKeyModifier(info.getKey(), jacId == 401);
[1712]966
[1691]967        //@formatter:off
968        writeLine(writer,
969            "<KeyEvent "
970            + "procTime=\"0\" "
[1719]971            + "duration=\"" + EVENT_DURATION + "\" "
[1691]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 "
[1704]987            + "keyCode=\"" + keyCode + "\" "
988            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
989            + "modifiers=\"" + currentKeyModifiers + "\" />"
[1691]990        );
991       
992        writeLine(writer, "</KeyEvent>");
993       
[1701]994        lastKeyTypedEvent.addRecordable();
[1691]995    }
996   
[1701]997    private String getKeyChar (int keyCode) {
998        if (keyCode >= 32 && keyCode < 127) {
999            return String.valueOf((char)keyCode);
1000        }
[1712]1001        return "_NO_LEGAL_XML_CHAR";
[1691]1002    }
[1704]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    }
[1671]1015}
Note: See TracBrowser for help on using the repository browser.