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

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