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

Last change on this file since 1955 was 1955, checked in by pharms, 9 years ago
  • eased the selection process of which sublist to handle first
File size: 99.2 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                InterleavingSubsequence interleavingWithMinSum = null;
1031                InterleavingSubsequence interleavingWithMinIndex = null;
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                        interleavingWithMinSum = interleaving;
1051                        overallMinSumOfCollisionIndexes = sumOfCollisionIndexes;
1052                    }
1053                    else if (overallMinSumOfCollisionIndexes == sumOfCollisionIndexes) {
1054                        // cannot decide between already found and new one
1055                        interleavingWithMinSum = null;
1056                    }
1057                   
1058                    if (overallMinimalCollisionIndex > minimalCollisionIndex) {
1059                        interleavingWithMinIndex = interleaving;
1060                        overallMinimalCollisionIndex = minimalCollisionIndex;
1061                    }
1062                    else if (overallMinimalCollisionIndex == minimalCollisionIndex) {
1063                        // cannot decide between already found and new one
1064                        interleavingWithMinIndex = null;
1065                    }
1066                }
1067               
1068                if (interleavingWithMinSum != null) {
1069                    for (InterleavingSubsequence interleaving : interleavings) {
1070                        if (interleaving != interleavingWithMinSum) {
1071                            appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
1072                        }
1073                    }
1074                   
1075                    continue;
1076                }
1077               
1078                if (interleavingWithMinIndex != null) {
1079                    for (InterleavingSubsequence interleaving : interleavings) {
1080                        if (interleaving != interleavingWithMinIndex) {
1081                            appData.getLastFoundSubsequences().remove(interleaving.getSubsequence());
1082                        }
1083                    }
1084                   
1085                    continue;
1086                }
1087               
1088                // At this point, we can not decide anymore. But it should also not
1089                // happen. Even if two subsequences ab and ba one of them should be
1090                // more often a successor or predecessor than the other if they
1091                // directly follow each other. If not, it can not be decided, which of
1092                // them to replace first. Perhaps the one occurring more often as
1093                // the first in a session than the other. But this can be implemented
1094                // later.
1095                dumpSorted(interleavings, Level.SEVERE);
1096               
1097                throw new RuntimeException
1098                    ("can not decide which detected subsequence to replace first.");
1099            }
1100        }
1101        while (interleavings.size() > 0);
1102
1103        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1104        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1105        // dumpSortedCollectionOfSubsequences
1106        //    ("to replace", appData.getLastFoundSubsequences().subsequences);
1107        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1108        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1109    }
1110
1111    /**
1112     *
1113     */
1114    private List<InterleavingSubsequence> getInterleavings(Subsequences        subsequences,
1115                                                           RuleApplicationData appData)
1116    {
1117        List<InterleavingSubsequence> interleavings = new LinkedList<InterleavingSubsequence>();
1118        List<Subsequence> potentialPredecessors = new LinkedList<Subsequence>();
1119        List<Subsequence> potentialSuccessors = new LinkedList<Subsequence>();
1120       
1121        Map<Subsequence, List<SubsequenceLocation>> allLocations =
1122            getLocations(subsequences, appData);
1123       
1124        for (Subsequence subsequence : subsequences) {
1125            // Console.traceln
1126            //    (Level.FINEST, "searching for interleavings of " + toPlainStr(subsequence));
1127
1128            potentialPredecessors.clear();
1129            potentialSuccessors.clear();
1130           
1131            getInterleavingSubsequences
1132                (subsequence, subsequences, potentialPredecessors, potentialSuccessors);
1133           
1134            if ((potentialPredecessors.size() <= 0) && (potentialSuccessors.size() <= 0)) {
1135                // subsequence is not interleaving with others. Check next one.
1136                continue;
1137            }
1138           
1139            // subsequence is interleaving. First, determine its locations in the user sessions.
1140            List<SubsequenceLocation> locations = allLocations.get(subsequence);
1141           
1142            List<Collision> precedingCollisions =
1143                getPrecedingCollisions(subsequence, locations, potentialPredecessors, appData);
1144           
1145            List<Collision> succeedingCollisions =
1146                getSucceedingCollisions(subsequence, locations, potentialSuccessors, appData);
1147           
1148            if ((precedingCollisions.size() <= 0) && (succeedingCollisions.size() <= 0)) {
1149                // subsequence is not interleaving with others. Check next one.
1150                continue;
1151            }
1152           
1153            interleavings.add(new InterleavingSubsequence(subsequence, precedingCollisions,
1154                                                          succeedingCollisions));
1155        }
1156       
1157        return interleavings;
1158    }
1159
1160    /**
1161     *
1162     */
1163    private void getInterleavingSubsequences(Subsequence       subsequence,
1164                                             Subsequences      allSubsequences,
1165                                             List<Subsequence> potentialPredecessors,
1166                                             List<Subsequence> potentialSuccessors)
1167    {
1168        TaskComparator comparator = identityTaskHandlingStrategy.getTaskComparator();
1169       
1170        for (Subsequence candidate : allSubsequences) {
1171            if (comparator.equals(subsequence.get(subsequence.size() - 1), candidate.get(0))) {
1172                potentialSuccessors.add(candidate);
1173            }
1174           
1175            if (comparator.equals(subsequence.get(0), candidate.get(candidate.size() - 1))) {
1176                potentialPredecessors.add(candidate);
1177            }
1178        }
1179
1180        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1181        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>
1182        // Console.traceln(Level.FINEST, "  potential successors of " + toPlainStr(subsequence));
1183        // dumpSortedCollectionOfSubsequences("    ", potentialSuccessors);
1184       
1185        // Console.traceln(Level.FINEST, "  potential predecessors of " + toPlainStr(subsequence));
1186        // dumpSortedCollectionOfSubsequences("    ", potentialPredecessors);
1187        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1188        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1189    }
1190
1191    /**
1192     *
1193     */
1194    private Map<Subsequence, List<SubsequenceLocation>> getLocations(Subsequences        subsequences,
1195                                                                     RuleApplicationData appData)
1196    {
1197        Map<Subsequence, List<SubsequenceLocation>> result =
1198            new IdentityHashMap<Subsequence, List<SubsequenceLocation>>();
1199       
1200        // fill the map with empty locations
1201        for (Subsequence subsequence : subsequences) {
1202            result.put(subsequence, new LinkedList<SubsequenceLocation>());
1203        }
1204       
1205        LinkedList<LinkedList<Subsequence>> candidates = new LinkedList<LinkedList<Subsequence>>();
1206
1207        for (IUserSession session : appData.getSessions()) {
1208            for (int i = 0; i < session.size(); i++) {
1209                ITask currentTask = session.get(i).getTask();
1210               
1211                // create a list of candidates that may start here
1212                LinkedList<Subsequence> candidatesToStartHere = new LinkedList<Subsequence>();
1213               
1214                for (Subsequence candidate : subsequences) {
1215                    if (candidate.get(0) == currentTask) {
1216                        candidatesToStartHere.add(candidate);
1217                    }
1218                }
1219               
1220                candidates.addFirst(candidatesToStartHere);
1221               
1222                // check if the other candidates continue here.
1223                ListIterator<LinkedList<Subsequence>> candidatesIt = candidates.listIterator(1);
1224                int index = 1;
1225                while (candidatesIt.hasNext()) {
1226                    ListIterator<Subsequence> candidateIt = candidatesIt.next().listIterator();
1227                    while (candidateIt.hasNext()) {
1228                        Subsequence candidate = candidateIt.next();
1229                       
1230                        if (candidate.get(index) == currentTask) {
1231                            if (candidate.size() == (index + 1)) {
1232                                // subsequence was fully matched --> store location
1233                                result.get(candidate).add
1234                                    (new SubsequenceLocation(session, i - candidate.size() + 1));
1235                                candidateIt.remove();
1236                            }
1237                        }
1238                        else if (candidate.get(index) != currentTask) {
1239                            // remove the candidate as it is not located here
1240                            candidateIt.remove();
1241                        }
1242                    }
1243                   
1244                    index++;
1245                }
1246               
1247                // remove potential continuation being empty
1248                while ((candidates.size() > 0) && (candidates.getLast().size() == 0)) {
1249                    candidates.removeLast();
1250                }
1251            }
1252        }
1253       
1254        return result;
1255    }
1256
1257    /**
1258     *
1259     */
1260    private List<Collision> getPrecedingCollisions(Subsequence               subsequence,
1261                                                   List<SubsequenceLocation> locations,
1262                                                   List<Subsequence>         potentialPredecessors,
1263                                                   RuleApplicationData       appData)
1264    {
1265        List<Collision> precedingCollisions = new LinkedList<Collision>();
1266        int interleavingStartIndex;
1267        int interleavingEndIndex;
1268       
1269        for (SubsequenceLocation location : locations) {
1270            for (Subsequence predecessor : potentialPredecessors) {
1271                // start where the interleaving would start
1272                interleavingStartIndex = location.getIndex() - predecessor.size() + 1;
1273               
1274                if (interleavingStartIndex >= 0) {
1275                    interleavingEndIndex =
1276                        getSubListIndex(location.getSession(), predecessor, interleavingStartIndex);
1277               
1278                    if (interleavingStartIndex == interleavingEndIndex) {
1279                        precedingCollisions.add(new Collision(location, subsequence, predecessor));
1280                    }
1281                }
1282            }
1283        }
1284       
1285        return precedingCollisions;
1286    }
1287
1288    /**
1289     *
1290     */
1291    private List<Collision> getSucceedingCollisions(Subsequence               subsequence,
1292                                                    List<SubsequenceLocation> locations,
1293                                                    List<Subsequence>         potentialPredecessors,
1294                                                    RuleApplicationData       appData)
1295    {
1296        List<Collision> succeedingCollisions = new LinkedList<Collision>();
1297        int interleavingStartIndex;
1298        int interleavingEndIndex;
1299
1300        for (SubsequenceLocation location : locations) {
1301            for (Subsequence successor : potentialPredecessors) {
1302                interleavingStartIndex = location.getIndex() + subsequence.size() - 1;
1303                interleavingEndIndex =
1304                    getSubListIndex(location.getSession(), successor, interleavingStartIndex);
1305
1306                if (interleavingStartIndex == interleavingEndIndex) {
1307                    succeedingCollisions.add(new Collision(location, subsequence, successor));
1308                }
1309            }
1310        }
1311       
1312        return succeedingCollisions;
1313    }
1314   
1315    /**
1316     *
1317     */
1318    private List<InterleavingSubsequence> getMostIntensiveInterleavings
1319        (List<InterleavingSubsequence> interleavings)
1320    {
1321        List<InterleavingSubsequence> mostIntensiveInterleavings =
1322            new LinkedList<InterleavingSubsequence>();
1323       
1324        int collisionCounter = 0;
1325       
1326        for (InterleavingSubsequence interleaving : interleavings) {
1327            if (interleaving.getCollisionCounter() > collisionCounter) {
1328                collisionCounter = interleaving.getCollisionCounter();
1329                mostIntensiveInterleavings.clear();
1330            }
1331           
1332            if (interleaving.getCollisionCounter() == collisionCounter) {
1333                mostIntensiveInterleavings.add(interleaving);
1334            }
1335        }
1336       
1337        return mostIntensiveInterleavings;
1338    }
1339
1340    /**
1341     *
1342     */
1343    private List<InterleavingSubsequence> getLeastInterleavingsAsPredecessor
1344        (List<InterleavingSubsequence> interleavings)
1345    {
1346        List<InterleavingSubsequence> leastInterleavingsAsPredecessor =
1347            new LinkedList<InterleavingSubsequence>();
1348           
1349        int collisionCounter = Integer.MAX_VALUE;
1350
1351        for (InterleavingSubsequence interleaving : interleavings) {
1352            if (interleaving.getSuccessorCollisionCounter() < collisionCounter) {
1353                collisionCounter = interleaving.getSuccessorCollisionCounter();
1354                leastInterleavingsAsPredecessor.clear();
1355            }
1356
1357            if (interleaving.getSuccessorCollisionCounter() == collisionCounter) {
1358                leastInterleavingsAsPredecessor.add(interleaving);
1359            }
1360        }
1361
1362        return leastInterleavingsAsPredecessor;
1363    }
1364
1365    /**
1366     *
1367     */
1368    private List<InterleavingSubsequence> getMostInterleavingsAsSuccessor
1369        (List<InterleavingSubsequence> interleavings)
1370    {
1371        List<InterleavingSubsequence> mostInterleavingsAsSuccessor =
1372            new LinkedList<InterleavingSubsequence>();
1373           
1374        int collisionCounter = 0;
1375
1376        for (InterleavingSubsequence interleaving : interleavings) {
1377            if (interleaving.getPredecessorCollisionCounter() > collisionCounter) {
1378                collisionCounter = interleaving.getPredecessorCollisionCounter();
1379                mostInterleavingsAsSuccessor.clear();
1380            }
1381
1382            if (interleaving.getPredecessorCollisionCounter() == collisionCounter) {
1383                mostInterleavingsAsSuccessor.add(interleaving);
1384            }
1385        }
1386
1387        return mostInterleavingsAsSuccessor;
1388    }
1389
1390    /**
1391     *
1392     */
1393    private List<InterleavingSubsequence> removeCollisionsOfOtherSubsequences
1394        (List<InterleavingSubsequence> interleavings)
1395    {
1396        List<InterleavingSubsequence> subsequencesWithoutOtherCollisions =
1397            new LinkedList<InterleavingSubsequence>();
1398       
1399        for (InterleavingSubsequence interleaving : interleavings) {
1400            List<Collision> reducedPredecessorCollisions = new LinkedList<>();
1401           
1402            for (Collision collision : interleaving.getPredecessorCollisions()) {
1403                for (InterleavingSubsequence otherInterleaving : interleavings) {
1404                    if (otherInterleaving.getSubsequence() == collision.getCollidingWith()) {
1405                        reducedPredecessorCollisions.add(collision);
1406                        break;
1407                    }
1408                }
1409            }
1410           
1411            List<Collision> reducedSuccessorCollisions = new LinkedList<>();
1412           
1413            for (Collision collision : interleaving.getSuccessorCollisions()) {
1414                for (InterleavingSubsequence otherInterleaving : interleavings) {
1415                    if (otherInterleaving.getSubsequence() == collision.getCollidingWith()) {
1416                        reducedSuccessorCollisions.add(collision);
1417                        break;
1418                    }
1419                }
1420            }
1421           
1422            subsequencesWithoutOtherCollisions.add
1423                (new InterleavingSubsequence(interleaving.getSubsequence(),
1424                                             reducedPredecessorCollisions,
1425                                             reducedSuccessorCollisions));
1426        }
1427       
1428        return subsequencesWithoutOtherCollisions;
1429    }
1430
1431    /**
1432     * @param appData the rule application data combining all data used for applying this rule
1433     */
1434    private void replaceSequencesOccurringMostOften(RuleApplicationData appData) {
1435        appData.detectedAndReplacedTasks(false);
1436
1437        if ((appData.getLastFoundSubsequences().size() > 0) &&
1438            (appData.getLastFoundSubsequences().getOccurrenceCount() > 1))
1439        {
1440            Console.traceln(Level.FINER, "replacing tasks occurrences");
1441
1442            for (Subsequence subsequence : appData.getLastFoundSubsequences()) {
1443                ISequence sequence = taskFactory.createNewSequence();
1444               
1445                Console.traceln(Level.FINEST, "replacing " + sequence.getId() + ": " + subsequence);
1446
1447                List<ISequenceInstance> sequenceInstances = replaceSubsequenceOccurrences
1448                    (subsequence, appData.getSessions(), sequence);
1449               
1450                harmonizeSequenceInstancesModel(sequence, sequenceInstances, subsequence.size());
1451                appData.detectedAndReplacedTasks
1452                    (appData.detectedAndReplacedTasks() || (sequenceInstances.size() > 0));
1453
1454                if (sequenceInstances.size() < appData.getLastFoundSubsequences().getOccurrenceCount()) {
1455                    Console.traceln(Level.FINE, sequence.getId() + ": replaced task only " +
1456                                    sequenceInstances.size() + " times instead of expected " +
1457                                    appData.getLastFoundSubsequences().getOccurrenceCount());
1458                }
1459            }
1460        }
1461    }
1462
1463    /**
1464     *
1465     */
1466    private void harmonizeSequenceInstancesModel(ISequence               sequence,
1467                                                 List<ISequenceInstance> sequenceInstances,
1468                                                 int                     sequenceLength)
1469    {
1470        TaskComparator comparator = preparationTaskHandlingStrategy.getTaskComparator();
1471       
1472        // ensure for each subtask that lexically different variants are preserved
1473        for (int subTaskIndex = 0; subTaskIndex < sequenceLength; subTaskIndex++) {
1474            List<ITask> subTaskVariants = new LinkedList<ITask>();
1475           
1476            for (ISequenceInstance sequenceInstance : sequenceInstances) {
1477                ITask candidate = sequenceInstance.get(subTaskIndex).getTask();
1478               
1479                boolean found = false;
1480               
1481                for (int i = 0; i < subTaskVariants.size(); i++) {
1482                    if (comparator.areLexicallyEqual(subTaskVariants.get(i), candidate)) {
1483                        taskBuilder.setTask
1484                            (sequenceInstance.get(subTaskIndex), subTaskVariants.get(i));
1485                       
1486                        found = true;
1487                        break;
1488                    }
1489                }
1490               
1491                if (!found) {
1492                    subTaskVariants.add(candidate);
1493                }
1494            }
1495           
1496            // if there are more than one lexically different variant of the sub task at
1497            // the considered position, adapt the sequence model at that position to have
1498            // a selection of the different variants. In this case also adapt the
1499            // generated sequence instances to correctly contain selection instances. If
1500            // there is only one variant of sub tasks at the given position, simply set
1501            // this variant as the sub task of the selection. In this case, the instances
1502            // can be preserved as is
1503            if (subTaskVariants.size() > 1) {
1504                ISelection selection = taskFactory.createNewSelection();
1505               
1506                for (ITask variant : subTaskVariants) {
1507                    taskBuilder.addChild(selection, variant);
1508                }
1509               
1510                taskBuilder.addChild(sequence, selection);
1511               
1512                for (ISequenceInstance instance : sequenceInstances) {
1513                    ISelectionInstance selectionInstance =
1514                        taskFactory.createNewTaskInstance(selection);
1515                    taskBuilder.setChild(selectionInstance, instance.get(subTaskIndex));
1516                    taskBuilder.setTaskInstance(instance, subTaskIndex, selectionInstance);
1517                }
1518            }
1519            else if (subTaskVariants.size() == 1) {
1520                taskBuilder.addChild(sequence, subTaskVariants.get(0));
1521            }
1522        }
1523    }
1524
1525    /**
1526     * @param tree
1527     */
1528    private List<ISequenceInstance> replaceSubsequenceOccurrences(Subsequence         subsequence,
1529                                                                  List<IUserSession>  sessions,
1530                                                                  ISequence           temporalTaskModel)
1531    {
1532        List<ISequenceInstance> sequenceInstances = new LinkedList<ISequenceInstance>();
1533       
1534        for (IUserSession session : sessions) {
1535            int index = -1;
1536           
1537            do {
1538                index = getSubListIndex(session, subsequence, ++index);
1539
1540                if (index > -1) {
1541                    // subsequence is found --> perform replacement
1542                    sequenceInstances.add
1543                        (RuleUtils.createNewSubSequenceInRange
1544                             (session, index, index + subsequence.size() - 1, temporalTaskModel,
1545                              taskFactory, taskBuilder));
1546                }
1547            }
1548            while (index > -1);
1549        }
1550       
1551        return sequenceInstances;
1552    }
1553
1554    /**
1555     * @param trie
1556     * @param object
1557     * @return
1558     */
1559    private int getSubListIndex(ITaskInstanceList   list,
1560                                Subsequence         subsequence,
1561                                int                 startIndex)
1562    {
1563        boolean matchFound;
1564        int result = -1;
1565       
1566        for (int i = startIndex; i <= list.size() - subsequence.size(); i++) {
1567            matchFound = true;
1568           
1569            for (int j = 0; j < subsequence.size(); j++) {
1570                // we prepared the task instances to refer to unique tasks, if they are treated
1571                // as equal. Therefore, we just compare the identity of the tasks of the task
1572                // instances
1573                if (list.get(i + j).getTask() != subsequence.get(j)) {
1574                    matchFound = false;
1575                    break;
1576                }
1577            }
1578           
1579            if (matchFound) {
1580                result = i;
1581                break;
1582            }
1583        }
1584       
1585        return result;
1586    }
1587
1588    /**
1589     * @param trie
1590     * @param object
1591     * @return
1592     */
1593    private int getSubListIndex(Subsequence longerSubsequence,
1594                                Subsequence shorterSubsequence,
1595                                int         startIndex)
1596    {
1597        boolean matchFound;
1598        int result = -1;
1599       
1600        for (int i = startIndex; i <= longerSubsequence.size() - shorterSubsequence.size(); i++) {
1601            matchFound = true;
1602           
1603            for (int j = 0; j < shorterSubsequence.size(); j++) {
1604                // we prepared the task instances to refer to unique tasks, if they are treated
1605                // as equal. Therefore, we just compare the identity of the tasks of the task
1606                // instances
1607                if (longerSubsequence.get(i + j) != shorterSubsequence.get(j)) {
1608                    matchFound = false;
1609                    break;
1610                }
1611            }
1612           
1613            if (matchFound) {
1614                result = i;
1615                break;
1616            }
1617        }
1618       
1619        return result;
1620    }
1621
1622//  /**
1623//   *
1624//   */
1625//  private void dumpSorted(List<InterleavingSubsequence> interleavings) {
1626//      dumpSorted(interleavings, Level.FINEST);
1627//  }
1628
1629    /**
1630     *
1631     */
1632    private void dumpSorted(List<InterleavingSubsequence> interleavings, Level level) {
1633        List<String> tmpList = new LinkedList<String>();
1634
1635        for (InterleavingSubsequence interleaving : interleavings) {
1636            String taskStr = toPlainStr(interleaving);
1637            int index;
1638            for (index = 0; index < tmpList.size(); index++) {
1639                if (taskStr.compareTo(tmpList.get(index)) > 0) {
1640                    break;
1641                }
1642            }
1643
1644            tmpList.add(index, taskStr);
1645        }
1646
1647        for (String task : tmpList) {
1648            Console.traceln(level, task);
1649        }
1650    }
1651
1652//    /**
1653//     *
1654//     */
1655//    private void dumpSortedCollectionOfSubsequences(String                  prefix,
1656//                                                    Collection<Subsequence> subsequences)
1657//    {
1658//        List<String> tmpList = new LinkedList<String>();
1659//
1660//        for (Subsequence subsequence : subsequences) {
1661//            String taskStr = toPlainStr(subsequence);
1662//            int index;
1663//            for (index = 0; index < tmpList.size(); index++) {
1664//                if (taskStr.compareTo(tmpList.get(index)) > 0) {
1665//                    break;
1666//                }
1667//            }
1668//
1669//            tmpList.add(index, taskStr);
1670//        }
1671//
1672//        for (String task : tmpList) {
1673//            Console.traceln(Level.FINEST, prefix + " " + task);
1674//        }
1675//    }
1676
1677    /**
1678     *
1679     */
1680    private String toPlainStr(InterleavingSubsequence interleaving) {
1681        StringBuffer result = new StringBuffer();
1682        result.append("interleavings of ");
1683        result.append(toPlainStr(interleaving.getSubsequence()));
1684        result.append("\n  predecessor collisions:\n");
1685
1686        for (Collision collision : interleaving.getPredecessorCollisions()) {
1687            result.append("    ");
1688            result.append(toPlainStr(collision.getCollidingWith()));
1689            result.append(" (");
1690            result.append(collision.getLocation());
1691            result.append(")\n");
1692        }
1693
1694        result.append("  successor collisions:\n");
1695
1696        for (Collision collision : interleaving.getSuccessorCollisions()) {
1697            result.append("    ");
1698            result.append(toPlainStr(collision.getCollidingWith()));
1699            result.append(" (");
1700            result.append(collision.getLocation());
1701            result.append(")\n");
1702        }
1703
1704        return result.toString();
1705    }
1706
1707    /**
1708     *
1709     */
1710    private String toPlainStr(Subsequence subsequence) {
1711        StringBuffer result = new StringBuffer();
1712        result.append(subsequence.size());
1713        result.append(": ");
1714        for (ITask task : subsequence) {
1715            DebuggingHelper.toPlainStr(task, result);
1716            result.append(" --> ");
1717        }
1718
1719        return result.toString();
1720    }
1721   
1722   
1723    /**
1724     * @author Patrick Harms
1725     */
1726    private class MaxCountAndLongestSubsequencesFinder implements TrieProcessor<ITask> {
1727       
1728        /**
1729         *
1730         */
1731        private int currentCount;
1732       
1733        /**
1734         *
1735         */
1736        private LinkedList<Subsequence> foundSubsequences = new LinkedList<Subsequence>();
1737
1738        /**
1739         *
1740         */
1741        public MaxCountAndLongestSubsequencesFinder() {
1742            super();
1743            this.currentCount = 0;
1744        }
1745
1746        /* (non-Javadoc)
1747         * @see de.ugoe.cs.autoquest.usageprofiles.TrieProcessor#process(java.util.List, int)
1748         */
1749        @Override
1750        public TrieProcessor.Result process(List<ITask> foundTask, int count) {
1751            if (foundTask.size() < 2) {
1752                // ignore single tasks
1753                return TrieProcessor.Result.CONTINUE;
1754            }
1755           
1756            if (count < 2) {
1757                // ignore singular occurrences
1758                return TrieProcessor.Result.SKIP_NODE;
1759            }
1760
1761            if (this.currentCount > count) {
1762                // ignore this children of this task, as they may have only smaller counts than
1763                // the already found tasks
1764                return TrieProcessor.Result.SKIP_NODE;
1765            }
1766           
1767            if (this.currentCount < count) {
1768                // the provided task occurs more often that all tasks found so far.
1769                // clear all found tasks and use the new count as the one searched for
1770                foundSubsequences.clear();
1771                this.currentCount = count;
1772            }
1773           
1774            if (this.currentCount == count) {
1775                // the task is of interest. Sort it into the other found tasks so that
1776                // the longest come first
1777                boolean added = false;
1778                for (int i = 0; i < foundSubsequences.size(); i++) {
1779                    if (foundSubsequences.get(i).size() < foundTask.size()) {
1780                        foundSubsequences.add(i, new Subsequence(currentCount, foundTask));
1781                        added = true;
1782                        break;
1783                    }
1784                }
1785               
1786                if (!added) {
1787                    foundSubsequences.add(new Subsequence(currentCount, foundTask));
1788                }
1789            }
1790           
1791            return TrieProcessor.Result.CONTINUE;
1792        }
1793
1794        /**
1795         *  @return
1796         */
1797        private Subsequences getFoundSubsequences() {
1798            removePermutationsOfShorterTasks();
1799            return new Subsequences(currentCount, foundSubsequences);
1800        }
1801
1802        /**
1803         *
1804         */
1805        private void removePermutationsOfShorterTasks() {
1806            // now iterate the sorted list and for each task remove all other tasks that are shorter
1807            // (i.e. come later in the sorted list) and that represent a subpart of the task
1808           
1809            boolean removeI;
1810           
1811            for (int i = 0; i < foundSubsequences.size();) {
1812                removeI = false;
1813                boolean removeJ;
1814               
1815                for (int j = i + 1; j < foundSubsequences.size();) {
1816                    removeJ = false;
1817                   
1818                    if (foundSubsequences.get(j).size() < foundSubsequences.get(i).size()) {
1819                        // found a task that is a potential subtask. Check for this and remove the
1820                        // subtask if needed
1821                        Subsequence longTask = foundSubsequences.get(i);
1822                        Subsequence shortTask = foundSubsequences.get(j);
1823                       
1824                        int index = getSubListIndex(longTask, shortTask, 0);
1825                        if (index > -1) {
1826                            if (index == 0) {
1827                                // check if the short task is included in the long task partially
1828                                // a second time. If so, prefer the short task
1829                                boolean secondInclude = true;
1830                                for (int pos = shortTask.size(); pos < longTask.size(); pos++) {
1831                                    // we prepared the task instances to refer to unique tasks, if
1832                                    // they are treated as equal. Therefore, we just compare the
1833                                    // identity of the tasks of the task instances
1834                                    if (longTask.get(pos) != shortTask.get(pos % shortTask.size()))
1835                                    {
1836                                        secondInclude = false;
1837                                        break;
1838                                    }
1839                                }
1840                               
1841                                if (secondInclude) {
1842                                    // delete the long task
1843                                    // Console.traceln(Level.FINEST, "1: short task is contained several times in longer one --> removing it");
1844                                    // Console.traceln(Level.FINEST, "short: " + toPlainStr(shortTask));
1845                                    // Console.traceln(Level.FINEST, "long: " + toPlainStr(longTask));
1846                                    removeI = true;
1847                                }
1848                                else {
1849                                    // Console.traceln(Level.FINEST, "2: short task is a part of a longer one --> removing it");
1850                                    // Console.traceln(Level.FINEST, "short: " + toPlainStr(shortTask));
1851                                    // Console.traceln(Level.FINEST, "long: " + toPlainStr(longTask));
1852                                    removeJ = true;
1853                                }
1854                            }
1855                            else {
1856                                // Console.traceln(Level.FINEST, "3: short task is a part of a longer one --> removing it");
1857                                // Console.traceln(Level.FINEST, "short: " + toPlainStr(shortTask));
1858                                // Console.traceln(Level.FINEST, "long: " + toPlainStr(longTask));
1859                                removeJ = true;
1860                            }
1861                        }
1862                    }
1863                   
1864                    if (removeJ) {
1865                        foundSubsequences.remove(j);
1866                    }
1867                    else {
1868                        j++;
1869                    }
1870                }
1871               
1872                if (removeI) {
1873                    foundSubsequences.remove(i);
1874                }
1875                else {
1876                    i++;
1877                }
1878            }
1879        }
1880
1881    }
1882
1883//    /**
1884//     * @author Patrick Harms
1885//     */
1886//    private class TrieStatisticsDumper implements TrieProcessor<ITaskInstance> {
1887//       
1888//        /**
1889//         *
1890//         */
1891//        private Map<Integer, Integer> counters = new HashMap<Integer, Integer>();
1892//       
1893//        /* (non-Javadoc)
1894//         * @see de.ugoe.cs.autoquest.usageprofiles.TrieProcessor#process(java.util.List, int)
1895//         */
1896//        @Override
1897//        public TrieProcessor.Result process(List<ITaskInstance> subsequence, int count) {
1898//            if (subsequence.size() == 1) {
1899//                Integer value = counters.get(count);
1900//               
1901//                if (value == null) {
1902//                    value = 0;
1903//                }
1904//               
1905//                counters.put(count, value + 1);
1906//               
1907//                return TrieProcessor.Result.CONTINUE;
1908//            }
1909//            else {
1910//                // ignore singular occurrences
1911//                return TrieProcessor.Result.SKIP_NODE;
1912//            }
1913//        }
1914//
1915//        /**
1916//         *  @return
1917//         */
1918//        public void dumpCounters() {
1919//            int dumpedCounters = 0;
1920//           
1921//            int count = 1;
1922//            while (dumpedCounters < counters.size()) {
1923//                Integer value = counters.get(count++);
1924//                if (value != null) {
1925//                    System.out.println(value + " symbols occurred " + count + " times");
1926//                    dumpedCounters++;
1927//                }
1928//            }
1929//        }
1930//
1931//    }
1932   
1933    /**
1934     *
1935     */
1936    private static class RuleApplicationData {
1937       
1938        /**
1939         *
1940         */
1941        private List<IUserSession> sessions;
1942       
1943        /**
1944         *
1945         */
1946        private TaskInstanceTrie lastTrie;
1947       
1948        /**
1949         * default and minimum trained subsequence length is 3
1950         */
1951        private int trainedSubsequenceLength = 3;
1952       
1953        /**
1954         *
1955         */
1956        private Subsequences lastFoundSubsequences = new Subsequences(Integer.MAX_VALUE, null);
1957       
1958        /**
1959         *
1960         */
1961        private boolean detectedAndReplacedTasks;
1962       
1963        /**
1964         *
1965         */
1966        private RuleApplicationResult result = new RuleApplicationResult();
1967       
1968        /**
1969         *
1970         */
1971        private StopWatch stopWatch = new StopWatch();
1972       
1973        /**
1974         *
1975         */
1976        private RuleApplicationData(List<IUserSession> sessions) {
1977            this.sessions = sessions;
1978        }
1979
1980        /**
1981         * @return the tree
1982         */
1983        private List<IUserSession> getSessions() {
1984            return sessions;
1985        }
1986
1987        /**
1988         * @param lastTrie the lastTrie to set
1989         */
1990        private void setLastTrie(TaskInstanceTrie lastTrie) {
1991            this.lastTrie = lastTrie;
1992        }
1993
1994        /**
1995         * @return the lastTrie
1996         */
1997        private TaskInstanceTrie getLastTrie() {
1998            return lastTrie;
1999        }
2000
2001        /**
2002         * @param trainedSubsequenceLength the trainedSubsequenceLength to set
2003         */
2004        private void setTrainedSubsequenceLength(int trainedSubsequenceLength) {
2005            this.trainedSubsequenceLength = trainedSubsequenceLength;
2006        }
2007
2008        /**
2009         * @return the trainedSubsequenceLength
2010         */
2011        private int getTrainedSubsequenceLength() {
2012            return trainedSubsequenceLength;
2013        }
2014
2015        /**
2016         * @param lastFoundSequences the lastFoundSequences to set
2017         */
2018        private void setLastFoundSubsequences(Subsequences lastFoundSequences) {
2019            this.lastFoundSubsequences = lastFoundSequences;
2020        }
2021
2022        /**
2023         * @return the lastFoundSequences
2024         */
2025        private Subsequences getLastFoundSubsequences() {
2026            return lastFoundSubsequences;
2027        }
2028
2029        /**
2030         *
2031         */
2032        private void detectedAndReplacedTasks(boolean detectedAndReplacedTasks) {
2033            this.detectedAndReplacedTasks = detectedAndReplacedTasks;
2034        }
2035
2036        /**
2037         *
2038         */
2039        private boolean detectedAndReplacedTasks() {
2040            return detectedAndReplacedTasks;
2041        }
2042       
2043        /**
2044         * @return the result
2045         */
2046        private RuleApplicationResult getResult() {
2047            return result;
2048        }
2049
2050        /**
2051         * @return the stopWatch
2052         */
2053        private StopWatch getStopWatch() {
2054            return stopWatch;
2055        }
2056
2057    }
2058   
2059    /**
2060     * @author Patrick Harms
2061     */
2062    private static class Subsequence implements Iterable<ITask> {
2063       
2064        /**
2065         *
2066         */
2067        private int occurrenceCount;
2068
2069        /**
2070         *
2071         */
2072        private List<ITask> subsequence;
2073       
2074        /**
2075         * @param occurrenceCount
2076         * @param subsequences
2077         */
2078        private Subsequence(int occurrenceCount, List<ITask> subsequence) {
2079            super();
2080            this.occurrenceCount = occurrenceCount;
2081            this.subsequence = new ArrayList<ITask>(subsequence);
2082        }
2083
2084        /**
2085         * @return
2086         */
2087        private int size() {
2088            return this.subsequence.size();
2089        }
2090
2091        /**
2092         * @return
2093         */
2094        private ITask get(int index) {
2095            return this.subsequence.get(index);
2096        }
2097
2098        /* (non-Javadoc)
2099         * @see java.lang.Iterable#iterator()
2100         */
2101        @Override
2102        public Iterator<ITask> iterator() {
2103            return this.subsequence.iterator();
2104        }
2105
2106        /* (non-Javadoc)
2107         * @see java.lang.Object#toString()
2108         */
2109        @Override
2110        public String toString() {
2111            return subsequence.toString() + " (" + occurrenceCount + ")";
2112        }
2113    }
2114
2115    /**
2116     * @author Patrick Harms
2117     */
2118    private static class Subsequences implements Iterable<Subsequence> {
2119       
2120        /**
2121         *
2122         */
2123        private int occurrenceCount;
2124       
2125        /**
2126         *
2127         */
2128        private LinkedList<Subsequence> subsequences;
2129
2130        /**
2131         * @param occurrenceCount
2132         * @param sequences
2133         */
2134        private Subsequences(int occurrenceCount, LinkedList<Subsequence> subsequences) {
2135            super();
2136            this.occurrenceCount = occurrenceCount;
2137            this.subsequences = subsequences;
2138        }
2139
2140        /**
2141         *
2142         */
2143        private void remove(Subsequence subsequence) {
2144            ListIterator<Subsequence> it = subsequences.listIterator();
2145           
2146            while (it.hasNext()) {
2147                // reference comparison is sufficient
2148                if (it.next() == subsequence) {
2149                    it.remove();
2150                }
2151            }
2152        }
2153
2154        /**
2155         * @return
2156         */
2157        private int getOccurrenceCount() {
2158            return occurrenceCount;
2159        }
2160
2161        /**
2162         * @return
2163         */
2164        private int size() {
2165            return this.subsequences.size();
2166        }
2167
2168        /* (non-Javadoc)
2169         * @see java.lang.Iterable#iterator()
2170         */
2171        @Override
2172        public Iterator<Subsequence> iterator() {
2173            return this.subsequences.iterator();
2174        }
2175
2176        /* (non-Javadoc)
2177         * @see java.lang.Object#toString()
2178         */
2179        @Override
2180        public String toString() {
2181            StringBuffer result = new StringBuffer();
2182            result.append(" subsequences occuring ");
2183            result.append(this.occurrenceCount);
2184            result.append(" times:\n");
2185           
2186            for (Subsequence subsequence : subsequences) {
2187                result.append(subsequence);
2188                result.append("\n");
2189            }
2190           
2191            return result.toString();
2192        }
2193    }
2194   
2195    /**
2196     * @author Patrick Harms
2197     */
2198    private static class InterleavingSubsequence {
2199       
2200        /** */
2201        private Subsequence subsequence;
2202       
2203        /** */
2204        private List<Collision> successorCollisions;
2205       
2206        /** */
2207        private List<Collision> predecessorCollisions;
2208
2209        /**
2210         *
2211         */
2212        private InterleavingSubsequence(Subsequence     subsequence,
2213                                        List<Collision> predecessorCollisions,
2214                                        List<Collision> successorCollisions)
2215        {
2216            super();
2217            this.subsequence = subsequence;
2218            this.predecessorCollisions = predecessorCollisions;
2219            this.successorCollisions = successorCollisions;
2220        }
2221
2222        /**
2223         *
2224         */
2225        private int getCollisionCounter() {
2226            return getSuccessorCollisionCounter() + getPredecessorCollisionCounter();
2227        }
2228
2229        /**
2230         *
2231         */
2232        private int getSuccessorCollisionCounter() {
2233            return successorCollisions.size();
2234        }
2235
2236        /**
2237         *
2238         */
2239        private List<Collision> getSuccessorCollisions() {
2240            return successorCollisions;
2241        }
2242
2243        /**
2244         *
2245         */
2246        private int getPredecessorCollisionCounter() {
2247            return predecessorCollisions.size();
2248        }
2249
2250        /**
2251         *
2252         */
2253        private List<Collision> getPredecessorCollisions() {
2254            return predecessorCollisions;
2255        }
2256
2257        /**
2258         *
2259         */
2260        private Subsequence getSubsequence() {
2261            return subsequence;
2262        }
2263       
2264        /* (non-Javadoc)
2265         * @see java.lang.Object#toString()
2266         */
2267        @Override
2268        public String toString() {
2269            return "interleaving subsequence " + subsequence.toString() + " (" +
2270                successorCollisions.size() + " successor, " + predecessorCollisions.size() +
2271                " predecessor)";
2272        }
2273    }
2274       
2275    /**
2276     * @author Patrick Harms
2277     */
2278    private static class SubsequenceLocation {
2279       
2280        /** */
2281        private IUserSession session;
2282       
2283        /** */
2284        private int index;
2285       
2286        /**
2287         *
2288         */
2289        private SubsequenceLocation(IUserSession session, int index) {
2290            this.session = session;
2291            this.index = index;
2292        }
2293
2294        /**
2295         * @return the session
2296         */
2297        private IUserSession getSession() {
2298            return session;
2299        }
2300
2301        /**
2302         * @return the index
2303         */
2304        private int getIndex() {
2305            return index;
2306        }
2307       
2308        /* (non-Javadoc)
2309         * @see java.lang.Object#toString()
2310         */
2311        @Override
2312        public String toString() {
2313            return "location (" + session + ", " + index + ")";
2314        }
2315    }
2316   
2317    /**
2318     * @author Patrick Harms
2319     */
2320    private static class Collision {
2321       
2322        /** */
2323        private SubsequenceLocation location;
2324       
2325        /** */
2326        private Subsequence subsequence;
2327       
2328        /** */
2329        private Subsequence collidingWith;
2330       
2331        /**
2332         *
2333         */
2334        private Collision(SubsequenceLocation location,
2335                          Subsequence         subsequence,
2336                          Subsequence         collidingWith)
2337        {
2338            this.location = location;
2339            this.subsequence = subsequence;
2340            this.collidingWith = collidingWith;
2341        }
2342
2343        /**
2344         * @return the collidingWith
2345         */
2346        private Subsequence getCollidingWith() {
2347            return collidingWith;
2348        }
2349
2350        /**
2351         * @return the location
2352         */
2353        private SubsequenceLocation getLocation() {
2354            return location;
2355        }
2356
2357        /**
2358         * @return the subsequence
2359         */
2360//        private Subsequence getSubsequence() {
2361//            return subsequence;
2362//        }
2363
2364        /* (non-Javadoc)
2365         * @see java.lang.Object#toString()
2366         */
2367        @Override
2368        public String toString() {
2369            return "collision (" + location + " ," + subsequence + ", " + collidingWith + ")";
2370        }
2371    }
2372   
2373    // methods for internal testing
2374//    private void checkMatchingOfSessions(List<List<Event>>  flattenedSessions,
2375//                                         List<IUserSession> sessions,
2376//                                         String             when)
2377//    {
2378//        List<List<Event>> currentFlattenedSessions = flattenSessions(sessions);
2379//        if (flattenedSessions.size() != currentFlattenedSessions.size()) {
2380//            System.out.println("################## number of sessions changed after " + when);
2381//        }
2382//        else {
2383//            for (int i = 0; i < flattenedSessions.size(); i++) {
2384//                List<Event> expected = flattenedSessions.get(i);
2385//                List<Event> current = currentFlattenedSessions.get(i);
2386//           
2387//                if (expected.size() != current.size()) {
2388//                    System.out.println
2389//                        ("################## length of session " + i + " changed after " + when);
2390//                }
2391//                else {
2392//                    for (int j = 0; j < expected.size(); j++) {
2393//                        if (!expected.get(j).equals(current.get(j))) {
2394//                            System.out.println("################## event " + j + " of session " +
2395//                                               i + " changed after " + when);
2396//                        }
2397//                    }
2398//                }
2399//            }     
2400//        }
2401//    }
2402//
2403//    private List<List<Event>> flattenSessions(List<IUserSession> sessions) {
2404//        List<List<Event>> flattenedSessions = new ArrayList<List<Event>>();
2405//        for (IUserSession session : sessions) {
2406//            List<Event> flattenedUserSession = new ArrayList<Event>();
2407//            flatten(session, flattenedUserSession);
2408//            flattenedSessions.add(flattenedUserSession);
2409//        }
2410//
2411//        return flattenedSessions;
2412//    }
2413//
2414//    private void flatten(IUserSession iUserSession, List<Event> flattenedUserSession) {
2415//        for (ITaskInstance instance : iUserSession) {
2416//            flatten(instance, flattenedUserSession);
2417//        }
2418//    }
2419//
2420//    private void flatten(ITaskInstance instance, List<Event> flattenedUserSession) {
2421//        if (instance instanceof ITaskInstanceList) {
2422//            for (ITaskInstance child : (ITaskInstanceList) instance) {
2423//                flatten(child, flattenedUserSession);
2424//            }
2425//        }
2426//        else if (instance instanceof ISelectionInstance) {
2427//            flatten(((ISelectionInstance) instance).getChild(), flattenedUserSession);
2428//        }
2429//        else if (instance instanceof IOptionalInstance) {
2430//            flatten(((IOptionalInstance) instance).getChild(), flattenedUserSession);
2431//        }
2432//        else if (instance instanceof IEventTaskInstance) {
2433//            flattenedUserSession.add(((IEventTaskInstance) instance).getEvent());
2434//        }
2435//    }
2436
2437}
Note: See TracBrowser for help on using the repository browser.