source: trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/TextInputStatisticsRule.java @ 1335

Last change on this file since 1335 was 1335, checked in by pharms, 10 years ago
  • corrected and extended usability evaluations
  • checking now more text field specific for required input formats and word repetitions
  • checking also for required scrolls
File size: 20.4 KB
RevLine 
[927]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
[922]15package de.ugoe.cs.autoquest.usability;
[442]16
17import java.text.DecimalFormat;
18import java.util.ArrayList;
[1149]19import java.util.Collection;
[442]20import java.util.HashMap;
[1335]21import java.util.HashSet;
22import java.util.LinkedList;
[442]23import java.util.List;
24import java.util.Map;
[1335]25import java.util.Set;
[442]26
[1300]27import de.ugoe.cs.autoquest.eventcore.IEventTarget;
28import de.ugoe.cs.autoquest.eventcore.IEventType;
[922]29import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
30import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea;
31import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField;
[1300]32import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
[1335]33import de.ugoe.cs.autoquest.tasktrees.treeifc.IIterationInstance;
34import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptionalInstance;
35import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelectionInstance;
36import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequenceInstance;
[1300]37import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
[1149]38import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
[1335]39import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
[442]40
41/**
42 * TODO comment
43 *
44 * @version $Revision: $ $Date: 16.07.2012$
45 * @author 2012, last modified by $Author: pharms$
46 */
[1335]47public class TextInputStatisticsRule implements UsabilityEvaluationRule {
[442]48
[561]49    /*
50     * (non-Javadoc)
51     *
52     * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
53     */
54    @Override
[1149]55    public UsabilityEvaluationResult evaluate(ITaskModel taskModel) {
[561]56        TextInputStatistics statistics = new TextInputStatistics();
[1335]57        calculateStatistics(taskModel.getUserSessions(), statistics);
[442]58
[561]59        UsabilityEvaluationResult results = new UsabilityEvaluationResult();
60        analyzeStatistics(statistics, results);
[442]61
[561]62        return results;
63    }
64
65    /**
[1301]66     *
[561]67     */
68    private void analyzeStatistics(TextInputStatistics       statistics,
[442]69                                   UsabilityEvaluationResult results)
70    {
[561]71        checkTextInputRatio(statistics, results);
72        checkTextFieldEntryRepetitions(statistics, results);
73        checkTextFieldNoLetterOrDigitInputs(statistics, results);
[442]74    }
[561]75
76    /**
[1301]77     *
[561]78     */
79    private void checkTextInputRatio(TextInputStatistics       statistics,
80                                     UsabilityEvaluationResult results)
[442]81    {
[561]82        float allTextFieldInputs =
83            statistics.getNoOfTextFieldInputs() + statistics.getNoOfTextAreaInputs();
[442]84
[561]85        float ratio = allTextFieldInputs / (float) statistics.getNoOfAllEvents();
[442]86
[561]87        UsabilityDefectSeverity severity = null;
88        if (ratio > 0.9) {
89            severity = UsabilityDefectSeverity.HIGH;
90        }
91        else if (ratio > 0.7) {
92            severity = UsabilityDefectSeverity.MEDIUM;
93        }
94        else if (ratio > 0.5) {
95            severity = UsabilityDefectSeverity.LOW;
96        }
97        else if (ratio > 0.3) {
98            severity = UsabilityDefectSeverity.INFO;
99        }
[442]100
[561]101        if (severity != null) {
102            Map<String, String> parameters = new HashMap<String, String>();
[1335]103            parameters.put("textInputRatio", DecimalFormat.getInstance().format(ratio * 100));
[561]104
105            results.addDefect
[1335]106                (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_RATIO, parameters);
[442]107        }
108    }
[561]109
110    /**
[1301]111     *
[561]112     */
113    private void checkTextFieldEntryRepetitions(TextInputStatistics       statistics,
114                                                UsabilityEvaluationResult results)
[442]115    {
[1335]116        List<TextFieldCorrelation> textFieldCorrelations =
117            statistics.determineTextFieldCorrelations();
118       
119        for (TextFieldCorrelation entry : textFieldCorrelations) {
120            int noOfUsagesOfTextField1 = statistics.getUsageCount(entry.textField1);
121            int noOfUsagesOfTextField2 = statistics.getUsageCount(entry.textField2);
122            int noOfUsagesOfTextField1WithSameTextInTextField2 = entry.enteredTexts.size();
123           
124            float ratioTextField1 =
125                noOfUsagesOfTextField1WithSameTextInTextField2 / (float) noOfUsagesOfTextField1;
126           
127            float ratioTextField2 =
128                noOfUsagesOfTextField1WithSameTextInTextField2 / (float) noOfUsagesOfTextField2;
129           
130            createTextFieldEntryRepetitionDefect
131                (ratioTextField1, entry.textField1, entry.textField2, results);
132           
133            createTextFieldEntryRepetitionDefect
134                (ratioTextField2, entry.textField2, entry.textField1, results);
135           
[496]136        }
[1335]137    }
[496]138
[1335]139    /**
140     *
141     */
142    private void createTextFieldEntryRepetitionDefect(float                     ratioOfEqualEntries,
143                                                      ITextField                textField1,
144                                                      ITextField                textField2,
145                                                      UsabilityEvaluationResult results)
146    {
[561]147        UsabilityDefectSeverity severity = null;
[1335]148        if (ratioOfEqualEntries > 0.9) {
[561]149            severity = UsabilityDefectSeverity.HIGH;
150        }
[1335]151        else if (ratioOfEqualEntries > 0.5) {
[561]152            severity = UsabilityDefectSeverity.MEDIUM;
153        }
[1335]154        else if (ratioOfEqualEntries > 0.2) {
[561]155            severity = UsabilityDefectSeverity.LOW;
156        }
[1335]157        else if (ratioOfEqualEntries > 0.1) {
[561]158            severity = UsabilityDefectSeverity.INFO;
159        }
[1335]160       
[561]161        if (severity != null) {
162            Map<String, String> parameters = new HashMap<String, String>();
[1335]163            parameters.put("textRepetitionRatio",
164                           DecimalFormat.getInstance().format(ratioOfEqualEntries * 100));
165            parameters.put("textField1", textField1.toString());
166            parameters.put("textField2", textField2.toString());
[561]167
168            results.addDefect
[1335]169                (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_REPETITIONS, parameters);
[561]170        }
[496]171    }
172
[561]173    /**
[1301]174     *
[561]175     */
176    private void checkTextFieldNoLetterOrDigitInputs(TextInputStatistics       statistics,
177                                                     UsabilityEvaluationResult results)
[442]178    {
[1335]179        for (ITextField textField : statistics.getAllTextFields()) {
180            int allCharactersCount = 0;
181            int noLetterOrDigitCount = 0;
[561]182
[1335]183            for (String textInput : statistics.getAllInputsInto(textField)) {
184                for (int j = 0; j < textInput.length(); j++) {
185                    if (!Character.isWhitespace(textInput.charAt(j))) {
186                        if (!Character.isLetterOrDigit(textInput.charAt(j))) {
[561]187                            noLetterOrDigitCount++;
188                        }
189                        allCharactersCount++;
190                    }
191                }
192            }
[442]193
[1335]194            float ratio = (float) noLetterOrDigitCount / (float) allCharactersCount;
[561]195
[1335]196            UsabilityDefectSeverity severity = null;
197            if (ratio > 0.1) { // every 10th sign
198                severity = UsabilityDefectSeverity.HIGH;
199            }
200            else if (ratio > 0.05) { // every 20th sign
201                severity = UsabilityDefectSeverity.MEDIUM;
202            }
203            else if (ratio > 0.02) { // every 50th sign
204                severity = UsabilityDefectSeverity.LOW;
205            }
206            else if (ratio > 0.01) { // every 100th sign
207                severity = UsabilityDefectSeverity.INFO;
208            }
[442]209
[1335]210            if (severity != null) {
211                Map<String, String> parameters = new HashMap<String, String>();
212                parameters.put("textField", textField.toString());
213                parameters.put("noLetterOrDigitRatio",
214                               DecimalFormat.getInstance().format(ratio * 100));
[561]215
[1335]216                results.addDefect
217                    (severity, UsabilityDefectDescription.TEXT_FIELD_NO_LETTER_OR_DIGIT_RATIO, parameters);
218            }
[561]219        }
[442]220    }
221
222    /**
[561]223     *
[442]224     */
[1335]225    private void calculateStatistics(Collection<IUserSession> sessions,
226                                     TextInputStatistics      statistics)
227    {
228        System.out.print("calculating statistics ... ");
229        for (IUserSession session : sessions) {
230            for (ITaskInstance taskInstance : session) {
231                calculateStatistics(taskInstance, session, statistics);
232            }
[1149]233        }
[1335]234        System.out.println("done");
[1149]235    }
236
237    /**
[1301]238     *
[1149]239     */
[1335]240    private void calculateStatistics(ITaskInstance       taskInstance,
241                                     IUserSession        session,
242                                     TextInputStatistics statistics)
243    {
244        if (isTextInput(taskInstance)) {
245            calculateStatistics((IEventTaskInstance) taskInstance, session, statistics);
[561]246        }
247        else {
[1335]248            if (taskInstance instanceof ISequenceInstance) {
249                for (ITaskInstance child : (ISequenceInstance) taskInstance) {
250                    calculateStatistics(child, session, statistics);
[561]251                }
252            }
[1335]253            else if (taskInstance instanceof IIterationInstance) {
254                for (ITaskInstance child : (IIterationInstance) taskInstance) {
255                    calculateStatistics(child, session, statistics);
256                }
257            }
258            else if (taskInstance instanceof ISelectionInstance) {
[1149]259                calculateStatistics
[1335]260                    (((ISelectionInstance) taskInstance).getChild(), session, statistics);
[1149]261            }
[1335]262            else if (taskInstance instanceof IOptionalInstance) {
263                calculateStatistics
264                    (((IOptionalInstance) taskInstance).getChild(), session, statistics);
265            }
266            else{
[1149]267                statistics.incrementNoOfOtherEventTasks();
268            }
[561]269        }
[442]270    }
271
272    /**
[1300]273     *
274     */
[1335]275    private boolean isTextInput(ITaskInstance taskInstance) {
276        if (taskInstance instanceof IEventTaskInstance) {
277            return ((IEventTaskInstance) taskInstance).getEvent().getType() instanceof TextInput;
[1300]278        }
279
280        return false;
281    }
282
283    /**
[1301]284     *
[442]285     */
[1335]286    private void calculateStatistics(IEventTaskInstance  taskInstance,
287                                     IUserSession        session,
288                                     TextInputStatistics statistics)
289    {
290        IEventType type = taskInstance.getEvent().getType();
291        IEventTarget target = taskInstance.getEvent().getTarget();
[442]292
[1335]293        if (type instanceof TextInput) {
294            if (target instanceof ITextField) {
295                statistics.addTextFieldInput(taskInstance, session);
[1300]296            }
[1335]297            else if (target instanceof ITextArea) {
298                statistics.addTextAreaInput(taskInstance, session);
299            }
[561]300        }
[442]301    }
302
303    /**
[1301]304     *
[442]305     */
[1335]306/*    private static String[] determineTextFragments(String enteredText) {
[561]307        List<String> fragments = new ArrayList<String>();
[442]308
[561]309        StringBuffer fragment = new StringBuffer();
310        char lastChar = 0;
311
312        for (int i = 0; i < enteredText.length(); i++) {
313            char currentChar = enteredText.charAt(i);
314
315            if (!isEqualCharacterType(lastChar, currentChar)) {
316                // the previous fragment ended. so finalize it and start a new one
317                if ((fragment != null) && (fragment.length() > 0)) {
[1335]318                    String fragmentStr = fragment.toString().trim();
319                   
320                    if (!"".equals(fragmentStr)) {
321                        fragments.add(fragmentStr);
322                    }
323                   
[561]324                    fragment = new StringBuffer();
325                }
326            }
327
328            fragment.append(currentChar);
329            lastChar = currentChar;
330        }
331
332        if ((fragment != null) && (fragment.length() > 0)) {
[1335]333            String fragmentStr = fragment.toString().trim();
334           
335            if (!"".equals(fragmentStr)) {
336                fragments.add(fragmentStr);
337            }
[561]338        }
339
340        return fragments.toArray(new String[fragments.size()]);
[442]341    }
342
343    /**
[1301]344     *
[442]345     */
[1335]346/*    private static boolean isEqualCharacterType(char char1, char char2) {
[561]347        return
348            ((char1 == char2) ||
349            (Character.isWhitespace(char1) && Character.isWhitespace(char2)) ||
350            (Character.isDigit(char1) && Character.isDigit(char2)) ||
351            (Character.isLetter(char1) && Character.isLetter(char2)) ||
352            (Character.isJavaIdentifierPart(char1) && Character.isJavaIdentifierPart(char2)));
[1335]353    }*/
[442]354
355    /**
[1335]356     *
[442]357     */
[1335]358    private static class TextInputStatistics {
[561]359       
360        /** */
[1335]361        private List<IEventTaskInstance> textFieldInputs = new ArrayList<IEventTaskInstance>();
362       
363        /** */
364        private Map<ITextField, List<String>> textFields = new HashMap<ITextField, List<String>>();
[442]365
[561]366        /** */
[1335]367        private List<IEventTaskInstance> textAreaInputs = new ArrayList<IEventTaskInstance>();
[561]368
369        /** */
[1335]370        private Map<IUserSession, Map<String, TextEntryData>> textEntries =
371            new HashMap<IUserSession, Map<String, TextEntryData>>();
372
373        /** */
[561]374        private int otherEventsCount;
375
376        /**
[1301]377         *
[561]378         */
[1335]379        private void addTextFieldInput(IEventTaskInstance instance, IUserSession session) {
380            String enteredText = ((TextInput) instance.getEvent().getType()).getEnteredText();
381           
382            if ((enteredText != null) && (!"".equals(enteredText.trim()))) {
383                enteredText = enteredText.trim();
384               
385                textFieldInputs.add(instance);
386
387                // store text entries into text fields
388                List<String> entries = textFields.get(instance.getEvent().getTarget());
389
390                if (entries == null) {
391                    entries = new LinkedList<String>();
392                    textFields.put((ITextField) instance.getEvent().getTarget(), entries);
393                }
394               
395                entries.add(enteredText);
396
397                // writing down all text entries in text fields to check later for cooccurrences in
398                // same session
399                Map<String, TextEntryData> sessionTextEntries = textEntries.get(session);
400
401                if (sessionTextEntries == null) {
402                    sessionTextEntries = new HashMap<String, TextEntryData>();
403                    textEntries.put(session, sessionTextEntries);
404                }
405
406                TextEntryData data = sessionTextEntries.get(enteredText);
407
408                if (data == null) {
409                    data = new TextEntryData(enteredText);
410                    sessionTextEntries.put(enteredText, data);
411                }
412
413                data.addTaskInstance(instance);
414            }
[561]415        }
[1335]416       
417        /**
418         *
419         */
420        public List<String> getAllInputsInto(ITextField textField) {
421            return textFields.get(textField);
422        }
[561]423
424        /**
[1301]425         *
[561]426         */
[1335]427        private int getUsageCount(ITextField textField) {
428            List<String> entries = textFields.get(textField);
429           
430            if (entries == null) {
431                return 0;
432            }
433            else {
434                return entries.size();
435            }
[561]436        }
437
438        /**
[1301]439         *
[561]440         */
[1335]441        private List<TextFieldCorrelation> determineTextFieldCorrelations() {
442            System.out.print("determining text field correlations of " + textFields.size() +
443                             " text fields ... ");
444            List<TextFieldCorrelation> correlations = new ArrayList<TextFieldCorrelation>();
445           
446            // we need an ordered list of text fields to be able compare all with each other
447            // through a nested loop
448            List<ITextField> textFieldList = getAllTextFields();
449           
450            List<TextEntryData> relevantTextEntryData = new LinkedList<TextEntryData>();
451           
452            for (Map<String, TextEntryData> sessionSpecEntries : textEntries.values()) {
453                for (TextEntryData data : sessionSpecEntries.values()) {
454                    if (data.textFields.size() > 1) {
455                        relevantTextEntryData.add(data);
456                    }
457                }
458            }
459           
460            for (int i = 0; i < (textFieldList.size() - 1); i++) {
461                for (int j = i + 1; j < textFieldList.size(); j++) {
462                    // count the number of times, in which the same text was entered in both
463                    // text fields within the same session
464                    List<String> sameEnteredTexts = new LinkedList<String>();
465
466                    for (TextEntryData data : relevantTextEntryData) {
467                        if (data.textFields.contains(textFieldList.get(i)) &&
468                            data.textFields.contains(textFieldList.get(j)))
469                        {
470                            sameEnteredTexts.add(data.enteredText);
471                        }
472                    }
473
474                    if (sameEnteredTexts.size() > 0) {
475                        // for the checked combination of text fields, there is at least once
476                        // the same text entered into both text fields during the same session
477                        correlations.add(new TextFieldCorrelation(textFieldList.get(i),
478                                                                  textFieldList.get(j),
479                                                                  sameEnteredTexts));
480                    }
481                }
482            }
483           
484            System.out.println("done");
485           
486            return correlations;
487        }
488
489        /**
490         *
491         */
492        private void addTextAreaInput(IEventTaskInstance instance, IUserSession session) {
493            textAreaInputs.add(instance);
494        }
495
496        /**
497         *
498         */
499        private int getNoOfAllEvents() {
[561]500            return textFieldInputs.size() + textAreaInputs.size() + otherEventsCount;
501        }
502
503        /**
[1301]504         *
[561]505         */
[1335]506        private int getNoOfTextFieldInputs() {
[561]507            return textFieldInputs.size();
508        }
509
510        /**
[1301]511         *
[561]512         */
[1335]513        private int getNoOfTextAreaInputs() {
514            return textAreaInputs.size();
[561]515        }
516
517        /**
[1301]518         *
[561]519         */
[1335]520        private void incrementNoOfOtherEventTasks() {
521            otherEventsCount++;
[561]522        }
523
524        /**
[1335]525         *
526         */
527        private List<ITextField> getAllTextFields() {
528            List<ITextField> textFieldList = new ArrayList<ITextField>(textFields.size());
529           
530            for (ITextField textField : textFields.keySet()) {
531                textFieldList.add(textField);
532            }
533           
534            return textFieldList;
535        }
536    }
537   
538    /**
539     *
540     */
541    private static class TextEntryData {
542       
543        /** */
544        private String enteredText;
545       
546        /** */
547        private List<IEventTaskInstance> respectiveTaskInstances =
548            new LinkedList<IEventTaskInstance>();
549
550        /** */
551        private Set<ITextField> textFields = new HashSet<ITextField>();
552       
553        /**
[1301]554         *
[561]555         */
[1335]556        private TextEntryData(String text) {
557            this.enteredText = text;
[561]558        }
559
560        /**
[1301]561         *
[561]562         */
[1335]563        private void addTaskInstance(IEventTaskInstance instance) {
564            respectiveTaskInstances.add(instance);
565            textFields.add((ITextField) instance.getEvent().getTarget());
[561]566        }
[1335]567       
568    }
[561]569
[1335]570    /**
571     *
572     */
573    private static class TextFieldCorrelation {
574       
575        /** */
576        private List<String> enteredTexts = new LinkedList<String>();
577
578        /** */
579        private ITextField textField1;
580       
581        /** */
582        private ITextField textField2;
583       
584        /**
585         *
586         */
587        private TextFieldCorrelation(ITextField   textField1,
588                                     ITextField   textField2,
589                                     List<String> enteredTexts)
590        {
591            this.textField1 = textField1;
592            this.textField2 = textField2;
593            this.enteredTexts = enteredTexts;
594        }
595
[442]596    }
597
598}
Note: See TracBrowser for help on using the repository browser.