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

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