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

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