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

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