// Copyright 2015 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.genericevents.commands;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import de.ugoe.cs.autoquest.CommandHelpers;
import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor;
import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskTraversingVisitor;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequenceInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstanceList;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskMetric;
import de.ugoe.cs.util.console.Command;
import de.ugoe.cs.util.console.Console;
import de.ugoe.cs.util.console.GlobalDataContainer;
/**
*
* TODO comment
*
*
* @author Patrick Harms
*/
public class CMDexportSogouQTaskTrees implements Command {
/* (non-Javadoc)
* @see de.ugoe.cs.util.console.Command#run(java.util.List)
*/
@Override
public void run(List parameters) {
String taskTreeName = null;
String path = null;
String numberStr = null;
boolean withTimestamp = false;
try {
for (int i = 0; i < parameters.size(); i++) {
String parameter = (String) parameters.get(i);
if (!parameter.startsWith("-")) {
if (taskTreeName == null) {
taskTreeName = parameter;
}
else if (path == null) {
path = parameter;
}
else if (numberStr == null) {
numberStr = parameter;
}
else {
throw new IllegalArgumentException("unrecognized parameter: " + parameter);
}
}
else {
if ("-withTimestamp".equals(parameter)) {
withTimestamp = true;
}
else {
throw new IllegalArgumentException("unrecognized parameter: " + parameter);
}
}
}
}
catch (IllegalArgumentException e) {
throw e;
}
catch (Exception e) {
throw new IllegalArgumentException("could not process parameters", e);
}
if (path == null) {
throw new IllegalArgumentException("no path to directory provided");
}
if (taskTreeName == null) {
throw new IllegalArgumentException("no task tree specified");
}
if (numberStr == null) {
throw new IllegalArgumentException("no number of tasks to be exported provided");
}
int tasksToExport;
try{
tasksToExport = Integer.parseInt(numberStr);
}
catch (Exception e) {
throw new IllegalArgumentException("invalid number of tasks to be exported: " +
numberStr, e);
}
File directory = new File(path);
if (!directory.isDirectory()) {
Console.printerrln(path + " is not a directory");
return;
}
if (directory.exists() && (directory.list().length > 0)) {
Console.printerrln(path + " is not empty");
return;
}
Object dataObject = GlobalDataContainer.getInstance().getData(taskTreeName);
if (dataObject == null) {
CommandHelpers.objectNotFoundMessage(taskTreeName);
return;
}
if (!(dataObject instanceof ITaskModel)) {
CommandHelpers.objectNotType(taskTreeName, "ITaskTree");
return;
}
ITaskModel taskModel = (ITaskModel) dataObject;
LinkedList sequencesToExport =
determineSequencesToExport(tasksToExport, taskModel);
int index = 1;
int width = Integer.toString(tasksToExport).length();
for (ISequence sequence : sequencesToExport) {
export(String.format("%0" + width + "d", index++),
sequence, taskModel, directory, withTimestamp);
}
}
/**
*
* TODO: comment
*
*
* @param tasksToExport
* @param taskModel
* @return
*/
private LinkedList determineSequencesToExport(int tasksToExport,
ITaskModel taskModel)
{
LinkedList sequencesToExport = new LinkedList<>();
for (ITask task : taskModel.getTasks()) {
if (task instanceof ISequence) {
int coverage =
taskModel.getTaskInfo(task).getMeasureValue(TaskMetric.EVENT_COVERAGE);
boolean inserted = false;
ListIterator it = sequencesToExport.listIterator();
while (it.hasNext()) {
ISequence candidate = it.next();
int candidateCoverage =
taskModel.getTaskInfo(candidate).getMeasureValue(TaskMetric.EVENT_COVERAGE);
if (coverage > candidateCoverage) {
it.previous();
it.add((ISequence) task);
inserted = true;
break;
}
}
if (!inserted) {
sequencesToExport.add((ISequence) task);
}
while (sequencesToExport.size() > tasksToExport) {
sequencesToExport.removeLast();
}
}
}
return sequencesToExport;
}
/**
*
* TODO: comment
*
*
* @param index
* @param sequence
* @param path
* @param withTimestamp
*/
private void export(String folderPrefix,
ISequence sequence,
ITaskModel taskModel,
File directory,
boolean withTimestamp)
{
File exportDirectory = new File(directory, folderPrefix + "_pattern" + sequence.getId());
if (exportDirectory.exists()) {
throw new IllegalArgumentException(exportDirectory + " already exists");
}
else {
exportDirectory.mkdirs();
}
createPatternFile(sequence, taskModel, withTimestamp, exportDirectory);
createSessionFiles(sequence, taskModel, withTimestamp, exportDirectory);
Console.println("exported " + sequence);
}
/**
*
* TODO: comment
*
*
* @param sequence
* @param taskModel
* @param withTimestamp
* @param exportDirectory
*/
private void createPatternFile(ISequence sequence,
ITaskModel taskModel,
boolean withTimestamp,
File exportDirectory)
{
// write the pattern file
File patternFile = new File(exportDirectory, "patternDetails.txt");
PrintStream out = null;
try {
out = new PrintStream(new FileOutputStream(patternFile));
out.println("########################################################################");
out.println("# Pattern");
out.println("########################################################################");
out.println();
dumpSequence(sequence, out);
out.println();
out.println("########################################################################");
out.println("# Statistics");
out.println("########################################################################");
out.println();
int value = taskModel.getTaskInfo(sequence).getMeasureValue(TaskMetric.COUNT);
out.println(" count: " + TaskMetric.COUNT.formatValue(value));
value = taskModel.getTaskInfo(sequence).getMeasureValue(TaskMetric.EVENT_COVERAGE);
out.println(" coverage: " + TaskMetric.EVENT_COVERAGE.formatValue(value));
value = taskModel.getTaskInfo(sequence).getMeasureValue(TaskMetric.EVENT_COVERAGE_RATIO);
out.println("coverage ratio: " + TaskMetric.EVENT_COVERAGE_RATIO.formatValue(value));
if (withTimestamp) {
long averageTimeBetweenEvents = getAverageTimeBetweenEvents(sequence);
out.println("\naverage event time interval: " + averageTimeBetweenEvents + "ms");
}
out.println();
out.println();
out.println("########################################################################");
out.println("# Execution variants");
out.println("########################################################################");
out.println();
int index = 1;
for (Collection variant : sequence.getExecutionVariants()) {
dumpExecutionVariant(index++, variant.size(), variant.iterator().next(), out);
out.println();
}
}
catch (IOException e) {
throw new IllegalArgumentException("could not write into file " + patternFile);
}
finally {
if (out != null) {
try {
out.close();
}
catch (Exception e) {
// ignore
}
}
}
}
/**
*
* TODO: comment
*
*
* @param sequence
* @return
*/
private long getAverageTimeBetweenEvents(ISequence sequence) {
int intervalCount = 0;
long cummulativeTime = 0;
for (ITaskInstance instance : sequence.getInstances()) {
final List timestamps = new ArrayList<>();
instance.accept(new DefaultTaskInstanceTraversingVisitor() {
@Override
public void visit(IEventTaskInstance eventTaskInstance) {
timestamps.add(eventTaskInstance.getEvent().getTimestamp());
}
});
for (int i = 1; i < timestamps.size(); i++) {
cummulativeTime += timestamps.get(i) - timestamps.get(i - 1);
intervalCount++;
}
}
return (cummulativeTime / intervalCount);
}
/**
*
* TODO: comment
*
*
* @param sequence
* @param stream
*/
private void dumpSequence(ISequence sequence, final PrintStream stream) {
final String[] indent = new String[1];
indent[0] = "";
sequence.accept(new DefaultTaskTraversingVisitor() {
@Override
public void visit(IEventTask eventTask) {
stream.print(indent[0]);
stream.println(eventTask.toString());
}
@Override
public void visit(IStructuringTemporalRelationship relationship) {
stream.print(indent[0]);
stream.print(relationship.toString());
stream.println(" {");
String indentBefore = indent[0];
indent[0] = indentBefore + " ";
super.visit(relationship);
indent[0] = indentBefore;
stream.print(indent[0]);
stream.println("}");
}
@Override
public void visit(IMarkingTemporalRelationship relationship) {
stream.print(indent[0]);
stream.print(relationship.toString());
stream.println(" {");
String indentBefore = indent[0];
indent[0] = indentBefore + " ";
super.visit(relationship);
indent[0] = indentBefore;
stream.print(indent[0]);
stream.println("}");
}
});
}
/**
*
*/
private void dumpExecutionVariant(int variantIndex,
int count,
ITaskInstance instance,
final PrintStream stream)
{
stream.println("\nVariant " + variantIndex + " (executed " + count + " times)\n");
final String[] indent = new String[1];
indent[0] = " ";
instance.accept(new DefaultTaskInstanceTraversingVisitor() {
@Override
public void visit(IEventTaskInstance eventTaskInstance) {
stream.print(indent[0]);
stream.println(eventTaskInstance.toString());
}
@Override
public void visit(ITaskInstanceList taskInstanceList) {
stream.print(indent[0]);
stream.print(taskInstanceList.toString());
stream.println(" {");
String indentBefore = indent[0];
indent[0] = indentBefore + " ";
super.visit(taskInstanceList);
indent[0] = indentBefore;
stream.print(indent[0]);
stream.println("}");
}
});
}
/**
*
* TODO: comment
*
*
* @param instance
* @param taskModel
*/
private void createSessionFiles(final ISequence sequence,
final ITaskModel taskModel,
final boolean withTimestamp,
final File exportDirectory)
{
final File sessionsDirectory = new File(exportDirectory, "sessions");
if (sessionsDirectory.exists()) {
throw new IllegalArgumentException(sessionsDirectory + " already exists");
}
else {
sessionsDirectory.mkdirs();
}
for (IUserSession session : taskModel.getUserSessions()) {
for (ITaskInstance instance : session) {
instance.accept(new DefaultTaskInstanceTraversingVisitor() {
@Override
public void visit(ISequenceInstance sequenceInstance) {
if (sequence == sequenceInstance.getTask()) {
createSessionFile(sequenceInstance, withTimestamp, sessionsDirectory);
}
super.visit(sequenceInstance);
}
});
}
}
}
/**
*
* TODO: comment
*
*
* @param instance
* @param taskModel
*/
private void createSessionFile(final ISequenceInstance instance,
final boolean withTimestamp,
final File exportDirectory)
{
// write the session file
final String userId = getUserOf(instance, exportDirectory);
final File sessionsDirectory = new File(exportDirectory, userId);
if (!sessionsDirectory.exists()) {
sessionsDirectory.mkdirs();
}
File sessionFile;
// a user may have many sessions, search for the next id of it.
int index = 0;
do {
sessionFile = new File
(sessionsDirectory, "session_" + String.format("%09d", ++index) + ".txt");
}
while (sessionFile.exists());
PrintStream out = null;
try {
out = new PrintStream(new FileOutputStream(sessionFile));
final PrintStream finalOut = out;
instance.accept(new DefaultTaskInstanceTraversingVisitor() {
@Override
public void visit(IEventTaskInstance eventTaskInstance) {
Event event = eventTaskInstance.getEvent();
if (withTimestamp) {
finalOut.print(event.getTimestamp());
finalOut.print("\t");
}
finalOut.print(userId);
finalOut.print("\t");
finalOut.print(event.getParameter("query"));
finalOut.print("\t");
finalOut.print(event.getParameter("selectedResultPage"));
finalOut.print(" ");
finalOut.print(event.getParameter("selectedResultIndex"));
finalOut.print("\t");
finalOut.println(event.getParameter("selectedResult"));
}
});
}
catch (IOException e) {
throw new IllegalArgumentException("could not write into file " + sessionFile);
}
finally {
if (out != null) {
try {
out.close();
}
catch (Exception e) {
// ignore
}
}
}
}
/**
*
* TODO: comment
*
*
* @param instance
* @return
*/
private String getUserOf(ISequenceInstance instance, final File exportDirectory) {
final String[] result = new String[1];
instance.accept(new DefaultTaskInstanceTraversingVisitor() {
@Override
public void visit(IEventTaskInstance eventTaskInstance) {
if (result[0] == null) {
result[0] = eventTaskInstance.getEvent().getParameter("userId");
}
}
@Override
public void visit(ITaskInstanceList taskInstanceList) {
if (result[0] == null) {
super.visit(taskInstanceList);
}
}
});
if (result[0] == null) {
// no user id stored with events --> generate generic unused one
File sessionFile = null;
int index = 0;
do {
sessionFile = new File
(exportDirectory, "session_" + String.format("%09d", ++index) + ".txt");
}
while (sessionFile.exists());
result[0] = String.format("%09d", index);
}
return result[0];
}
/* (non-Javadoc)
* @see de.ugoe.cs.util.console.Command#help()
*/
@Override
public String help() {
return "exportSogouQTaskTrees " +
"{-withTimestamp}";
}
}