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
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.util.ArrayList;
18import java.util.Collection;
19import java.util.HashMap;
20import java.util.HashSet;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.Map;
24import java.util.Set;
25
26import de.ugoe.cs.autoquest.eventcore.IEventTarget;
27import de.ugoe.cs.autoquest.eventcore.IEventType;
28import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
29import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea;
30import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField;
31import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
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;
36import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
37import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
38import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
39
40/**
41 * TODO comment
42 *
43 * @version $Revision: $ $Date: 16.07.2012$
44 * @author 2012, last modified by $Author: pharms$
45 */
46public class TextInputStatisticsRule implements UsabilityEvaluationRule {
47
48    /*
49     * (non-Javadoc)
50     *
51     * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
52     */
53    @Override
54    public UsabilityEvaluationResult evaluate(ITaskModel taskModel) {
55        TextInputStatistics statistics = new TextInputStatistics();
56        calculateStatistics(taskModel.getUserSessions(), statistics);
57
58        UsabilityEvaluationResult results = new UsabilityEvaluationResult(taskModel);
59        analyzeStatistics(statistics, results);
60
61        return results;
62    }
63
64    /**
65     *
66     */
67    private void analyzeStatistics(TextInputStatistics       statistics,
68                                   UsabilityEvaluationResult results)
69    {
70        checkTextInputRatio(statistics, results);
71        checkTextFieldEntryRepetitions(statistics, results);
72        checkTextFieldNoLetterOrDigitInputs(statistics, results);
73    }
74
75    /**
76     *
77     */
78    private void checkTextInputRatio(TextInputStatistics       statistics,
79                                     UsabilityEvaluationResult results)
80    {
81        int allTextFieldInputs =
82            statistics.getNoOfTextFieldInputs() + statistics.getNoOfTextAreaInputs();
83
84        int ratio = 1000 * allTextFieldInputs / statistics.getNoOfAllEvents();
85
86        // TODO comment magic numbers
87        UsabilityDefectSeverity severity = UsabilityDefectSeverity.getSeverity
88            (ratio, 900, 700, 500, 300);
89       
90        if (severity != null) {
91            Map<String, Object> parameters = new HashMap<String, Object>();
92            parameters.put("textInputRatio", (ratio / 10));
93
94            results.addDefect
95                (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_RATIO, parameters);
96        }
97    }
98
99    /**
100     *
101     */
102    private void checkTextFieldEntryRepetitions(TextInputStatistics       statistics,
103                                                UsabilityEvaluationResult results)
104    {
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           
113            int ratioTextField1 =
114                1000 * noOfUsagesOfTextField1WithSameTextInTextField2 / noOfUsagesOfTextField1;
115           
116            int ratioTextField2 =
117                1000 * noOfUsagesOfTextField1WithSameTextInTextField2 / noOfUsagesOfTextField2;
118
119            createTextFieldEntryRepetitionDefect(ratioTextField1, entry.textField1,
120                                                 entry.textField2, results);
121           
122            createTextFieldEntryRepetitionDefect(ratioTextField2, entry.textField2,
123                                                 entry.textField1, results);
124           
125        }
126    }
127
128    /**
129     *
130     */
131    private void createTextFieldEntryRepetitionDefect(int                       ratioOfEqualEntries,
132                                                      ITextField                textField1,
133                                                      ITextField                textField2,
134                                                      UsabilityEvaluationResult results)
135    {
136        // TODO comment magic numbers
137        UsabilityDefectSeverity severity = UsabilityDefectSeverity.getSeverity
138            (ratioOfEqualEntries, 900, 500, 200, 100);
139       
140        if (severity != null) {
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);
145
146            results.addDefect
147                (severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_REPETITIONS, parameters);
148        }
149    }
150
151    /**
152     *
153     */
154    private void checkTextFieldNoLetterOrDigitInputs(TextInputStatistics       statistics,
155                                                     UsabilityEvaluationResult results)
156    {
157        for (ITextField textField : statistics.getAllTextFields()) {
158            int allCharactersCount = 0;
159            int noLetterOrDigitCount = 0;
160
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))) {
165                            noLetterOrDigitCount++;
166                        }
167                        allCharactersCount++;
168                    }
169                }
170            }
171
172            int ratio = 1000 * noLetterOrDigitCount / allCharactersCount;
173
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                                                    );
181
182            if (severity != null) {
183                Map<String, Object> parameters = new HashMap<String, Object>();
184                parameters.put("textField", textField);
185                parameters.put("noLetterOrDigitRatio", (ratio / 10));
186
187                results.addDefect
188                    (severity, UsabilityDefectDescription.TEXT_FIELD_NO_LETTER_OR_DIGIT_RATIO,
189                     parameters);
190            }
191        }
192    }
193
194    /**
195     *
196     */
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            }
205        }
206        System.out.println("done");
207    }
208
209    /**
210     *
211     */
212    private void calculateStatistics(ITaskInstance       taskInstance,
213                                     IUserSession        session,
214                                     TextInputStatistics statistics)
215    {
216        if (isTextInput(taskInstance)) {
217            calculateStatistics((IEventTaskInstance) taskInstance, session, statistics);
218        }
219        else {
220            if (taskInstance instanceof ISequenceInstance) {
221                for (ITaskInstance child : (ISequenceInstance) taskInstance) {
222                    calculateStatistics(child, session, statistics);
223                }
224            }
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) {
231                calculateStatistics
232                    (((ISelectionInstance) taskInstance).getChild(), session, statistics);
233            }
234            else if (taskInstance instanceof IOptionalInstance) {
235                calculateStatistics
236                    (((IOptionalInstance) taskInstance).getChild(), session, statistics);
237            }
238            else{
239                statistics.incrementNoOfOtherEventTasks();
240            }
241        }
242    }
243
244    /**
245     *
246     */
247    private boolean isTextInput(ITaskInstance taskInstance) {
248        if (taskInstance instanceof IEventTaskInstance) {
249            return ((IEventTaskInstance) taskInstance).getEvent().getType() instanceof TextInput;
250        }
251
252        return false;
253    }
254
255    /**
256     *
257     */
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();
264
265        if (type instanceof TextInput) {
266            if (target instanceof ITextField) {
267                statistics.addTextFieldInput(taskInstance, session);
268            }
269            else if (target instanceof ITextArea) {
270                statistics.addTextAreaInput(taskInstance, session);
271            }
272        }
273    }
274
275    /**
276     *
277     */
278/*    private static String[] determineTextFragments(String enteredText) {
279        List<String> fragments = new ArrayList<String>();
280
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)) {
290                    String fragmentStr = fragment.toString().trim();
291                   
292                    if (!"".equals(fragmentStr)) {
293                        fragments.add(fragmentStr);
294                    }
295                   
296                    fragment = new StringBuffer();
297                }
298            }
299
300            fragment.append(currentChar);
301            lastChar = currentChar;
302        }
303
304        if ((fragment != null) && (fragment.length() > 0)) {
305            String fragmentStr = fragment.toString().trim();
306           
307            if (!"".equals(fragmentStr)) {
308                fragments.add(fragmentStr);
309            }
310        }
311
312        return fragments.toArray(new String[fragments.size()]);
313    }
314
315    /**
316     *
317     */
318/*    private static boolean isEqualCharacterType(char char1, char char2) {
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)));
325    }*/
326
327    /**
328     *
329     */
330    private static class TextInputStatistics {
331       
332        /** */
333        private List<IEventTaskInstance> textFieldInputs = new ArrayList<IEventTaskInstance>();
334       
335        /** */
336        private Map<ITextField, List<String>> textFields = new HashMap<ITextField, List<String>>();
337
338        /** */
339        private List<IEventTaskInstance> textAreaInputs = new ArrayList<IEventTaskInstance>();
340
341        /** */
342        private Map<IUserSession, Map<String, TextEntryData>> textEntries =
343            new HashMap<IUserSession, Map<String, TextEntryData>>();
344
345        /** */
346        private int otherEventsCount;
347
348        /**
349         *
350         */
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            }
387        }
388       
389        /**
390         *
391         */
392        public List<String> getAllInputsInto(ITextField textField) {
393            return textFields.get(textField);
394        }
395
396        /**
397         *
398         */
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            }
408        }
409
410        /**
411         *
412         */
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() {
472            return textFieldInputs.size() + textAreaInputs.size() + otherEventsCount;
473        }
474
475        /**
476         *
477         */
478        private int getNoOfTextFieldInputs() {
479            return textFieldInputs.size();
480        }
481
482        /**
483         *
484         */
485        private int getNoOfTextAreaInputs() {
486            return textAreaInputs.size();
487        }
488
489        /**
490         *
491         */
492        private void incrementNoOfOtherEventTasks() {
493            otherEventsCount++;
494        }
495
496        /**
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        /**
526         *
527         */
528        private TextEntryData(String text) {
529            this.enteredText = text;
530        }
531
532        /**
533         *
534         */
535        private void addTaskInstance(IEventTaskInstance instance) {
536            respectiveTaskInstances.add(instance);
537            textFields.add((ITextField) instance.getEvent().getTarget());
538        }
539       
540    }
541
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
568    }
569
570}
Note: See TracBrowser for help on using the repository browser.