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

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