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

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

it is now possible to replay arbitrary jfc programs (not recommended though because of ID problem)

  • Property svn:mime-type set to text/plain
File size: 19.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.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> <class> <basepath> <classpathext> {<initclass>}";
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        String classpath;
126        String basepath;
127        String classpathext;
128        String initclass = "";
129        try {
130            filename = (String) parameters.get(0);
131            sequencesName = (String) parameters.get(1);
132            classpath = (String) parameters.get(2);
133            basepath = (String) parameters.get(3);
134            classpathext = (String) parameters.get(4);
135        }
136        catch (Exception e) {
137            throw new IllegalArgumentException();
138        }
139
140        if (parameters.size() > 5) {
141            initclass = (String) parameters.get(5);
142        }
143
144        Collection<List<Event>> sequences = null;
145        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName);
146        if (dataObject == null) {
147            CommandHelpers.objectNotFoundMessage(sequencesName);
148            return;
149        }
150        if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
151            CommandHelpers.objectNotType(sequencesName, "Collection<List<Event<?>>>");
152            return;
153        }
154
155        sequences = (Collection<List<Event>>) dataObject;
156
157        // map which maps VirtualKeys back to awt key modifier codes
158        modifiers = new HashMap<>();
159        modifiers.put(VirtualKey.SHIFT, 1);
160        modifiers.put(VirtualKey.CONTROL, 2);
161        modifiers.put(VirtualKey.ALT, 8);
162        modifiers.put(VirtualKey.ALT_GRAPH, 32);
163        currentKeyModifiers = 0;
164
165        writeJacaretoXML(sequences, filename, classpath, initclass, basepath, classpathext);
166    }
167
168    private void writeLine(BufferedWriter writer, String line) throws IOException {
169        writer.write(line);
170        writer.newLine();
171    }
172
173    private void writeJacaretoHead(BufferedWriter writer,
174                                   String classname,
175                                   String initclass,
176                                   String basepath,
177                                   String classpathext) throws IOException
178    {
179        writeLine(writer, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
180        writeLine(writer, "<JacaretoStructure>");
181        writeLine(writer, "<Record>");
182
183        // TODO: This header content is basically copy+paste from a
184        // specific jacareto replay file right now.
185        // Some things such as screen resolution and especially the
186        // application starter details need to be changed for general cases.
187        writeLine(writer,
188                  "<Calendar procTime=\"0\" duration=\"0\" year=\"2000\" month=\"1\" date=\"11\" hour=\"1\" min=\"1\" sec=\"1\" uuid=\"06831ba1-f28a-4e05-b46e-ce9d8f9ffa0f\" />");
189        writeLine(writer,
190                  "<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\" />");
191        writeLine(writer,
192                  "<KeyboardState procTime=\"0\" duration=\"0\" isNumLockOn=\"false\" isScrollLockOn=\"false\" isCapsLockOn=\"false\" applyIsNumLockOn=\"true\" applyIsScrollLockOn=\"true\" applyIsCapsLockOn=\"true\" uuid=\"28146f79-9fc7-49f9-b4a8-5866a7625683\" />");
193        writeLine(writer, "<ComponentMode numberPopupMenues=\"true\" />");
194        //@formatter:off
195        writeLine(writer, "<ApplicationStarter "
196            + "procTime=\"0\" "
197            + "duration=\"0\" "
198            + "name=\"Autoquest Replay\" "
199            + "class=\"" + classname + "\" "
200            + "initclass=\"" + initclass + "\" "
201            + "basepath=\"" + basepath + "\" "
202            + "classpathext=\"" + classpathext + "\" "
203            + "detectDuration=\"false\" "
204            + "captureparams=\"\" "
205            + "replayparams=\"\" "
206            + "uuid=\"a7b7d7b9-caa9-4d6d-b052-cf74d353275e\" />"
207        );
208        //@formatter:on
209    }
210
211    private void writeJacaretoEvents(BufferedWriter writer, Collection<List<Event>> sequences)
212        throws IOException
213    {
214        structure = new StructureNode("RootElement");
215        // reference the elements that we included in the header
216        structure.addRecordable(); // Calendar
217        structure.addRecordable(); // SystemInfo
218        structure.addRecordable(); // KeyboardState
219        structure.addRecordable(); // ComponentMode
220        structure.addRecordable(); // ApplicationStarter
221
222        for (List<Event> sequence : sequences) {
223            for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
224                Event event = eventIter.next();
225
226                if (event.getType() instanceof MouseButtonDown) {
227                    lastKeySequenceEvent = null;
228
229                    lastMouseClickEvent = new StructureNode("MouseClick");
230                    writeMouseClickEvent(writer, event, 501);
231                }
232                else if (event.getType() instanceof MouseButtonUp) {
233                    lastKeySequenceEvent = null;
234
235                    writeMouseClickEvent(writer, event, 502);
236                }
237                else if (event.getType() instanceof MouseClick) {
238                    lastKeySequenceEvent = null;
239
240                    writeMouseClickEvent(writer, event, 500);
241                    // FIXME: don't always write an item action
242                    writeItemActionEvent(writer, event);
243                    // FIXME: don't write it all here because there
244                    // might be no item action at all
245                    if (lastFocusChangeEvent == null) {
246                        // write structure sequentially
247                        structure.children.add(lastMouseClickEvent);
248                        structure.children.add(lastItemActionEvent);
249                    }
250                    else {
251                        // with nested structure
252                        structure.children.add(lastItemActionEvent);
253                        lastItemActionEvent.children.add(0, lastFocusChangeEvent);
254                        lastFocusChangeEvent.children.add(0, lastMouseClickEvent);
255
256                        lastFocusChangeEvent = null;
257                    }
258                }
259                else if (event.getType() instanceof KeyboardFocusChange) {
260                    lastKeySequenceEvent = null;
261
262                    writeFocusChangeEvent(writer, event);
263                }
264                else if (event.getType() instanceof KeyPressed) {
265                    if (lastKeySequenceEvent == null) {
266                        lastKeySequenceEvent = structure.add("KeySequence");
267                    }
268                    lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped");
269                    writeKeyEvent(writer, event, 401);
270                }
271                else if (event.getType() instanceof KeyReleased) {
272                    writeKeyEvent(writer, event, 402);
273                }
274            }
275        }
276    }
277
278    private void writeJacaretoTail(BufferedWriter writer) throws IOException {
279        writeLine(writer, "</Record>");
280
281        // write the recording's structure
282        writeLine(writer, "<Structure>");
283        writer.write(structure.toString());
284        // close root element
285        writeLine(writer, "</Structure>");
286    }
287
288    private void writeJacaretoXML(Collection<List<Event>> sequences,
289                                  String filename,
290                                  String classpath,
291                                  String initclass,
292                                  String basepath,
293                                  String classpathext)
294    {
295        BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml"));
296
297        try {
298            writeJacaretoHead(writer, classpath, initclass, basepath, classpathext);
299            writeJacaretoEvents(writer, sequences);
300            writeJacaretoTail(writer);
301            writeLine(writer, "</JacaretoStructure>");
302
303            writer.flush();
304            writer.close();
305        }
306        catch (IOException e) {
307            Console.printerrln("Unable to write Jacareto replay file " + filename);
308        }
309    }
310
311    /**
312     * <p>
313     * Helper function that opens the replay file for writing.
314     * </p>
315     *
316     * @param filename
317     *            name and path of the replay file
318     * @param encoding
319     *            file encoding, empty string for platform default
320     * @return {@link OutputStreamWriter} that writes to the replay file
321     */
322    private OutputStreamWriter openReplayFile(String filename) {
323        File file = new File(filename);
324        boolean fileCreated;
325        try {
326            fileCreated = file.createNewFile();
327            if (!fileCreated) {
328                Console.traceln(Level.INFO, "Created logfile " + filename);
329            }
330            else {
331                Console.traceln(Level.INFO, "Overwrote existing logfile " + filename);
332            }
333        }
334        catch (IOException e) {
335            Console.printerrln("Unable to create file " + filename);
336            Console.logException(e);
337        }
338        OutputStreamWriter writer = null;
339        try {
340            writer = new OutputStreamWriter(new FileOutputStream(file));
341        }
342        catch (IOException e) {
343            Console.printerrln("Unable to open file for writing (read-only file):" + filename);
344            Console.logException(e);
345        }
346        return writer;
347    }
348
349    private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException {
350        JFCGUIElement target = (JFCGUIElement) event.getTarget();
351        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
352
353        //@formatter:off
354        writeLine(writer,
355            "<ItemEvent "
356            + "procTime=\"0\" "
357            + "duration=\"0\" "
358            + "source=\"" + target.getJacaretoHierarchy() + "\" "
359            + "class=\"" + target.getSpecification().getType() + "\" "
360            + "uuid=\"" + UUID.randomUUID() + "\" "
361            + "ID=\"701\" "
362            + "item=\"\" "
363            + "stateChange=\"1\" />"
364        );
365        writeLine(writer,
366            "<ActionEvent "
367            + "procTime=\"0\" "
368            + "duration=\"0\" "
369            + "source=\"" + target.getJacaretoHierarchy() + "\" "
370            + "class=\"" + target.getSpecification().getType() + "\" "
371            + "uuid=\"" + UUID.randomUUID() + "\" "
372            + "ID=\"1001\" "
373            + "command=" + target.getName() + " "
374            + "modifiers=\"" + getButtonModifier(info) + "\" />"
375        );
376        //@formatter:on
377        lastItemActionEvent = new StructureNode("ItemStateChange");
378        lastItemActionEvent.addRecordable();
379        lastItemActionEvent.addRecordable();
380    }
381
382    private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException {
383        KeyboardFocusChange info = (KeyboardFocusChange) event.getType();
384        JFCGUIElement target = (JFCGUIElement) event.getTarget();
385
386        if (currentFocus != null) {
387            lastFocusChangeEvent = new StructureNode("FocusChange");
388
389            // focus lost on old target
390            writeFocusEvent(writer, info, currentFocus, 1005);
391            // focus gained on new target
392            writeFocusEvent(writer, info, target, 1004);
393        }
394        else {
395            // TODO: it seems like Jacareto wants a window activation before
396            // the first focus event but that is not the case in autoquest,
397            // skip for now
398        }
399
400        currentFocus = target;
401    }
402
403    private void writeFocusEvent(BufferedWriter writer,
404                                 KeyboardFocusChange info,
405                                 JFCGUIElement target,
406                                 int jacId) throws IOException
407    {
408        //@formatter:off
409        writeLine(writer,
410            "<FocusEvent "
411            + "procTime=\"0\" "
412            + "duration=\"0\" "
413            + "source=\"" + target.getJacaretoHierarchy() + "\" "
414            + "class=\"" + target.getSpecification().getType() + "\" "
415            + "uuid=\"" + UUID.randomUUID() + "\" "
416            + "ID=\"" + jacId + "\" "
417            + "component=\"null\" "
418            + "root=\"" + target.getJacaretoRoot() + "\" "
419            + "xPos=\"0\" "
420            + "yPos=\"0\" "
421            + "width=\"0\" "
422            + "height=\"0\" "
423            + "isTemporary=\"false\" />"
424        );
425        //@formatter:on
426        lastFocusChangeEvent.addRecordable();
427    }
428
429    private void writeMouseClickEvent(BufferedWriter writer, Event event, int jacId)
430        throws IOException
431    {
432        MouseButtonInteraction info = (MouseButtonInteraction) event.getType();
433        JFCGUIElement target = (JFCGUIElement) event.getTarget();
434        int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1;
435
436        // TODO: change procTime and duration to adequate values
437        //@formatter:off
438        writeLine(writer,
439            "<MouseEvent "
440            + "procTime=\"0\" "
441            + "duration=\"150\" "
442            + "source=\"" + target.getJacaretoHierarchy() + "\" "
443            + "class=\"" + target.getSpecification().getType() + "\" "
444            + "uuid=\"" + UUID.randomUUID() + "\" "
445            + "ID=\"" + jacId + "\" "
446            + "component=\"null\" "
447            + "root=\"" + target.getJacaretoRoot() + "\" "
448            + "xPos=\"0\" "
449            + "yPos=\"0\" "
450            + "width=\"0\" "
451            + "height=\"0\" "
452            + "when=\"" + event.getTimestamp() + "\" "
453            + "isConsumed=\"false\">"
454        );
455        writeLine(writer,
456            "<MouseInfo "
457            + "xPosition=\"" + info.getX() + "\" "
458            + "yPosition=\"" + info.getY() + "\" "
459            + "rootX=\"0\" "
460            + "rootY=\"0\" "
461            + "clickCount=\"" + clickCount + "\" "
462            + "modifiers=\"" + getButtonModifier(info) + "\" "
463            + "isPopupTrigger=\"false\" />"
464        );
465        writeLine(writer, "</MouseEvent>");
466        //@formatter:on
467
468        lastMouseClickEvent.addRecordable();
469    }
470
471    private int getButtonModifier(MouseButtonInteraction info) {
472        switch (info.getButton())
473        {
474            case LEFT:
475                return 16;
476            case MIDDLE:
477                return 8;
478            case RIGHT:
479                return 4;
480            default:
481                // TODO: handle unknown mouse button
482                return -1;
483        }
484    }
485
486    private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException {
487        KeyInteraction info = (KeyInteraction) event.getType();
488        JFCGUIElement target = (JFCGUIElement) event.getTarget();
489        int keyCode = info.getKey().getVirtualKeyCode();
490
491        applyKeyModifier(info.getKey(), jacId == 401);
492
493        //@formatter:off
494        writeLine(writer,
495            "<KeyEvent "
496            + "procTime=\"0\" "
497            + "duration=\"150\" "
498            + "source=\"" + target.getJacaretoHierarchy() + "\" "
499            + "class=\"" + target.getSpecification().getType() + "\" "
500            + "uuid=\"" + UUID.randomUUID() + "\" "
501            + "ID=\"" + jacId + "\" "
502            + "component=\"null\" "
503            + "root=\"" + target.getJacaretoRoot() + "\" "
504            + "xPos=\"0\" "
505            + "yPos=\"0\" "
506            + "width=\"0\" "
507            + "height=\"0\" "
508            + "when=\"" + event.getTimestamp() + "\" "
509            + "isConsumed=\"false\">"
510        );
511        writeLine(writer,
512            "<KeyInfo "
513            + "keyCode=\"" + keyCode + "\" "
514            + "keyChar=\"" + getKeyChar(keyCode) + "\" "
515            + "modifiers=\"" + currentKeyModifiers + "\" />"
516        );
517       
518        writeLine(writer, "</KeyEvent>");
519       
520        lastKeyTypedEvent.addRecordable();
521    }
522   
523    private String getKeyChar (int keyCode) {
524        if (keyCode >= 32 && keyCode < 127) {
525            return String.valueOf((char)keyCode);
526        }
527
528        return "_NO_LEGAL_XML_CHAR";
529    }
530   
531    private void applyKeyModifier (VirtualKey key, boolean set) {
532        Integer modifier = modifiers.get(key);
533        if (modifier != null) {
534            if (set) {
535                currentKeyModifiers |= modifier;
536            }
537            else {
538                currentKeyModifiers &= ~modifier;
539            }
540        }
541    }
542}
Note: See TracBrowser for help on using the repository browser.