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

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

Finally implement focus events; this was a lot more work than expected. The <structure> part of a Jacareto xml file is now represented by a tree because it seems really hard to write this directly as a sequence.

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