// 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.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.logging.Level; import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor; import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship; import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence; import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel; import de.ugoe.cs.util.console.Console; /** * TODO comment * * @version $Revision: $ $Date: 16.07.2012$ * @author 2012, last modified by $Author: pharms$ */ public class UsabilityEvaluationManager { /** */ private List rules = new ArrayList(); /** * */ public UsabilityEvaluationManager() { super(); init(); } /** * */ private void init() { // rules.add(new TaskTreeTestRule()); rules.add(new EventCoverageRatioRule()); rules.add(new RequiredInefficientActionsRule()); rules.add(new TargetDistanceRule()); rules.add(new MissingFeedbackRule()); rules.add(new TaskRetryRule()); rules.add(new DataEntryMethodChangeRule()); rules.add(new CommonTaskRateRule()); rules.add(new TextInputStatisticsRule()); rules.add(new CheckBoxMultipleSelectionRule()); rules.add(new MisleadingClickCueRule()); rules.add(new DefaultCursorPositioningRule()); rules.add(new DefaultValueRule()); rules.add(new UnusedGUIElementsRule()); // rules.add(new TaskCooccurrenceRule()); } /** * */ public UsabilityEvaluationResult evaluateUsability(ITaskModel taskModel, int maxCount, boolean onlyMostRepresentative) { Console.traceln(Level.INFO, "evaluating usability of task model " + taskModel); List interimResults = new ArrayList(); Set mostRepresentativeTasks = null; for (UsabilityEvaluationRule rule : rules) { Console.traceln(Level.INFO, "\napplying rule " + rule.getClass().getSimpleName()); UsabilityEvaluationResult ruleResult = rule.evaluate(taskModel); Map> smellGroups = new HashMap<>(); for (UsabilitySmell smell : ruleResult.getAllSmells()) { List smellGroup = smellGroups.get(smell.getBriefDescription()); if (smellGroup == null) { smellGroup = new LinkedList<>(); smellGroups.put(smell.getBriefDescription(), smellGroup); } smellGroup.add(smell); } for (Map.Entry> smellGroup : smellGroups.entrySet()) { Console.traceln(Level.INFO, "the rule found " + smellGroup.getValue().size() + " usability smells of type \"" + smellGroup.getKey() + "\""); ruleResult = new UsabilityEvaluationResult(taskModel, smellGroup.getValue()); checkDuplicates(ruleResult); if ((onlyMostRepresentative) || (maxCount < ruleResult.getAllSmells().size())) { LinkedList sortedSmells = new LinkedList<>(); if (onlyMostRepresentative) { Console.traceln(Level.INFO, "filtering for smells that refer to only most " + "representative tasks."); } for (UsabilitySmell smell : ruleResult.getAllSmells()) { if (onlyMostRepresentative && (smell.getSmellingTask() != null)) { if (mostRepresentativeTasks == null) { mostRepresentativeTasks = getMostRepresentativeSequences(taskModel); } if (!mostRepresentativeTasks.contains(smell.getSmellingTask())) { continue; } } ListIterator iterator = sortedSmells.listIterator(); boolean added = false; while (iterator.hasNext()) { if (iterator.next().getIntensity().getEventCoverage() < smell.getIntensity().getEventCoverage()) { iterator.previous(); iterator.add(smell); added = true; break; } } if (!added) { sortedSmells.add(smell); } } if (maxCount < ruleResult.getAllSmells().size()) { Console.traceln(Level.INFO, "filtering for " + maxCount + " smells of same type with highest event coverage."); while (sortedSmells.size() > maxCount) { sortedSmells.removeLast(); } } Console.traceln(Level.INFO, sortedSmells.size() + " remaining."); ruleResult = new UsabilityEvaluationResult(taskModel, sortedSmells); checkDuplicates(ruleResult); } interimResults.add(ruleResult); } } UsabilityEvaluationResult result = new UsabilityEvaluationResult(taskModel, interimResults); Console.println("the evaluation result contains " + result.getAllSmells().size() + " smells."); return result; } /** *

* TODO: comment *

* * @param taskModel * @return */ private Set getMostRepresentativeSequences(ITaskModel taskModel) { Map> coverageCounts = new HashMap<>(); Map> coverages = new HashMap<>(); int maxCoverage = 0; for (ITask task : taskModel.getTasks()) { if (task instanceof ISequence) { final Set coveredEvents = new HashSet<>(); for (ITaskInstance instance : task.getInstances()) { instance.accept(new DefaultTaskInstanceTraversingVisitor() { @Override public void visit(IEventTaskInstance eventTaskInstance) { coveredEvents.add(eventTaskInstance); } }); } coverages.put((ISequence) task, coveredEvents); List tasksWithSameCoverage = coverageCounts.get(coveredEvents.size()); if (tasksWithSameCoverage == null) { tasksWithSameCoverage = new LinkedList<>(); coverageCounts.put(coveredEvents.size(), tasksWithSameCoverage); } tasksWithSameCoverage.add((ISequence) task); maxCoverage = Math.max(maxCoverage, coveredEvents.size()); } } Set mostRepresentativeSequences = new HashSet<>(); for (int i = maxCoverage; i > 0; i--) { List sequencesWithSameCoverage = coverageCounts.get(i); if (sequencesWithSameCoverage == null) { continue; } for (ISequence sequence : sequencesWithSameCoverage) { mostRepresentativeSequences.add(sequence); } if ((100 * mostRepresentativeSequences.size() / coverages.size()) > 20) { break; } } return mostRepresentativeSequences; } /** * */ private void checkDuplicates(UsabilityEvaluationResult result) { List referredTasks = new ArrayList(); for (UsabilitySmell smell : result.getAllSmells()) { if (smell.getSmellingTask() != null) { referredTasks.add(smell.getSmellingTask()); } } int counter = 0; for (int i = 0; i < referredTasks.size(); i++) { for (int j = 0; j < referredTasks.size(); j++) { if (isChildOf(referredTasks.get(i), referredTasks.get(j))) { counter++; break; } } } if (counter > 0) { Console.traceln(Level.INFO, counter + " of the findings are duplicates in " + "that they refer to tasks whose parent tasks are also " + "referred by the findings"); } } /** * */ private boolean isChildOf(final ITask potChild, ITask potParent) { if (potParent instanceof IStructuringTemporalRelationship) { for (ITask child : ((IStructuringTemporalRelationship) potParent).getChildren()) { if ((child == potChild) || isChildOf(potChild, child)) { return true; } } } else if (potParent instanceof IMarkingTemporalRelationship) { ITask child = ((IMarkingTemporalRelationship) potParent).getMarkedTask(); if ((child == potChild) || isChildOf(potChild, child)) { return true; } } return false; } }