// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.usability; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.math3.stat.descriptive.SummaryStatistics; import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor; import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence; import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequenceInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel; import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession; import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskTreeUtils; /** * TODO comment * * @version $Revision: $ $Date: 16.07.2012$ * @author 2012, last modified by $Author: pharms$ */ public class CommonTaskRateRule implements UsabilityEvaluationRule { /* * (non-Javadoc) * * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree) */ @Override public UsabilityEvaluationResult evaluate(ITaskModel taskModel) { SummaryStatistics statistics = new SummaryStatistics(); SummaryStatistics mpStatistics = new SummaryStatistics(); int allObserved = calculateStatistics (taskModel.getUserSessions(), TaskTreeUtils.getMostProminentTasks(taskModel), statistics, mpStatistics); UsabilityEvaluationResult results = new UsabilityEvaluationResult(taskModel); analyzeStatistics(statistics, false, allObserved, results); analyzeStatistics(mpStatistics, true, allObserved, results); return results; } /** * */ private void analyzeStatistics(SummaryStatistics statistics, boolean mostProminentSequencesOnly, int allObserved, UsabilityEvaluationResult results) { // the mean indicates how many different root nodes 10 subsequent actions have. // The mean should tend to 0.1 (all subsequent 10 actions are covered by the same root task). // The ratio must be similar, i.e., if the mean is 0.1 the ratio is 0, if the mean is 1.0 // the ratio is 1000 if (statistics.getN() > 0) { double mean = statistics.getMean(); int ratio = (int) (10000 * (mean - 0.1) / 9); UsabilitySmellIntensity intensity = UsabilitySmellIntensity.getIntensity(ratio, allObserved, -1); if (intensity != null) { Map parameters = new HashMap(); parameters.put("ratio", ((float) Math.round(mean * 100)) / 10); if (mostProminentSequencesOnly) { parameters.put("tasksType", "representative tasks"); } else { parameters.put("tasksType", "tasks"); } results.addSmell(intensity, UsabilitySmellDescription.COMMON_TASK_RATE, parameters); } } } /** * */ private int calculateStatistics(List sessions, final Set mostProminentTasks, final SummaryStatistics statistics, final SummaryStatistics mpStatistics) { final LinkedList rootNodes = new LinkedList<>(); final LinkedList mpRootNodes = new LinkedList<>(); final List leafNodes = new ArrayList<>(); // determine for always 10 subsequent actions the root tasks performed for them. // then determine, the ratio of different root tasks to 10. for (IUserSession session : sessions) { rootNodes.clear(); mpRootNodes.clear(); for (final ITaskInstance currentRoot : session) { currentRoot.accept(new DefaultTaskInstanceTraversingVisitor() { private ITaskInstance currentMpRoot = null; @Override public void visit(ISequenceInstance sequenceInstance) { boolean currentInstancesIsMpRoot = false; if (mostProminentTasks.contains(sequenceInstance.getSequence())) { if (currentMpRoot == null) { currentMpRoot = sequenceInstance; currentInstancesIsMpRoot = true; } // else already detected most prominent root task } super.visit(sequenceInstance); if (currentInstancesIsMpRoot) { // if the current instance is also the root instance considering only // most prominent sequences, then reset the stored instance to null // after traversing this task currentMpRoot = null; } } @Override public void visit(IEventTaskInstance eventTaskInstance) { rootNodes.add(currentRoot); mpRootNodes.add(currentMpRoot != null ? currentMpRoot : currentRoot); leafNodes.add(eventTaskInstance); if (rootNodes.size() >= 10) { statistics.addValue(getTaskCoverageMeasure(rootNodes)); while (rootNodes.size() >= 10) { rootNodes.removeFirst(); } } if (mpRootNodes.size() >= 10) { mpStatistics.addValue(getTaskCoverageMeasure(mpRootNodes)); while (mpRootNodes.size() >= 10) { mpRootNodes.removeFirst(); } } } }); } } return leafNodes.size(); } /** * */ private double getTaskCoverageMeasure(LinkedList rootNodes) { int noOfDiffRoots = 0; ITaskInstance prevRoot = null; for (ITaskInstance root : rootNodes) { if (!root.equals(prevRoot)) { noOfDiffRoots++; } prevRoot = root; } return (double) noOfDiffRoots / rootNodes.size(); } }