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

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

event durations are now configurable at compile time

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