// 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.List; import java.util.Map; 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.IEventTask; import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance; import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship; 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; /** * TODO comment * * @version $Revision: $ $Date: 16.07.2012$ * @author 2012, last modified by $Author: pharms$ */ public class TextInputStatisticsRule implements de.ugoe.cs.autoquest.usability.UsabilityEvaluationRule { /* * (non-Javadoc) * * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree) */ @Override public UsabilityEvaluationResult evaluate(ITaskModel taskModel) { TextInputStatistics statistics = new TextInputStatistics(); calculateStatistics(taskModel.getTasks(), statistics); UsabilityEvaluationResult results = new UsabilityEvaluationResult(); analyzeStatistics(statistics, results); return results; } /** * TODO: comment * * @param statistics * @param results */ private void analyzeStatistics(TextInputStatistics statistics, UsabilityEvaluationResult results) { checkTextInputRatio(statistics, results); checkTextFieldEntryRepetitions(statistics, results); checkTextFieldNoLetterOrDigitInputs(statistics, results); } /** * TODO: comment * * @param statistics * @param 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 (new UsabilityDefect(severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_RATIO, parameters)); } } /** * TODO: comment * * @param statistics * @param results */ private void checkTextFieldEntryRepetitions(TextInputStatistics statistics, UsabilityEvaluationResult results) { Map words = new HashMap(); int numberOfRepeatedWords = 0; int maxRepetitions = 0; for (int i = 0; i < statistics.getNoOfTextFieldInputs(); i++) { String[] fragments = statistics.getTextFieldInputFragments(i); for (String fragment : fragments) { if (!"".equals(fragment.trim())) { Integer count = words.get(fragment); if (count == null) { words.put(fragment, 1); } else { count++; words.put(fragment, count); maxRepetitions = Math.max(count, maxRepetitions); if (count == 2) { // do not calculate repeated words several times numberOfRepeatedWords++; } } } } } UsabilityDefectSeverity severity = null; if ((numberOfRepeatedWords > 10) || (maxRepetitions > 10)) { severity = UsabilityDefectSeverity.HIGH; } else if ((numberOfRepeatedWords > 4) || (maxRepetitions > 4)) { severity = UsabilityDefectSeverity.MEDIUM; } else if ((numberOfRepeatedWords > 2) || (maxRepetitions > 2)) { severity = UsabilityDefectSeverity.LOW; } else if ((numberOfRepeatedWords > 1) || (maxRepetitions > 1)) { severity = UsabilityDefectSeverity.INFO; } if (severity != null) { Map parameters = new HashMap(); parameters.put("textRepetitionRatio", numberOfRepeatedWords + " repeated tokens, up to " + maxRepetitions + " repetitions per token"); results.addDefect (new UsabilityDefect(severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_REPETITIONS, parameters)); } } /** * TODO: comment * * @param statistics * @param results */ private void checkTextFieldNoLetterOrDigitInputs(TextInputStatistics statistics, UsabilityEvaluationResult results) { int allCharactersCount = 0; int noLetterOrDigitCount = 0; for (int i = 0; i < statistics.getNoOfTextFieldInputs(); i++) { String[] fragments = statistics.getTextFieldInputFragments(i); for (String fragment : fragments) { String effectiveFragment = fragment.trim(); for (int j = 0; j < effectiveFragment.length(); j++) { if (!Character.isWhitespace(effectiveFragment.charAt(j))) { if (!Character.isLetterOrDigit(effectiveFragment.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("noLetterOrDigitRatio", allCharactersCount + " entered characters of " + "which " + noLetterOrDigitCount + " were no letter or digit"); results.addDefect (new UsabilityDefect(severity, UsabilityDefectDescription.TEXT_FIELD_NO_LETTER_OR_DIGIT_RATIO, parameters)); } } /** * TODO: comment * * @param taskTree * @param statistics */ private void calculateStatistics(Collection tasks, TextInputStatistics statistics) { for (ITask task : tasks) { calculateStatistics(task, statistics); } } /** * TODO: comment * * @param taskTree * @param statistics */ private void calculateStatistics(ITask task, TextInputStatistics statistics) { if (isTextInput(task)) { calculateStatistics((IEventTask) task, statistics); } else { if (task instanceof IStructuringTemporalRelationship) { for (ITask child : ((IStructuringTemporalRelationship) task).getChildren()) { calculateStatistics(child, statistics); } } else if (task instanceof IMarkingTemporalRelationship) { calculateStatistics (((IMarkingTemporalRelationship) task).getMarkedTask(), statistics); } else { statistics.incrementNoOfOtherEventTasks(); } } } /** *

* TODO: comment *

* * @param task * @return */ private boolean isTextInput(ITask task) { if (task.getInstances().size() > 0) { ITaskInstance instance = task.getInstances().iterator().next(); if (instance instanceof IEventTaskInstance) { return ((IEventTaskInstance) instance).getEvent().getType() instanceof TextInput; } } return false; } /** * TODO: comment * * @param taskTree * @param statistics */ private void calculateStatistics(IEventTask node, TextInputStatistics statistics) { for (ITaskInstance instance : node.getInstances()) { IEventType type = ((IEventTaskInstance) instance).getEvent().getType(); IEventTarget target = ((IEventTaskInstance) instance).getEvent().getTarget(); if (type instanceof TextInput) { String[] fragments = determineTextFragments(((TextInput) type).getEnteredText()); if (target instanceof ITextField) { statistics.addTextFieldInput(node, fragments); } else if (target instanceof ITextArea) { statistics.addTextAreaInput(node, fragments); } } } } /** * TODO: comment * * @param enteredText * @return */ private 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)) { fragments.add(fragment.toString()); fragment = new StringBuffer(); } } fragment.append(currentChar); lastChar = currentChar; } if ((fragment != null) && (fragment.length() > 0)) { fragments.add(fragment.toString()); } return fragments.toArray(new String[fragments.size()]); } /** * TODO: comment * * @param lastChar * @param currentChar * @return */ private 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))); } /** * TODO comment * * @version $Revision: $ $Date: 16.07.2012$ * @author 2012, last modified by $Author: pharms$ */ public static class TextInputStatistics { /** */ private List textFieldInputs = new ArrayList(); /** */ private List textAreaInputs = new ArrayList(); /** */ private int otherEventsCount; /** * TODO: comment * * @param node * @param fragments * */ public void addTextFieldInput(IEventTask node, String[] fragments) { textFieldInputs.add(new Object[] { node, fragments }); } /** * TODO: comment * * @param node * @param fragments * */ public void addTextAreaInput(IEventTask node, String[] fragments) { textAreaInputs.add(new Object[] { node, fragments }); } /** * TODO: comment * * @return */ public int getNoOfAllEvents() { return textFieldInputs.size() + textAreaInputs.size() + otherEventsCount; } /** * TODO: comment * * @return */ public int getNoOfTextFieldInputs() { return textFieldInputs.size(); } /** * TODO: comment * * @param i * @return */ public String[] getTextFieldInputFragments(int index) { return (String[]) textFieldInputs.get(index)[1]; } /** * TODO: comment * * @return */ public int getNoOfTextAreaInputs() { return textAreaInputs.size(); } /** * TODO: comment * * @param i * @return */ public String[] getTextAreaInputFragments(int index) { return (String[]) textAreaInputs.get(index)[1]; } /** * TODO: comment * */ public void incrementNoOfOtherEventTasks() { otherEventsCount++; } } }