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
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();
[1883]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       
[1684]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\" "
[1883]837            + "command=\"" + StringEscapeUtils.escapeXml(cmd) + "\" "
[1685]838            + "modifiers=\"" + getButtonModifier(info) + "\" />"
[1684]839        );
840        //@formatter:on
[1689]841        lastItemActionEvent = new StructureNode("ItemStateChange");
842        lastItemActionEvent.addRecordable();
843        lastItemActionEvent.addRecordable();
[1684]844    }
845
[1688]846    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
[1686]847        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
848        JFCGUIElement target = (JFCGUIElement) event.getTarget();
849
850        if (currentFocus != null) {
[1689]851            lastFocusChangeEvent = new StructureNode("FocusChange");
[1688]852
[1686]853            // focus lost on old target
[1688]854            writeFocusEvent(writer, info, currentFocus, 1005);
[1686]855            // focus gained on new target
[1688]856            writeFocusEvent(writer, info, target, 1004);
[1686]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
[1689]890        lastFocusChangeEvent.addRecordable();
[1686]891    }
892
[1825]893    private void writeMouseClickEvent(BufferedWriter writer,
894                                      Event event,
895                                      JFCGUIElement target,
896                                      int duration,
897                                      int jacId) throws IOException
[1683]898    {
[1685]899        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
[1838]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();
[1685]912        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
913
[1683]914        //@formatter:off
915        writeLine(writer,
916            "<MouseEvent "
917            + "procTime=\"0\" "
[1808]918            + "duration=\"" + duration + "\" "
[1683]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\" "
[1685]929            + "when=\"" + event.getTimestamp() + "\" "
[1683]930            + "isConsumed=\"false\">"
931        );
932        writeLine(writer,
933            "<MouseInfo "
[1838]934            + "xPosition=\"" + x + "\" "
935            + "yPosition=\"" + y + "\" "
[1683]936            + "rootX=\"0\" "
937            + "rootY=\"0\" "
[1685]938            + "clickCount=\"" + clickCount + "\" "
939            + "modifiers=\"" + getButtonModifier(info) + "\" "
[1683]940            + "isPopupTrigger=\"false\" />"
941        );
942        writeLine(writer, "</MouseEvent>");
943        //@formatter:on
944
[1689]945        lastMouseClickEvent.addRecordable();
[1683]946    }
[1685]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    }
[1691]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();
[1704]966        int keyCode = info.getKey().getVirtualKeyCode();
[1691]967
[1704]968        applyKeyModifier(info.getKey(), jacId == 401);
[1712]969
[1691]970        //@formatter:off
971        writeLine(writer,
972            "<KeyEvent "
973            + "procTime=\"0\" "
[1719]974            + "duration=\"" + EVENT_DURATION + "\" "
[1691]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 "
[1704]990            + "keyCode=\"" + keyCode + "\" "
991            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
992            + "modifiers=\"" + currentKeyModifiers + "\" />"
[1691]993        );
994       
995        writeLine(writer, "</KeyEvent>");
996       
[1701]997        lastKeyTypedEvent.addRecordable();
[1691]998    }
999   
[1701]1000    private String getKeyChar (int keyCode) {
1001        if (keyCode >= 32 && keyCode < 127) {
1002            return String.valueOf((char)keyCode);
1003        }
[1712]1004        return "_NO_LEGAL_XML_CHAR";
[1691]1005    }
[1704]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    }
[1671]1018}
Note: See TracBrowser for help on using the repository browser.