// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.plugin.jfc.commands; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.logging.Level; import de.ugoe.cs.autoquest.CommandHelpers; import de.ugoe.cs.autoquest.SequenceInstanceOf; import de.ugoe.cs.util.console.Command; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.eventcore.gui.*; import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey; import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement; import de.ugoe.cs.util.console.Console; import de.ugoe.cs.util.console.GlobalDataContainer; // helper class for the tree like structure part within a Jacareto file class StructureNode { public static int nextRef = 0; public String content; public ArrayList children; public StructureNode(String type) { setContent(type); children = new ArrayList(); } public StructureNode() { content = ""; children = new ArrayList(); } public void setContent(String type) { content = ""; } public StructureNode add(String type) { StructureNode node = new StructureNode(type); children.add(node); return node; } public void addRecordable() { children.add(new StructureNode()); } @Override public String toString() { String separator = System.getProperty("line.separator"); String result = content + separator; for (StructureNode child : children) { result += child.toString(); } if (content.endsWith("/>")) { return result; } return result + "" + separator; } } /** *

* Command to create a Jacareto xml replay file from stored sessions. *

* * @author Daniel May * @version 1.0 */ public class CMDgenerateJacaretoReplay implements Command { private static final int EVENT_DURATION = 150; private JFCGUIElement currentFocus; private StructureNode structure; private StructureNode lastKeySequenceEvent; private StructureNode lastKeyTypedEvent; private int currentKeyModifiers; private HashMap modifiers; private StructureNode lastMouseClickEvent; private StructureNode lastFocusChangeEvent; private StructureNode lastItemActionEvent; /* * (non-Javadoc) * * @see de.ugoe.cs.util.console.Command#help() */ @Override public String help() { return "generateJacaretoReplay {}"; } /* * (non-Javadoc) * * @see de.ugoe.cs.util.console.Command#run(java.util.List) */ @SuppressWarnings("unchecked") @Override public void run(List parameters) { String filename; String sequencesName; String classpath; String basepath; String classpathext; String initclass = ""; try { filename = (String) parameters.get(0); sequencesName = (String) parameters.get(1); classpath = (String) parameters.get(2); basepath = (String) parameters.get(3); classpathext = (String) parameters.get(4); } catch (Exception e) { throw new IllegalArgumentException(); } if (parameters.size() > 5) { initclass = (String) parameters.get(5); } Collection> sequences = null; Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName); if (dataObject == null) { CommandHelpers.objectNotFoundMessage(sequencesName); return; } if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) { CommandHelpers.objectNotType(sequencesName, "Collection>>"); return; } sequences = (Collection>) dataObject; // map which maps VirtualKeys back to awt key modifier codes modifiers = new HashMap<>(); modifiers.put(VirtualKey.SHIFT, 1); modifiers.put(VirtualKey.CONTROL, 2); modifiers.put(VirtualKey.ALT, 8); modifiers.put(VirtualKey.ALT_GRAPH, 32); currentKeyModifiers = 0; StructureNode.nextRef = 0; writeJacaretoXML(sequences, filename, classpath, initclass, basepath, classpathext); } private void writeLine(BufferedWriter writer, String line) throws IOException { writer.write(line); writer.newLine(); } private void writeJacaretoHead(BufferedWriter writer, String classname, String initclass, String basepath, String classpathext) throws IOException { Calendar now = Calendar.getInstance(); writeLine(writer, ""); writeLine(writer, ""); writeLine(writer, ""); //@formatter:off writeLine(writer, "" ); writeLine(writer, ""); writeLine(writer, ""); writeLine(writer, ""); writeLine(writer, "" ); //@formatter:on } private void writeJacaretoEvents(BufferedWriter writer, Collection> sequences) throws IOException { structure = new StructureNode("RootElement"); // reference the elements that we included in the header structure.addRecordable(); // Calendar structure.addRecordable(); // SystemInfo structure.addRecordable(); // KeyboardState structure.addRecordable(); // ComponentMode structure.addRecordable(); // ApplicationStarter for (List sequence : sequences) { for (Iterator eventIter = sequence.iterator(); eventIter.hasNext();) { Event event = eventIter.next(); /* System.out.println(event.getType()); System.out.println(event.getTarget()); System.out.println("---"); */ if (event.getType() instanceof MouseButtonDown) { lastKeySequenceEvent = null; checkIfMouseDragged(); lastMouseClickEvent = new StructureNode("MouseClick"); writeMouseClickEvent(writer, event, 501); } else if (event.getType() instanceof MouseButtonUp) { lastKeySequenceEvent = null; writeMouseClickEvent(writer, event, 502); } else if (event.getType() instanceof MouseClick) { lastKeySequenceEvent = null; writeMouseClickEvent(writer, event, 500); // FIXME: don't always write an item action writeItemActionEvent(writer, event); // FIXME: don't write it all here because there // might be no item action at all if (lastFocusChangeEvent == null) { // write structure sequentially structure.children.add(lastMouseClickEvent); structure.children.add(lastItemActionEvent); } else { // with nested structure structure.children.add(lastItemActionEvent); lastItemActionEvent.children.add(0, lastFocusChangeEvent); lastFocusChangeEvent.children.add(0, lastMouseClickEvent); lastFocusChangeEvent = null; lastMouseClickEvent = null; } } else if (event.getType() instanceof KeyboardFocusChange) { lastKeySequenceEvent = null; writeFocusChangeEvent(writer, event); } else if (event.getType() instanceof KeyPressed) { // checkIfMouseDragged(); if (lastKeySequenceEvent == null) { lastKeySequenceEvent = structure.add("KeySequence"); } lastKeyTypedEvent = lastKeySequenceEvent.add("KeyTyped"); writeKeyEvent(writer, event, 401); } else if (event.getType() instanceof KeyReleased) { // checkIfMouseDragged(); writeKeyEvent(writer, event, 402); } } } } private void checkIfMouseDragged() { if (lastMouseClickEvent != null) { // this was not really a click, just a drag lastMouseClickEvent.setContent("MouseDrag"); structure.children.add(lastMouseClickEvent); lastMouseClickEvent = null; } } private void writeJacaretoTail(BufferedWriter writer) throws IOException { writeLine(writer, ""); // write the recording's structure writeLine(writer, ""); writer.write(structure.toString()); // close root element writeLine(writer, ""); } private void writeJacaretoXML(Collection> sequences, String filename, String classpath, String initclass, String basepath, String classpathext) { BufferedWriter writer = new BufferedWriter(openReplayFile(filename + ".xml")); try { writeJacaretoHead(writer, classpath, initclass, basepath, classpathext); writeJacaretoEvents(writer, sequences); writeJacaretoTail(writer); writeLine(writer, ""); writer.flush(); writer.close(); } catch (IOException e) { Console.printerrln("Unable to write Jacareto replay file " + filename); } } /** *

