source: trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/SequenceForTaskDetectionRule.java @ 2131

Last change on this file since 2131 was 2131, checked in by pharms, 8 years ago
  • several performance improvements in sequence and collision detection
  • added possibility to select the minimal amount of action instances a detected sequence should cover
File size: 108.3 KB
RevLine 
[1113]1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
[1107]15package de.ugoe.cs.autoquest.tasktrees.temporalrelation;
16
[1855]17import java.util.ArrayList;
[1285]18import java.util.HashMap;
19import java.util.HashSet;
[1855]20import java.util.IdentityHashMap;
[1107]21import java.util.Iterator;
22import java.util.LinkedList;
23import java.util.List;
[1855]24import java.util.ListIterator;
[1285]25import java.util.Map;
26import java.util.Set;
27import java.util.logging.Level;
[1107]28
[1146]29import de.ugoe.cs.autoquest.tasktrees.taskequality.TaskEquality;
[1855]30import de.ugoe.cs.autoquest.tasktrees.temporalrelation.utils.DebuggingHelper;
[2131]31import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor;
32import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
[1127]33import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
[1294]34import de.ugoe.cs.autoquest.tasktrees.treeifc.IIterationInstance;
[1767]35import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptional;
36import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptionalInstance;
[1127]37import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
[1294]38import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelectionInstance;
[1107]39import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
[1294]40import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequenceInstance;
[1146]41import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
42import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskBuilder;
43import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskFactory;
44import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
45import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstanceList;
46import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
[1256]47import de.ugoe.cs.autoquest.usageprofiles.SymbolMap;
[1401]48import de.ugoe.cs.autoquest.usageprofiles.Trie;
[1119]49import de.ugoe.cs.autoquest.usageprofiles.TrieProcessor;
[1129]50import de.ugoe.cs.util.StopWatch;
[1285]51import de.ugoe.cs.util.console.Console;
[1107]52
53/**
54 * <p>
[1401]55 * This class implements the major rule for creating task trees based on a set of recorded
56 * user sessions. For this, it first harmonizes all tasks. This eases later comparison. Then it
57 * searches the sessions for iterations and replaces them accordingly. Then it searches for sub
58 * sequences being the longest and occurring most often. For each found sub sequence, it replaces
59 * the occurrences by creating appropriate {@link ISequence}s. Afterwards, again searches for
60 * iterations and then again for sub sequences until no more replacements are done.
[1107]61 * </p>
[1401]62 * <p>
63 * For determining the longest sequence occurring most often, the implementation uses a
64 * {@link Trie}. The depth of the tree is initially 3. If the algorithm has a longest sequence
65 * occurring most often whose length is equal to the depth of the trie, it recalculates the trie
66 * with an increased depth.
67 * </p>
[1107]68 *
69 * @author Patrick Harms
70 */
[1146]71class SequenceForTaskDetectionRule implements ISessionScopeRule {
[1107]72   
73    /**
74     * <p>
[1146]75     * the task factory to be used for creating substructures for the temporal
[1401]76     * relationships identified during rul application
[1107]77     * </p>
78     */
[1146]79    private ITaskFactory taskFactory;
[1107]80    /**
81     * <p>
[1146]82     * the task builder to be used for creating substructures for the temporal relationships
[1107]83     * identified during rule application
84     * </p>
85     */
[1146]86    private ITaskBuilder taskBuilder;
[1107]87
88    /**
89     * <p>
[1401]90     * the task handling strategy to be used for comparing tasks for preparation, i.e., before
91     * the tasks are harmonized
[1107]92     * </p>
93     */
[1285]94    private TaskHandlingStrategy preparationTaskHandlingStrategy;
95   
[1107]96    /**
97     * <p>
[1401]98     * the task handling strategy to be used for comparing tasks during iteration detection an trie
99     * generation, i.e., after the tasks are harmonized
[1285]100     * </p>
101     */
102    private TaskHandlingStrategy identityTaskHandlingStrategy;;
103   
104    /**
105     * <p>
[2131]106     * the minimum number of event task instances a sequence must cover to still detect it as a
107     * representative task
108     * </p>
109     */
110    private int minimumSequenceCoverage;;
111   
112    /**
113     * <p>
[1401]114     * instantiates the rule and initializes it with a task equality to be considered when
115     * comparing tasks as well as a task factory and builder to be used for creating task
116     * structures.
[1107]117     * </p>
[1401]118     *
[2131]119     * @param minimalTaskEquality     the task equality to be considered when comparing tasks
120     * @param taskFactory             the task factory to be used for creating substructures
121     * @param taskBuilder             the task builder to be used for creating substructures
122     * @param minimumSequenceCoverage the minimum number of event task instances a sequence must
123     *                                cover to still detect it as a representative task
[1107]124     */
[1189]125    SequenceForTaskDetectionRule(TaskEquality minimalTaskEquality,
126                                 ITaskFactory taskFactory,
[2131]127                                 ITaskBuilder taskBuilder,
128                                 int          minimumSequenceCoverage)
[1107]129    {
[1146]130        this.taskFactory = taskFactory;
131        this.taskBuilder = taskBuilder;
[2131]132        this.minimumSequenceCoverage = minimumSequenceCoverage;
[1107]133       
[1285]134        this.preparationTaskHandlingStrategy = new TaskHandlingStrategy(minimalTaskEquality);
135        this.identityTaskHandlingStrategy = new TaskHandlingStrategy(TaskEquality.IDENTICAL);
[1107]136    }
137
[1119]138    /* (non-Javadoc)
139     * @see java.lang.Object#toString()
140     */
141    @Override
142    public String toString() {
[1127]143        return "SequenceForTaskDetectionRule";
[1119]144    }
145
[1146]146    /* (non-Javadoc)
147     * @see de.ugoe.cs.autoquest.tasktrees.temporalrelation.ISessionScopeRule#apply(java.util.List)
[1107]148     */
149    @Override
[1146]150    public RuleApplicationResult apply(List<IUserSession> sessions) {
[1127]151        RuleApplicationData appData = new RuleApplicationData(sessions);
[1337]152       
[1127]153        // this is the real rule application. Loop while something is replaced.
[1256]154        harmonizeEventTaskInstancesModel(appData);
[1107]155        do {
[1127]156            System.out.println();
[1107]157           
[1127]158            appData.getStopWatch().start("whole loop");
159            detectAndReplaceIterations(appData);
[1133]160
[1127]161            appData.getStopWatch().start("task replacement");
162            detectAndReplaceTasks(appData);
163            appData.getStopWatch().stop("task replacement");
164            appData.getStopWatch().stop("whole loop");
[1107]165           
[1146]166            //((TaskTreeNodeComparator) taskComparator).getStopWatch().dumpStatistics(System.out);
167            //((TaskTreeNodeComparator) taskComparator).getStopWatch().reset();
[1127]168           
169            appData.getStopWatch().dumpStatistics(System.out);
170            appData.getStopWatch().reset();
171           
[1107]172        }
[1146]173        while (appData.detectedAndReplacedTasks());
[1107]174       
[1356]175        Console.println
[1146]176            ("created " + appData.getResult().getNewlyCreatedTasks().size() +
177             " new tasks and " + appData.getResult().getNewlyCreatedTaskInstances().size() +
178             " appropriate instances\n");
[1107]179       
[1146]180        if ((appData.getResult().getNewlyCreatedTasks().size() > 0) ||
181            (appData.getResult().getNewlyCreatedTaskInstances().size() > 0))
182        {
[1127]183            appData.getResult().setRuleApplicationStatus(RuleApplicationStatus.FINISHED);
[1119]184        }
185       
[1127]186        return appData.getResult();
187    }
188
189    /**
[1256]190     * <p>
[1401]191     * harmonizes the event task instances by unifying tasks. This is done, as initially the
192     * event tasks being equal with respect to the considered task equality are distinct objects.
193     * The comparison of these distinct objects is more time consuming than comparing the object
194     * references.
[1256]195     * </p>
196     *
[1401]197     * @param appData the rule application data combining all data used for applying this rule
[1127]198     */
[1256]199    private void harmonizeEventTaskInstancesModel(RuleApplicationData appData) {
[1285]200        Console.traceln(Level.INFO, "harmonizing task model of event task instances");
[1256]201        appData.getStopWatch().start("harmonizing event tasks");
[1285]202
[1855]203        SymbolMap<ITask, ITask> uniqueTasks = preparationTaskHandlingStrategy.createSymbolMap();
204        TaskComparator comparator = preparationTaskHandlingStrategy.getTaskComparator();
[1256]205       
206        int unifiedTasks = 0;
[1285]207        ITask task;
[1256]208        List<IUserSession> sessions = appData.getSessions();
[1356]209        int sessionNo = 0;
[1256]210        for (IUserSession session : sessions) {
[1356]211            Console.traceln(Level.FINE, "handling " + (++sessionNo) + ". " + session);
[1256]212            for (ITaskInstance taskInstance : session) {
[1855]213                task = uniqueTasks.getValue(taskInstance.getTask());
[1256]214               
215                if (task == null) {
[1855]216                    uniqueTasks.addSymbol(taskInstance.getTask(), taskInstance.getTask());
[1256]217                }
218                else {
219                    taskBuilder.setTask(taskInstance, task);
220                    unifiedTasks++;
221                }
222            }
[1285]223           
224            comparator.clearBuffers();
[1256]225        }
226       
227        appData.getStopWatch().stop("harmonizing event tasks");
[1285]228        Console.traceln(Level.INFO, "harmonized " + unifiedTasks + " task occurrences (still " +
229                        uniqueTasks.size() + " different tasks)");
230
[1256]231        appData.getStopWatch().dumpStatistics(System.out);
[1285]232        appData.getStopWatch().reset();
[1256]233    }
234
235    /**
[1401]236     * <p>
237     * searches for direct iterations of single tasks in all sequences and replaces them with
238     * {@link IIteration}s, respectively appropriate instances. Also all single occurrences of
239     * a task that is iterated somewhen are replaced with iterations to have again an efficient
240     * way for task comparisons.
241     * </p>
242     *
243     * @param appData the rule application data combining all data used for applying this rule
[1256]244     */
[1127]245    private void detectAndReplaceIterations(RuleApplicationData appData) {
[1285]246        Console.traceln(Level.FINE, "detecting iterations");
[1127]247        appData.getStopWatch().start("detecting iterations");
[1119]248       
[1146]249        List<IUserSession> sessions = appData.getSessions();
[1127]250       
[1285]251        Set<ITask> iteratedTasks = searchIteratedTasks(sessions);
[1146]252       
[1285]253        if (iteratedTasks.size() > 0) {
254            replaceIterationsOf(iteratedTasks, sessions, appData);
[1127]255        }
256       
257        appData.getStopWatch().stop("detecting iterations");
[1285]258        Console.traceln(Level.INFO, "replaced " + iteratedTasks.size() + " iterated tasks");
[1127]259    }
260
261    /**
[1401]262     * <p>
263     * searches the provided sessions for task iterations. If a task is iterated, it is added
264     * to the returned set.
265     * </p>
266     *
267     * @param the session to search for iterations in
268     *
269     * @return a set of tasks being iterated somewhere
[1127]270     */
[1285]271    private Set<ITask> searchIteratedTasks(List<IUserSession> sessions) {
272        Set<ITask> iteratedTasks = new HashSet<ITask>();
[1146]273        for (IUserSession session : sessions) {
274            for (int i = 0; i < (session.size() - 1); i++) {
[1285]275                // we prepared the task instances to refer to unique tasks, if they are treated
276                // as equal. Therefore, we just compare the identity of the tasks of the task
277                // instances
278                if (session.get(i).getTask() == session.get(i + 1).getTask()) {
279                    iteratedTasks.add(session.get(i).getTask());
[1146]280                }
281            }
282        }
283       
[1285]284        return iteratedTasks;
[1146]285    }
286
287    /**
[1401]288     * <p>
289     * replaces all occurrences of all tasks provided in the set with iterations
290     * </p>
[1146]291     *
[1401]292     * @param iteratedTasks the tasks to be replaced with iterations
293     * @param sessions      the sessions in which the tasks are to be replaced
294     * @param appData       the rule application data combining all data used for applying this rule
[1146]295     */
[1285]296    private void replaceIterationsOf(Set<ITask>          iteratedTasks,
[1146]297                                     List<IUserSession>  sessions,
298                                     RuleApplicationData appData)
[1127]299    {
[1285]300        Map<ITask, IIteration> iterations = new HashMap<ITask, IIteration>();
[1767]301        Map<IIteration, IOptional> optionals = new HashMap<IIteration, IOptional>();
[1294]302        Map<IIteration, List<IIterationInstance>> iterationInstances =
303                new HashMap<IIteration, List<IIterationInstance>>();
[1285]304           
305        for (ITask iteratedTask : iteratedTasks) {
306            IIteration iteration = taskFactory.createNewIteration();
307            iterations.put(iteratedTask, iteration);
[1767]308           
309            if (iteratedTask instanceof IOptional) {
310                IOptional optional = taskFactory.createNewOptional();
311                taskBuilder.setMarkedTask(optional, iteration);
312                optionals.put(iteration, optional);
313            }
314           
[1294]315            iterationInstances.put(iteration, new LinkedList<IIterationInstance>());
[1285]316        }
317       
[1767]318        IOptionalInstance optionalInstance;
[1337]319        IIterationInstance iterationInstance;
[1127]320       
[1146]321        for (IUserSession session : sessions) {
322            int index = 0;
[1767]323            optionalInstance = null;
[1337]324            iterationInstance = null;
[1767]325            boolean inReplacement = false;
[1337]326
[1146]327            while (index < session.size()) {
[1285]328                // we prepared the task instances to refer to unique tasks, if they are treated
329                // as equal. Therefore, we just compare the identity of the tasks of the task
330                // instances
331                ITask currentTask = session.get(index).getTask();
332                IIteration iteration = iterations.get(currentTask);
333                if (iteration != null) {
[1767]334                    if (!inReplacement || (iterationInstance.getTask() != iteration)) {
335                        if (currentTask instanceof IOptional) {
336                            IOptional optional = optionals.get(iteration);
337                            optionalInstance = taskFactory.createNewTaskInstance(optional);
338                            taskBuilder.addTaskInstance(session, index, optionalInstance);
339                        }
340                        else {
341                            iterationInstance = taskFactory.createNewTaskInstance(iteration);
342                            iterationInstances.get(iteration).add(iterationInstance);
343                            taskBuilder.addTaskInstance(session, index, iterationInstance);
344                        }
345                        inReplacement = true;
[1146]346                        index++;
347                    }
348                   
[1767]349                    if (currentTask instanceof IOptional) {
350                        ITaskInstance child = ((IOptionalInstance) session.get(index)).getChild();
351                       
352                        if (child != null) {
353                            if (iterationInstance == null) {
354                                iterationInstance = taskFactory.createNewTaskInstance(iteration);
355                                iterationInstances.get(iteration).add(iterationInstance);
356                                taskBuilder.setChild(optionalInstance, iterationInstance);
357                            }
358                            taskBuilder.addChild(iterationInstance, child);
359                        }
360                    }
361                    else {
362                        taskBuilder.addChild(iterationInstance, session.get(index));
363                    }
364                   
[1146]365                    taskBuilder.removeTaskInstance(session, index);
366                }
367                else {
368                    if (iterationInstance != null) {
369                        iterationInstance = null;
[1767]370                        optionalInstance = null;
371                        inReplacement = false;
[1146]372                    }
373                    index++;
374                }
375            }
376        }
377       
[1294]378        for (Map.Entry<IIteration, List<IIterationInstance>> entry : iterationInstances.entrySet())
379        {
[1285]380            harmonizeIterationInstancesModel(entry.getKey(), entry.getValue());
381        }
[1146]382    }
[1127]383
[1146]384    /**
[1401]385     * <p>
386     * TODO clarify why this is done
387     * </p>
[1146]388     */
[1294]389    private void harmonizeIterationInstancesModel(IIteration               iteration,
390                                                  List<IIterationInstance> iterationInstances)
[1146]391    {
392        List<ITask> iteratedTaskVariants = new LinkedList<ITask>();
[1855]393        TaskComparator comparator = preparationTaskHandlingStrategy.getTaskComparator();
[1146]394       
395        // merge the lexically different variants of iterated task to a unique list
[1294]396        for (IIterationInstance iterationInstance : iterationInstances) {
[1146]397            for (ITaskInstance executionVariant : iterationInstance) {
398                ITask candidate = executionVariant.getTask();
[1127]399           
[1146]400                boolean found = false;
401                for (ITask taskVariant : iteratedTaskVariants) {
[1285]402                    if (comparator.areLexicallyEqual(taskVariant, candidate)) {
[1146]403                        taskBuilder.setTask(executionVariant, taskVariant);
404                        found = true;
405                        break;
406                    }
[1127]407                }
[1146]408               
409                if (!found) {
410                    iteratedTaskVariants.add(candidate);
411                }
[1107]412            }
413        }
414       
[1146]415        // if there are more than one lexically different variant of iterated tasks, adapt the
416        // iteration model to be a selection of different variants. In this case also adapt
417        // the generated iteration instances to correctly contain selection instances. If there
418        // is only one variant of an iterated task, simply set this as the marked task of the
419        // iteration. In this case, the instances can be preserved as is
420        if (iteratedTaskVariants.size() > 1) {
421            ISelection selection = taskFactory.createNewSelection();
422           
423            for (ITask variant : iteratedTaskVariants) {
424                taskBuilder.addChild(selection, variant);
425            }
426           
427            taskBuilder.setMarkedTask(iteration, selection);
428           
[1294]429            for (IIterationInstance instance : iterationInstances) {
[1146]430                for (int i = 0; i < instance.size(); i++) {
[1294]431                    ISelectionInstance selectionInstance =
432                        taskFactory.createNewTaskInstance(selection);
433                    taskBuilder.setChild(selectionInstance, instance.get(i));
[1146]434                    taskBuilder.setTaskInstance(instance, i, selectionInstance);
435                }
436            }
437        }
438        else {
439            taskBuilder.setMarkedTask(iteration, iteratedTaskVariants.get(0));
440        }
[1107]441    }
442
443    /**
[1401]444     * TODO go on commenting
445     * @param appData the rule application data combining all data used for applying this rule
[1127]446     */
447    private void detectAndReplaceTasks(RuleApplicationData appData) {
[1285]448        Console.traceln(Level.FINE, "detecting and replacing tasks");
[1127]449        appData.getStopWatch().start("detecting tasks");
450       
[1855]451        getSubsequencesOccuringMostOften(appData);
452       
453        appData.getStopWatch().stop("detecting tasks");
454        appData.getStopWatch().start("sorting tasks");
455       
456        removeInterleavingTasks(appData);
[1127]457
[1855]458        appData.getStopWatch().stop("sorting tasks");
[1127]459        appData.getStopWatch().start("replacing tasks");
460       
461        replaceSequencesOccurringMostOften(appData);
462       
463        appData.getStopWatch().stop("replacing tasks");
[1855]464        Console.traceln(Level.INFO, "detected and replaced " + appData.getLastFoundSubsequences().size() +
465                        " tasks occuring " + appData.getLastFoundSubsequences().getOccurrenceCount() +
[1287]466                        " times");
[1127]467    }
468
469    /**
[1401]470     * @param appData the rule application data combining all data used for applying this rule
[1107]471     */
[1855]472    private void getSubsequencesOccuringMostOften(RuleApplicationData appData) {
473        Console.traceln(Level.FINER, "determining most prominent subsequences");
[1119]474
[1855]475        Subsequences subsequences;
[1119]476        boolean createNewTrie = (appData.getLastTrie() == null) ||
[1146]477            appData.detectedAndReplacedTasks(); // tree has changed
[1107]478       
[1119]479        do {
480            if (createNewTrie) {
481                createNewTrie(appData);
482            }
[1107]483           
[2131]484            appData.getStopWatch().start("applying finder");
[1855]485            MaxCountAndLongestSubsequencesFinder finder = new MaxCountAndLongestSubsequencesFinder();
[1119]486            appData.getLastTrie().process(finder);
[2131]487            appData.getStopWatch().stop("applying finder");
[1119]488       
[2131]489            appData.getStopWatch().start("remove permutations");
[1855]490            subsequences = finder.getFoundSubsequences();
[2131]491            appData.getStopWatch().stop("remove permutations");
[1119]492           
[2131]493            appData.getStopWatch().start("drop unrepresentative");
494            dropUnrepresentative(subsequences, appData);
495            appData.getStopWatch().stop("drop unrepresentative");
496           
[1119]497            createNewTrie = false;
498           
[1855]499            for (Subsequence subsequence : subsequences) {
500                if (subsequence.size() >= appData.getTrainedSubsequenceLength()) {
[1119]501                    // Trie must be recreated with a longer sequence length to be sure that
502                    // the found sequences occurring most often are found in their whole length
[1888]503                    appData.setTrainedSubsequenceLength(appData.getTrainedSubsequenceLength() + 3);
[1119]504                    createNewTrie = true;
505                    break;
506                }
[1107]507            }
508        }
[1119]509        while (createNewTrie);
510       
[1256]511        // create a normal trie for comparison
512        /*System.out.println("creating test trie for comparison");
513       
514        appData.getStopWatch().start("testtrie");
[1285]515        Trie<ITaskInstance> trie = new Trie<ITaskInstance>(identityTaskComparator);
[1256]516       
517        for (IUserSession session : appData.getSessions()) {
518            trie.train(session.getExecutedTasks(), appData.getTrainedSequenceLength());
519        }
520       
521        appData.getStopWatch().stop("testtrie");
522       
523        MaxCountAndLongestTasksFinder finder = new MaxCountAndLongestTasksFinder();
524        trie.process(finder);
525
526        boolean allTasksEqual = finder.getFoundTasks().size() == tasks.size();
527       
528        allTasksEqual &= finder.getFoundTasks().occurrenceCount == tasks.occurrenceCount;
529       
530        for (List<ITaskInstance> task1 : finder.getFoundTasks()) {
531            boolean foundTask = false;
532            for (List<ITaskInstance> task2 : tasks) {
533                boolean tasksEqual = false;
534                if (task1.size() == task2.size()) {
535                    tasksEqual = true;
536                    for (int i = 0; i < task1.size(); i++) {
[1285]537                        if (!identityTaskComparator.equals(task1.get(i).getTask(), task2.get(i).getTask()))
[1256]538                        {
539                            tasksEqual = false;
540                        }
541                    }
542                }
543               
544                if (tasksEqual) {
545                    foundTask = true;
546                    break;
547                }
548            }
549           
550            if (!foundTask) {
551                System.out.println("different is " + task1);
552                allTasksEqual = false;
553                break;
554            }
555        }
556       
557        if (!allTasksEqual) {
558            System.out.println(finder.getFoundTasks());
559            System.out.println(tasks);
560           
561            throw new IllegalArgumentException("both tries calculated different subsequences");
[1285]562        }
[1256]563       
564        /*TrieStatisticsDumper dumper = new TrieStatisticsDumper();
565        appData.getLastTrie().process(dumper);
566        dumper.dumpCounters();*/
567       
[1855]568        appData.setLastFoundSubsequences(subsequences);
[1127]569
[1855]570        Console.traceln(Level.FINE, "found " + appData.getLastFoundSubsequences().size() +
571                        " tasks occurring " +
572                        appData.getLastFoundSubsequences().getOccurrenceCount() + " times");
[1107]573    }
574
575    /**
[2131]576     * <p>
577     * TODO: comment
578     * </p>
579     *
580     * @param subsequences
581     */
582    private void dropUnrepresentative(Subsequences subsequences, RuleApplicationData appData) {
583        Map<Subsequence, List<SubsequenceLocation>> locations = getLocations(subsequences, appData);
584       
585        for (Map.Entry<Subsequence, List<SubsequenceLocation>> entry : locations.entrySet()) {
586            final int[] coveredActionInstances = new int[1];
587            for (SubsequenceLocation loc : entry.getValue()) {
588                for (int i = loc.getIndex(); i < loc.getIndex() + entry.getKey().size(); i++) {
589                    ITaskInstance taskInstance = loc.getSession().get(i);
590                   
591                    taskInstance.accept(new DefaultTaskInstanceTraversingVisitor() {
592                        @Override
593                        public void visit(IEventTaskInstance eventTaskInstance) {
594                            coveredActionInstances[0]++;
595                        }
596                    });
597                }
598            }
599           
600            if (coveredActionInstances[0] < minimumSequenceCoverage) {
601                subsequences.remove(entry.getKey());
602            }
603        }
604    }
605
606    /**
[1401]607     * @param appData the rule application data combining all data used for applying this rule
[1107]608     */
[1127]609    private void createNewTrie(RuleApplicationData appData) {
[1855]610        Console.traceln(Level.FINEST, "training trie with a maximum sequence length of " +
611                        appData.getTrainedSubsequenceLength());
[1119]612
613        appData.getStopWatch().start("training trie");
[1285]614       
615        // we prepared the task instances to refer to unique tasks, if they are treated
616        // as equal. Therefore, we just compare the identity of the tasks of the task
617        // instances
618        appData.setLastTrie(new TaskInstanceTrie(identityTaskHandlingStrategy));
[1119]619   
[1256]620        appData.getLastTrie().trainSessions
[1855]621            (appData.getSessions(), appData.getTrainedSubsequenceLength());
[1127]622       
[1119]623        appData.getStopWatch().stop("training trie");
624    }
625
626    /**
[1855]627     * from the last found tasks, sort out those that interleave with each other as for them always
628     * the same replacement order needs to be taken.
629     */
[1955]630//    private void removeInterleavingTasksOld(RuleApplicationData appData) {
631//        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
632//        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
633//        // dumpSortedCollectionOfSubsequences
634//        //     ("found task", appData.getLastFoundSubsequences().subsequences);
635//        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
636//        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
637//
638//        List<InterleavingSubsequence> interleavings;
639//       
640//        do {
641//            interleavings = getInterleavings(appData.getLastFoundSubsequences(), appData);
642//           
643//            if (interleavings.size() > 0) {
644//                // dumpSorted(interleavings);
645//
646//                // Console.traceln(Level.FINEST, "found " + interleavings.size() + " subsequences " +
647//                //                 "--> checking for most real interleavings");
648//               
649//                // we found interleaving subsequences. We need to decide for them, which to replace
650//                // first. For this, we need to remove some of them from the list of detected
651//                // subsequences in an ordered way to always have the same prioritization for
652//                // replacements. The ones to be removed are those with most interleavings.
653//                interleavings = getMostIntensiveInterleavings(interleavings);
654//               
655//                //dumpSorted(interleavings);
656//               
657//                if (interleavings.size() == 1) {
658//                    // Console.traceln(Level.FINEST, "found one most interleaving subsequence " +
659//                    //                 "--> removing it and checking for further interleavings");
660//
661//                    // we have exactly one most interleaving detected subsequence. In this case,
662//                    // we remove it from the list of found subsequences and check again, if their
663//                    // are remaining interleavings. If not, we have the final list of non
664//                    // interleaving subsequences to be replaced. The order or their replacement
665//                    // doesn't matter as they are not interleaving.
666//                    appData.getLastFoundSubsequences().remove
667//                        (interleavings.get(0).getSubsequence());
668//                   
669//                    continue;
670//                }
671//                else if (interleavings.size() == 0) {
672//                    // this should never happen
673//                    throw new RuntimeException("Implementation error: don't know how I got here");
674//                }
675//               
676//                // Console.traceln(Level.FINEST, "found " + interleavings.size() + " most " +
677//                //                 "interleaving subsequences --> checking which are most often a " +
678//                //                 "successor");
679//
680//                // there are several subsequences with the same amount of interleavings.
681//                // Check which of them is the most often a successor. This should be removed.
682//                interleavings = getMostInterleavingsAsSuccessor(interleavings);
683//               
684//                // dumpSorted(interleavings);
685//
686//                if (interleavings.size() == 1) {
687//                    //  Console.traceln(Level.FINEST, "found one interleaving subsequence being most " +
688//                    //                  "often a successor --> removing it and checking for further " +
689//                    //                  "interleavings");
690//
691//                    // we have exactly one interleaving subsequence that is most often a successor
692//                    // of the others. Remove it and try again to see if sufficient interleavings
693//                    // were cleared.
694//                    appData.getLastFoundSubsequences().remove
695//                        (interleavings.get(0).getSubsequence());
696//                   
697//                    continue;
698//                }
699//                else if (interleavings.size() == 0) {
700//                    // this should never happen
701//                    throw new RuntimeException("Implementation error: don't know how I got here");
702//                }
703//               
704//                // Console.traceln(Level.FINEST, "found " + interleavings.size() +
705//                //                 " most interleaving subsequences being most often a successor " +
706//                //                 "--> checking which is most seldom a predecessor");
707//
708//                // there are several subsequences with the same amount of interleavings being also
709//                // the same times a successor of another subsequence. Hence, check which of them is
710//                // the least often a predecessor. This should be removed.
711//                interleavings = getLeastInterleavingsAsPredecessor(interleavings);
712//               
713//                //dumpSorted(interleavings);
714//
715//                if (interleavings.size() == 1) {
716//                    // Console.traceln(Level.FINEST, "found one interleaving subsequence being most " +
717//                    //                 "often a successor and most seldom a predecessor --> " +
718//                    //                 "removing it and checking for further interleavings");
719//
720//                    // we have exactly one interleaving subsequence that is most often a successor
721//                    // and most seldom a predecessor of the others. Remove it and try again to see
722//                    // if sufficient interleavings were cleared.
723//                    appData.getLastFoundSubsequences().remove
724//                        (interleavings.get(0).getSubsequence());
725//                   
726//                    continue;
727//                }
728//                else if (interleavings.size() == 0) {
729//                    // this should never happen
730//                    throw new RuntimeException("Implementation error: don't know how I got here");
731//                }
732//               
733//                // the remaining subsequences are interleaving the same amount of times and are
734//                // used the same amount of times as successors and as predecessors by all other
735//                // detected interleaving subsequences. Now lets check, which of them is mostly used
736//                // as successor and most seldom used as predecessor only considering the remaining
737//                // subsequences. If some of them do not interfere with the others, remove them and
738//                // try again to see if sufficient interleavings were cleared.
739//               
740//                // Console.traceln(Level.FINEST, "found " + interleavings.size() +
741//                //                 " most interleaving subsequences being most often a successor " +
742//                //                 "and most seldom a predecessor --> removing collision being not " +
743//                //                 "between themselves");
744//
745//                interleavings = removeCollisionsOfOtherSubsequences(interleavings);
746//               
747//                // dumpSorted(interleavings);
748//               
749//                // Console.traceln(Level.FINEST, "removed collisions of other subsequences not " +
750//                //                 "belonging to the remaining interleaving subsequences --> " +
751//                //                 "checking of the remaining interleaving are most often a " +
752//                //                 "successor of another one");
753//               
754//                interleavings = getMostInterleavingsAsSuccessor(interleavings);
755//                interleavings = removeCollisionsOfOtherSubsequences(interleavings);
756//               
757//                // dumpSorted(interleavings);
758//
759//                if (interleavings.size() == 1) {
760//                    // Console.traceln(Level.FINEST, "found one interleaving subsequence being most " +
761//                    //                 "often a successor in comparison to the other remaining " +
762//                    //                 "interleavings --> removing it and checking for further " +
763//                    //                 "interleavings");
764//
765//                    appData.getLastFoundSubsequences().remove
766//                        (interleavings.get(0).getSubsequence());
767//                   
768//                    continue;
769//                }
770//                else if (interleavings.size() == 0) {
771//                    // this should never happen
772//                    throw new RuntimeException("Implementation error: don't know how I got here");
773//                }
774//               
775//                // Console.traceln(Level.FINEST, "found " + interleavings.size() +
776//                //                 " most interleaving subsequences being most often a successor in " +
777//                //                 "comparison to the other remaining interleavings --> checking " +
778//                //                 "which is most seldom a predecessor");
779//
780//                interleavings = getLeastInterleavingsAsPredecessor(interleavings);
781//                interleavings = removeCollisionsOfOtherSubsequences(interleavings);
782//               
783//                // dumpSorted(interleavings);
784//
785//                if (interleavings.size() == 1) {
786//                    // Console.traceln(Level.FINEST, "found one interleaving subsequence being most " +
787//                    //                 "often a successor and most seldom a predecessor in " +
788//                    //                 "comparison to the other remaining interleavings --> " +
789//                    //                 "removing it and checking for further interleavings");
790//
791//                    appData.getLastFoundSubsequences().remove
792//                        (interleavings.get(0).getSubsequence());
793//                   
794//                    continue;
795//                }
796//                else if (interleavings.size() == 0) {
797//                    // this should never happen
798//                    throw new RuntimeException("Implementation error: don't know how I got here");
799//                }
800//               
801//                // Console.traceln(Level.FINEST, "found " + interleavings.size() +
802//                //                 " most interleaving subsequences being most often a successor " +
803//                //                 "and most seldom a predecessor in comparison to the other " +
804//                //                 "remaining interleavings --> this may happen if there are no " +
805//                //                 "collisions between them anymore. If so, remove all of them at " +
806//                //                 "once.");
807//
808//                int collisionCount = 0;
809//               
810//                for (InterleavingSubsequence subsequence : interleavings) {
811//                    collisionCount += subsequence.getCollisionCounter();
812//                }
813//               
814//                if (collisionCount == 0) {
815//                    // Console.traceln(Level.FINEST, "remaining interleaving subsequences have no " +
816//                    //                 "further collisions with each other --> removing all of them " +
817//                    //                 "and checking for further interleavings");
818//                   
819//                    for (InterleavingSubsequence subsequence : interleavings) {
820//                        appData.getLastFoundSubsequences().remove(subsequence.getSubsequence());
821//                    }
822//                   
823//                    continue;
824//                }
825//
826//                // Console.traceln(Level.FINEST, "remaining interleaving subsequences still have " +
827//                //                 "collisions with each other --> decide based on their occurrences" +
828//                //                 " in the sessions (remove those, coming later in comparison to " +
829//                //                 "the others)");
830//               
831//                // determine the predecessor collisions being the last in all sessions. Those
832//                // collisions show the interleaving subsequence occurring last
833//                Map<IUserSession, Collision> sessions =
834//                    new IdentityHashMap<IUserSession, Collision>();
835//               
836//                for (InterleavingSubsequence interleaving : interleavings) {
837//                    for (Collision coll : interleaving.getPredecessorCollisions()) {
838//                        Collision maxPosColl = sessions.get(coll.getLocation().getSession());
839//                       
840//                        if ((maxPosColl == null) ||
841//                            (maxPosColl.getLocation().getIndex() < coll.getLocation().getIndex()))
842//                        {
843//                            sessions.put(coll.getLocation().getSession(), coll);
844//                        }
845//                    }
846//                }
847//               
848//                // determine, which of the subsequences occurs most often as the last one
849//                // interleaving with another
850//                Map<Subsequence, Integer> lastOccurrenceCounters =
851//                    new IdentityHashMap<Subsequence, Integer>();
852//               
853//                for (Collision coll : sessions.values()) {
854//                    Integer counter = lastOccurrenceCounters.get(coll.getSubsequence());
855//                   
856//                    if (counter == null) {
857//                        lastOccurrenceCounters.put(coll.getSubsequence(), 1);
858//                    }
859//                    else {
860//                        lastOccurrenceCounters.put(coll.getSubsequence(), counter + 1);
861//                    }
862//                }
863//               
864//                int maxCounter = 0;
865//                Subsequence toRemove = null;
866//               
867//                for (Map.Entry<Subsequence, Integer> entry : lastOccurrenceCounters.entrySet()) {
868//                    if (entry.getValue() > maxCounter) {
869//                        maxCounter = entry.getValue();
870//                        toRemove = entry.getKey();
871//                    }
872//                    else if (entry.getValue() == maxCounter) {
873//                        // we can not decide again
874//                        toRemove = null;
875//                        break;
876//                    }
877//                }
878//               
879//                if (toRemove != null) {
880//                    // Console.traceln(Level.FINEST, "one of the remaining interleaving subsequences" +
881//                    //                 " is most often the last to occur --> removing it and " +
882//                    //                 "checking for further interleavings");
883//           
884//                    appData.getLastFoundSubsequences().remove(toRemove);
885//                    continue;
886//                }
887//               
888//                // checking now, if there is one sequence, having the lowest index for any of
889//                // its collisions and preserve it. If there are several ones with the same minimum
890//                // index, check if they are independent. If not, throw an exception
891//                int minPosInAnySequence = Integer.MAX_VALUE;
892//                List<InterleavingSubsequence> subsequencesWithMinPos = new LinkedList<>();
893//               
894//                for (InterleavingSubsequence interleaving : interleavings) {
895//                    for (Collision collision : interleaving.getSuccessorCollisions()) {
896//                        if (minPosInAnySequence > collision.getLocation().getIndex()) {
897//                            minPosInAnySequence = collision.getLocation().getIndex();
898//                            subsequencesWithMinPos.clear();
899//                        }
900//                       
901//                        if (minPosInAnySequence == collision.getLocation().getIndex()) {
902//                            // several have the same min pos --> undecidable which to prefer
903//                            subsequencesWithMinPos.add(interleaving);
904//                        }
905//                    }
906//                }
907//               
908//                if (subsequencesWithMinPos.size() > 0) {
909//                    List<Subsequence> subsequencesToRemove = new LinkedList<Subsequence>();
910//                   
911//                    for (Subsequence candidate : appData.getLastFoundSubsequences()) {
912//                        boolean found = false;
913//                       
914//                        for (InterleavingSubsequence subsequenceWithMinPos : subsequencesWithMinPos)
915//                        {
916//                            if (candidate == subsequenceWithMinPos.getSubsequence()) {
917//                                found = true;
918//                            }
919//                        }
920//                       
921//                        if (!found) {
922//                            subsequencesToRemove.add(candidate);
923//                        }
924//                    }
925//                   
926//                    if (subsequencesToRemove.size() > 0) {
927//                        for (Subsequence candidate : subsequencesToRemove) {
928//                            appData.getLastFoundSubsequences().remove(candidate);
929//                        }
930//                   
931//                        continue;
932//                    }
933//                }
934//               
935//                // At this point, we can not decide anymore. But it should also not
936//                // happen. Even if two subsequences ab and ba one of them should be
937//                // more often a successor or predecessor than the other if they
938//                // directly follow each other. If not, it can not be decided, which of
939//                // them to replace first. Perhaps the one occurring more often as
940//                // the first in a session that the other. But this can be implemented
941//                // later.
942//                dumpSorted(interleavings, Level.SEVERE);
943//               
944//                throw new RuntimeException
945//                    ("can not decide which detected subsequence to replace first.");
946//            }
947//        }
948//        while (interleavings.size() > 0);
949//
950//        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
951//        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
952//        // dumpSortedCollectionOfSubsequences
953//        //    ("to replace", appData.getLastFoundSubsequences().subsequences);
954//        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
955//        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
956//    }
957
958    /**
959     * from the last found tasks, sort out those that interleave with each other as for them always
960     * the same replacement order needs to be taken.
961     */
[1855]962    private void removeInterleavingTasks(RuleApplicationData appData) {
963        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
964        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
965        // dumpSortedCollectionOfSubsequences
966        //     ("found task", appData.getLastFoundSubsequences().subsequences);
967        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
968        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
969
970        List<InterleavingSubsequence> interleavings;
971       
972        do {
973            interleavings = getInterleavings(appData.getLastFoundSubsequences(), appData);
974           
975            if (interleavings.size() > 0) {
976                // dumpSorted(interleavings);
977
978                // Console.traceln(Level.FINEST, "found " + interleavings.size() + " subsequences " +
979                //                 "--> checking for most real interleavings");
980               
981                // we found interleaving subsequences. We need to decide for them, which to replace
982                // first. For this, we need to remove some of them from the list of detected
983                // subsequences in an ordered way to always have the same prioritization for
984                // replacements. The ones to be removed are those with most interleavings.
985                interleavings = getMostIntensiveInterleavings(interleavings);
986               
987                //dumpSorted(interleavings);
988               
[1955]989                if (interleavings.size() < appData.getLastFoundSubsequences().size()) {
990                    // Console.traceln(Level.FINEST, "found most interleaving subsequences " +
991                    //                 "--> removing them and checking for further interleavings");
[1855]992
[1955]993                    // we have most interleaving subsequences. As these are less than all detected
994                    // subsequences, we remove them and check if there are remaining interleavings.
995                    for (InterleavingSubsequence interleaving : interleavings) {
996                        appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
997                    }
[1855]998                   
999                    continue;
1000                }
1001                else if (interleavings.size() == 0) {
1002                    // this should never happen
1003                    throw new RuntimeException("Implementation error: don't know how I got here");
1004                }
1005               
1006                // Console.traceln(Level.FINEST, "found " + interleavings.size() + " most " +
1007                //                 "interleaving subsequences --> checking which are most often a " +
1008                //                 "successor");
1009
[1955]1010                // all detected subsequences are also interleaving with the same amount of time
1011                // check, which of them is most often the successor in a collision
1012                interleavings = removeCollisionsOfOtherSubsequences(interleavings);
[1855]1013                interleavings = getMostInterleavingsAsSuccessor(interleavings);
1014               
1015                // dumpSorted(interleavings);
1016
[1955]1017                if (interleavings.size() < appData.getLastFoundSubsequences().size()) {
1018                    //  Console.traceln(Level.FINEST, "found interleaving subsequences being most " +
1019                    //                  "often a successor --> removing them and checking for further " +
[1855]1020                    //                  "interleavings");
1021
[1955]1022                    // we have interleaving subsequences that are most often a successor
1023                    // of the others. As they are less than all detected subsequences, remove them
1024                    // and try again to see if sufficient interleavings were cleared.
1025                    for (InterleavingSubsequence interleaving : interleavings) {
1026                        appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
1027                    }
[1855]1028                   
1029                    continue;
1030                }
1031                else if (interleavings.size() == 0) {
1032                    // this should never happen
1033                    throw new RuntimeException("Implementation error: don't know how I got here");
1034                }
1035               
1036                // Console.traceln(Level.FINEST, "found " + interleavings.size() +
1037                //                 " most interleaving subsequences being most often a successor " +
1038                //                 "--> checking which is most seldom a predecessor");
1039
[1955]1040                // all detected subsequences have the same amount of interleavings being also
[1855]1041                // the same times a successor of another subsequence. Hence, check which of them is
1042                // the least often a predecessor. This should be removed.
[1955]1043                interleavings = removeCollisionsOfOtherSubsequences(interleavings);
[1855]1044                interleavings = getLeastInterleavingsAsPredecessor(interleavings);
1045               
1046                //dumpSorted(interleavings);
1047
[1955]1048                if (interleavings.size() < appData.getLastFoundSubsequences().size()) {
1049                    // Console.traceln(Level.FINEST, "found interleaving subsequences being most " +
[1855]1050                    //                 "often a successor and most seldom a predecessor --> " +
[1955]1051                    //                 "removing them and checking for further interleavings");
[1855]1052
[1955]1053                    // we have interleaving subsequences that are most often a successor
1054                    // and most seldom a predecessor of the others. As they are less than all
1055                    // detected subsequences, remove them and try again to see
[1855]1056                    // if sufficient interleavings were cleared.
[1955]1057                    for (InterleavingSubsequence interleaving : interleavings) {
1058                        appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
1059                    }
[1855]1060                   
1061                    continue;
1062                }
1063                else if (interleavings.size() == 0) {
1064                    // this should never happen
1065                    throw new RuntimeException("Implementation error: don't know how I got here");
1066                }
1067               
1068                // the remaining subsequences are interleaving the same amount of times and are
1069                // used the same amount of times as successors and as predecessors by all other
[1955]1070                // detected interleaving subsequences.
[1855]1071               
1072                // Console.traceln(Level.FINEST, "found " + interleavings.size() +
1073                //                 " most interleaving subsequences being most often a successor " +
1074                //                 "and most seldom a predecessor in comparison to the other " +
[1955]1075                //                 "remaining interleavings --> decide based on their occurrences" +
[1855]1076                //                 " in the sessions (remove those, coming later in comparison to " +
1077                //                 "the others)");
1078               
[1955]1079                // determine the subsequences whose sum of the indexes of collisions is smallest or
1080                // who has the smallest collision index in all sessions and remove all others
1081                int overallMinSumOfCollisionIndexes = Integer.MAX_VALUE;
1082                int overallMinimalCollisionIndex = Integer.MAX_VALUE;
[1958]1083                List<InterleavingSubsequence> interleavingsWithMinSum = new LinkedList<>();
1084                List<InterleavingSubsequence> interleavingsWithMinIndex = new LinkedList<>();
[1855]1085               
1086                for (InterleavingSubsequence interleaving : interleavings) {
[1955]1087                    int sumOfCollisionIndexes = 0;
1088                    int minimalCollisionIndex = Integer.MAX_VALUE;
1089                   
[1855]1090                    for (Collision coll : interleaving.getPredecessorCollisions()) {
[1955]1091                        sumOfCollisionIndexes += coll.getLocation().getIndex();
1092                        minimalCollisionIndex =
1093                            Math.min(minimalCollisionIndex, coll.getLocation().getIndex());
[1855]1094                    }
1095                   
[1955]1096                    for (Collision coll : interleaving.getSuccessorCollisions()) {
1097                        sumOfCollisionIndexes += coll.getLocation().getIndex();
1098                        minimalCollisionIndex =
1099                            Math.min(minimalCollisionIndex, coll.getLocation().getIndex());
[1855]1100                    }
[1955]1101                   
1102                    if (overallMinSumOfCollisionIndexes > sumOfCollisionIndexes) {
[1958]1103                        interleavingsWithMinSum.clear();
1104                        interleavingsWithMinSum.add(interleaving);
[1955]1105                        overallMinSumOfCollisionIndexes = sumOfCollisionIndexes;
[1855]1106                    }
[1955]1107                    else if (overallMinSumOfCollisionIndexes == sumOfCollisionIndexes) {
[1958]1108                        interleavingsWithMinSum.add(interleaving);
[1855]1109                    }
[1955]1110                   
1111                    if (overallMinimalCollisionIndex > minimalCollisionIndex) {
[1958]1112                        interleavingsWithMinIndex.clear();
1113                        interleavingsWithMinIndex.add(interleaving);
[1955]1114                        overallMinimalCollisionIndex = minimalCollisionIndex;
[1855]1115                    }
[1955]1116                    else if (overallMinimalCollisionIndex == minimalCollisionIndex) {
[1958]1117                        interleavingsWithMinIndex.add(interleaving);
[1955]1118                    }
[1855]1119                }
1120               
[1958]1121                if (interleavingsWithMinSum.size() < appData.getLastFoundSubsequences().size()) {
[1955]1122                    for (InterleavingSubsequence interleaving : interleavings) {
[1958]1123                        boolean found = false;
1124                        for (InterleavingSubsequence candidate : interleavingsWithMinSum) {
1125                            if (candidate == interleaving) {
1126                                found = true;
1127                                break;
1128                            }
1129                        }
1130                       
1131                        if (!found) {
[1955]1132                            appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
[1855]1133                        }
1134                    }
[1955]1135                   
1136                    continue;
[1855]1137                }
1138               
[1958]1139                if (interleavingsWithMinIndex.size() < appData.getLastFoundSubsequences().size()) {
[1955]1140                    for (InterleavingSubsequence interleaving : interleavings) {
[1958]1141                        boolean found = false;
1142                        for (InterleavingSubsequence candidate : interleavingsWithMinIndex) {
1143                            if (candidate == interleaving) {
1144                                found = true;
1145                                break;
1146                            }
1147                        }
1148                       
1149                        if (!found) {
[1955]1150                            appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
[1888]1151                        }
[1855]1152                    }
1153                   
[1955]1154                    continue;
[1855]1155                }
1156               
[2131]1157                // the remaining subsequences are interleaving the same amount of times, are
1158                // used the same amount of times as successors and predecessors by all other
1159                // detected interleaving subsequences, and their sum of the indexes of collisions
1160                // is smallest or their collision index in all sessions is the smallest. This may
1161                // happen for a subsequence aba for which there is an occurrence ababa. That is,
1162                // the subsequence interleaves with itself. We can try to solve this by shortening
1163                // the detected subsequence by one. Let us see, what happens.
1164                /*boolean changedSubsequence = false;
1165                for (InterleavingSubsequence interleaving : interleavings) {
1166                    if (interleaving.getSubsequence().size() > 2) {
1167                        appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
1168                        List<ITask> shortenedSubsequence = new LinkedList<>();
1169                        for (int i = 0; i < interleaving.getSubsequence().size() - 1; i++) {
1170                            shortenedSubsequence.add(interleaving.getSubsequence().get(i));
1171                        }
1172                        appData.getLastFoundSubsequences().subsequences.add
1173                            (new Subsequence(interleaving.getSubsequence().occurrenceCount,
1174                                             shortenedSubsequence));
1175                       
1176                        changedSubsequence = true;
1177                    }
1178                }
1179               
1180                if (changedSubsequence) {
1181                    continue;
1182                }*/
1183               
1184               
[1855]1185                // At this point, we can not decide anymore. But it should also not
1186                // happen. Even if two subsequences ab and ba one of them should be
1187                // more often a successor or predecessor than the other if they
1188                // directly follow each other. If not, it can not be decided, which of
1189                // them to replace first. Perhaps the one occurring more often as
[1955]1190                // the first in a session than the other. But this can be implemented
[1855]1191                // later.
1192                dumpSorted(interleavings, Level.SEVERE);
1193               
1194                throw new RuntimeException
1195                    ("can not decide which detected subsequence to replace first.");
1196            }
1197        }
1198        while (interleavings.size() > 0);
1199
1200        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1201        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1202        // dumpSortedCollectionOfSubsequences
1203        //    ("to replace", appData.getLastFoundSubsequences().subsequences);
1204        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1205        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1206    }
1207
1208    /**
1209     *
1210     */
1211    private List<InterleavingSubsequence> getInterleavings(Subsequences        subsequences,
1212                                                           RuleApplicationData appData)
1213    {
[2131]1214        appData.getStopWatch().start("determine interleavings");
[1855]1215        List<InterleavingSubsequence> interleavings = new LinkedList<InterleavingSubsequence>();
1216        List<Subsequence> potentialPredecessors = new LinkedList<Subsequence>();
1217        List<Subsequence> potentialSuccessors = new LinkedList<Subsequence>();
1218       
[2131]1219        appData.getStopWatch().start("determine locations");
[1855]1220        Map<Subsequence, List<SubsequenceLocation>> allLocations =
1221            getLocations(subsequences, appData);
[2131]1222        appData.getStopWatch().stop("determine locations");
[1855]1223       
1224        for (Subsequence subsequence : subsequences) {
1225            // Console.traceln
1226            //    (Level.FINEST, "searching for interleavings of " + toPlainStr(subsequence));
1227
1228            potentialPredecessors.clear();
1229            potentialSuccessors.clear();
1230           
1231            getInterleavingSubsequences
1232                (subsequence, subsequences, potentialPredecessors, potentialSuccessors);
1233           
1234            if ((potentialPredecessors.size() <= 0) && (potentialSuccessors.size() <= 0)) {
1235                // subsequence is not interleaving with others. Check next one.
1236                continue;
1237            }
1238           
1239            // subsequence is interleaving. First, determine its locations in the user sessions.
1240           
1241            List<Collision> precedingCollisions =
[2131]1242                getPrecedingCollisions(subsequence, potentialPredecessors, allLocations, appData);
[1855]1243           
1244            List<Collision> succeedingCollisions =
[2131]1245                getSucceedingCollisions(subsequence, potentialSuccessors, allLocations, appData);
[1855]1246           
1247            if ((precedingCollisions.size() <= 0) && (succeedingCollisions.size() <= 0)) {
1248                // subsequence is not interleaving with others. Check next one.
1249                continue;
1250            }
1251           
1252            interleavings.add(new InterleavingSubsequence(subsequence, precedingCollisions,
1253                                                          succeedingCollisions));
1254        }
1255       
[2131]1256        appData.getStopWatch().stop("determine interleavings");
[1855]1257        return interleavings;
1258    }
1259
1260    /**
1261     *
1262     */
1263    private void getInterleavingSubsequences(Subsequence       subsequence,
1264                                             Subsequences      allSubsequences,
1265                                             List<Subsequence> potentialPredecessors,
1266                                             List<Subsequence> potentialSuccessors)
1267    {
1268        TaskComparator comparator = identityTaskHandlingStrategy.getTaskComparator();
1269       
1270        for (Subsequence candidate : allSubsequences) {
1271            if (comparator.equals(subsequence.get(subsequence.size() - 1), candidate.get(0))) {
1272                potentialSuccessors.add(candidate);
1273            }
1274           
1275            if (comparator.equals(subsequence.get(0), candidate.get(candidate.size() - 1))) {
1276                potentialPredecessors.add(candidate);
1277            }
1278        }
1279
1280        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1281        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1282        // Console.traceln(Level.FINEST, "  potential successors of " + toPlainStr(subsequence));
1283        // dumpSortedCollectionOfSubsequences("    ", potentialSuccessors);
1284       
1285        // Console.traceln(Level.FINEST, "  potential predecessors of " + toPlainStr(subsequence));
1286        // dumpSortedCollectionOfSubsequences("    ", potentialPredecessors);
1287        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1288        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1289    }
1290
1291    /**
1292     *
1293     */
1294    private Map<Subsequence, List<SubsequenceLocation>> getLocations(Subsequences        subsequences,
1295                                                                     RuleApplicationData appData)
1296    {
1297        Map<Subsequence, List<SubsequenceLocation>> result =
1298            new IdentityHashMap<Subsequence, List<SubsequenceLocation>>();
1299       
1300        // fill the map with empty locations
1301        for (Subsequence subsequence : subsequences) {
1302            result.put(subsequence, new LinkedList<SubsequenceLocation>());
1303        }
1304       
[2131]1305        /*
[1855]1306        LinkedList<LinkedList<Subsequence>> candidates = new LinkedList<LinkedList<Subsequence>>();
1307
1308        for (IUserSession session : appData.getSessions()) {
1309            for (int i = 0; i < session.size(); i++) {
1310                ITask currentTask = session.get(i).getTask();
1311               
1312                // create a list of candidates that may start here
1313                LinkedList<Subsequence> candidatesToStartHere = new LinkedList<Subsequence>();
1314               
1315                for (Subsequence candidate : subsequences) {
1316                    if (candidate.get(0) == currentTask) {
1317                        candidatesToStartHere.add(candidate);
1318                    }
1319                }
1320               
1321                candidates.addFirst(candidatesToStartHere);
1322               
1323                // check if the other candidates continue here.
1324                ListIterator<LinkedList<Subsequence>> candidatesIt = candidates.listIterator(1);
1325                int index = 1;
1326                while (candidatesIt.hasNext()) {
1327                    ListIterator<Subsequence> candidateIt = candidatesIt.next().listIterator();
1328                    while (candidateIt.hasNext()) {
1329                        Subsequence candidate = candidateIt.next();
1330                       
1331                        if (candidate.get(index) == currentTask) {
1332                            if (candidate.size() == (index + 1)) {
1333                                // subsequence was fully matched --> store location
1334                                result.get(candidate).add
1335                                    (new SubsequenceLocation(session, i - candidate.size() + 1));
1336                                candidateIt.remove();
1337                            }
1338                        }
1339                        else if (candidate.get(index) != currentTask) {
1340                            // remove the candidate as it is not located here
1341                            candidateIt.remove();
1342                        }
1343                    }
1344                   
1345                    index++;
1346                }
1347               
1348                // remove potential continuation being empty
1349                while ((candidates.size() > 0) && (candidates.getLast().size() == 0)) {
1350                    candidates.removeLast();
1351                }
1352            }
[2131]1353        }*/
1354       
1355        /*
1356        for (IUserSession session : appData.getSessions()) {
1357            LinkedList<Iterator<ITask>> candidateIterators = new LinkedList<>();
1358            LinkedList<Subsequence> candidates = new LinkedList<>();
1359
1360            for (int i = 0; i < session.size(); i++) {
1361                ITask currentTask = session.get(i).getTask();
1362               
1363                // create a list of candidates that may start here
1364                for (Subsequence candidate : subsequences) {
1365                    if (candidate.get(0) == currentTask) {
1366                        candidates.add(candidate);
1367                        candidateIterators.add(candidate.iterator());
1368                    }
1369                }
1370               
1371                // check which candidates continue/end here.
1372                ListIterator<Iterator<ITask>> candidateItsIt = candidateIterators.listIterator();
1373                ListIterator<Subsequence> candidatesIt = candidates.listIterator();
1374                while (candidateItsIt.hasNext()) {
1375                    Iterator<ITask> candidateIterator = candidateItsIt.next();
1376                    Subsequence candidate = candidatesIt.next();
1377                    if (candidateIterator.hasNext()) {
1378                        ITask expectedTask = candidateIterator.next();
1379                        if (expectedTask != currentTask) {
1380                            // remove the iterator and the candidate from both lists.
1381                            candidatesIt.remove();
1382                            candidateItsIt.remove();
1383                        }
1384                        // else leave iterator as is and continue
1385                    }
1386                    else {
1387                        // the candidate belonging to the iterator fully matched. Hence, store the
1388                        // location and drop the candidate and its iterator
1389                        candidatesIt.remove();
1390                        candidateItsIt.remove();
1391                       
1392                        result.get(candidate).add
1393                            (new SubsequenceLocation(session, i - candidate.size()));
1394                    }
1395                }
1396            }
1397           
1398            // check, which further iterators match with the session end.
1399            ListIterator<Iterator<ITask>> candidateItsIt = candidateIterators.listIterator();
1400            ListIterator<Subsequence> candidatesIt = candidates.listIterator();
1401            while (candidatesIt.hasNext()) {
1402                Iterator<ITask> candidateIterator = candidateItsIt.next();
1403                Subsequence candidate = candidatesIt.next();
1404                if (!candidateIterator.hasNext()) {
1405                    // the candidate belonging to the iterator fully matched. Hence, store the
1406                    // location and drop the candidate and its iterator
1407                    result.get(candidate).add
1408                        (new SubsequenceLocation(session, session.size() - candidate.size()));
1409                }
1410            }
1411        }*/
1412       
1413        for (IUserSession session : appData.getSessions()) {
1414            for (Subsequence candidate : subsequences) {
1415                int index = -1;
1416                do {
1417                    index = getSubListIndex(session, candidate, index + 1);
1418               
1419                    if (index > -1) {
1420                        result.get(candidate).add(new SubsequenceLocation(session, index));
1421                    }
1422                }
1423                while (index > -1);
1424            }
[1855]1425        }
1426       
1427        return result;
1428    }
1429
1430    /**
1431     *
1432     */
[2131]1433    private List<Collision> getPrecedingCollisions
1434        (Subsequence                                 subsequence,
1435         List<Subsequence>                           potentialPredecessors,
1436         Map<Subsequence, List<SubsequenceLocation>> allLocations,
1437         RuleApplicationData                         appData)
[1855]1438    {
1439        List<Collision> precedingCollisions = new LinkedList<Collision>();
1440       
[2131]1441        for (SubsequenceLocation location : allLocations.get(subsequence)) {
[1855]1442            for (Subsequence predecessor : potentialPredecessors) {
[2131]1443                for (SubsequenceLocation predecessorLocation : allLocations.get(predecessor)) {
1444                    if ((location.getSession() == predecessorLocation.getSession()) &&
1445                        (location.getIndex() - predecessor.size() + 1 == predecessorLocation.getIndex()))
1446                    {
[1855]1447                        precedingCollisions.add(new Collision(location, subsequence, predecessor));
1448                    }
1449                }
1450            }
1451        }
1452       
1453        return precedingCollisions;
1454    }
1455
1456    /**
1457     *
1458     */
[2131]1459    private List<Collision> getSucceedingCollisions
1460        (Subsequence                                 subsequence,
1461         List<Subsequence>                           potentialSuccessors,
1462         Map<Subsequence, List<SubsequenceLocation>> allLocations,
1463         RuleApplicationData                         appData)
[1855]1464    {
1465        List<Collision> succeedingCollisions = new LinkedList<Collision>();
1466
[2131]1467        for (SubsequenceLocation location : allLocations.get(subsequence)) {
1468            for (Subsequence successor : potentialSuccessors) {
1469                for (SubsequenceLocation successorLocation : allLocations.get(successor)) {
1470                    if ((location.getSession() == successorLocation.getSession()) &&
1471                        (location.getIndex() + subsequence.size() - 1 == successorLocation.getIndex()))
1472                    {
1473                        succeedingCollisions.add(new Collision(location, subsequence, successor));
1474                    }
[1855]1475                }
1476            }
1477        }
1478       
1479        return succeedingCollisions;
1480    }
1481   
1482    /**
1483     *
1484     */
1485    private List<InterleavingSubsequence> getMostIntensiveInterleavings
1486        (List<InterleavingSubsequence> interleavings)
1487    {
1488        List<InterleavingSubsequence> mostIntensiveInterleavings =
1489            new LinkedList<InterleavingSubsequence>();
1490       
1491        int collisionCounter = 0;
1492       
1493        for (InterleavingSubsequence interleaving : interleavings) {
1494            if (interleaving.getCollisionCounter() > collisionCounter) {
1495                collisionCounter = interleaving.getCollisionCounter();
1496                mostIntensiveInterleavings.clear();
1497            }
1498           
1499            if (interleaving.getCollisionCounter() == collisionCounter) {
1500                mostIntensiveInterleavings.add(interleaving);
1501            }
1502        }
1503       
1504        return mostIntensiveInterleavings;
1505    }
1506
1507    /**
1508     *
1509     */
1510    private List<InterleavingSubsequence> getLeastInterleavingsAsPredecessor
1511        (List<InterleavingSubsequence> interleavings)
1512    {
1513        List<InterleavingSubsequence> leastInterleavingsAsPredecessor =
1514            new LinkedList<InterleavingSubsequence>();
1515           
1516        int collisionCounter = Integer.MAX_VALUE;
1517
1518        for (InterleavingSubsequence interleaving : interleavings) {
1519            if (interleaving.getSuccessorCollisionCounter() < collisionCounter) {
1520                collisionCounter = interleaving.getSuccessorCollisionCounter();
1521                leastInterleavingsAsPredecessor.clear();
1522            }
1523
1524            if (interleaving.getSuccessorCollisionCounter() == collisionCounter) {
1525                leastInterleavingsAsPredecessor.add(interleaving);
1526            }
1527        }
1528
1529        return leastInterleavingsAsPredecessor;
1530    }
1531
1532    /**
1533     *
1534     */
1535    private List<InterleavingSubsequence> getMostInterleavingsAsSuccessor
1536        (List<InterleavingSubsequence> interleavings)
1537    {
1538        List<InterleavingSubsequence> mostInterleavingsAsSuccessor =
1539            new LinkedList<InterleavingSubsequence>();
1540           
1541        int collisionCounter = 0;
1542
1543        for (InterleavingSubsequence interleaving : interleavings) {
1544            if (interleaving.getPredecessorCollisionCounter() > collisionCounter) {
1545                collisionCounter = interleaving.getPredecessorCollisionCounter();
1546                mostInterleavingsAsSuccessor.clear();
1547            }
1548
1549            if (interleaving.getPredecessorCollisionCounter() == collisionCounter) {
1550                mostInterleavingsAsSuccessor.add(interleaving);
1551            }
1552        }
1553
1554        return mostInterleavingsAsSuccessor;
1555    }
1556
1557    /**
1558     *
1559     */
1560    private List<InterleavingSubsequence> removeCollisionsOfOtherSubsequences
1561        (List<InterleavingSubsequence> interleavings)
1562    {
1563        List<InterleavingSubsequence> subsequencesWithoutOtherCollisions =
1564            new LinkedList<InterleavingSubsequence>();
1565       
1566        for (InterleavingSubsequence interleaving : interleavings) {
1567            List<Collision> reducedPredecessorCollisions = new LinkedList<>();
1568           
1569            for (Collision collision : interleaving.getPredecessorCollisions()) {
1570                for (InterleavingSubsequence otherInterleaving : interleavings) {
1571                    if (otherInterleaving.getSubsequence() == collision.getCollidingWith()) {
1572                        reducedPredecessorCollisions.add(collision);
1573                        break;
1574                    }
1575                }
1576            }
1577           
1578            List<Collision> reducedSuccessorCollisions = new LinkedList<>();
1579           
1580            for (Collision collision : interleaving.getSuccessorCollisions()) {
1581                for (InterleavingSubsequence otherInterleaving : interleavings) {
1582                    if (otherInterleaving.getSubsequence() == collision.getCollidingWith()) {
1583                        reducedSuccessorCollisions.add(collision);
1584                        break;
1585                    }
1586                }
1587            }
1588           
1589            subsequencesWithoutOtherCollisions.add
1590                (new InterleavingSubsequence(interleaving.getSubsequence(),
1591                                             reducedPredecessorCollisions,
1592                                             reducedSuccessorCollisions));
1593        }
1594       
1595        return subsequencesWithoutOtherCollisions;
1596    }
1597
1598    /**
[1401]1599     * @param appData the rule application data combining all data used for applying this rule
[1127]1600     */
1601    private void replaceSequencesOccurringMostOften(RuleApplicationData appData) {
[1146]1602        appData.detectedAndReplacedTasks(false);
[1127]1603
[1855]1604        if ((appData.getLastFoundSubsequences().size() > 0) &&
1605            (appData.getLastFoundSubsequences().getOccurrenceCount() > 1))
[1127]1606        {
[1285]1607            Console.traceln(Level.FINER, "replacing tasks occurrences");
[1127]1608
[1855]1609            for (Subsequence subsequence : appData.getLastFoundSubsequences()) {
[1146]1610                ISequence sequence = taskFactory.createNewSequence();
1611               
[1855]1612                Console.traceln(Level.FINEST, "replacing " + sequence.getId() + ": " + subsequence);
[1127]1613
[1855]1614                List<ISequenceInstance> sequenceInstances = replaceSubsequenceOccurrences
1615                    (subsequence, appData.getSessions(), sequence);
[1119]1616               
[1855]1617                harmonizeSequenceInstancesModel(sequence, sequenceInstances, subsequence.size());
[1285]1618                appData.detectedAndReplacedTasks
1619                    (appData.detectedAndReplacedTasks() || (sequenceInstances.size() > 0));
[1127]1620
[1855]1621                if (sequenceInstances.size() < appData.getLastFoundSubsequences().getOccurrenceCount()) {
[1285]1622                    Console.traceln(Level.FINE, sequence.getId() + ": replaced task only " +
1623                                    sequenceInstances.size() + " times instead of expected " +
[1855]1624                                    appData.getLastFoundSubsequences().getOccurrenceCount());
[1127]1625                }
1626            }
1627        }
1628    }
1629
1630    /**
[1146]1631     *
1632     */
[1294]1633    private void harmonizeSequenceInstancesModel(ISequence               sequence,
1634                                                 List<ISequenceInstance> sequenceInstances,
1635                                                 int                     sequenceLength)
[1146]1636    {
[1855]1637        TaskComparator comparator = preparationTaskHandlingStrategy.getTaskComparator();
[1146]1638       
1639        // ensure for each subtask that lexically different variants are preserved
1640        for (int subTaskIndex = 0; subTaskIndex < sequenceLength; subTaskIndex++) {
1641            List<ITask> subTaskVariants = new LinkedList<ITask>();
1642           
[1294]1643            for (ISequenceInstance sequenceInstance : sequenceInstances) {
[1146]1644                ITask candidate = sequenceInstance.get(subTaskIndex).getTask();
1645               
1646                boolean found = false;
1647               
1648                for (int i = 0; i < subTaskVariants.size(); i++) {
[1285]1649                    if (comparator.areLexicallyEqual(subTaskVariants.get(i), candidate)) {
[1146]1650                        taskBuilder.setTask
1651                            (sequenceInstance.get(subTaskIndex), subTaskVariants.get(i));
1652                       
1653                        found = true;
1654                        break;
1655                    }
1656                }
1657               
1658                if (!found) {
1659                    subTaskVariants.add(candidate);
1660                }
1661            }
1662           
1663            // if there are more than one lexically different variant of the sub task at
1664            // the considered position, adapt the sequence model at that position to have
1665            // a selection of the different variants. In this case also adapt the
1666            // generated sequence instances to correctly contain selection instances. If
1667            // there is only one variant of sub tasks at the given position, simply set
1668            // this variant as the sub task of the selection. In this case, the instances
1669            // can be preserved as is
1670            if (subTaskVariants.size() > 1) {
1671                ISelection selection = taskFactory.createNewSelection();
1672               
1673                for (ITask variant : subTaskVariants) {
1674                    taskBuilder.addChild(selection, variant);
1675                }
1676               
1677                taskBuilder.addChild(sequence, selection);
1678               
[1294]1679                for (ISequenceInstance instance : sequenceInstances) {
1680                    ISelectionInstance selectionInstance =
[1146]1681                        taskFactory.createNewTaskInstance(selection);
[1294]1682                    taskBuilder.setChild(selectionInstance, instance.get(subTaskIndex));
[1146]1683                    taskBuilder.setTaskInstance(instance, subTaskIndex, selectionInstance);
1684                }
1685            }
1686            else if (subTaskVariants.size() == 1) {
1687                taskBuilder.addChild(sequence, subTaskVariants.get(0));
1688            }
1689        }
1690    }
1691
1692    /**
[1127]1693     * @param tree
1694     */
[1855]1695    private List<ISequenceInstance> replaceSubsequenceOccurrences(Subsequence         subsequence,
1696                                                                  List<IUserSession>  sessions,
1697                                                                  ISequence           temporalTaskModel)
[1127]1698    {
[1294]1699        List<ISequenceInstance> sequenceInstances = new LinkedList<ISequenceInstance>();
[1146]1700       
1701        for (IUserSession session : sessions) {
[1127]1702            int index = -1;
[1855]1703           
[1127]1704            do {
[1855]1705                index = getSubListIndex(session, subsequence, ++index);
[1127]1706
1707                if (index > -1) {
[1855]1708                    // subsequence is found --> perform replacement
[1146]1709                    sequenceInstances.add
1710                        (RuleUtils.createNewSubSequenceInRange
[1855]1711                             (session, index, index + subsequence.size() - 1, temporalTaskModel,
[1146]1712                              taskFactory, taskBuilder));
[1107]1713                }
1714            }
[1127]1715            while (index > -1);
[1107]1716        }
[1146]1717       
1718        return sequenceInstances;
[1107]1719    }
1720
1721    /**
[1146]1722     * @param trie
1723     * @param object
[1127]1724     * @return
1725     */
[1146]1726    private int getSubListIndex(ITaskInstanceList   list,
[1855]1727                                Subsequence         subsequence,
[1146]1728                                int                 startIndex)
[1127]1729    {
[1146]1730        boolean matchFound;
1731        int result = -1;
[1127]1732       
[1855]1733        for (int i = startIndex; i <= list.size() - subsequence.size(); i++) {
[1146]1734            matchFound = true;
[1127]1735           
[1855]1736            for (int j = 0; j < subsequence.size(); j++) {
[1285]1737                // we prepared the task instances to refer to unique tasks, if they are treated
1738                // as equal. Therefore, we just compare the identity of the tasks of the task
1739                // instances
[1855]1740                if (list.get(i + j).getTask() != subsequence.get(j)) {
[1146]1741                    matchFound = false;
1742                    break;
1743                }
[1127]1744            }
1745           
[1146]1746            if (matchFound) {
1747                result = i;
1748                break;
1749            }
[1127]1750        }
[1107]1751       
[1146]1752        return result;
[1107]1753    }
1754
1755    /**
1756     * @param trie
1757     * @param object
1758     * @return
1759     */
[1855]1760    private int getSubListIndex(Subsequence longerSubsequence,
1761                                Subsequence shorterSubsequence,
1762                                int         startIndex)
[1107]1763    {
1764        boolean matchFound;
1765        int result = -1;
1766       
[1855]1767        for (int i = startIndex; i <= longerSubsequence.size() - shorterSubsequence.size(); i++) {
[1107]1768            matchFound = true;
1769           
[1855]1770            for (int j = 0; j < shorterSubsequence.size(); j++) {
[1285]1771                // we prepared the task instances to refer to unique tasks, if they are treated
1772                // as equal. Therefore, we just compare the identity of the tasks of the task
1773                // instances
[1855]1774                if (longerSubsequence.get(i + j) != shorterSubsequence.get(j)) {
[1107]1775                    matchFound = false;
1776                    break;
1777                }
1778            }
1779           
1780            if (matchFound) {
1781                result = i;
1782                break;
1783            }
1784        }
1785       
1786        return result;
1787    }
[1855]1788
1789//  /**
1790//   *
1791//   */
1792//  private void dumpSorted(List<InterleavingSubsequence> interleavings) {
1793//      dumpSorted(interleavings, Level.FINEST);
1794//  }
1795
1796    /**
1797     *
1798     */
1799    private void dumpSorted(List<InterleavingSubsequence> interleavings, Level level) {
1800        List<String> tmpList = new LinkedList<String>();
1801
1802        for (InterleavingSubsequence interleaving : interleavings) {
1803            String taskStr = toPlainStr(interleaving);
1804            int index;
1805            for (index = 0; index < tmpList.size(); index++) {
1806                if (taskStr.compareTo(tmpList.get(index)) > 0) {
1807                    break;
1808                }
1809            }
1810
1811            tmpList.add(index, taskStr);
1812        }
1813
1814        for (String task : tmpList) {
1815            Console.traceln(level, task);
1816        }
1817    }
1818
1819//    /**
1820//     *
1821//     */
1822//    private void dumpSortedCollectionOfSubsequences(String                  prefix,
1823//                                                    Collection<Subsequence> subsequences)
1824//    {
1825//        List<String> tmpList = new LinkedList<String>();
1826//
1827//        for (Subsequence subsequence : subsequences) {
1828//            String taskStr = toPlainStr(subsequence);
1829//            int index;
1830//            for (index = 0; index < tmpList.size(); index++) {
1831//                if (taskStr.compareTo(tmpList.get(index)) > 0) {
1832//                    break;
1833//                }
1834//            }
1835//
1836//            tmpList.add(index, taskStr);
1837//        }
1838//
1839//        for (String task : tmpList) {
1840//            Console.traceln(Level.FINEST, prefix + " " + task);
1841//        }
1842//    }
1843
1844    /**
1845     *
1846     */
1847    private String toPlainStr(InterleavingSubsequence interleaving) {
1848        StringBuffer result = new StringBuffer();
1849        result.append("interleavings of ");
1850        result.append(toPlainStr(interleaving.getSubsequence()));
1851        result.append("\n  predecessor collisions:\n");
1852
1853        for (Collision collision : interleaving.getPredecessorCollisions()) {
1854            result.append("    ");
1855            result.append(toPlainStr(collision.getCollidingWith()));
1856            result.append(" (");
1857            result.append(collision.getLocation());
1858            result.append(")\n");
1859        }
1860
1861        result.append("  successor collisions:\n");
1862
1863        for (Collision collision : interleaving.getSuccessorCollisions()) {
1864            result.append("    ");
1865            result.append(toPlainStr(collision.getCollidingWith()));
1866            result.append(" (");
1867            result.append(collision.getLocation());
1868            result.append(")\n");
1869        }
1870
1871        return result.toString();
1872    }
1873
1874    /**
1875     *
1876     */
1877    private String toPlainStr(Subsequence subsequence) {
1878        StringBuffer result = new StringBuffer();
1879        result.append(subsequence.size());
1880        result.append(": ");
1881        for (ITask task : subsequence) {
1882            DebuggingHelper.toPlainStr(task, result);
1883            result.append(" --> ");
1884        }
1885
1886        return result.toString();
1887    }
[1107]1888   
[1855]1889   
[1119]1890    /**
1891     * @author Patrick Harms
1892     */
[1855]1893    private class MaxCountAndLongestSubsequencesFinder implements TrieProcessor<ITask> {
[1119]1894       
1895        /**
1896         *
1897         */
1898        private int currentCount;
1899       
1900        /**
1901         *
1902         */
[1855]1903        private LinkedList<Subsequence> foundSubsequences = new LinkedList<Subsequence>();
[1119]1904
1905        /**
1906         *
1907         */
[1855]1908        public MaxCountAndLongestSubsequencesFinder() {
[1119]1909            super();
1910            this.currentCount = 0;
1911        }
1912
1913        /* (non-Javadoc)
1914         * @see de.ugoe.cs.autoquest.usageprofiles.TrieProcessor#process(java.util.List, int)
1915         */
1916        @Override
[1855]1917        public TrieProcessor.Result process(List<ITask> foundTask, int count) {
[1146]1918            if (foundTask.size() < 2) {
1919                // ignore single tasks
[1119]1920                return TrieProcessor.Result.CONTINUE;
1921            }
1922           
1923            if (count < 2) {
1924                // ignore singular occurrences
1925                return TrieProcessor.Result.SKIP_NODE;
1926            }
1927
1928            if (this.currentCount > count) {
[1146]1929                // ignore this children of this task, as they may have only smaller counts than
[1127]1930                // the already found tasks
[1119]1931                return TrieProcessor.Result.SKIP_NODE;
1932            }
1933           
1934            if (this.currentCount < count) {
[1127]1935                // the provided task occurs more often that all tasks found so far.
1936                // clear all found tasks and use the new count as the one searched for
[1855]1937                foundSubsequences.clear();
[1119]1938                this.currentCount = count;
1939            }
1940           
1941            if (this.currentCount == count) {
[2131]1942                /*
1943                // this is a potential performance improvement:
1944                // the task is of interest. Ensure that only the longest remain
1945                if ((foundSubsequences.size() > 0) &&
1946                    (foundSubsequences.get(0).size() < foundTask.size()))
1947                {
1948                    foundSubsequences.clear();
1949                }
1950               
1951                foundSubsequences.add(new Subsequence(currentCount, foundTask));*/
1952               
[1127]1953                // the task is of interest. Sort it into the other found tasks so that
[1119]1954                // the longest come first
1955                boolean added = false;
[1855]1956                for (int i = 0; i < foundSubsequences.size(); i++) {
[2131]1957                    if (foundSubsequences.get(i).size() <= foundTask.size()) {
[1855]1958                        foundSubsequences.add(i, new Subsequence(currentCount, foundTask));
[1119]1959                        added = true;
1960                        break;
1961                    }
1962                }
1963               
1964                if (!added) {
[1855]1965                    foundSubsequences.add(new Subsequence(currentCount, foundTask));
[1119]1966                }
1967            }
1968           
1969            return TrieProcessor.Result.CONTINUE;
1970        }
1971
1972        /**
[1133]1973         *  @return
[1119]1974         */
[1855]1975        private Subsequences getFoundSubsequences() {
[1119]1976            removePermutationsOfShorterTasks();
[1855]1977            return new Subsequences(currentCount, foundSubsequences);
[1119]1978        }
1979
1980        /**
1981         *
1982         */
1983        private void removePermutationsOfShorterTasks() {
1984            // now iterate the sorted list and for each task remove all other tasks that are shorter
1985            // (i.e. come later in the sorted list) and that represent a subpart of the task
[1855]1986           
[2131]1987            boolean removeFirst;
1988            ListIterator<Subsequence> iterator1 = foundSubsequences.listIterator();
[1855]1989           
[2131]1990            while (iterator1.hasNext()) {
1991                removeFirst = false;
1992                boolean removeSecond;
1993                Subsequence longTask = iterator1.next();
[1855]1994               
[2131]1995                ListIterator<Subsequence> iterator2 =
1996                    foundSubsequences.listIterator(iterator1.nextIndex());
1997               
1998                while (iterator2.hasNext()) {
1999                    removeSecond = false;
2000                    Subsequence shortTask = iterator2.next();
[1855]2001                   
[2131]2002                    if (shortTask.size() < longTask.size()) {
[1119]2003                        // found a task that is a potential subtask. Check for this and remove the
2004                        // subtask if needed
2005                       
[1855]2006                        int index = getSubListIndex(longTask, shortTask, 0);
2007                        if (index > -1) {
2008                            if (index == 0) {
2009                                // check if the short task is included in the long task partially
2010                                // a second time. If so, prefer the short task
2011                                boolean secondInclude = true;
2012                                for (int pos = shortTask.size(); pos < longTask.size(); pos++) {
2013                                    // we prepared the task instances to refer to unique tasks, if
2014                                    // they are treated as equal. Therefore, we just compare the
2015                                    // identity of the tasks of the task instances
2016                                    if (longTask.get(pos) != shortTask.get(pos % shortTask.size()))
2017                                    {
2018                                        secondInclude = false;
2019                                        break;
2020                                    }
2021                                }
2022                               
2023                                if (secondInclude) {
2024                                    // delete the long task
2025                                    // Console.traceln(Level.FINEST, "1: short task is contained several times in longer one --> removing it");
2026                                    // Console.traceln(Level.FINEST, "short: " + toPlainStr(shortTask));
2027                                    // Console.traceln(Level.FINEST, "long: " + toPlainStr(longTask));
[2131]2028                                    removeFirst = true;
[1855]2029                                }
2030                                else {
2031                                    // Console.traceln(Level.FINEST, "2: short task is a part of a longer one --> removing it");
2032                                    // Console.traceln(Level.FINEST, "short: " + toPlainStr(shortTask));
2033                                    // Console.traceln(Level.FINEST, "long: " + toPlainStr(longTask));
[2131]2034                                    removeSecond = true;
[1855]2035                                }
2036                            }
2037                            else {
2038                                // Console.traceln(Level.FINEST, "3: short task is a part of a longer one --> removing it");
2039                                // Console.traceln(Level.FINEST, "short: " + toPlainStr(shortTask));
2040                                // Console.traceln(Level.FINEST, "long: " + toPlainStr(longTask));
[2131]2041                                removeSecond = true;
[1855]2042                            }
[1119]2043                        }
2044                    }
[1855]2045                   
[2131]2046                    if (removeSecond) {
2047                        // unfortunately, this invalidates the first iterator. So recreate it after
2048                        // the removal. As the removed element will be after the current position of
2049                        // the first iterator, it is sufficient to remember its location
2050                        int indexOf1 = iterator1.nextIndex() - 1;
2051                        iterator2.remove();
2052                        iterator1 = foundSubsequences.listIterator(indexOf1);
2053                        iterator1.next();
[1855]2054                    }
[1119]2055                }
[1855]2056               
[2131]2057                if (removeFirst) {
2058                    iterator1.remove();
[1855]2059                }
[1119]2060            }
2061        }
2062
2063    }
[1855]2064
[1256]2065//    /**
2066//     * @author Patrick Harms
2067//     */
2068//    private class TrieStatisticsDumper implements TrieProcessor<ITaskInstance> {
2069//       
2070//        /**
2071//         *
2072//         */
2073//        private Map<Integer, Integer> counters = new HashMap<Integer, Integer>();
2074//       
2075//        /* (non-Javadoc)
2076//         * @see de.ugoe.cs.autoquest.usageprofiles.TrieProcessor#process(java.util.List, int)
2077//         */
2078//        @Override
2079//        public TrieProcessor.Result process(List<ITaskInstance> subsequence, int count) {
2080//            if (subsequence.size() == 1) {
2081//                Integer value = counters.get(count);
2082//               
2083//                if (value == null) {
2084//                    value = 0;
2085//                }
2086//               
2087//                counters.put(count, value + 1);
2088//               
2089//                return TrieProcessor.Result.CONTINUE;
2090//            }
2091//            else {
2092//                // ignore singular occurrences
2093//                return TrieProcessor.Result.SKIP_NODE;
2094//            }
2095//        }
2096//
2097//        /**
2098//         *  @return
2099//         */
2100//        public void dumpCounters() {
2101//            int dumpedCounters = 0;
2102//           
2103//            int count = 1;
2104//            while (dumpedCounters < counters.size()) {
2105//                Integer value = counters.get(count++);
2106//                if (value != null) {
2107//                    System.out.println(value + " symbols occurred " + count + " times");
2108//                    dumpedCounters++;
2109//                }
2110//            }
2111//        }
2112//
2113//    }
2114   
[1119]2115    /**
2116     *
2117     */
[1133]2118    private static class RuleApplicationData {
[1119]2119       
2120        /**
2121         *
2122         */
[1146]2123        private List<IUserSession> sessions;
[1119]2124       
2125        /**
2126         *
2127         */
[1256]2128        private TaskInstanceTrie lastTrie;
[1119]2129       
2130        /**
[1855]2131         * default and minimum trained subsequence length is 3
[1119]2132         */
[1855]2133        private int trainedSubsequenceLength = 3;
[1119]2134       
2135        /**
2136         *
2137         */
[1855]2138        private Subsequences lastFoundSubsequences = new Subsequences(Integer.MAX_VALUE, null);
[1119]2139       
2140        /**
[1127]2141         *
[1119]2142         */
[1146]2143        private boolean detectedAndReplacedTasks;
[1119]2144       
2145        /**
2146         *
2147         */
[1127]2148        private RuleApplicationResult result = new RuleApplicationResult();
2149       
2150        /**
2151         *
2152         */
[1119]2153        private StopWatch stopWatch = new StopWatch();
2154       
2155        /**
2156         *
2157         */
[1146]2158        private RuleApplicationData(List<IUserSession> sessions) {
[1127]2159            this.sessions = sessions;
[1119]2160        }
2161
2162        /**
[1127]2163         * @return the tree
[1119]2164         */
[1146]2165        private List<IUserSession> getSessions() {
[1127]2166            return sessions;
[1119]2167        }
2168
2169        /**
[1127]2170         * @param lastTrie the lastTrie to set
[1119]2171         */
[1256]2172        private void setLastTrie(TaskInstanceTrie lastTrie) {
[1127]2173            this.lastTrie = lastTrie;
[1119]2174        }
2175
2176        /**
2177         * @return the lastTrie
2178         */
[1256]2179        private TaskInstanceTrie getLastTrie() {
[1119]2180            return lastTrie;
2181        }
2182
2183        /**
[1855]2184         * @param trainedSubsequenceLength the trainedSubsequenceLength to set
[1127]2185         */
[1855]2186        private void setTrainedSubsequenceLength(int trainedSubsequenceLength) {
2187            this.trainedSubsequenceLength = trainedSubsequenceLength;
[1127]2188        }
2189
2190        /**
[1855]2191         * @return the trainedSubsequenceLength
[1119]2192         */
[1855]2193        private int getTrainedSubsequenceLength() {
2194            return trainedSubsequenceLength;
[1119]2195        }
2196
2197        /**
[1127]2198         * @param lastFoundSequences the lastFoundSequences to set
[1119]2199         */
[1855]2200        private void setLastFoundSubsequences(Subsequences lastFoundSequences) {
2201            this.lastFoundSubsequences = lastFoundSequences;
[1119]2202        }
2203
2204        /**
[1127]2205         * @return the lastFoundSequences
[1119]2206         */
[1855]2207        private Subsequences getLastFoundSubsequences() {
2208            return lastFoundSubsequences;
[1119]2209        }
2210
2211        /**
2212         *
2213         */
[1146]2214        private void detectedAndReplacedTasks(boolean detectedAndReplacedTasks) {
2215            this.detectedAndReplacedTasks = detectedAndReplacedTasks;
[1119]2216        }
2217
2218        /**
2219         *
2220         */
[1146]2221        private boolean detectedAndReplacedTasks() {
2222            return detectedAndReplacedTasks;
[1119]2223        }
[1127]2224       
2225        /**
2226         * @return the result
2227         */
2228        private RuleApplicationResult getResult() {
2229            return result;
2230        }
2231
2232        /**
2233         * @return the stopWatch
2234         */
2235        private StopWatch getStopWatch() {
2236            return stopWatch;
2237        }
2238
[1119]2239    }
2240   
[1855]2241    /**
2242     * @author Patrick Harms
2243     */
2244    private static class Subsequence implements Iterable<ITask> {
2245       
2246        /**
2247         *
2248         */
2249        private int occurrenceCount;
[1119]2250
[1855]2251        /**
2252         *
2253         */
2254        private List<ITask> subsequence;
2255       
2256        /**
2257         * @param occurrenceCount
2258         * @param subsequences
2259         */
2260        private Subsequence(int occurrenceCount, List<ITask> subsequence) {
2261            super();
2262            this.occurrenceCount = occurrenceCount;
2263            this.subsequence = new ArrayList<ITask>(subsequence);
2264        }
2265
2266        /**
2267         * @return
2268         */
2269        private int size() {
2270            return this.subsequence.size();
2271        }
2272
2273        /**
2274         * @return
2275         */
2276        private ITask get(int index) {
2277            return this.subsequence.get(index);
2278        }
2279
2280        /* (non-Javadoc)
2281         * @see java.lang.Iterable#iterator()
2282         */
2283        @Override
2284        public Iterator<ITask> iterator() {
2285            return this.subsequence.iterator();
2286        }
2287
2288        /* (non-Javadoc)
2289         * @see java.lang.Object#toString()
2290         */
2291        @Override
2292        public String toString() {
2293            return subsequence.toString() + " (" + occurrenceCount + ")";
2294        }
2295    }
2296
[1119]2297    /**
2298     * @author Patrick Harms
2299     */
[1855]2300    private static class Subsequences implements Iterable<Subsequence> {
[1119]2301       
2302        /**
2303         *
2304         */
2305        private int occurrenceCount;
2306       
2307        /**
2308         *
2309         */
[1855]2310        private LinkedList<Subsequence> subsequences;
[1119]2311
2312        /**
2313         * @param occurrenceCount
2314         * @param sequences
2315         */
[1855]2316        private Subsequences(int occurrenceCount, LinkedList<Subsequence> subsequences) {
[1119]2317            super();
2318            this.occurrenceCount = occurrenceCount;
[1855]2319            this.subsequences = subsequences;
[1119]2320        }
2321
2322        /**
[1855]2323         *
2324         */
2325        private void remove(Subsequence subsequence) {
2326            ListIterator<Subsequence> it = subsequences.listIterator();
2327           
2328            while (it.hasNext()) {
2329                // reference comparison is sufficient
2330                if (it.next() == subsequence) {
2331                    it.remove();
2332                }
2333            }
2334        }
2335
2336        /**
[1119]2337         * @return
2338         */
2339        private int getOccurrenceCount() {
2340            return occurrenceCount;
2341        }
2342
2343        /**
2344         * @return
2345         */
2346        private int size() {
[1855]2347            return this.subsequences.size();
[1119]2348        }
2349
2350        /* (non-Javadoc)
2351         * @see java.lang.Iterable#iterator()
2352         */
2353        @Override
[1855]2354        public Iterator<Subsequence> iterator() {
2355            return this.subsequences.iterator();
[1119]2356        }
2357
[1146]2358        /* (non-Javadoc)
[1256]2359         * @see java.lang.Object#toString()
[1146]2360         */
2361        @Override
[1256]2362        public String toString() {
2363            StringBuffer result = new StringBuffer();
[1855]2364            result.append(" subsequences occuring ");
[1256]2365            result.append(this.occurrenceCount);
[1855]2366            result.append(" times:\n");
[1146]2367           
[1855]2368            for (Subsequence subsequence : subsequences) {
2369                result.append(subsequence);
[1256]2370                result.append("\n");
[1146]2371            }
2372           
[1256]2373            return result.toString();
[1146]2374        }
[1855]2375    }
2376   
2377    /**
2378     * @author Patrick Harms
2379     */
2380    private static class InterleavingSubsequence {
2381       
2382        /** */
2383        private Subsequence subsequence;
2384       
2385        /** */
2386        private List<Collision> successorCollisions;
2387       
2388        /** */
2389        private List<Collision> predecessorCollisions;
[1146]2390
[1855]2391        /**
2392         *
2393         */
2394        private InterleavingSubsequence(Subsequence     subsequence,
2395                                        List<Collision> predecessorCollisions,
2396                                        List<Collision> successorCollisions)
2397        {
2398            super();
2399            this.subsequence = subsequence;
2400            this.predecessorCollisions = predecessorCollisions;
2401            this.successorCollisions = successorCollisions;
2402        }
2403
2404        /**
2405         *
2406         */
2407        private int getCollisionCounter() {
2408            return getSuccessorCollisionCounter() + getPredecessorCollisionCounter();
2409        }
2410
2411        /**
2412         *
2413         */
2414        private int getSuccessorCollisionCounter() {
2415            return successorCollisions.size();
2416        }
2417
2418        /**
2419         *
2420         */
2421        private List<Collision> getSuccessorCollisions() {
2422            return successorCollisions;
2423        }
2424
2425        /**
2426         *
2427         */
2428        private int getPredecessorCollisionCounter() {
2429            return predecessorCollisions.size();
2430        }
2431
2432        /**
2433         *
2434         */
2435        private List<Collision> getPredecessorCollisions() {
2436            return predecessorCollisions;
2437        }
2438
2439        /**
2440         *
2441         */
2442        private Subsequence getSubsequence() {
2443            return subsequence;
2444        }
2445       
2446        /* (non-Javadoc)
2447         * @see java.lang.Object#toString()
2448         */
2449        @Override
2450        public String toString() {
2451            return "interleaving subsequence " + subsequence.toString() + " (" +
2452                successorCollisions.size() + " successor, " + predecessorCollisions.size() +
2453                " predecessor)";
2454        }
[1146]2455    }
[1855]2456       
2457    /**
2458     * @author Patrick Harms
2459     */
2460    private static class SubsequenceLocation {
2461       
2462        /** */
2463        private IUserSession session;
2464       
2465        /** */
2466        private int index;
2467       
2468        /**
2469         *
2470         */
2471        private SubsequenceLocation(IUserSession session, int index) {
2472            this.session = session;
2473            this.index = index;
2474        }
2475
2476        /**
2477         * @return the session
2478         */
2479        private IUserSession getSession() {
2480            return session;
2481        }
2482
2483        /**
2484         * @return the index
2485         */
2486        private int getIndex() {
2487            return index;
2488        }
2489       
2490        /* (non-Javadoc)
2491         * @see java.lang.Object#toString()
2492         */
2493        @Override
2494        public String toString() {
2495            return "location (" + session + ", " + index + ")";
2496        }
2497    }
[1337]2498   
[1855]2499    /**
2500     * @author Patrick Harms
2501     */
2502    private static class Collision {
2503       
2504        /** */
2505        private SubsequenceLocation location;
2506       
2507        /** */
2508        private Subsequence subsequence;
2509       
2510        /** */
2511        private Subsequence collidingWith;
2512       
2513        /**
2514         *
2515         */
2516        private Collision(SubsequenceLocation location,
2517                          Subsequence         subsequence,
2518                          Subsequence         collidingWith)
2519        {
2520            this.location = location;
2521            this.subsequence = subsequence;
2522            this.collidingWith = collidingWith;
2523        }
2524
2525        /**
2526         * @return the collidingWith
2527         */
2528        private Subsequence getCollidingWith() {
2529            return collidingWith;
2530        }
2531
2532        /**
2533         * @return the location
2534         */
2535        private SubsequenceLocation getLocation() {
2536            return location;
2537        }
2538
2539        /**
2540         * @return the subsequence
2541         */
[1955]2542//        private Subsequence getSubsequence() {
2543//            return subsequence;
2544//        }
[1855]2545
2546        /* (non-Javadoc)
2547         * @see java.lang.Object#toString()
2548         */
2549        @Override
2550        public String toString() {
2551            return "collision (" + location + " ," + subsequence + ", " + collidingWith + ")";
2552        }
2553    }
2554   
[1337]2555    // methods for internal testing
2556//    private void checkMatchingOfSessions(List<List<Event>>  flattenedSessions,
2557//                                         List<IUserSession> sessions,
2558//                                         String             when)
2559//    {
2560//        List<List<Event>> currentFlattenedSessions = flattenSessions(sessions);
2561//        if (flattenedSessions.size() != currentFlattenedSessions.size()) {
2562//            System.out.println("################## number of sessions changed after " + when);
2563//        }
2564//        else {
2565//            for (int i = 0; i < flattenedSessions.size(); i++) {
2566//                List<Event> expected = flattenedSessions.get(i);
2567//                List<Event> current = currentFlattenedSessions.get(i);
2568//           
2569//                if (expected.size() != current.size()) {
2570//                    System.out.println
2571//                        ("################## length of session " + i + " changed after " + when);
2572//                }
2573//                else {
2574//                    for (int j = 0; j < expected.size(); j++) {
2575//                        if (!expected.get(j).equals(current.get(j))) {
2576//                            System.out.println("################## event " + j + " of session " +
2577//                                               i + " changed after " + when);
2578//                        }
2579//                    }
2580//                }
2581//            }     
2582//        }
2583//    }
2584//
2585//    private List<List<Event>> flattenSessions(List<IUserSession> sessions) {
2586//        List<List<Event>> flattenedSessions = new ArrayList<List<Event>>();
2587//        for (IUserSession session : sessions) {
2588//            List<Event> flattenedUserSession = new ArrayList<Event>();
2589//            flatten(session, flattenedUserSession);
2590//            flattenedSessions.add(flattenedUserSession);
2591//        }
2592//
2593//        return flattenedSessions;
2594//    }
2595//
2596//    private void flatten(IUserSession iUserSession, List<Event> flattenedUserSession) {
2597//        for (ITaskInstance instance : iUserSession) {
2598//            flatten(instance, flattenedUserSession);
2599//        }
2600//    }
2601//
2602//    private void flatten(ITaskInstance instance, List<Event> flattenedUserSession) {
2603//        if (instance instanceof ITaskInstanceList) {
2604//            for (ITaskInstance child : (ITaskInstanceList) instance) {
2605//                flatten(child, flattenedUserSession);
2606//            }
2607//        }
2608//        else if (instance instanceof ISelectionInstance) {
2609//            flatten(((ISelectionInstance) instance).getChild(), flattenedUserSession);
2610//        }
2611//        else if (instance instanceof IOptionalInstance) {
2612//            flatten(((IOptionalInstance) instance).getChild(), flattenedUserSession);
2613//        }
2614//        else if (instance instanceof IEventTaskInstance) {
2615//            flattenedUserSession.add(((IEventTaskInstance) instance).getEvent());
2616//        }
2617//    }
2618
[1107]2619}
Note: See TracBrowser for help on using the repository browser.