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

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

rudimentary mouse drag support, this seems to require a lot of pixel precision that is not there currently

  • Property svn:mime-type set to text/plain
File size: 20.1 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
17import java.io.BufferedWriter;
[1673]18import java.io.File;
19import java.io.FileOutputStream;
[1671]20import java.io.IOException;
[1673]21import java.io.OutputStreamWriter;
[1671]22import java.util.ArrayList;
23import java.util.Collection;
[1704]24import java.util.HashMap;
[1671]25import java.util.Iterator;
26import java.util.List;
[1678]27import java.util.UUID;
[1673]28import java.util.logging.Level;
[1671]29
30import de.ugoe.cs.autoquest.CommandHelpers;
31import de.ugoe.cs.autoquest.SequenceInstanceOf;
[1673]32import de.ugoe.cs.util.console.Command;
[1671]33import de.ugoe.cs.autoquest.eventcore.Event;
[1685]34import de.ugoe.cs.autoquest.eventcore.gui.*;
[1691]35import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
[1673]36import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
[1671]37import de.ugoe.cs.util.console.Console;
38import de.ugoe.cs.util.console.GlobalDataContainer;
39
[1688]40// helper class for the tree like structure part within a Jacareto file
41class StructureNode {
[1713]42    public static int nextRef = 0;
[1689]43
[1688]44    public String content;
45    public ArrayList<StructureNode> children;
46
[1689]47    public StructureNode(String type) {
[1714]48        setContent(type);
[1689]49        children = new ArrayList<StructureNode>();
[1688]50    }
51
[1689]52    public StructureNode() {
53        content = "<Recordable ref=\"" + (nextRef++) + "\" />";
54        children = new ArrayList<StructureNode>();
55    }
56
[1714]57    public void setContent(String type) {
58        content = "<StructureElement class=\"jacareto.struct." + type + "\">";
59    }
60
[1689]61    public StructureNode add(String type) {
62        StructureNode node = new StructureNode(type);
[1688]63        children.add(node);
64        return node;
65    }
66
[1689]67    public void addRecordable() {
68        children.add(new StructureNode());
69    }
70
[1688]71    @Override
72    public String toString() {
73        String separator = System.getProperty("line.separator");
74        String result = content + separator;
75
76        for (StructureNode child : children) {
77            result += child.toString();
78        }
79
[1689]80        if (content.endsWith("/>")) {
[1688]81            return result;
82        }
83        return result + "</StructureElement>" + separator;
84    }
85}
86
[1671]87/**
88 * <p>
89 * Command to create a Jacareto xml replay file from stored sessions.
90 * </p>
91 *
92 * @author Daniel May
93 * @version 1.0
94 */
[1673]95public class CMDgenerateJacaretoReplay implements Command {
[1686]96    private JFCGUIElement currentFocus;
[1688]97    private StructureNode structure;
[1704]98
[1701]99    private StructureNode lastKeySequenceEvent;
100    private StructureNode lastKeyTypedEvent;
[1704]101    private int currentKeyModifiers;
102
103    private HashMap<VirtualKey, Integer> modifiers;
[1712]104
[1688]105    private StructureNode lastMouseClickEvent;
106    private StructureNode lastFocusChangeEvent;
107    private StructureNode lastItemActionEvent;
108
[1671]109    /*
110     * (non-Javadoc)
111     *
112     * @see de.ugoe.cs.util.console.Command#help()
113     */
114    @Override
115    public String help() {
[1712]116        return "generateJacaretoReplay <filename> <sequences> <class> <basepath> <classpathext> {<initclass>}";
[1671]117    }
118
119    /*
120     * (non-Javadoc)
121     *
122     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
123     */
124    @SuppressWarnings("unchecked")
125    @Override
126    public void run(List<Object> parameters) {
127        String filename;
128        String sequencesName;
[1712]129        String classpath;
130        String basepath;
131        String classpathext;
132        String initclass = "";
[1671]133        try {
134            filename = (String) parameters.get(0);
135            sequencesName = (String) parameters.get(1);
[1712]136            classpath = (String) parameters.get(2);
137            basepath = (String) parameters.get(3);
138            classpathext = (String) parameters.get(4);
[1671]139        }
140        catch (Exception e) {
141            throw new IllegalArgumentException();
142        }
143
[1712]144        if (parameters.size() > 5) {
145            initclass = (String) parameters.get(5);
146        }
147
[1671]148        Collection<List<Event>> sequences = null;
149        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName);
150        if (dataObject == null) {
151            CommandHelpers.objectNotFoundMessage(sequencesName);
152            return;
153        }
154        if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
155            CommandHelpers.objectNotType(sequencesName, "Collection<List<Event<?>>>");
156            return;
157        }
158
159        sequences = (Collection<List<Event>>) dataObject;
160
[1704]161        // map which maps VirtualKeys back to awt key modifier codes
162        modifiers = new HashMap<>();
163        modifiers.put(VirtualKey.SHIFT, 1);
164        modifiers.put(VirtualKey.CONTROL, 2);
165        modifiers.put(VirtualKey.ALT, 8);
166        modifiers.put(VirtualKey.ALT_GRAPH, 32);
167        currentKeyModifiers = 0;
[1712]168
[1713]169        StructureNode.nextRef = 0;
170
[1712]171        writeJacaretoXML(sequences, filename, classpath, initclass, basepath, classpathext);
[1671]172    }
173
174    private void writeLine(BufferedWriter writer, String line) throws IOException {
175        writer.write(line);
176        writer.newLine();
177    }
178
[1712]179    private void writeJacaretoHead(BufferedWriter writer,
180                                   String classname,
181                                   String initclass,
182                                   String basepath,
183                                   String classpathext) throws IOException
184    {
[1671]185        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
186        writeLine(writer, "<JacaretoStructure>");
187        writeLine(writer, "<Record>");
188
189        // TODO: This header content is basically copy+paste from a
190        // specific jacareto replay file right now.
191        writeLine(writer,
[1712]192                  "<Calendar procTime=\"0\" duration=\"0\" year=\"2000\" month=\"1\" date=\"11\" hour=\"1\" min=\"1\" sec=\"1\" uuid=\"06831ba1-f28a-4e05-b46e-ce9d8f9ffa0f\" />");
[1671]193        writeLine(writer,
194                  "<SystemInfo procTime=\"0\" duration=\"0\" screenWidth=\"2646\" screenHeight=\"1024\" javaVersion=\"1.7.0_65\" lookAndFeel=\"javax.swing.plaf.metal.MetalLookAndFeel\" uuid=\"720f430f-52cf-4d8b-9fbe-58434f766efe\" />");
195        writeLine(writer,
196                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
[1678]197        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
[1712]198        //@formatter:off
199        writeLine(writer, "<ApplicationStarter "
200            + "procTime=\"0\" "
201            + "duration=\"0\" "
202            + "name=\"Autoquest Replay\" "
203            + "class=\"" + classname + "\" "
204            + "initclass=\"" + initclass + "\" "
205            + "basepath=\"" + basepath + "\" "
206            + "classpathext=\"" + classpathext + "\" "
207            + "detectDuration=\"false\" "
208            + "captureparams=\"\" "
209            + "replayparams=\"\" "
210            + "uuid=\"a7b7d7b9-caa9-4d6d-b052-cf74d353275e\" />"
211        );
212        //@formatter:on
[1671]213    }
214
[1688]215    private void writeJacaretoEvents(BufferedWriter writer, Collection<List<Event>> sequences)
[1671]216        throws IOException
217    {
[1689]218        structure = new StructureNode("RootElement");
[1671]219        // reference the elements that we included in the header
[1689]220        structure.addRecordable(); // Calendar
221        structure.addRecordable(); // SystemInfo
222        structure.addRecordable(); // KeyboardState
223        structure.addRecordable(); // ComponentMode
224        structure.addRecordable(); // ApplicationStarter
[1671]225
226        for (List<Event> sequence : sequences) {
227            for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
228                Event event = eventIter.next();
229
[1714]230                System.out.println(event.getType());
231                System.out.println(event.getTarget());
232                System.out.println("---");
233
[1685]234                if (event.getType() instanceof MouseButtonDown) {
[1701]235                    lastKeySequenceEvent = null;
[1714]236                    checkIfMouseDragged();
[1701]237
[1689]238                    lastMouseClickEvent = new StructureNode("MouseClick");
[1688]239                    writeMouseClickEvent(writer, event, 501);
[1684]240                }
[1685]241                else if (event.getType() instanceof MouseButtonUp) {
[1701]242                    lastKeySequenceEvent = null;
243
[1688]244                    writeMouseClickEvent(writer, event, 502);
[1685]245                }
246                else if (event.getType() instanceof MouseClick) {
[1701]247                    lastKeySequenceEvent = null;
248
[1688]249                    writeMouseClickEvent(writer, event, 500);
[1685]250                    // FIXME: don't always write an item action
[1688]251                    writeItemActionEvent(writer, event);
252                    // FIXME: don't write it all here because there
253                    // might be no item action at all
254                    if (lastFocusChangeEvent == null) {
255                        // write structure sequentially
256                        structure.children.add(lastMouseClickEvent);
257                        structure.children.add(lastItemActionEvent);
258                    }
259                    else {
260                        // with nested structure
261                        structure.children.add(lastItemActionEvent);
262                        lastItemActionEvent.children.add(0, lastFocusChangeEvent);
263                        lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
264
265                        lastFocusChangeEvent = null;
[1714]266                        lastMouseClickEvent = null;
[1688]267                    }
[1685]268                }
[1686]269                else if (event.getType() instanceof KeyboardFocusChange) {
[1701]270                    lastKeySequenceEvent = null;
271
[1688]272                    writeFocusChangeEvent(writer, event);
[1686]273                }
[1691]274                else if (event.getType() instanceof KeyPressed) {
[1714]275                    checkIfMouseDragged();
276
[1701]277                    if (lastKeySequenceEvent == null) {
278                        lastKeySequenceEvent = structure.add("KeySequence");
279                    }
280                    lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
281                    writeKeyEvent(writer, event, 401);
[1691]282                }
283                else if (event.getType() instanceof KeyReleased) {
[1714]284                    checkIfMouseDragged();
285
[1701]286                    writeKeyEvent(writer, event, 402);
[1691]287                }
[1671]288            }
289        }
290    }
291
[1714]292    private void checkIfMouseDragged() {
293        if (lastMouseClickEvent != null) {
294            // this was not really a click, just a drag
295            lastMouseClickEvent.setContent("MouseDrag");
296            structure.children.add(lastMouseClickEvent);
297            lastMouseClickEvent = null;
298        }
299    }
300
[1688]301    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
[1671]302        writeLine(writer, "</Record>");
303
304        // write the recording's structure
305        writeLine(writer, "<Structure>");
[1688]306        writer.write(structure.toString());
[1678]307        // close root element
[1671]308        writeLine(writer, "</Structure>");
309    }
310
[1712]311    private void writeJacaretoXML(Collection<List<Event>> sequences,
312                                  String filename,
313                                  String classpath,
314                                  String initclass,
315                                  String basepath,
316                                  String classpathext)
317    {
[1671]318        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
319
320        try {
[1712]321            writeJacaretoHead(writer, classpath, initclass, basepath, classpathext);
[1688]322            writeJacaretoEvents(writer, sequences);
323            writeJacaretoTail(writer);
[1678]324            writeLine(writer, "</JacaretoStructure>");
[1671]325
326            writer.flush();
327            writer.close();
328        }
329        catch (IOException e) {
330            Console.printerrln("Unable to write Jacareto replay file " + filename);
331        }
332    }
333
[1673]334    /**
335     * <p>
336     * Helper function that opens the replay file for writing.
337     * </p>
338     *
339     * @param filename
340     *            name and path of the replay file
341     * @param encoding
342     *            file encoding, empty string for platform default
343     * @return {@link OutputStreamWriter} that writes to the replay file
344     */
345    private OutputStreamWriter openReplayFile(String filename) {
346        File file = new File(filename);
347        boolean fileCreated;
348        try {
349            fileCreated = file.createNewFile();
350            if (!fileCreated) {
351                Console.traceln(Level.INFO, "Created logfile " + filename);
352            }
353            else {
354                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
355            }
356        }
357        catch (IOException e) {
358            Console.printerrln("Unable to create file " + filename);
359            Console.logException(e);
360        }
361        OutputStreamWriter writer = null;
362        try {
363            writer = new OutputStreamWriter(new FileOutputStream(file));
364        }
365        catch (IOException e) {
366            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
367            Console.logException(e);
368        }
369        return writer;
370    }
[1683]371
[1688]372    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
[1685]373        JFCGUIElement target = (JFCGUIElement) event.getTarget();
374        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
375
[1684]376        //@formatter:off
377        writeLine(writer,
378            "<ItemEvent "
379            + "procTime=\"0\" "
380            + "duration=\"0\" "
381            + "source=\"" + target.getJacaretoHierarchy() + "\" "
382            + "class=\"" + target.getSpecification().getType() + "\" "
383            + "uuid=\"" + UUID.randomUUID() + "\" "
384            + "ID=\"701\" "
385            + "item=\"\" "
386            + "stateChange=\"1\" />"
387        );
388        writeLine(writer,
389            "<ActionEvent "
390            + "procTime=\"0\" "
391            + "duration=\"0\" "
392            + "source=\"" + target.getJacaretoHierarchy() + "\" "
393            + "class=\"" + target.getSpecification().getType() + "\" "
394            + "uuid=\"" + UUID.randomUUID() + "\" "
395            + "ID=\"1001\" "
396            + "command=" + target.getName() + " "
[1685]397            + "modifiers=\"" + getButtonModifier(info) + "\" />"
[1684]398        );
399        //@formatter:on
[1689]400        lastItemActionEvent = new StructureNode("ItemStateChange");
401        lastItemActionEvent.addRecordable();
402        lastItemActionEvent.addRecordable();
[1684]403    }
404
[1688]405    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
[1686]406        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
407        JFCGUIElement target = (JFCGUIElement) event.getTarget();
408
409        if (currentFocus != null) {
[1689]410            lastFocusChangeEvent = new StructureNode("FocusChange");
[1688]411
[1686]412            // focus lost on old target
[1688]413            writeFocusEvent(writer, info, currentFocus, 1005);
[1686]414            // focus gained on new target
[1688]415            writeFocusEvent(writer, info, target, 1004);
[1686]416        }
417        else {
418            // TODO: it seems like Jacareto wants a window activation before
419            // the first focus event but that is not the case in autoquest,
420            // skip for now
421        }
422
423        currentFocus = target;
424    }
425
426    private void writeFocusEvent(BufferedWriter writer,
427                                 KeyboardFocusChange info,
428                                 JFCGUIElement target,
429                                 int jacId) throws IOException
430    {
431        //@formatter:off
432        writeLine(writer,
433            "<FocusEvent "
434            + "procTime=\"0\" "
435            + "duration=\"0\" "
436            + "source=\"" + target.getJacaretoHierarchy() + "\" "
437            + "class=\"" + target.getSpecification().getType() + "\" "
438            + "uuid=\"" + UUID.randomUUID() + "\" "
439            + "ID=\"" + jacId + "\" "
440            + "component=\"null\" "
441            + "root=\"" + target.getJacaretoRoot() + "\" "
442            + "xPos=\"0\" "
443            + "yPos=\"0\" "
444            + "width=\"0\" "
445            + "height=\"0\" "
446            + "isTemporary=\"false\" />"
447        );
448        //@formatter:on
[1689]449        lastFocusChangeEvent.addRecordable();
[1686]450    }
451
[1688]452    private void writeMouseClickEvent(BufferedWriter writer, Event event, int jacId)
453        throws IOException
[1683]454    {
[1685]455        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
456        JFCGUIElement target = (JFCGUIElement) event.getTarget();
457        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
458
[1683]459        // TODO: change procTime and duration to adequate values
460        //@formatter:off
461        writeLine(writer,
462            "<MouseEvent "
463            + "procTime=\"0\" "
464            + "duration=\"150\" "
465            + "source=\"" + target.getJacaretoHierarchy() + "\" "
466            + "class=\"" + target.getSpecification().getType() + "\" "
467            + "uuid=\"" + UUID.randomUUID() + "\" "
468            + "ID=\"" + jacId + "\" "
469            + "component=\"null\" "
470            + "root=\"" + target.getJacaretoRoot() + "\" "
471            + "xPos=\"0\" "
472            + "yPos=\"0\" "
473            + "width=\"0\" "
474            + "height=\"0\" "
[1685]475            + "when=\"" + event.getTimestamp() + "\" "
[1683]476            + "isConsumed=\"false\">"
477        );
478        writeLine(writer,
479            "<MouseInfo "
[1686]480            + "xPosition=\"" + info.getX() + "\" "
481            + "yPosition=\"" + info.getY() + "\" "
[1683]482            + "rootX=\"0\" "
483            + "rootY=\"0\" "
[1685]484            + "clickCount=\"" + clickCount + "\" "
485            + "modifiers=\"" + getButtonModifier(info) + "\" "
[1683]486            + "isPopupTrigger=\"false\" />"
487        );
488        writeLine(writer, "</MouseEvent>");
489        //@formatter:on
490
[1689]491        lastMouseClickEvent.addRecordable();
[1683]492    }
[1685]493
494    private int getButtonModifier(MouseButtonInteraction info) {
495        switch (info.getButton())
496        {
497            case LEFT:
498                return 16;
499            case MIDDLE:
500                return 8;
501            case RIGHT:
502                return 4;
503            default:
504                // TODO: handle unknown mouse button
505                return -1;
506        }
507    }
[1691]508
509    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
510        KeyInteraction info = (KeyInteraction) event.getType();
511        JFCGUIElement target = (JFCGUIElement) event.getTarget();
[1704]512        int keyCode = info.getKey().getVirtualKeyCode();
[1691]513
[1704]514        applyKeyModifier(info.getKey(), jacId == 401);
[1712]515
[1691]516        //@formatter:off
517        writeLine(writer,
518            "<KeyEvent "
519            + "procTime=\"0\" "
520            + "duration=\"150\" "
521            + "source=\"" + target.getJacaretoHierarchy() + "\" "
522            + "class=\"" + target.getSpecification().getType() + "\" "
523            + "uuid=\"" + UUID.randomUUID() + "\" "
524            + "ID=\"" + jacId + "\" "
525            + "component=\"null\" "
526            + "root=\"" + target.getJacaretoRoot() + "\" "
527            + "xPos=\"0\" "
528            + "yPos=\"0\" "
529            + "width=\"0\" "
530            + "height=\"0\" "
531            + "when=\"" + event.getTimestamp() + "\" "
532            + "isConsumed=\"false\">"
533        );
534        writeLine(writer,
535            "<KeyInfo "
[1704]536            + "keyCode=\"" + keyCode + "\" "
537            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
538            + "modifiers=\"" + currentKeyModifiers + "\" />"
[1691]539        );
540       
541        writeLine(writer, "</KeyEvent>");
542       
[1701]543        lastKeyTypedEvent.addRecordable();
[1691]544    }
545   
[1701]546    private String getKeyChar (int keyCode) {
547        if (keyCode >= 32 && keyCode < 127) {
548            return String.valueOf((char)keyCode);
549        }
[1712]550        return "_NO_LEGAL_XML_CHAR";
[1691]551    }
[1704]552   
553    private void applyKeyModifier (VirtualKey key, boolean set) {
554        Integer modifier = modifiers.get(key);
555        if (modifier != null) {
556            if (set) {
557                currentKeyModifiers |= modifier;
558            }
559            else {
560                currentKeyModifiers &= ~modifier;
561            }
562        }
563    }
[1671]564}
Note: See TracBrowser for help on using the repository browser.