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
Line 
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
15package de.ugoe.cs.autoquest.usability;
16
17import java.text.DecimalFormat;
18import java.util.ArrayList;
19import java.util.Collection;
20import java.util.HashMap;
21import java.util.HashSet;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26
27import de.ugoe.cs.autoquest.eventcore.IEventTarget;
28import de.ugoe.cs.autoquest.eventcore.IEventType;
29import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
30import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea;
31import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField;
32import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
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;
37import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
38import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
39import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
40
41/**
42 * TODO comment
43 *
44 * @version $Revision: $ $Date: 16.07.2012$
45 * @author 2012, last modified by $Author: pharms$
46 */
47public class TextInputStatisticsRule implements UsabilityEvaluationRule {
48
49    /*
50     * (non-Javadoc)
51     *
52     * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
53     */
54    @Override
55    public UsabilityEvaluationResult evaluate(ITaskModel taskModel) {
56        TextInputStatistics statistics = new TextInputStatistics();
57        calculateStatistics(taskModel.getUserSessions(), statistics);
58
59        UsabilityEvaluationResult results = new UsabilityEvaluationResult();
60        analyzeStatistics(statistics, results);
61
62        return results;
63    }
64
65    /**
66     *
67     */
68    private void analyzeStatistics(TextInputStatistics       statistics,
69                                   UsabilityEvaluationResult results)
70    {
71        checkTextInputRatio(statistics, results);
72        checkTextFieldEntryRepetitions(statistics, results);
73        checkTextFieldNoLetterOrDigitInputs(statistics, results);
74    }
75
76    /**
77     *
78     */
79    private void checkTextInputRatio(TextInputStatistics       statistics,
80                                     UsabilityEvaluationResult results)
81    {
82        float allTextFieldInputs =
83            statistics.getNoOfTextFieldInputs() + statistics.getNoOfTextAreaInputs();
84
85        float ratio = allTextFieldInputs / (float) statistics.getNoOfAllEvents();
86
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        }
100
101        if (severity != null) {
102            Map<String, String> parameters = new HashMap<String, String>();
103            parameters.put("textInputRatio", DecimalFormat.getInstance().format(ratio * 100));
104
105            results.addDefect
106                (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_RATIO, parameters);
107        }
108    }
109
110    /**
111     *
112     */
113    private void checkTextFieldEntryRepetitions(TextInputStatistics       statistics,
114                                                UsabilityEvaluationResult results)
115    {
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           
136        }
137    }
138
139    /**
140     *
141     */
142    private void createTextFieldEntryRepetitionDefect(float                     ratioOfEqualEntries,
143                                                      ITextField                textField1,
144                                                      ITextField                textField2,
145                                                      UsabilityEvaluationResult results)
146    {
147        UsabilityDefectSeverity severity = null;
148        if (ratioOfEqualEntries > 0.9) {
149            severity = UsabilityDefectSeverity.HIGH;
150        }
151        else if (ratioOfEqualEntries > 0.5) {
152            severity = UsabilityDefectSeverity.MEDIUM;
153        }
154        else if (ratioOfEqualEntries > 0.2) {
155            severity = UsabilityDefectSeverity.LOW;
156        }
157        else if (ratioOfEqualEntries > 0.1) {
158            severity = UsabilityDefectSeverity.INFO;
159        }
160       
161        if (severity != null) {
162            Map<String, String> parameters = new HashMap<String, String>();
163            parameters.put("textRepetitionRatio",
164                           DecimalFormat.getInstance().format(ratioOfEqualEntries * 100));
165            parameters.put("textField1", textField1.toString());
166            parameters.put("textField2", textField2.toString());
167
168            results.addDefect
169                (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_REPETITIONS, parameters);
170        }
171    }
172
173    /**
174     *
175     */
176    private void checkTextFieldNoLetterOrDigitInputs(TextInputStatistics       statistics,
177                                                     UsabilityEvaluationResult results)
178    {
179        for (ITextField textField : statistics.getAllTextFields()) {
180            int allCharactersCount = 0;
181            int noLetterOrDigitCount = 0;
182
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))) {
187                            noLetterOrDigitCount++;
188                        }
189                        allCharactersCount++;
190                    }
191                }
192            }
193
194            float ratio = (float) noLetterOrDigitCount / (float) allCharactersCount;
195
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            }
209
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));
215
216                results.addDefect
217                    (severity, UsabilityDefectDescription.TEXT_FIELD_NO_LETTER_OR_DIGIT_RATIO, parameters);
218            }
219        }
220    }
221
222    /**
223     *
224     */
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            }
233        }
234        System.out.println("done");
235    }
236
237    /**
238     *
239     */
240    private void calculateStatistics(ITaskInstance       taskInstance,
241                                     IUserSession        session,
242                                     TextInputStatistics statistics)
243    {
244        if (isTextInput(taskInstance)) {
245            calculateStatistics((IEventTaskInstance) taskInstance, session, statistics);
246        }
247        else {
248            if (taskInstance instanceof ISequenceInstance) {
249                for (ITaskInstance child : (ISequenceInstance) taskInstance) {
250                    calculateStatistics(child, session, statistics);
251                }
252            }
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) {
259                calculateStatistics
260                    (((ISelectionInstance) taskInstance).getChild(), session, statistics);
261            }
262            else if (taskInstance instanceof IOptionalInstance) {
263                calculateStatistics
264                    (((IOptionalInstance) taskInstance).getChild(), session, statistics);
265            }
266            else{
267                statistics.incrementNoOfOtherEventTasks();
268            }
269        }
270    }
271
272    /**
273     *
274     */
275    private boolean isTextInput(ITaskInstance taskInstance) {
276        if (taskInstance instanceof IEventTaskInstance) {
277            return ((IEventTaskInstance) taskInstance).getEvent().getType() instanceof TextInput;
278        }
279
280        return false;
281    }
282
283    /**
284     *
285     */
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();
292
293        if (type instanceof TextInput) {
294            if (target instanceof ITextField) {
295                statistics.addTextFieldInput(taskInstance, session);
296            }
297            else if (target instanceof ITextArea) {
298                statistics.addTextAreaInput(taskInstance, session);
299            }
300        }
301    }
302
303    /**
304     *
305     */
306/*    private static String[] determineTextFragments(String enteredText) {
307        List<String> fragments = new ArrayList<String>();
308
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)) {
318                    String fragmentStr = fragment.toString().trim();
319                   
320                    if (!"".equals(fragmentStr)) {
321                        fragments.add(fragmentStr);
322                    }
323                   
324                    fragment = new StringBuffer();
325                }
326            }
327
328            fragment.append(currentChar);
329            lastChar = currentChar;
330        }
331
332        if ((fragment != null) && (fragment.length() > 0)) {
333            String fragmentStr = fragment.toString().trim();
334           
335            if (!"".equals(fragmentStr)) {
336                fragments.add(fragmentStr);
337            }
338        }
339
340        return fragments.toArray(new String[fragments.size()]);
341    }
342
343    /**
344     *
345     */
346/*    private static boolean isEqualCharacterType(char char1, char char2) {
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)));
353    }*/
354
355    /**
356     *
357     */
358    private static class TextInputStatistics {
359       
360        /** */
361        private List<IEventTaskInstance> textFieldInputs = new ArrayList<IEventTaskInstance>();
362       
363        /** */
364        private Map<ITextField, List<String>> textFields = new HashMap<ITextField, List<String>>();
365
366        /** */
367        private List<IEventTaskInstance> textAreaInputs = new ArrayList<IEventTaskInstance>();
368
369        /** */
370        private Map<IUserSession, Map<String, TextEntryData>> textEntries =
371            new HashMap<IUserSession, Map<String, TextEntryData>>();
372
373        /** */
374        private int otherEventsCount;
375
376        /**
377         *
378         */
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            }
415        }
416       
417        /**
418         *
419         */
420        public List<String> getAllInputsInto(ITextField textField) {
421            return textFields.get(textField);
422        }
423
424        /**
425         *
426         */
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            }
436        }
437
438        /**
439         *
440         */
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() {
500            return textFieldInputs.size() + textAreaInputs.size() + otherEventsCount;
501        }
502
503        /**
504         *
505         */
506        private int getNoOfTextFieldInputs() {
507            return textFieldInputs.size();
508        }
509
510        /**
511         *
512         */
513        private int getNoOfTextAreaInputs() {
514            return textAreaInputs.size();
515        }
516
517        /**
518         *
519         */
520        private void incrementNoOfOtherEventTasks() {
521            otherEventsCount++;
522        }
523
524        /**
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        /**
554         *
555         */
556        private TextEntryData(String text) {
557            this.enteredText = text;
558        }
559
560        /**
561         *
562         */
563        private void addTaskInstance(IEventTaskInstance instance) {
564            respectiveTaskInstances.add(instance);
565            textFields.add((ITextField) instance.getEvent().getTarget());
566        }
567       
568    }
569
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
596    }
597
598}
Note: See TracBrowser for help on using the repository browser.