// 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.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import de.ugoe.cs.autoquest.eventcore.IEventTarget; import de.ugoe.cs.autoquest.eventcore.IEventType; import de.ugoe.cs.autoquest.eventcore.gui.TextInput; import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea; import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField; import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.IIterationInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptionalInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelectionInstance; 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; /** * TODO comment * * @version $Revision: $ $Date: 16.07.2012$ * @author 2012, last modified by $Author: pharms$ */ public class TextInputStatisticsRule implements UsabilityEvaluationRule { /* * (non-Javadoc) * * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree) */ @Override public UsabilityEvaluationResult evaluate(ITaskModel taskModel) { TextInputStatistics statistics = new TextInputStatistics(); calculateStatistics(taskModel.getUserSessions(), statistics); UsabilityEvaluationResult results = new UsabilityEvaluationResult(); analyzeStatistics(statistics, results); return results; } /** * */ private void analyzeStatistics(TextInputStatistics statistics, UsabilityEvaluationResult results) { checkTextInputRatio(statistics, results); checkTextFieldEntryRepetitions(statistics, results); checkTextFieldNoLetterOrDigitInputs(statistics, results); } /** * */ private void checkTextInputRatio(TextInputStatistics statistics, UsabilityEvaluationResult results) { float allTextFieldInputs = statistics.getNoOfTextFieldInputs() + statistics.getNoOfTextAreaInputs(); float ratio = allTextFieldInputs / (float) statistics.getNoOfAllEvents(); UsabilityDefectSeverity severity = null; if (ratio > 0.9) { severity = UsabilityDefectSeverity.HIGH; } else if (ratio > 0.7) { severity = UsabilityDefectSeverity.MEDIUM; } else if (ratio > 0.5) { severity = UsabilityDefectSeverity.LOW; } else if (ratio > 0.3) { severity = UsabilityDefectSeverity.INFO; } if (severity != null) { Map parameters = new HashMap(); parameters.put("textInputRatio", DecimalFormat.getInstance().format(ratio * 100)); results.addDefect (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_RATIO, parameters); } } /** * */ private void checkTextFieldEntryRepetitions(TextInputStatistics statistics, UsabilityEvaluationResult results) { List textFieldCorrelations = statistics.determineTextFieldCorrelations(); for (TextFieldCorrelation entry : textFieldCorrelations) { int noOfUsagesOfTextField1 = statistics.getUsageCount(entry.textField1); int noOfUsagesOfTextField2 = statistics.getUsageCount(entry.textField2); int noOfUsagesOfTextField1WithSameTextInTextField2 = entry.enteredTexts.size(); float ratioTextField1 = noOfUsagesOfTextField1WithSameTextInTextField2 / (float) noOfUsagesOfTextField1; float ratioTextField2 = noOfUsagesOfTextField1WithSameTextInTextField2 / (float) noOfUsagesOfTextField2; createTextFieldEntryRepetitionDefect (ratioTextField1, entry.textField1, entry.textField2, results); createTextFieldEntryRepetitionDefect (ratioTextField2, entry.textField2, entry.textField1, results); } } /** * */ private void createTextFieldEntryRepetitionDefect(float ratioOfEqualEntries, ITextField textField1, ITextField textField2, UsabilityEvaluationResult results) { UsabilityDefectSeverity severity = null; if (ratioOfEqualEntries > 0.9) { severity = UsabilityDefectSeverity.HIGH; } else if (ratioOfEqualEntries > 0.5) { severity = UsabilityDefectSeverity.MEDIUM; } else if (ratioOfEqualEntries > 0.2) { severity = UsabilityDefectSeverity.LOW; } else if (ratioOfEqualEntries > 0.1) { severity = UsabilityDefectSeverity.INFO; } if (severity != null) { Map parameters = new HashMap(); parameters.put("textRepetitionRatio", DecimalFormat.getInstance().format(ratioOfEqualEntries * 100)); parameters.put("textField1", textField1.toString()); parameters.put("textField2", textField2.toString()); results.addDefect (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_REPETITIONS, parameters); } } /** * */ private void checkTextFieldNoLetterOrDigitInputs(TextInputStatistics statistics, UsabilityEvaluationResult results) { for (ITextField textField : statistics.getAllTextFields()) { int allCharactersCount = 0; int noLetterOrDigitCount = 0; for (String textInput : statistics.getAllInputsInto(textField)) { for (int j = 0; j < textInput.length(); j++) { if (!Character.isWhitespace(textInput.charAt(j))) { if (!Character.isLetterOrDigit(textInput.charAt(j))) { noLetterOrDigitCount++; } allCharactersCount++; } } } float ratio = (float) noLetterOrDigitCount / (float) allCharactersCount; UsabilityDefectSeverity severity = null; if (ratio > 0.1) { // every 10th sign severity = UsabilityDefectSeverity.HIGH; } else if (ratio > 0.05) { // every 20th sign severity = UsabilityDefectSeverity.MEDIUM; } else if (ratio > 0.02) { // every 50th sign severity = UsabilityDefectSeverity.LOW; } else if (ratio > 0.01) { // every 100th sign severity = UsabilityDefectSeverity.INFO; } if (severity != null) { Map parameters = new HashMap(); parameters.put("textField", textField.toString()); parameters.put("noLetterOrDigitRatio", DecimalFormat.getInstance().format(ratio * 100)); results.addDefect (severity, UsabilityDefectDescription.TEXT_FIELD_NO_LETTER_OR_DIGIT_RATIO, parameters); } } } /** * */ private void calculateStatistics(Collection sessions, TextInputStatistics statistics) { System.out.print("calculating statistics ... "); for (IUserSession session : sessions) { for (ITaskInstance taskInstance : session) { calculateStatistics(taskInstance, session, statistics); } } System.out.println("done"); } /** * */ private void calculateStatistics(ITaskInstance taskInstance, IUserSession session, TextInputStatistics statistics) { if (isTextInput(taskInstance)) { calculateStatistics((IEventTaskInstance) taskInstance, session, statistics); } else { if (taskInstance instanceof ISequenceInstance) { for (ITaskInstance child : (ISequenceInstance) taskInstance) { calculateStatistics(child, session, statistics); } } else if (taskInstance instanceof IIterationInstance) { for (ITaskInstance child : (IIterationInstance) taskInstance) { calculateStatistics(child, session, statistics); } } else if (taskInstance instanceof ISelectionInstance) { calculateStatistics (((ISelectionInstance) taskInstance).getChild(), session, statistics); } else if (taskInstance instanceof IOptionalInstance) { calculateStatistics (((IOptionalInstance) taskInstance).getChild(), session, statistics); } else{ statistics.incrementNoOfOtherEventTasks(); } } } /** * */ private boolean isTextInput(ITaskInstance taskInstance) { if (taskInstance instanceof IEventTaskInstance) { return ((IEventTaskInstance) taskInstance).getEvent().getType() instanceof TextInput; } return false; } /** * */ private void calculateStatistics(IEventTaskInstance taskInstance, IUserSession session, TextInputStatistics statistics) { IEventType type = taskInstance.getEvent().getType(); IEventTarget target = taskInstance.getEvent().getTarget(); if (type instanceof TextInput) { if (target instanceof ITextField) { statistics.addTextFieldInput(taskInstance, session); } else if (target instanceof ITextArea) { statistics.addTextAreaInput(taskInstance, session); } } } /** * */ /* private static String[] determineTextFragments(String enteredText) { List fragments = new ArrayList(); StringBuffer fragment = new StringBuffer(); char lastChar = 0; for (int i = 0; i < enteredText.length(); i++) { char currentChar = enteredText.charAt(i); if (!isEqualCharacterType(lastChar, currentChar)) { // the previous fragment ended. so finalize it and start a new one if ((fragment != null) && (fragment.length() > 0)) { String fragmentStr = fragment.toString().trim(); if (!"".equals(fragmentStr)) { fragments.add(fragmentStr); } fragment = new StringBuffer(); } } fragment.append(currentChar); lastChar = currentChar; } if ((fragment != null) && (fragment.length() > 0)) { String fragmentStr = fragment.toString().trim(); if (!"".equals(fragmentStr)) { fragments.add(fragmentStr); } } return fragments.toArray(new String[fragments.size()]); } /** * */ /* private static boolean isEqualCharacterType(char char1, char char2) { return ((char1 == char2) || (Character.isWhitespace(char1) && Character.isWhitespace(char2)) || (Character.isDigit(char1) && Character.isDigit(char2)) || (Character.isLetter(char1) && Character.isLetter(char2)) || (Character.isJavaIdentifierPart(char1) && Character.isJavaIdentifierPart(char2))); }*/ /** * */ private static class TextInputStatistics { /** */ private List textFieldInputs = new ArrayList(); /** */ private Map> textFields = new HashMap>(); /** */ private List textAreaInputs = new ArrayList(); /** */ private Map> textEntries = new HashMap>(); /** */ private int otherEventsCount; /** * */ private void addTextFieldInput(IEventTaskInstance instance, IUserSession session) { String enteredText = ((TextInput) instance.getEvent().getType()).getEnteredText(); if ((enteredText != null) && (!"".equals(enteredText.trim()))) { enteredText = enteredText.trim(); textFieldInputs.add(instance); // store text entries into text fields List entries = textFields.get(instance.getEvent().getTarget()); if (entries == null) { entries = new LinkedList(); textFields.put((ITextField) instance.getEvent().getTarget(), entries); } entries.add(enteredText); // writing down all text entries in text fields to check later for cooccurrences in // same session Map sessionTextEntries = textEntries.get(session); if (sessionTextEntries == null) { sessionTextEntries = new HashMap(); textEntries.put(session, sessionTextEntries); } TextEntryData data = sessionTextEntries.get(enteredText); if (data == null) { data = new TextEntryData(enteredText); sessionTextEntries.put(enteredText, data); } data.addTaskInstance(instance); } } /** * */ public List getAllInputsInto(ITextField textField) { return textFields.get(textField); } /** * */ private int getUsageCount(ITextField textField) { List entries = textFields.get(textField); if (entries == null) { return 0; } else { return entries.size(); } } /** * */ private List determineTextFieldCorrelations() { System.out.print("determining text field correlations of " + textFields.size() + " text fields ... "); List correlations = new ArrayList(); // we need an ordered list of text fields to be able compare all with each other // through a nested loop List textFieldList = getAllTextFields(); List relevantTextEntryData = new LinkedList(); for (Map sessionSpecEntries : textEntries.values()) { for (TextEntryData data : sessionSpecEntries.values()) { if (data.textFields.size() > 1) { relevantTextEntryData.add(data); } } } for (int i = 0; i < (textFieldList.size() - 1); i++) { for (int j = i + 1; j < textFieldList.size(); j++) { // count the number of times, in which the same text was entered in both // text fields within the same session List sameEnteredTexts = new LinkedList(); for (TextEntryData data : relevantTextEntryData) { if (data.textFields.contains(textFieldList.get(i)) && data.textFields.contains(textFieldList.get(j))) { sameEnteredTexts.add(data.enteredText); } } if (sameEnteredTexts.size() > 0) { // for the checked combination of text fields, there is at least once // the same text entered into both text fields during the same session correlations.add(new TextFieldCorrelation(textFieldList.get(i), textFieldList.get(j), sameEnteredTexts)); } } } System.out.println("done"); return correlations; } /** * */ private void addTextAreaInput(IEventTaskInstance instance, IUserSession session) { textAreaInputs.add(instance); } /** * */ private int getNoOfAllEvents() { return textFieldInputs.size() + textAreaInputs.size() + otherEventsCount; } /** * */ private int getNoOfTextFieldInputs() { return textFieldInputs.size(); } /** * */ private int getNoOfTextAreaInputs() { return textAreaInputs.size(); } /** * */ private void incrementNoOfOtherEventTasks() { otherEventsCount++; } /** * */ private List getAllTextFields() { List textFieldList = new ArrayList(textFields.size()); for (ITextField textField : textFields.keySet()) { textFieldList.add(textField); } return textFieldList; } } /** * */ private static class TextEntryData { /** */ private String enteredText; /** */ private List respectiveTaskInstances = new LinkedList(); /** */ private Set textFields = new HashSet(); /** * */ private TextEntryData(String text) { this.enteredText = text; } /** * */ private void addTaskInstance(IEventTaskInstance instance) { respectiveTaskInstances.add(instance); textFields.add((ITextField) instance.getEvent().getTarget()); } } /** * */ private static class TextFieldCorrelation { /** */ private List enteredTexts = new LinkedList(); /** */ private ITextField textField1; /** */ private ITextField textField2; /** * */ private TextFieldCorrelation(ITextField textField1, ITextField textField2, List enteredTexts) { this.textField1 = textField1; this.textField2 = textField2; this.enteredTexts = enteredTexts; } } }