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

Last change on this file since 1300 was 1300, checked in by pharms, 11 years ago
  • rework of task model to move event instance stuff to task instances
  • introduction of sequence, selection, iteration and optional instances
File size: 14.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.text.DecimalFormat;
18import java.util.ArrayList;
19import java.util.Collection;
20import java.util.HashMap;
21import java.util.List;
22import java.util.Map;
23
24import de.ugoe.cs.autoquest.eventcore.IEventTarget;
25import de.ugoe.cs.autoquest.eventcore.IEventType;
26import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
27import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea;
28import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField;
29import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
30import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
31import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship;
32import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship;
33import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
34import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
35import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
36
37/**
38 * TODO comment
39 *
40 * @version $Revision: $ $Date: 16.07.2012$
41 * @author 2012, last modified by $Author: pharms$
42 */
43public class TextInputStatisticsRule implements de.ugoe.cs.autoquest.usability.UsabilityEvaluationRule {
44
45    /*
46     * (non-Javadoc)
47     *
48     * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
49     */
50    @Override
51    public UsabilityEvaluationResult evaluate(ITaskModel taskModel) {
52        TextInputStatistics statistics = new TextInputStatistics();
53        calculateStatistics(taskModel.getTasks(), statistics);
54
55        UsabilityEvaluationResult results = new UsabilityEvaluationResult();
56        analyzeStatistics(statistics, results);
57
58        return results;
59    }
60
61    /**
62     * TODO: comment
63     *
64     * @param statistics
65     * @param results
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     * TODO: comment
77     *
78     * @param statistics
79     * @param results
80     */
81    private void checkTextInputRatio(TextInputStatistics       statistics,
82                                     UsabilityEvaluationResult results)
83    {
84        float allTextFieldInputs =
85            statistics.getNoOfTextFieldInputs() + statistics.getNoOfTextAreaInputs();
86
87        float ratio = allTextFieldInputs / (float) statistics.getNoOfAllEvents();
88
89        UsabilityDefectSeverity severity = null;
90        if (ratio > 0.9) {
91            severity = UsabilityDefectSeverity.HIGH;
92        }
93        else if (ratio > 0.7) {
94            severity = UsabilityDefectSeverity.MEDIUM;
95        }
96        else if (ratio > 0.5) {
97            severity = UsabilityDefectSeverity.LOW;
98        }
99        else if (ratio > 0.3) {
100            severity = UsabilityDefectSeverity.INFO;
101        }
102
103        if (severity != null) {
104            Map<String, String> parameters = new HashMap<String, String>();
105            parameters.put("textInputRatio", DecimalFormat.getInstance().format(ratio * 100) + "%");
106
107            results.addDefect
108                (new UsabilityDefect(severity, UsabilityDefectDescription.TEXT_FIELD_INPUT_RATIO,
109                                     parameters));
110        }
111    }
112
113    /**
114     * TODO: comment
115     *
116     * @param statistics
117     * @param results
118     */
119    private void checkTextFieldEntryRepetitions(TextInputStatistics       statistics,
120                                                UsabilityEvaluationResult results)
121    {
122        Map<String, Integer> words = new HashMap<String, Integer>();
123        int numberOfRepeatedWords = 0;
124        int maxRepetitions = 0;
125
126        for (int i = 0; i < statistics.getNoOfTextFieldInputs(); i++) {
127            String[] fragments = statistics.getTextFieldInputFragments(i);
128            for (String fragment : fragments) {
129                if (!"".equals(fragment.trim())) {
130                    Integer count = words.get(fragment);
131                    if (count == null) {
132                        words.put(fragment, 1);
133                    }
134                    else {
135                        count++;
136                        words.put(fragment, count);
137                        maxRepetitions = Math.max(count, maxRepetitions);
138
139                        if (count == 2) {
140                            // do not calculate repeated words several times
141                            numberOfRepeatedWords++;
142                        }
143                    }
144                }
145            }
146        }
147
148        UsabilityDefectSeverity severity = null;
149        if ((numberOfRepeatedWords > 10) || (maxRepetitions > 10)) {
150            severity = UsabilityDefectSeverity.HIGH;
151        }
152        else if ((numberOfRepeatedWords > 4) || (maxRepetitions > 4)) {
153            severity = UsabilityDefectSeverity.MEDIUM;
154        }
155        else if ((numberOfRepeatedWords > 2) || (maxRepetitions > 2)) {
156            severity = UsabilityDefectSeverity.LOW;
157        }
158        else if ((numberOfRepeatedWords > 1) || (maxRepetitions > 1)) {
159            severity = UsabilityDefectSeverity.INFO;
160        }
161
162        if (severity != null) {
163            Map<String, String> parameters = new HashMap<String, String>();
164            parameters.put("textRepetitionRatio", numberOfRepeatedWords +
165                           " repeated tokens, up to " + maxRepetitions + " repetitions per token");
166
167            results.addDefect
168                (new UsabilityDefect(severity,
169                                     UsabilityDefectDescription.TEXT_FIELD_INPUT_REPETITIONS,
170                                     parameters));
171        }
172    }
173
174    /**
175     * TODO: comment
176     *
177     * @param statistics
178     * @param results
179     */
180    private void checkTextFieldNoLetterOrDigitInputs(TextInputStatistics       statistics,
181                                                     UsabilityEvaluationResult results)
182    {
183        int allCharactersCount = 0;
184        int noLetterOrDigitCount = 0;
185
186        for (int i = 0; i < statistics.getNoOfTextFieldInputs(); i++) {
187            String[] fragments = statistics.getTextFieldInputFragments(i);
188            for (String fragment : fragments) {
189                String effectiveFragment = fragment.trim();
190                for (int j = 0; j < effectiveFragment.length(); j++) {
191                    if (!Character.isWhitespace(effectiveFragment.charAt(j))) {
192                        if (!Character.isLetterOrDigit(effectiveFragment.charAt(j))) {
193                            noLetterOrDigitCount++;
194                        }
195                        allCharactersCount++;
196                    }
197                }
198            }
199        }
200
201        float ratio = (float) noLetterOrDigitCount / (float) allCharactersCount;
202
203        UsabilityDefectSeverity severity = null;
204        if (ratio > 0.1) // every 10th sign
205        {
206            severity = UsabilityDefectSeverity.HIGH;
207        }
208        else if (ratio > 0.05) // every 20th sign
209        {
210            severity = UsabilityDefectSeverity.MEDIUM;
211        }
212        else if (ratio > 0.02) // every 50th sign
213        {
214            severity = UsabilityDefectSeverity.LOW;
215        }
216        else if (ratio > 0.01) // every 100th sign
217        {
218            severity = UsabilityDefectSeverity.INFO;
219        }
220
221        if (severity != null) {
222            Map<String, String> parameters = new HashMap<String, String>();
223            parameters.put("noLetterOrDigitRatio", allCharactersCount + " entered characters of " +
224                           "which " + noLetterOrDigitCount + " were no letter or digit");
225
226            results.addDefect
227                (new UsabilityDefect(severity,
228                                     UsabilityDefectDescription.TEXT_FIELD_NO_LETTER_OR_DIGIT_RATIO,
229                                     parameters));
230        }
231    }
232
233    /**
234     * TODO: comment
235     *
236     * @param taskTree
237     * @param statistics
238     */
239    private void calculateStatistics(Collection<ITask> tasks, TextInputStatistics statistics) {
240        for (ITask task : tasks) {
241            calculateStatistics(task, statistics);
242        }
243    }
244
245    /**
246     * TODO: comment
247     *
248     * @param taskTree
249     * @param statistics
250     */
251    private void calculateStatistics(ITask task, TextInputStatistics statistics) {
252       
253        if (isTextInput(task)) {
254            calculateStatistics((IEventTask) task, statistics);
255        }
256        else {
257            if (task instanceof IStructuringTemporalRelationship) {
258                for (ITask child : ((IStructuringTemporalRelationship) task).getChildren()) {
259                    calculateStatistics(child, statistics);
260                }
261            }
262            else if (task instanceof IMarkingTemporalRelationship) {
263                calculateStatistics
264                    (((IMarkingTemporalRelationship) task).getMarkedTask(), statistics);
265            }
266            else {
267                statistics.incrementNoOfOtherEventTasks();
268            }
269        }
270    }
271
272    /**
273     * <p>
274     * TODO: comment
275     * </p>
276     *
277     * @param task
278     * @return
279     */
280    private boolean isTextInput(ITask task) {
281        if (task.getInstances().size() > 0) {
282            ITaskInstance instance = task.getInstances().iterator().next();
283            if (instance instanceof IEventTaskInstance) {
284                return ((IEventTaskInstance) instance).getEvent().getType() instanceof TextInput;
285            }
286        }
287
288        return false;
289    }
290
291    /**
292     * TODO: comment
293     *
294     * @param taskTree
295     * @param statistics
296     */
297    private void calculateStatistics(IEventTask node, TextInputStatistics statistics) {
298       
299        for (ITaskInstance instance : node.getInstances()) {
300            IEventType type = ((IEventTaskInstance) instance).getEvent().getType();
301            IEventTarget target = ((IEventTaskInstance) instance).getEvent().getTarget();
302
303            if (type instanceof TextInput) {
304                String[] fragments =
305                    determineTextFragments(((TextInput) type).getEnteredText());
306               
307                if (target instanceof ITextField) {
308                    statistics.addTextFieldInput(node, fragments);
309                }
310                else if (target instanceof ITextArea) {
311                    statistics.addTextAreaInput(node, fragments);
312                }
313            }
314        }
315    }
316
317    /**
318     * TODO: comment
319     *
320     * @param enteredText
321     * @return
322     */
323    private String[] determineTextFragments(String enteredText) {
324        List<String> fragments = new ArrayList<String>();
325
326        StringBuffer fragment = new StringBuffer();
327        char lastChar = 0;
328
329        for (int i = 0; i < enteredText.length(); i++) {
330            char currentChar = enteredText.charAt(i);
331
332            if (!isEqualCharacterType(lastChar, currentChar)) {
333                // the previous fragment ended. so finalize it and start a new one
334                if ((fragment != null) && (fragment.length() > 0)) {
335                    fragments.add(fragment.toString());
336                    fragment = new StringBuffer();
337                }
338            }
339
340            fragment.append(currentChar);
341            lastChar = currentChar;
342        }
343
344        if ((fragment != null) && (fragment.length() > 0)) {
345            fragments.add(fragment.toString());
346        }
347
348        return fragments.toArray(new String[fragments.size()]);
349    }
350
351    /**
352     * TODO: comment
353     *
354     * @param lastChar
355     * @param currentChar
356     * @return
357     */
358    private boolean isEqualCharacterType(char char1, char char2) {
359        return
360            ((char1 == char2) ||
361            (Character.isWhitespace(char1) && Character.isWhitespace(char2)) ||
362            (Character.isDigit(char1) && Character.isDigit(char2)) ||
363            (Character.isLetter(char1) && Character.isLetter(char2)) ||
364            (Character.isJavaIdentifierPart(char1) && Character.isJavaIdentifierPart(char2)));
365    }
366
367    /**
368     * TODO comment
369     *
370     * @version $Revision: $ $Date: 16.07.2012$
371     * @author 2012, last modified by $Author: pharms$
372     */
373    public static class TextInputStatistics {
374       
375        /** */
376        private List<Object[]> textFieldInputs = new ArrayList<Object[]>();
377
378        /** */
379        private List<Object[]> textAreaInputs = new ArrayList<Object[]>();
380
381        /** */
382        private int otherEventsCount;
383
384        /**
385         * TODO: comment
386         *
387         * @param node
388         * @param fragments
389         *
390         */
391        public void addTextFieldInput(IEventTask node, String[] fragments) {
392            textFieldInputs.add(new Object[] { node, fragments });
393        }
394
395        /**
396         * TODO: comment
397         *
398         * @param node
399         * @param fragments
400         *
401         */
402        public void addTextAreaInput(IEventTask node, String[] fragments) {
403            textAreaInputs.add(new Object[] { node, fragments });
404        }
405
406        /**
407         * TODO: comment
408         *
409         * @return
410         */
411        public int getNoOfAllEvents() {
412            return textFieldInputs.size() + textAreaInputs.size() + otherEventsCount;
413        }
414
415        /**
416         * TODO: comment
417         *
418         * @return
419         */
420        public int getNoOfTextFieldInputs() {
421            return textFieldInputs.size();
422        }
423
424        /**
425         * TODO: comment
426         *
427         * @param i
428         * @return
429         */
430        public String[] getTextFieldInputFragments(int index) {
431            return (String[]) textFieldInputs.get(index)[1];
432        }
433
434        /**
435         * TODO: comment
436         *
437         * @return
438         */
439        public int getNoOfTextAreaInputs() {
440            return textAreaInputs.size();
441        }
442
443        /**
444         * TODO: comment
445         *
446         * @param i
447         * @return
448         */
449        public String[] getTextAreaInputFragments(int index) {
450            return (String[]) textAreaInputs.get(index)[1];
451        }
452
453        /**
454         * TODO: comment
455         *
456         */
457        public void incrementNoOfOtherEventTasks() {
458            otherEventsCount++;
459        }
460
461    }
462
463}
Note: See TracBrowser for help on using the repository browser.