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

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