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

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

implement keypress modifiers: ctrl, shift, alt and alt graph - should be enough for now

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