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

Last change on this file since 1918 was 1918, checked in by pharms, 9 years ago
  • extension with further smell detections
  • may not fully work. But Hudson is more happy because compile errors should be gone
File size: 24.4 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.Iterator;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.ListIterator;
25import java.util.Map;
26import java.util.Set;
27
28import org.apache.commons.math3.stat.inference.ChiSquareTest;
29
30import de.ugoe.cs.autoquest.eventcore.Event;
31import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
32import de.ugoe.cs.autoquest.eventcore.gui.ValueSelection;
33import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
34import de.ugoe.cs.autoquest.eventcore.guimodel.ICheckBox;
35import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
36import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec;
37import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIView;
38import de.ugoe.cs.autoquest.eventcore.guimodel.IRadioButton;
39import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor;
40import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
41import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
42import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
43import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
44import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
45import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
46
47/**
48 * TODO comment
49 *
50 * @version $Revision: $ $Date: 16.07.2012$
51 * @author 2012, last modified by $Author: pharms$
52 */
53public class DefaultValueRule implements UsabilityEvaluationRule {
54   
55    /** */
56    private static String DEFAULT_VALUE = "DEFAULT_VALUE";
57   
58    /*
59     * (non-Javadoc)
60     *
61     * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
62     */
63    @Override
64    public UsabilityEvaluationResult evaluate(ITaskModel taskModel) {
65        System.out.println("determining value selection targets");
66        Set<ValueSelectionTarget> targets = getValueSelectionTargets(taskModel.getTasks());
67        System.out.println("found " + targets.size() + " targets");
68       
69        System.out.println("grouping radio buttons and check boxes targets");
70        condenseRadioButtonAndCheckBoxGroups(targets);
71        System.out.println(targets.size() + " remaining");
72       
73        System.out.println("calculating statistics");
74        ValueChangeStatistics statistics = new ValueChangeStatistics(targets);
75        calculateStatistics(taskModel.getUserSessions(), statistics);
76
77        System.out.println("analyzing statistics");
78        UsabilityEvaluationResult results = new UsabilityEvaluationResult(taskModel);
79        analyzeStatistics(statistics, results);
80
81        return results;
82    }
83
84    /**
85     *
86     */
87    private void analyzeStatistics(ValueChangeStatistics     statistics,
88                                   UsabilityEvaluationResult results)
89    {
90        System.out.println
91            ("determined " + statistics.getViewsWithValueSelections().size() + " views");
92       
93        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
94        int valueSelectionTargetCounter = 0;
95       
96        for (IGUIView view : statistics.getViewsWithValueSelections()) {
97            valueSelectionTargetCounter += statistics.getValueSelectionTargetsInView(view).size();
98        }
99       
100        if (valueSelectionTargetCounter != statistics.selectedValues.size()) {
101            throw new IllegalStateException("this should not happen");
102        }
103        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
104       
105        for (IGUIView view : statistics.getViewsWithValueSelections()) {
106            //System.out.println("view " + view + " has " +
107            //                   statistics.getValueSelectionTargetsInView(view) +
108            //                   " value selection targets");
109            for (ValueSelectionTarget target : statistics.getValueSelectionTargetsInView(view)) {
110                analyzeStatistics
111                    (view, target, statistics.getStatisticsForValueSelectionTarget(target), results);
112            }
113        }
114    }
115
116    /**
117     *
118     */
119    private void analyzeStatistics(IGUIView                  view,
120                                   ValueSelectionTarget      target,
121                                   Map<Object, Integer>      selectedValues,
122                                   UsabilityEvaluationResult results)
123    {
124        //System.out.println("  analyzing selected values for " + target);
125        long[] observed = new long[selectedValues.size()];
126        long allObserved = 0;
127        long maxObserved = 0;
128        LinkedList<Object> mostOftenSelected = new LinkedList<>();
129       
130        int i = 0;
131        for (Map.Entry<Object, Integer> selectedValue : selectedValues.entrySet()) {
132            //System.out.println("    " + selectedValue.getValue() + " \t " + selectedValue.getKey());
133           
134            observed[i++] = selectedValue.getValue();
135            allObserved += selectedValue.getValue();
136            maxObserved = Math.max(maxObserved, selectedValue.getValue());
137           
138            if (mostOftenSelected.size() > 0) {
139                ListIterator<Object> iterator = mostOftenSelected.listIterator();
140                while (iterator.hasNext()) {
141                    if (selectedValues.get(iterator.next()) < selectedValue.getValue()) {
142                        iterator.previous();
143                        iterator.add(selectedValue.getKey());
144                   
145                        while (mostOftenSelected.size() > 5) {
146                            mostOftenSelected.removeLast();
147                        }
148                   
149                        break;
150                    }
151                }
152            }
153            else {
154                mostOftenSelected.add(selectedValue.getKey());
155            }
156        }
157       
158        double[] expected = new double[observed.length];
159        double expectedFrequency = ((double) allObserved) / expected.length;
160       
161        for (i = 0; i < expected.length; i++) {
162            expected[i] = expectedFrequency;
163        }
164       
165        if ((expected.length == 1) ||
166            (new ChiSquareTest().chiSquareTest(expected, observed, 0.05)))
167        {
168            // values are not equally distributed.
169           
170            // if the default value is most often selected, everything is fine. If not, smell is
171            // detected
172            if (!DEFAULT_VALUE.equals(mostOftenSelected.get(0))) {
173                UsabilitySmellIntensity intensity = UsabilitySmellIntensity.getIntensity
174                    ((int) (1000 * maxObserved / allObserved), (int) allObserved, -1);
175               
176                if (intensity != null) {
177                    List<String> mostOftenSelectedValues =
178                        new ArrayList<>(mostOftenSelected.size());
179               
180                    for (Object oftenSelected : mostOftenSelected) {
181                        mostOftenSelectedValues.add
182                            (oftenSelected + " (" +
183                             (100.0 * selectedValues.get(oftenSelected) / allObserved) + "%)");
184                    }
185               
186                    Map<String, Object> parameters = new HashMap<String, Object>();
187                    parameters.put("view", view);
188                    parameters.put("guiElement", target);
189                    parameters.put("selectedValues", mostOftenSelectedValues);
190
191                    results.addSmell(intensity, UsabilitySmellDescription.GOOD_DEFAULTS, parameters);
192                }
193            }
194        }
195    }
196
197    /**
198     *
199     */
200    private Set<ValueSelectionTarget> getValueSelectionTargets(Collection<ITask> tasks) {
201        Set<ValueSelectionTarget> result = new HashSet<>();
202       
203        for (ITask task : tasks) {
204            if (task instanceof IEventTask) {
205                for (ITaskInstance instance : task.getInstances()) {
206                    if (isValueSelection(instance)) {
207                        result.add(newValueSelectionTarget(instance));
208                    }
209                }
210            }
211        }
212       
213        return result;
214    }
215
216    /**
217     *
218     */
219    private boolean isValueSelection(ITaskInstance instance) {
220        return (instance instanceof IEventTaskInstance) &&
221            ((((IEventTaskInstance) instance).getEvent().getType() instanceof TextInput) ||
222             (((IEventTaskInstance) instance).getEvent().getType() instanceof ValueSelection));
223    }
224
225    /**
226     *
227     */
228    private static ValueSelectionTarget newValueSelectionTarget(ITaskInstance instance) {
229        Event event = ((IEventTaskInstance) instance).getEvent();
230        return new ValueSelectionTarget((IGUIElement) event.getTarget());
231    }
232   
233    /**
234     *
235     */
236    private void condenseRadioButtonAndCheckBoxGroups(Set<ValueSelectionTarget> targets) {
237        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
238        Set<IGUIView> viewsBefore = new HashSet<IGUIView>();
239       
240        for (ValueSelectionTarget target : targets) {
241            viewsBefore.add(target.getView());
242        }
243        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
244       
245        condenseGuiElementGroups(targets, IRadioButton.class);
246        condenseGuiElementGroups(targets, ICheckBox.class);
247       
248        // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
249        Set<IGUIView> viewsAfter = new HashSet<IGUIView>();
250       
251        for (ValueSelectionTarget target : targets) {
252            viewsAfter.add(target.getView());
253        }
254       
255        if (viewsBefore.size() != viewsAfter.size()) {
256            throw new IllegalStateException("this should not happen");
257        }
258       
259        for (IGUIView view : viewsBefore) {
260            if (!viewsAfter.contains(view)) {
261                throw new IllegalStateException("this should not happen too");
262            }
263        }
264        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
265    }
266   
267    /**
268     *
269     */
270    private void condenseGuiElementGroups(Set<ValueSelectionTarget> targets, Class<?> type) {
271        Map<IGUIView, List<IGUIElement>> guiElementsInViews = new HashMap<>();
272       
273        // determine the targets under consideration
274        for (ValueSelectionTarget target : targets) {
275            if (type.isInstance(target.getTarget())) {
276                List<IGUIElement> guiElementsInView = guiElementsInViews.get(target.getView());
277
278                if (guiElementsInView == null) {
279                    guiElementsInView = new LinkedList<IGUIElement>();
280                    guiElementsInViews.put(target.getView(), guiElementsInView);
281                }
282               
283                guiElementsInView.add(target);
284            }
285        }
286
287        for (Map.Entry<IGUIView, List<IGUIElement>> guiElements :
288                 guiElementsInViews.entrySet())
289        {
290            Map<IGUIElement, List<IGUIElement>> groups =
291                RuleUtils.getGroups(guiElements.getValue(), 3);
292
293            for (Map.Entry<IGUIElement, List<IGUIElement>> group : groups.entrySet()) {
294                //System.out.println("replacing");
295
296                // it is important to provide the correct view here, as the view of group.getKey()
297                // for HTML stuff may not be correct due to GUI model condensing
298                ValueSelectionTargetGroup valueSelectionTargetGroup =
299                    new ValueSelectionTargetGroup(group.getKey(), guiElements.getKey());
300               
301                for (IGUIElement toBeMerged : group.getValue()) {
302                    //System.out.println(targets.size() + "  " + toBeMerged.getView() + "  " +
303                    //                   toBeMerged);
304                   
305                    valueSelectionTargetGroup.addTargetToGroup(toBeMerged);
306                    targets.remove(toBeMerged);
307                }
308               
309                //System.out.println("with " + valueSelectionTargetGroup.getView() + "  " +
310                //                   valueSelectionTargetGroup);
311                targets.add(valueSelectionTargetGroup);
312            }
313        }
314    }
315
316    /**
317     *
318     */
319    private void calculateStatistics(List<IUserSession>          sessions,
320                                     final ValueChangeStatistics statistics)
321    {
322        final IGUIView[] currentView = new IGUIView[1];
323        final List<IEventTaskInstance> valueChangesInViewDisplay = new ArrayList<>();
324       
325        for (IUserSession session : sessions) {
326            currentView[0] = null;
327            valueChangesInViewDisplay.clear();
328           
329            for (final ITaskInstance currentRoot : session) {
330                currentRoot.accept(new DefaultTaskInstanceTraversingVisitor() {
331                    @Override
332                    public void visit(IEventTaskInstance eventTaskInstance) {
333                        if (eventTaskInstance.getEvent().getTarget() instanceof IGUIElement) {
334                            IGUIView view =
335                                ((IGUIElement) eventTaskInstance.getEvent().getTarget()).getView();
336                           
337                            if ((currentView[0] == null) && (view != null)) {
338                                currentView[0] = view;
339                                valueChangesInViewDisplay.clear();
340                            }
341                            else if ((currentView[0] != null) && (!currentView[0].equals(view))) {
342                                statistics.addValueChangesInViewDisplay
343                                    (currentView[0], valueChangesInViewDisplay);
344                               
345                                currentView[0] = view;
346                                valueChangesInViewDisplay.clear();
347                            }
348                        }
349                       
350                        if (isValueSelection(eventTaskInstance)) {
351                            valueChangesInViewDisplay.add(eventTaskInstance);
352                        }
353                    }
354                });
355            }
356           
357            // add the selected values of the last shown view in the session
358            if (currentView[0] != null) {
359                statistics.addValueChangesInViewDisplay(currentView[0], valueChangesInViewDisplay);
360            }
361        }
362    }
363
364    /**
365     *
366     */
367    private static class ValueChangeStatistics {
368       
369        /** */
370        private Map<ValueSelectionTarget, ValueSelectionTarget> targetReplacements =
371            new HashMap<>();
372       
373        /** */
374        private Map<IGUIView, List<ValueSelectionTarget>> valueSelectionTargetsInView =
375            new HashMap<>();
376       
377        /** */
378        private Map<ValueSelectionTarget, Map<Object, Integer>> selectedValues = new HashMap<>();
379
380        /**
381         *
382         */
383        public ValueChangeStatistics(Set<ValueSelectionTarget> targets) {
384            for (ValueSelectionTarget target : targets) {
385                if (target instanceof ValueSelectionTargetGroup) {
386                    for (IGUIElement groupElement : (ValueSelectionTargetGroup) target) {
387                        targetReplacements.put((ValueSelectionTarget) groupElement, target);
388                    }
389                }
390                else {
391                    targetReplacements.put((ValueSelectionTarget) target, target);
392                }
393               
394                List<ValueSelectionTarget> targetsInView =
395                    valueSelectionTargetsInView.get(target.getView());
396               
397                if (targetsInView == null) {
398                    targetsInView = new LinkedList<ValueSelectionTarget>();
399                    valueSelectionTargetsInView.put(target.getView(), targetsInView);
400                }
401               
402                targetsInView.add((ValueSelectionTarget) target);
403            }
404        }
405
406        /**
407         *
408         */
409        private void addValueChangesInViewDisplay(IGUIView                 view,
410                                                  List<IEventTaskInstance> valueChanges)
411        {
412            if (!valueSelectionTargetsInView.containsKey(view)) {
413                if (!valueChanges.isEmpty()) {
414                    throw new IllegalStateException("this should not happen, as we already " +
415                                                    "recorded the views with possible value " +
416                                                    "changes at this point");
417                }
418               
419                // view does not contain value changes --> return
420                return;
421            }
422           
423            //System.out.println
424            //    ("handling " + valueChanges.size() + " value changes on view " + view);
425       
426            Map<ValueSelectionTarget, Object> lastSelectedValues = new HashMap<>();
427           
428            // determine the last selected value for each of the value selection targets that were
429            // actively selected by the user
430            for (IEventTaskInstance valueChange : valueChanges) {
431                ValueSelectionTarget target = newValueSelectionTarget(valueChange);
432                Object selectedValue;
433               
434                if (valueChange.getEvent().getType() instanceof TextInput) {
435                    selectedValue = ((TextInput) valueChange.getEvent().getType()).getEnteredText();
436                }
437                else if (valueChange.getEvent().getType() instanceof ValueSelection) {
438                    selectedValue =
439                        ((ValueSelection<?>) valueChange.getEvent().getType()).getSelectedValue();
440                   
441                    if ((target instanceof IRadioButton) || (target instanceof ICheckBox)) {
442                        selectedValue = selectedValue + " (" + target + ")";
443                    }
444                }
445                else {
446                    throw new IllegalStateException("the implementation needs to be extended to " +
447                                                    "handle further value change types");
448                }
449               
450                target = targetReplacements.get(target);
451                lastSelectedValues.put(target, selectedValue);
452            }
453           
454            // add the default value selection for the unchanged value selection targets
455            for (ValueSelectionTarget target : valueSelectionTargetsInView.get(view)) {
456                if (!lastSelectedValues.containsKey(target)) {
457                    lastSelectedValues.put(target, DEFAULT_VALUE);
458                }
459            }
460           
461            // and now store the statistics
462            for (Map.Entry<ValueSelectionTarget, Object> selectedValue :
463                     lastSelectedValues.entrySet())
464            {
465                Map<Object, Integer> statistics = selectedValues.get(selectedValue.getKey());
466               
467                if (statistics == null) {
468                    statistics = new HashMap<>();
469                    selectedValues.put(selectedValue.getKey(), statistics);
470                }
471               
472                Integer counter = statistics.get(selectedValue.getValue());
473               
474                if (counter == null) {
475                    statistics.put(selectedValue.getValue(), 1);
476                }
477                else {
478                    statistics.put(selectedValue.getValue(), counter + 1);
479                }
480            }
481        }
482
483        /**
484         *
485         */
486        private Collection<IGUIView> getViewsWithValueSelections() {
487            return valueSelectionTargetsInView.keySet();
488        }
489
490        /**
491         *
492         */
493        private Collection<ValueSelectionTarget> getValueSelectionTargetsInView(IGUIView view) {
494            return valueSelectionTargetsInView.get(view);
495        }
496
497        /**
498         *
499         */
500        private Map<Object, Integer> getStatisticsForValueSelectionTarget
501            (ValueSelectionTarget target)
502        {
503            return selectedValues.get(target);
504        }
505    }
506
507    /**
508     *
509     */
510    private static class ValueSelectionTarget implements IGUIElement {
511
512        /**  */
513        private static final long serialVersionUID = 1L;
514
515        /** */
516        private IGUIView view;
517       
518        /** */
519        private IGUIElement target;
520       
521        /**
522         *
523         */
524        private ValueSelectionTarget(IGUIElement target) {
525            this.view = target.getView();
526            this.target = target;
527        }
528       
529        /**
530         *
531         */
532        private ValueSelectionTarget(IGUIElement target, IGUIView view) {
533            this.view = view;
534            this.target = target;
535        }
536
537        /**
538         *
539         */
540        public IGUIElementSpec getSpecification() {
541            return target.getSpecification();
542        }
543
544        /**
545         *
546         */
547        public String getPlatform() {
548            return target.getPlatform();
549        }
550
551        /**
552         *
553         */
554        public IGUIElement getParent() {
555            return target.getParent();
556        }
557
558        /**
559         *
560         */
561        public String getStringIdentifier() {
562            return target.getStringIdentifier();
563        }
564
565        /**
566         *
567         */
568        public GUIModel getGUIModel() {
569            return target.getGUIModel();
570        }
571
572        /**
573         *
574         */
575        public boolean isUsed() {
576            return target.isUsed();
577        }
578
579        /**
580         *
581         */
582        public void markUsed() {
583            target.markUsed();
584        }
585
586        /**
587         *
588         */
589        public boolean equals(Object obj) {
590            if (this == obj) {
591                return true;
592            }
593            else if (obj instanceof ValueSelectionTarget) {
594                ValueSelectionTarget other = (ValueSelectionTarget) obj;
595                return (other.view.equals(this.view) && other.target.equals(this.target));
596            }
597           
598            return false;
599        }
600
601        /**
602         *
603         */
604        public int hashCode() {
605            return view.hashCode();
606        }
607
608        /**
609         *
610         */
611        public void updateSpecification(IGUIElementSpec furtherSpec) {
612            target.updateSpecification(furtherSpec);
613        }
614
615        /**
616         *
617         */
618        public void addEqualGUIElement(IGUIElement equalElement) {
619            target.addEqualGUIElement(equalElement);
620        }
621
622        /**
623         *
624         */
625        public double getDistanceTo(IGUIElement otherElement) {
626            return target.getDistanceTo(otherElement);
627        }
628
629        /* (non-Javadoc)
630         * @see java.lang.Object#toString()
631         */
632        @Override
633        public String toString() {
634            return target.toString();
635        }
636
637        /**
638         *
639         */
640        public IGUIView getView() {
641            return view;
642        }
643
644        /**
645         *
646         */
647        private IGUIElement getTarget() {
648            return target;
649        }
650
651    }
652
653    /**
654     *
655     */
656    private static class ValueSelectionTargetGroup extends ValueSelectionTarget
657        implements Iterable<IGUIElement>
658    {
659
660        /**  */
661        private static final long serialVersionUID = 1L;
662       
663        /** */
664        private List<IGUIElement> groupedTargets = new LinkedList<>();
665       
666        /**
667         *
668         */
669        private ValueSelectionTargetGroup(IGUIElement target, IGUIView view) {
670            super(target, view);
671        }
672
673        /**
674         *
675         */
676        private void addTargetToGroup(IGUIElement target) {
677            groupedTargets.add(target);
678        }
679
680        /* (non-Javadoc)
681         * @see java.lang.Iterable#iterator()
682         */
683        @Override
684        public Iterator<IGUIElement> iterator() {
685            return groupedTargets.iterator();
686        }
687
688        /* (non-Javadoc)
689         * @see java.lang.Object#toString()
690         */
691        @Override
692        public String toString() {
693            return "group(" + groupedTargets.size() + " targets, view " + super.getView() + ")";
694        }
695    }
696}
Note: See TracBrowser for help on using the repository browser.