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

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