* Helper function that opens the replay file for writing. *

* * @param filename * name and path of the replay file * @param encoding * file encoding, empty string for platform default * @return {@link OutputStreamWriter} that writes to the replay file */ private OutputStreamWriter openReplayFile(String filename) { File file = new File(filename); boolean fileCreated; try { fileCreated = file.createNewFile(); if (!fileCreated) { Console.traceln(Level.INFO, "Created logfile " + filename); } else { Console.traceln(Level.INFO, "Overwrote existing logfile " + filename); } } catch (IOException e) { Console.printerrln("Unable to create file " + filename); Console.logException(e); } OutputStreamWriter writer = null; try { writer = new OutputStreamWriter(new FileOutputStream(file)); } catch (IOException e) { Console.printerrln("Unable to open file for writing (read-only file):" + filename); Console.logException(e); } return writer; } private void writeItemActionEvent(BufferedWriter writer, Event event) throws IOException { JFCGUIElement target = (JFCGUIElement) event.getTarget(); MouseButtonInteraction info = (MouseButtonInteraction) event.getType(); //@formatter:off writeLine(writer, "" ); writeLine(writer, "" ); //@formatter:on lastItemActionEvent = new StructureNode("ItemStateChange"); lastItemActionEvent.addRecordable(); lastItemActionEvent.addRecordable(); } private void writeFocusChangeEvent(BufferedWriter writer, Event event) throws IOException { KeyboardFocusChange info = (KeyboardFocusChange) event.getType(); JFCGUIElement target = (JFCGUIElement) event.getTarget(); if (currentFocus != null) { lastFocusChangeEvent = new StructureNode("FocusChange"); // focus lost on old target writeFocusEvent(writer, info, currentFocus, 1005); // focus gained on new target writeFocusEvent(writer, info, target, 1004); } else { // TODO: it seems like Jacareto wants a window activation before // the first focus event but that is not the case in autoquest, // skip for now } currentFocus = target; } private void writeFocusEvent(BufferedWriter writer, KeyboardFocusChange info, JFCGUIElement target, int jacId) throws IOException { //@formatter:off writeLine(writer, "" ); //@formatter:on lastFocusChangeEvent.addRecordable(); } private void writeMouseClickEvent(BufferedWriter writer, Event event, int jacId) throws IOException { MouseButtonInteraction info = (MouseButtonInteraction) event.getType(); JFCGUIElement target = (JFCGUIElement) event.getTarget(); int clickCount = event.getType() instanceof MouseDoubleClick ? 2 : 1; // TODO: change procTime and duration to adequate values //@formatter:off writeLine(writer, "" ); writeLine(writer, "" ); writeLine(writer, ""); //@formatter:on lastMouseClickEvent.addRecordable(); } private int getButtonModifier(MouseButtonInteraction info) { switch (info.getButton()) { case LEFT: return 16; case MIDDLE: return 8; case RIGHT: return 4; default: // TODO: handle unknown mouse button return -1; } } private void writeKeyEvent(BufferedWriter writer, Event event, int jacId) throws IOException { KeyInteraction info = (KeyInteraction) event.getType(); JFCGUIElement target = (JFCGUIElement) event.getTarget(); int keyCode = info.getKey().getVirtualKeyCode(); applyKeyModifier(info.getKey(), jacId == 401); //@formatter:off writeLine(writer, "" ); writeLine(writer, "" ); writeLine(writer, ""); lastKeyTypedEvent.addRecordable(); } private String getKeyChar (int keyCode) { if (keyCode >= 32 && keyCode < 127) { return String.valueOf((char)keyCode); } return "_NO_LEGAL_XML_CHAR"; } private void applyKeyModifier (VirtualKey key, boolean set) { Integer modifier = modifiers.get(key); if (modifier != null) { if (set) { currentKeyModifiers |= modifier; } else { currentKeyModifiers &= ~modifier; } } } }