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

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