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

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