Index: /trunk/autoquest-plugin-genericevents/.classpath
===================================================================
--- /trunk/autoquest-plugin-genericevents/.classpath	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/.classpath	(revision 2153)
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
Index: /trunk/autoquest-plugin-genericevents/.project
===================================================================
--- /trunk/autoquest-plugin-genericevents/.project	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/.project	(revision 2153)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>autoquest-plugin-genericevents</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
Index: /trunk/autoquest-plugin-genericevents/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /trunk/autoquest-plugin-genericevents/.settings/org.eclipse.jdt.core.prefs	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/.settings/org.eclipse.jdt.core.prefs	(revision 2153)
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.7
Index: /trunk/autoquest-plugin-genericevents/.settings/org.eclipse.m2e.core.prefs
===================================================================
--- /trunk/autoquest-plugin-genericevents/.settings/org.eclipse.m2e.core.prefs	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/.settings/org.eclipse.m2e.core.prefs	(revision 2153)
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
Index: /trunk/autoquest-plugin-genericevents/pom.xml
===================================================================
--- /trunk/autoquest-plugin-genericevents/pom.xml	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/pom.xml	(revision 2153)
@@ -0,0 +1,63 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>de.ugoe.cs.autoquest</groupId>
+        <artifactId>autoquest</artifactId>
+        <version>0.2.2-SNAPSHOT</version>
+        <relativePath>../autoquest/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>autoquest-plugin-genericevents</artifactId>
+    <name>autoquest-plugin-genericevents</name>
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+    <scm>
+        <url>https://autoquest.informatik.uni-goettingen.de/svn/autoquest/trunk/autoquest-plugin-genericevents</url>
+        <connection>scm:svn:https://autoquest.informatik.uni-goettingen.de/svn/autoquest/trunk/autoquest-plugin-genericevents</connection>
+    </scm>
+    <dependencies>
+        <dependency>
+            <groupId>de.ugoe.cs</groupId>
+            <artifactId>java-utils</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>de.ugoe.cs.autoquest</groupId>
+            <artifactId>autoquest-plugin-core</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>de.ugoe.cs.autoquest</groupId>
+            <artifactId>autoquest-core-events</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>de.ugoe.cs.autoquest</groupId>
+            <artifactId>autoquest-core-tasktrees</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.7</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addClasspath>true</addClasspath>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/AbstractDefaultLogParser.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/AbstractDefaultLogParser.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/AbstractDefaultLogParser.java	(revision 2153)
@@ -0,0 +1,486 @@
+//   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.genericevents;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.autoquest.eventcore.HierarchicalEventTargetModel;
+import de.ugoe.cs.autoquest.eventcore.HierarchicalEventTargetTree;
+import de.ugoe.cs.autoquest.plugin.genericevents.eventCore.GenericEventTarget;
+import de.ugoe.cs.autoquest.plugin.genericevents.eventCore.GenericEventTargetFactory;
+import de.ugoe.cs.autoquest.plugin.genericevents.eventCore.GenericEventTargetSpec;
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * This class provides the functionality to parse XML log files generated by monitors of
+ * AutoQUEST. The result of parsing a file is a collection of event sequences and a target model.
+ * This class must be extended by implementing a subclass and the abstract method to complete
+ * the implementation.
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ * 
+ */
+public abstract class AbstractDefaultLogParser extends DefaultHandler {
+    
+    /**
+     * <p>
+     * Collection of event sequences that is contained in the parsed log file.
+     * </p>
+     */
+    private Collection<List<Event>> sequences;
+
+    /**
+     * <p>
+     * Internal handle to the parsed target structure, stored in a GUIElementTree
+     * </p>
+     */
+    private HierarchicalEventTargetTree<String, GenericEventTarget, GenericEventTargetSpec> targetTree;
+
+    /**
+     * <p>
+     * Id of the event target currently being parsed.
+     * </p>
+     */
+    private String currentTargetId;
+
+    /**
+     * <p>
+     * the buffer for event targets already parsed but not processed yet (because e.g. the parent
+     * target has not been parsed yet)
+     * </p>
+     */
+    private List<BufferEntry> targetBuffer;
+
+    /**
+     * <p>
+     * Internal handle to type of the event currently being parsed.
+     * </p>
+     */
+    private String currentEventType;
+
+    /**
+     * <p>
+     * the buffer for events already parsed but not processed yet (because e.g. the target
+     * has not been parsed yet)
+     * </p>
+     */
+    private List<BufferEntry> eventBuffer;
+
+    /**
+     * <p>
+     * Internal handle to the parameters of the entity currently being parsed.
+     * </p>
+     */
+    private Map<String, String> currentParameters;
+
+    /**
+     * <p>
+     * Internal handle to the sequence currently being parsed.
+     * </p>
+     */
+    private List<Event> currentSequence;
+
+    /**
+     * <p>
+     * the handle to the locator for correctly throwing exceptions with location information
+     * </p>
+     */
+    private Locator locator;
+
+    /**
+     * <p>
+     * Constructor. Creates a new logParser.
+     * </p>
+     */
+    public AbstractDefaultLogParser() {
+        sequences = new LinkedList<List<Event>>();
+        
+        targetTree = new HierarchicalEventTargetTree<String, GenericEventTarget, GenericEventTargetSpec>
+            (new HierarchicalEventTargetModel<GenericEventTarget>(false),
+             new GenericEventTargetFactory());
+        
+        targetBuffer = new LinkedList<BufferEntry>();
+        eventBuffer = new LinkedList<BufferEntry>();
+        currentSequence = new LinkedList<Event>();
+    }
+
+    /**
+     * <p>
+     * Parses a log file written by an AutoQUEST monitor and creates a collection of event
+     * sequences.
+     * </p>
+     * 
+     * @param filename
+     *            name and path of the log file
+     *
+     * @throws SAXException in the case, the file could not be parsed
+     */
+    public void parseFile(String filename) throws SAXException {
+        if (filename == null) {
+            throw new IllegalArgumentException("filename must not be null");
+        }
+
+        parseFile(new File(filename));
+    }
+
+    /**
+     * <p>
+     * Parses a log file written by an AutoQUEST monitor and creates a collection of event
+     * sequences.
+     * </p>
+     * 
+     * @param file
+     *            file to be parsed
+     *
+     * @throws SAXException in the case, the file could not be parsed
+     */
+    public void parseFile(File file) throws SAXException {
+        if (file == null) {
+            throw new IllegalArgumentException("file must not be null");
+        }
+        SAXParserFactory spf = SAXParserFactory.newInstance();
+        spf.setValidating(true);
+        SAXParser saxParser = null;
+        InputSource inputSource = null;
+        try {
+            saxParser = spf.newSAXParser();
+            inputSource =
+                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
+        }
+        catch (UnsupportedEncodingException e) {
+            Console.printerrln("Error parsing file " + file.getName());
+            Console.logException(e);
+            throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
+        }
+        catch (ParserConfigurationException e) {
+            Console.printerrln("Error parsing file " + file.getName());
+            Console.logException(e);
+            throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
+        }
+        catch (FileNotFoundException e) {
+            Console.printerrln("Error parsing file " + file.getName());
+            Console.logException(e);
+            throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
+        }
+        
+        // we parse a new file. So clear the buffers.
+        targetBuffer.clear();
+        eventBuffer.clear();
+        
+        if (inputSource != null) {
+            inputSource.setSystemId("file://" + file.getAbsolutePath());
+            try {
+                if (saxParser == null) {
+                    throw new RuntimeException("SaxParser creation failed");
+                }
+                saxParser.parse(inputSource, this);
+            }
+            catch (IOException e) {
+                Console.printerrln("Error parsing file " + file.getName());
+                Console.logException(e);
+                throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
+            }
+            catch (SAXParseException e) {
+                if ("XML document structures must start and end within the same entity.".equals
+                    (e.getMessage()))
+                {
+                    // this only denotes, that the final session tag is missing, because the
+                    // file wasn't completed. Ignore this but complete the session
+                    eventBuffer.add(new BufferEntry("sessionSwitch", null));
+                    processEvents();
+                }
+                else {
+                    throw e;
+                }
+            }
+        }
+        
+        if (targetBuffer.size() > 0) {
+            Console.println(targetBuffer.size() + " GUI elements not processed");
+        }
+        
+        if (eventBuffer.size() > 0) {
+            Console.printerrln(eventBuffer.size() + " events not processed");
+        }
+    }
+
+    /**
+     * <p>
+     * Returns a collection of event sequences that was obtained from parsing log files.
+     * </p>
+     * 
+     * @return
+     */
+    public Collection<List<Event>> getSequences() {
+        return sequences;
+    }
+
+    /**
+     * <p>
+     * Returns the target model that is obtained from parsing log files.
+     * </p>
+     * 
+     * @return GUIModel
+     */
+    public HierarchicalEventTargetModel<GenericEventTarget> getHierarchicalEventTargetModel() {
+        return targetTree.getHierarchicalEventTargetModel();
+    }
+
+    /* (non-Javadoc)
+     * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
+     */
+    @Override
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
+     * java.lang.String, org.xml.sax.Attributes)
+     */
+    @Override
+    public void startElement(String uri, String localName, String qName, Attributes atts)
+        throws SAXException
+    {
+        if (qName.equals("session")) {
+            // do nothing
+        }
+        else if (qName.equals("target")) {
+            currentTargetId = atts.getValue("id");
+            currentParameters = new HashMap<String, String>();
+        }
+        else if (qName.equals("event")) {
+            currentEventType = atts.getValue("type");
+            currentParameters = new HashMap<String, String>();
+        }
+        else if (qName.equals("param")) {
+            String paramName = atts.getValue("name");
+            if ((currentTargetId != null) || (currentEventType != null)) {
+                currentParameters.put(paramName, atts.getValue("value"));
+            }
+            else {
+                throw new SAXException("param tag found where it should not be.");
+            }
+        }
+        else {
+            throw new SAXException("unknown tag found: " + qName);
+        }
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
+     * java.lang.String)
+     */
+    @Override
+    public void endElement(String uri, String localName, String qName) throws SAXException {
+        if (qName.equals("session") && (eventBuffer != null)) {
+            eventBuffer.add(new BufferEntry("sessionSwitch", null));
+            processTargets();
+            processEvents();
+        }
+        else if (qName.equals("target") && (currentTargetId != null)) {
+            targetBuffer.add(0, new BufferEntry(currentTargetId, currentParameters));
+            currentTargetId = null;
+            currentParameters = null;
+        }
+        else if (qName.equals("event") && (currentEventType != null)) {
+            eventBuffer.add(new BufferEntry(currentEventType, currentParameters));
+            currentEventType = null;
+            currentParameters = null;
+        }
+    }
+
+    /**
+     * <p>
+     * called to handle parsed targets
+     * </p>
+     *
+     * @param id         the id of the parsed target
+     * @param parameters all parameters parsed for the target
+     * 
+     * @return true, if the target could be handled. In this case this method is not called
+     *         again for the same target. Otherwise the method is called later again. This
+     *         may be required, if a child target is parsed before the parent target
+     */
+    protected abstract boolean handleTarget(String id, Map<String, String> parameters) 
+        throws SAXException;
+    
+    /**
+     * <p>
+     * called to handle parsed events
+     * </p>
+     *
+     * @param type       the type of the parsed event
+     * @param parameters the parameters of the parsed event
+     * 
+     * @return true, if the event could be handled. In this case this method is not called
+     *         again for the same event. Otherwise the method is called later again. This
+     *         may be required, if e.g. the target of the event is not yet parsed
+     */
+    protected abstract boolean handleEvent(String type, Map<String, String> parameters)
+        throws SAXException;
+    
+    /**
+     * <p>
+     * returns the tree of parsed targets, which consists of the ids of the targets
+     * </p>
+     *
+     * @return as described
+     */
+    protected HierarchicalEventTargetTree<String, GenericEventTarget, GenericEventTargetSpec> getTargetTree() {
+        return targetTree;
+    }
+
+    /**
+     * <p>
+     * adds an event to the currently parsed sequence of events
+     * </p>
+     *
+     * @param event
+     */
+    protected void addToSequence(Event event) {
+        currentSequence.add(event);
+    }
+
+    /**
+     * <p>
+     * this method internally processes targets, that have been parsed but not processed yet.
+     * I.e., for such targets, either the method {@link #handleTarget(String, Map)} has
+     * not been called yet, or it returned false for the previous calls. In this case, the method
+     * is called (again). Furthermore, the processing of events is initiated by a call to
+     * {@link #processEvents()}.
+     * </p>
+     */
+    private void processTargets() throws SAXException {
+        int processedElements = 0;
+        boolean processedElement;
+        
+        do {
+            processedElement = false;
+            
+            // search for the next GUI element that can be processed (use an iterator on the 
+            // linked list, as this is most effective)
+            Iterator<BufferEntry> iterator = targetBuffer.iterator();
+            while (iterator.hasNext()) {
+                BufferEntry entry = iterator.next();
+                
+                if (handleTarget(entry.id, entry.parameters)) {
+                    iterator.remove();
+                    processedElements++;
+                    processedElement = true;
+                }
+            }
+        }
+        while (processedElement);
+        
+        if (processedElements > 0) {
+            processEvents();
+        }
+    }
+
+    /**
+     * <p>
+     * this method internally processes events, that have been parsed but not processed yet.
+     * I.e., for such events, either the method {@link #handleEvent(String, Map)} has
+     * not been called yet, or it returned false for the previous calls. In this case, the method
+     * is called (again).
+     * </p>
+     */
+    private void processEvents() throws SAXException {
+        boolean processedEvent;
+        
+        do {
+            processedEvent = false;
+            // check, if the next event can be processed
+            if (eventBuffer.size() > 0) {
+                BufferEntry entry = eventBuffer.get(0);
+                
+                if ((entry != null) && (entry.id != null) && (entry.parameters != null)) {
+                    
+                    processedEvent = handleEvent(entry.id, entry.parameters);
+
+                    if (processedEvent) {
+                        eventBuffer.remove(0);
+                    }
+                }
+                else {
+                    // the entry signals a session switch. Close the current session and start the
+                    // next one
+                    if (currentSequence.size() > 0) {
+                        sequences.add(currentSequence);
+                        currentSequence = new LinkedList<Event>();
+                    }
+                    eventBuffer.remove(0);
+                    processedEvent = true;
+                }
+            }
+        }
+        while (processedEvent);
+    }
+
+    /**
+     * <p>
+     * This class is used internally for storing events and targets in lists.
+     * </p>
+     */
+    private static class BufferEntry {
+        
+        /** */
+        private String id;
+        
+        /** */
+        private Map<String, String> parameters;
+        
+        /**
+         * 
+         */
+        private BufferEntry(String id, Map<String, String> parameters) {
+            this.id = id;
+            this.parameters = parameters;
+        }
+    }
+
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/GENERICEVENTSPlugin.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/GENERICEVENTSPlugin.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/GENERICEVENTSPlugin.java	(revision 2153)
@@ -0,0 +1,60 @@
+//   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.genericevents;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import de.ugoe.cs.autoquest.plugin.AutoQUESTPlugin;
+
+/**
+ * <p>
+ * Identifier class for the AutoQUEST HTML plug-in.
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ */
+public class GENERICEVENTSPlugin implements AutoQUESTPlugin {
+
+    /**
+     * <p>
+     * The command packages of this plug-in.
+     * </p>
+     */
+    private final static String[] commandPackages = new String[]
+        { "de.ugoe.cs.autoquest.plugin.genericevents.commands" };
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see de.ugoe.cs.autoquest.plugin.AutoQUESTPlugin#getTitle()
+     */
+    @Override
+    public String getTitle() {
+        return "Generic-Event-Plugin";
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see de.ugoe.cs.autoquest.plugin.AutoQUESTPlugin#getCommandPackages()
+     */
+    @Override
+    public List<String> getCommandPackages() {
+        return Collections.unmodifiableList(Arrays.asList(commandPackages));
+    }
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/GenericEventLogParser.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/GenericEventLogParser.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/GenericEventLogParser.java	(revision 2153)
@@ -0,0 +1,94 @@
+//   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.genericevents;
+
+import java.util.Map;
+
+import org.xml.sax.SAXException;
+
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.autoquest.eventcore.EventTargetModelException;
+import de.ugoe.cs.autoquest.eventcore.IEventType;
+import de.ugoe.cs.autoquest.eventcore.StringEventType;
+import de.ugoe.cs.autoquest.plugin.genericevents.eventCore.GenericEventTarget;
+import de.ugoe.cs.autoquest.plugin.genericevents.eventCore.GenericEventTargetSpec;
+
+/**
+ * <p>
+ * This class provides the functionality to parse XML log files generated by the generic event
+ * monitor of AutoQUEST. The result of parsing a file is a collection of event sequences and a
+ * target model.
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ * 
+ */
+public class GenericEventLogParser extends AbstractDefaultLogParser {
+    
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
+     */
+    @Override
+    protected boolean handleTarget(String id, Map<String, String> parameters)
+        throws SAXException
+    {
+        String parentId = parameters.get("parent");
+        
+        GenericEventTargetSpec specification = new GenericEventTargetSpec(id, parameters);
+        
+        try {
+            super.getTargetTree().add(id, parentId, specification);
+        }
+        catch (EventTargetModelException e) {
+            throw new SAXException("could not handle generic event target with id " +
+                                   id + ": " + e.getMessage(), e);
+        }
+        
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String, Map)
+     */
+    @Override
+    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
+        String targetId = parameters.get("targetId");
+        
+        if (targetId == null) {
+            throw new SAXException("event does not have a target id");
+        }
+        
+        GenericEventTarget target = super.getTargetTree().find(targetId);
+
+        IEventType eventType = new StringEventType(type);
+        
+        if (eventType != null) {
+            Event event = new Event(eventType, target);
+
+            String timestampStr = parameters.get("timestamp");
+        
+            if (timestampStr != null) {
+                event.setTimestamp(Long.parseLong(timestampStr));
+            }
+        
+            super.addToSequence(event);
+        }
+        // else ignore unknown event type
+
+        return true;
+    }
+
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDexportSogouQTaskTrees.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDexportSogouQTaskTrees.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDexportSogouQTaskTrees.java	(revision 2153)
@@ -0,0 +1,585 @@
+//   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;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @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<Object> 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<ISequence> 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);
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param tasksToExport
+     * @param taskModel
+     * @return
+     */
+    private LinkedList<ISequence> determineSequencesToExport(int        tasksToExport,
+                                                             ITaskModel taskModel)
+    {
+        LinkedList<ISequence> 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<ISequence> 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;
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @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);
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @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<ITaskInstance> 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
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param sequence
+     * @return
+     */
+    private long getAverageTimeBetweenEvents(ISequence sequence) {
+        int intervalCount = 0;
+        long cummulativeTime = 0;
+        for (ITaskInstance instance : sequence.getInstances()) {
+            final List<Long> 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);
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @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("}");
+            }
+        });
+    }
+
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @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);
+                    }
+                });
+            }
+        }
+    }
+
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @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
+                }
+            }
+        }
+    }
+    
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @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 <tasktree> <directory> <numberOfTasksToExport> " +
+            "{-withTimestamp}";
+    }
+
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseDirGenericEvents.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseDirGenericEvents.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseDirGenericEvents.java	(revision 2153)
@@ -0,0 +1,137 @@
+//   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.genericevents.commands;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Level;
+
+import de.ugoe.cs.autoquest.CommandHelpers;
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTargetModel;
+import de.ugoe.cs.autoquest.plugin.genericevents.GenericEventLogParser;
+import de.ugoe.cs.util.console.Command;
+import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.util.console.GlobalDataContainer;
+
+/**
+ * <p>
+ * Command that tries to parse all files in a folder as if they were log files generated by the
+ * generic event monitor. The result is one set of sequences for all files (not one set of
+ * sequences for each file!).
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ */
+public class CMDparseDirGenericEvents implements Command {
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
+     */
+    @Override
+    public void run(List<Object> parameters) {
+        String path = null;
+        String sequencesName = null;
+
+        try {
+            for (int i = 0; i < parameters.size(); i++) {
+                String param = (String) parameters.get(i);
+                if (path == null) {
+                    path = param;
+                }
+                else if (sequencesName == null) {
+                    sequencesName = param;
+                }
+            }
+        }
+        catch (Exception e) {
+            throw new IllegalArgumentException("illegal parameters provided: " + e);
+        }
+        
+        if (sequencesName == null) {
+            sequencesName = "sequences";
+        }
+
+        File folder = new File(path);
+        if (!folder.isDirectory()) {
+            Console.printerrln(path + " is not a directory");
+            return;
+        }
+
+        GenericEventLogParser parser = new GenericEventLogParser();
+
+        parseFile(folder, parser);
+
+        Collection<List<Event>> sequences = parser.getSequences();
+
+        IHierarchicalEventTargetModel<?> targets = parser.getHierarchicalEventTargetModel();
+
+        if (GlobalDataContainer.getInstance().addData(sequencesName, sequences)) {
+            CommandHelpers.dataOverwritten(sequencesName);
+        }
+
+        if (GlobalDataContainer.getInstance().addData(sequencesName + "_targets", targets)) {
+            CommandHelpers.dataOverwritten(sequencesName + "_targets");
+        }
+    }
+
+    /**
+     * <p>
+     * recursive method for parsing a directory structures
+     * </p>
+     *
+     * @param file   the file object to be parsed. If the file is a folder, the method calls itself
+     *               for all children
+     * @param parser the parser to use for parsing the files.
+     */
+    private void parseFile(File file, GenericEventLogParser parser) {
+        if (file.isDirectory()) {
+            String[] children = file.list();
+            Arrays.sort(children);
+            
+            for (String child : children) {
+                File childFile = new File(file, child);
+                parseFile(childFile, parser);
+            }
+        }
+        else if (file.isFile()) {
+            String source = file.getAbsolutePath();
+            Console.traceln(Level.INFO, "Processing file: " + source);
+
+            try {
+                parser.parseFile(file);
+            }
+            catch (Exception e) {
+                Console.printerrln("Could not parse " + source + ": " + e.getMessage());
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see de.ugoe.cs.util.console.Command#help()
+     */
+    @Override
+    public String help() {
+        return "parseDirGenericEvents <directory> [<sequencesName>]";
+    }
+
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseDirSogouQData.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseDirSogouQData.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseDirSogouQData.java	(revision 2153)
@@ -0,0 +1,154 @@
+//   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.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.ugoe.cs.autoquest.CommandHelpers;
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.util.console.Command;
+import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.util.console.GlobalDataContainer;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class CMDparseDirSogouQData implements Command {
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
+     */
+    @Override
+    public void run(List<Object> parameters) {
+        String path = null;
+        String sequencesName = null;
+        boolean hasTimestamp = false;
+        boolean ignoreQuery = false;
+        boolean compareDomainOnly = false;
+        
+        try {
+            for (int i = 0; i < parameters.size(); i++) {
+                String parameter = (String) parameters.get(i);
+                if (!parameter.startsWith("-")) {
+                    if (path == null) {
+                        path = parameter;
+                    }
+                    else if (sequencesName == null) {
+                        sequencesName = parameter;
+                    }
+                    else {
+                        throw new IllegalArgumentException("unrecognized parameter: " + parameter);
+                    }
+                }
+                else {
+                    if ("-hasTimestamp".equals(parameter)) {
+                        hasTimestamp = true;
+                    }
+                    else if ("-ignoreQuery".equals(parameter)) {
+                        ignoreQuery = true;
+                    }
+                    else if ("-compareDomainOnly".equals(parameter)) {
+                        compareDomainOnly = 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 (sequencesName == null) {
+            sequencesName = "sequences";
+        }
+
+        File folder = new File(path);
+        if (!folder.isDirectory()) {
+            Console.printerrln(path + " is not a directory");
+            return;
+        }
+        
+        Map<String, List<Event>> userSessions = new HashMap<>();
+        traverseFolder(folder, userSessions, hasTimestamp, ignoreQuery, compareDomainOnly);
+        
+        Collection<List<Event>> sequences = new ArrayList<>();
+        
+        for (List<Event> session : userSessions.values()) {
+            if (session.size() > 1) {
+                sequences.add(session);
+            }
+        }
+
+        if (GlobalDataContainer.getInstance().addData(sequencesName, sequences)) {
+            CommandHelpers.dataOverwritten(sequencesName);
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param folder
+     */
+    private void traverseFolder(File folder, Map<String,
+                                List<Event>> allUserSessions,
+                                boolean      hasTimestamp,
+                                boolean      ignoreQuery,
+                                boolean      compareDomainOnly)
+    {
+        if (folder.isDirectory()) {
+            for (File child : folder.listFiles()) {
+                traverseFolder(child, allUserSessions, hasTimestamp, ignoreQuery, compareDomainOnly);
+            }
+        }
+        else {
+            Map<String, List<Event>> userSessions = new SogouQDataFileParser().parseFile
+                (folder, hasTimestamp, ignoreQuery, compareDomainOnly);
+            
+            if (userSessions != null) {
+                allUserSessions.putAll(userSessions);
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.util.console.Command#help()
+     */
+    @Override
+    public String help() {
+        return "parseDirSogouQData <folder> {<sequencesName>} {-hasTimestamp} {-ignoreQuery} " +
+            "{-compareDomainOnly}";
+    }
+
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseSogouQDataFile.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseSogouQDataFile.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/CMDparseSogouQDataFile.java	(revision 2153)
@@ -0,0 +1,125 @@
+//   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.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import de.ugoe.cs.autoquest.CommandHelpers;
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.util.console.Command;
+import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.util.console.GlobalDataContainer;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class CMDparseSogouQDataFile implements Command {
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
+     */
+    @Override
+    public void run(List<Object> parameters) {
+        String path = null;
+        String sequencesName = null;
+        boolean hasTimestamp = false;
+        boolean ignoreQuery = false;
+        boolean compareDomainOnly = false;
+        
+        try {
+            for (int i = 0; i < parameters.size(); i++) {
+                String parameter = (String) parameters.get(i);
+                if (!parameter.startsWith("-")) {
+                    if (path == null) {
+                        path = parameter;
+                    }
+                    else if (sequencesName == null) {
+                        sequencesName = parameter;
+                    }
+                    else {
+                        throw new IllegalArgumentException("unrecognized parameter: " + parameter);
+                    }
+                }
+                else {
+                    if ("-hasTimestamp".equals(parameter)) {
+                        hasTimestamp = true;
+                    }
+                    else if ("-ignoreQuery".equals(parameter)) {
+                        ignoreQuery = true;
+                    }
+                    else if ("-compareDomainOnly".equals(parameter)) {
+                        compareDomainOnly = 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 file provided");
+        }
+        
+        if (sequencesName == null) {
+            sequencesName = "sequences";
+        }
+
+        File file = new File(path);
+        if (file.isDirectory()) {
+            Console.printerrln(path + " is not a file but a directory");
+            return;
+        }
+        
+        Map<String, List<Event>> userSessions = new SogouQDataFileParser().parseFile
+            (file, hasTimestamp, ignoreQuery, compareDomainOnly);
+        
+        Collection<List<Event>> sequences = new ArrayList<>();
+        
+        for (List<Event> session : userSessions.values()) {
+            if (session.size() > 1) {
+                sequences.add(session);
+            }
+        }
+
+        if (GlobalDataContainer.getInstance().addData(sequencesName, sequences)) {
+            CommandHelpers.dataOverwritten(sequencesName);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.util.console.Command#help()
+     */
+    @Override
+    public String help() {
+        return "parseSogouQDataFile <file> {<sequencesName>} {-hasTimestamp} {-ignoreQuery} " +
+            "{-compareDomainOnly}";
+    }
+
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/SogouQDataFileParser.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/SogouQDataFileParser.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/commands/SogouQDataFileParser.java	(revision 2153)
@@ -0,0 +1,176 @@
+//   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.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.autoquest.eventcore.StringEventType;
+import de.ugoe.cs.autoquest.plugin.genericevents.eventCore.GenericEventTarget;
+import de.ugoe.cs.autoquest.plugin.genericevents.eventCore.GenericEventTargetSpec;
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class SogouQDataFileParser {
+    
+    /** */
+    private static DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
+
+    /**
+     * 
+     */
+    Map<String, List<Event>> parseFile(File    file,
+                                       boolean hasTimestamp,
+                                       boolean ignoreQuery,
+                                       boolean compareDomainOnly)
+    {
+        Console.println("reading file " + file);
+        Map<String, List<Event>> userSessions = new HashMap<>();
+        try {
+            BufferedReader reader = new BufferedReader
+                (new InputStreamReader(new FileInputStream(file), "GB2312"));
+            
+            String line = null;
+            long lastTimeStamp = 0;
+            
+            int timestampIndex = 0;
+            int userIdIndex = 1;
+            
+            if (!hasTimestamp) {
+                timestampIndex = -1;
+                userIdIndex = 0;
+            }
+            
+            do {
+                line = reader.readLine();
+                if (line != null) {
+                    String[] elements = line.split("\t");
+                    
+                    String userId = elements[userIdIndex].intern();
+                    
+                    if (hasTimestamp) {
+                        try {
+                            long timestamp = TIME_FORMAT.parse(elements[timestampIndex]).getTime();
+                            if (timestamp > lastTimeStamp) {
+                                lastTimeStamp = timestamp;
+                            }
+                            else {
+                                lastTimeStamp++;
+                            }
+                        }
+                        catch (ParseException e) {
+                            // just ignore this and count next
+                            lastTimeStamp++;
+                        }
+                    }
+                    
+                    StringBuffer query = new StringBuffer();
+                    
+                    for (int i = userIdIndex + 1; i < elements.length - 2; i++) {
+                        query.append(elements[i]);
+                    }
+                    
+                    String queryStr = query.toString().intern();
+                    
+                    String selectedResultPage =
+                        elements[elements.length - 2].split(" ")[0].intern();
+                    String selectedResultIndex =
+                        elements[elements.length - 2].split(" ")[1].intern();
+                    String selectedResult = elements[elements.length - 1];
+                    
+                    String fullSelectedResult = selectedResult;
+                    
+                    if (compareDomainOnly) {
+                        int index = selectedResult.indexOf("://");
+                        // ensure with the second condition, that we do not match something in the
+                        // url query
+                        if ((index >= 0) && (index < 15)) {
+                            selectedResult = selectedResult.substring(index + 3);
+                        }
+                        
+                        index = selectedResult.indexOf("/");
+                        if (index > 0) {
+                            selectedResult = selectedResult.substring(0, index);
+                        }
+                    }
+                    
+                    selectedResult = selectedResult.intern();
+                    
+                    Event event;
+                    
+                    GenericEventTargetSpec spec = new GenericEventTargetSpec(selectedResult, null);
+                    
+                    if (!ignoreQuery) {
+                        event = new Event(new StringEventType("query for " + queryStr),
+                                          new GenericEventTarget(spec, null));
+                    }
+                    else {
+                        event = new Event(new StringEventType("query"),
+                                          new GenericEventTarget(spec, null));
+                    }
+                    
+                    event.setTimestamp(lastTimeStamp);
+                    event.setParameter("userId".intern(), userId);
+                    event.setParameter("query".intern(), queryStr);
+                    event.setParameter("selectedResultPage".intern(), selectedResultPage);
+                    event.setParameter("selectedResultIndex".intern(), selectedResultIndex);
+                    event.setParameter("selectedResult".intern(), fullSelectedResult);
+                    
+                    List<Event> session = userSessions.get(userId);
+                    
+                    if (session == null) {
+                        session = new ArrayList<>();
+                        userSessions.put(userId, session);
+                    }
+                    
+                    session.add(event);
+                }
+            }
+            while (line != null);
+            
+            reader.close();
+        }
+        catch (FileNotFoundException e) {
+            Console.printerrln("could not read " + file);
+            Console.logException(e);
+            return null;
+        }
+        catch (IOException e) {
+            Console.printerrln("problem while reading a line from " + file);
+            Console.logException(e);
+            return null;
+        }
+        
+        return userSessions;
+    }
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTarget.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTarget.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTarget.java	(revision 2153)
@@ -0,0 +1,84 @@
+//   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.eventCore;
+
+import de.ugoe.cs.autoquest.eventcore.AbstractDefaultHierarchicalEventTarget;
+import de.ugoe.cs.autoquest.eventcore.IEventTargetSpec;
+import de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class GenericEventTarget extends AbstractDefaultHierarchicalEventTarget
+    implements IHierarchicalEventTarget
+{
+
+    /**  */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param specification
+     * @param parent
+     */
+    public GenericEventTarget(GenericEventTargetSpec specification, GenericEventTarget parent) {
+        super(specification, parent);
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.eventcore.IEventTarget#getPlatform()
+     */
+    @Override
+    public String getPlatform() {
+        return "Generic Event";
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.eventcore.IEventTarget#getStringIdentifier()
+     */
+    @Override
+    public String getStringIdentifier() {
+        return ((GenericEventTargetSpec) super.getSpecification()).getId();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return super.getSpecification().toString();
+    }
+
+    /* (non-Javadoc)
+     * @see IHierarchicalEventTarget#updateSpecification(IEventTargetSpec)
+     */
+    @Override
+    public void updateSpecification(IEventTargetSpec furtherSpec) {
+        if (!(furtherSpec instanceof GenericEventTargetSpec)) {
+            throw new IllegalArgumentException
+                ("can only handle specifications of type GenericEventTargetSpec");
+        }
+        
+        ((GenericEventTargetSpec) super.getSpecification()).mergeWith
+            ((GenericEventTargetSpec) furtherSpec);
+    }
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTargetFactory.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTargetFactory.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTargetFactory.java	(revision 2153)
@@ -0,0 +1,74 @@
+//   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.eventCore;
+
+import de.ugoe.cs.autoquest.eventcore.EventTargetModelConfigurationException;
+import de.ugoe.cs.autoquest.eventcore.HierarchicalEventTargetModel;
+import de.ugoe.cs.autoquest.eventcore.IEventTargetFactory;
+import de.ugoe.cs.autoquest.eventcore.IEventTargetSpec;
+import de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class GenericEventTargetFactory implements IEventTargetFactory {
+
+    /* (non-Javadoc)
+     * @see IEventTargetFactory#instantiateEventTarget(IEventTargetSpec, IHierarchicalEventTarget)
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends IHierarchicalEventTarget> T instantiateEventTarget(IEventTargetSpec specification,
+                                                                         T                parent)
+        throws EventTargetModelConfigurationException
+    {
+        if ((specification instanceof GenericEventTargetSpec) &&
+            ((parent == null) || (parent instanceof GenericEventTarget)))
+        {
+            return (T) instantiateEventTarget((GenericEventTargetSpec) specification,
+                                              (GenericEventTarget) parent);
+        }
+        else {
+            throw new IllegalArgumentException
+                ("can only handle parameters of type GenericEventTargetSpec and GenericEventTarget");
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see IEventTargetFactory#instantiateEventTarget(IEventTargetSpec, IHierarchicalEventTarget)
+     */
+    public GenericEventTarget instantiateEventTarget(GenericEventTargetSpec specification,
+                                                     GenericEventTarget     parent)
+        throws EventTargetModelConfigurationException
+    {
+        return new GenericEventTarget(specification, parent);
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.eventcore.IEventTargetFactory#instantiateGroup(java.lang.String, de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget, de.ugoe.cs.autoquest.eventcore.HierarchicalEventTargetModel)
+     */
+    @Override
+    public <T extends IHierarchicalEventTarget> T instantiateGroup(String                          groupName,
+                                                                   T                               parent,
+                                                                   HierarchicalEventTargetModel<T> hierarchicalEventTargetModel)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+}
Index: /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTargetSpec.java
===================================================================
--- /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTargetSpec.java	(revision 2153)
+++ /trunk/autoquest-plugin-genericevents/src/main/java/de/ugoe/cs/autoquest/plugin/genericevents/eventCore/GenericEventTargetSpec.java	(revision 2153)
@@ -0,0 +1,126 @@
+//   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.eventCore;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import de.ugoe.cs.autoquest.eventcore.IEventTargetSpec;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class GenericEventTargetSpec implements IEventTargetSpec {
+
+    /**  */
+    private static final long serialVersionUID = 1L;
+    
+    /** */
+    private Map<String, String> targetParameters;
+
+    /** */
+    private String targetId;
+    
+    /** */
+    private String name;
+
+    /**
+     *
+     */
+    public GenericEventTargetSpec(String id, Map<String, String> parameters) {
+        this.targetId = id;
+        this.targetParameters = parameters;
+        
+        name = parameters.get("name");
+        
+        if (name == null) {
+            name = targetId;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see IEventTargetSpec#getSimilarity(IEventTargetSpec)
+     */
+    @Override
+    public boolean getSimilarity(IEventTargetSpec other) {
+        if (this == other) {
+            return true;
+        }
+        else if (other instanceof GenericEventTargetSpec) {
+            Map<String, String> otherParameters = ((GenericEventTargetSpec) other).targetParameters;
+            
+            if (targetParameters == null) {
+                return otherParameters == null;
+            }
+            else if (otherParameters == null) {
+                return false;
+            }
+            else if (targetParameters.size() != otherParameters.size()) {
+                return false;
+            }
+            else {
+                for (Map.Entry<String, String> entry : targetParameters.entrySet()) {
+                    String otherValue = otherParameters.get(entry.getKey());
+                    if ((otherValue != entry.getValue()) &&
+                        ((otherValue == null) || (!otherValue.equals(entry.getValue()))))
+                    {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            
+        }
+        else {
+            return false;
+        }
+    }
+
+    /**
+     * 
+     *
+     * @return
+     */
+    public String getId() {
+        return targetId;
+    }
+
+    /**
+     *
+     */
+    public void mergeWith(GenericEventTargetSpec furtherSpec) {
+        if (furtherSpec.targetParameters != null) {
+            if (targetParameters == null) {
+                targetParameters = new HashMap<>(furtherSpec.targetParameters);
+            }
+            else {
+                targetParameters.putAll(furtherSpec.targetParameters);
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return name;
+    }
+
+}
