1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.netbeans.jemmy.operators;
24
25import java.awt.Component;
26import java.awt.Container;
27import java.text.ParseException;
28import java.util.Date;
29import java.util.Hashtable;
30import java.util.List;
31
32import javax.swing.JComponent;
33import javax.swing.JSpinner;
34import javax.swing.SpinnerDateModel;
35import javax.swing.SpinnerListModel;
36import javax.swing.SpinnerModel;
37import javax.swing.SpinnerNumberModel;
38import javax.swing.SwingConstants;
39import javax.swing.event.ChangeListener;
40import javax.swing.plaf.SpinnerUI;
41
42import org.netbeans.jemmy.Action;
43import org.netbeans.jemmy.ComponentChooser;
44import org.netbeans.jemmy.ComponentSearcher;
45import org.netbeans.jemmy.JemmyException;
46import org.netbeans.jemmy.Outputable;
47import org.netbeans.jemmy.TestOut;
48import org.netbeans.jemmy.TimeoutExpiredException;
49import org.netbeans.jemmy.Timeoutable;
50import org.netbeans.jemmy.Timeouts;
51import org.netbeans.jemmy.drivers.DriverManager;
52import org.netbeans.jemmy.drivers.ScrollDriver;
53import org.netbeans.jemmy.drivers.scrolling.ScrollAdjuster;
54
55/**
56 * Provides methods to work with {@code javax.swing.JSpinner} component
57 * <br>
58 *
59 * @see NumberSpinnerOperator
60 * @see ListSpinnerOperator
61 * @see DateSpinnerOperator
62 *
63 * @author Alexandre Iline (alexandre.iline@oracle.com)
64 */
65public class JSpinnerOperator extends JComponentOperator
66        implements Timeoutable, Outputable {
67
68    /**
69     * Identifier for a "value" property.
70     *
71     * @see #getDump
72     */
73    public static final String VALUE_DPROP = "Value";
74
75    private final static long WHOLE_SCROLL_TIMEOUT = 60000;
76
77    private Timeouts timeouts;
78    private TestOut output;
79
80    private ScrollDriver driver;
81
82    private JButtonOperator increaseOperator = null;
83    private JButtonOperator decreaseOperator = null;
84
85    /**
86     * Constructor.
87     *
88     * @param b JSpinner component.
89     */
90    public JSpinnerOperator(JSpinner b) {
91        super(b);
92        driver = DriverManager.getScrollDriver(getClass());
93    }
94
95    /**
96     * Constructs a JSpinnerOperator object.
97     *
98     * @param cont a container
99     * @param chooser a component chooser specifying searching criteria.
100     * @param index an index between appropriate ones.
101     * @throws TimeoutExpiredException
102     */
103    public JSpinnerOperator(ContainerOperator<?> cont, ComponentChooser chooser, int index) {
104        this((JSpinner) cont.
105                waitSubComponent(new JSpinnerFinder(chooser),
106                        index));
107        copyEnvironment(cont);
108    }
109
110    /**
111     * Constructs a JSpinnerOperator object.
112     *
113     * @param cont a container
114     * @param chooser a component chooser specifying searching criteria.
115     * @throws TimeoutExpiredException
116     */
117    public JSpinnerOperator(ContainerOperator<?> cont, ComponentChooser chooser) {
118        this(cont, chooser, 0);
119    }
120
121    /**
122     * Constructs a JSpinnerOperator object.
123     *
124     * @param cont The operator for a container containing the sought for
125     * button.
126     * @param text toString() representation of the current spinner value.
127     * @param index Ordinal component index. The first component has
128     * {@code index} 0.
129     * @throws TimeoutExpiredException
130     */
131    public JSpinnerOperator(ContainerOperator<?> cont, String text, int index) {
132        this((JSpinner) waitComponent(cont,
133                new JSpinnerByTextFinder(text,
134                        cont.getComparator()),
135                index));
136        copyEnvironment(cont);
137    }
138
139    /**
140     * Constructs a JSpinnerOperator object.
141     *
142     * @param cont The operator for a container containing the sought for
143     * button.
144     * @param text toString() representation of the current spinner value.
145     * @throws TimeoutExpiredException
146     */
147    public JSpinnerOperator(ContainerOperator<?> cont, String text) {
148        this(cont, text, 0);
149    }
150
151    /**
152     * Constructor. Waits component in container first. Uses cont's timeout and
153     * output for waiting and to init operator.
154     *
155     * @param cont Operator pointing a container to search component in.
156     * @param index Ordinal component index.
157     * @throws TimeoutExpiredException
158     */
159    public JSpinnerOperator(ContainerOperator<?> cont, int index) {
160        this((JSpinner) waitComponent(cont,
161                new JSpinnerFinder(),
162                index));
163        copyEnvironment(cont);
164    }
165
166    /**
167     * Constructor. Waits component in container first. Uses cont's timeout and
168     * output for waiting and to init operator.
169     *
170     * @param cont Operator pointing a container to search component in.
171     * @throws TimeoutExpiredException
172     */
173    public JSpinnerOperator(ContainerOperator<?> cont) {
174        this(cont, 0);
175    }
176
177    /**
178     * Searches JSpinner in container.
179     *
180     * @param cont Container to search component in.
181     * @param chooser org.netbeans.jemmy.ComponentChooser implementation.
182     * @param index Ordinal component index.
183     * @return JSpinner instance or null if component was not found.
184     */
185    public static JSpinner findJSpinner(Container cont, ComponentChooser chooser, int index) {
186        return (JSpinner) findComponent(cont, new JSpinnerFinder(chooser), index);
187    }
188
189    /**
190     * Searches 0'th JSpinner in container.
191     *
192     * @param cont Container to search component in.
193     * @param chooser org.netbeans.jemmy.ComponentChooser implementation.
194     * @return JSpinner instance or null if component was not found.
195     */
196    public static JSpinner findJSpinner(Container cont, ComponentChooser chooser) {
197        return findJSpinner(cont, chooser, 0);
198    }
199
200    /**
201     * Searches JSpinner in container.
202     *
203     * @param cont Container to search component in.
204     * @param index Ordinal component index.
205     * @return JSpinner instance or null if component was not found.
206     */
207    public static JSpinner findJSpinner(Container cont, int index) {
208        return findJSpinner(cont, ComponentSearcher.getTrueChooser(Integer.toString(index) + "'th JSpinner instance"), index);
209    }
210
211    /**
212     * Searches 0'th JSpinner in container.
213     *
214     * @param cont Container to search component in.
215     * @return JSpinner instance or null if component was not found.
216     */
217    public static JSpinner findJSpinner(Container cont) {
218        return findJSpinner(cont, 0);
219    }
220
221    /**
222     * Waits JSpinner in container.
223     *
224     * @param cont Container to search component in.
225     * @param chooser org.netbeans.jemmy.ComponentChooser implementation.
226     * @param index Ordinal component index.
227     * @return JSpinner instance or null if component was not displayed.
228     * @throws TimeoutExpiredException
229     */
230    public static JSpinner waitJSpinner(Container cont, ComponentChooser chooser, int index) {
231        return (JSpinner) waitComponent(cont, new JSpinnerFinder(chooser), index);
232    }
233
234    /**
235     * Waits 0'th JSpinner in container.
236     *
237     * @param cont Container to search component in.
238     * @param chooser org.netbeans.jemmy.ComponentChooser implementation.
239     * @return JSpinner instance or null if component was not displayed.
240     * @throws TimeoutExpiredException
241     */
242    public static JSpinner waitJSpinner(Container cont, ComponentChooser chooser) {
243        return waitJSpinner(cont, chooser, 0);
244    }
245
246    /**
247     * Waits JSpinner in container.
248     *
249     * @param cont Container to search component in.
250     * @param index Ordinal component index.
251     * @return JSpinner instance or null if component was not displayed.
252     * @throws TimeoutExpiredException
253     */
254    public static JSpinner waitJSpinner(Container cont, int index) {
255        return waitJSpinner(cont, ComponentSearcher.getTrueChooser(Integer.toString(index) + "'th JSpinner instance"), index);
256    }
257
258    /**
259     * Waits 0'th JSpinner in container.
260     *
261     * @param cont Container to search component in.
262     * @return JSpinner instance or null if component was not displayed.
263     * @throws TimeoutExpiredException
264     */
265    public static JSpinner waitJSpinner(Container cont) {
266        return waitJSpinner(cont, 0);
267    }
268
269    /**
270     * Checks operator's model type.
271     *
272     * @param oper an operator to check model
273     * @param modelClass a model class.
274     * @throws SpinnerModelException if an operator's model is not an instance
275     * of specified class.
276     */
277    public static void checkModel(JSpinnerOperator oper, Class<?> modelClass) {
278        if (!modelClass.isInstance(oper.getModel())) {
279            throw (new SpinnerModelException("JSpinner model is not a " + modelClass.getName(),
280                    oper.getSource()));
281        }
282    }
283
284    static {
285        Timeouts.initDefault("JSpinnerOperator.WholeScrollTimeout", WHOLE_SCROLL_TIMEOUT);
286    }
287
288    @Override
289    public void setOutput(TestOut out) {
290        output = out;
291        super.setOutput(output.createErrorOutput());
292    }
293
294    @Override
295    public TestOut getOutput() {
296        return output;
297    }
298
299    @Override
300    public void setTimeouts(Timeouts timeouts) {
301        this.timeouts = timeouts;
302        super.setTimeouts(timeouts);
303    }
304
305    @Override
306    public Timeouts getTimeouts() {
307        return timeouts;
308    }
309
310    /**
311     * Returns an instance of {@code NumberSpinnerOperator} operator, the
312     * operator used for {@code JSpinner} having
313     * {@code SpinnerNumberModel} model.
314     *
315     * @return a {@code NumberSpinnerOperator} created for the same
316     * {@code JSpinner} as this operator.
317     * @throws SpinnerModelException if an operator's model is not an instance
318     * of {@code SpinnerNumberModel}
319     */
320    public NumberSpinnerOperator getNumberSpinner() {
321        return new NumberSpinnerOperator(this);
322    }
323
324    /**
325     * Returns an instance of {@code ListSpinnerOperator} operator, the
326     * operator used for {@code JSpinner} having
327     * {@code SpinnerListModel} model.
328     *
329     * @return a {@code ListSpinnerOperator} created for the same
330     * {@code JSpinner} as this operator.
331     * @throws SpinnerModelException if an operator's model is not an instance
332     * of {@code SpinnerListModel}
333     */
334    public ListSpinnerOperator getListSpinner() {
335        return new ListSpinnerOperator(this);
336    }
337
338    /**
339     * Returns an instance of {@code DateSpinnerOperator} operator, the
340     * operator used for {@code JSpinner} having
341     * {@code SpinnerDateModel} model.
342     *
343     * @return a {@code DateSpinnerOperator} created for the same
344     * {@code JSpinner} as this operator.
345     * @throws SpinnerModelException if an operator's model is not an instance
346     * of {@code SpinnerDateModel}
347     */
348    public DateSpinnerOperator getDateSpinner() {
349        return new DateSpinnerOperator(this);
350    }
351
352    /**
353     * Scrolls to reach a condition specified by {@code ScrollAdjuster}
354     *
355     * @param adj scrolling criteria.
356     */
357    public void scrollTo(final ScrollAdjuster adj) {
358        produceTimeRestricted(new Action<Void, Void>() {
359            @Override
360            public Void launch(Void obj) {
361                driver.scroll(JSpinnerOperator.this, adj);
362                return null;
363            }
364
365            @Override
366            public String getDescription() {
367                return "Scrolling";
368            }
369
370            @Override
371            public String toString() {
372                return "JSpinnerOperator.scrollTo.Action{description = " + getDescription() + '}';
373            }
374        }, "JSpinnerOperator.WholeScrollTimeout");
375    }
376
377    /**
378     * Scrolls to maximum value.
379     *
380     * @throws SpinnerModelException if an operator's model does not have a
381     * maximum value.
382     */
383    public void scrollToMaximum() {
384        produceTimeRestricted(new Action<Void, Void>() {
385            @Override
386            public Void launch(Void obj) {
387                driver.scrollToMaximum(JSpinnerOperator.this, SwingConstants.VERTICAL);
388                return null;
389            }
390
391            @Override
392            public String getDescription() {
393                return "Scrolling";
394            }
395
396            @Override
397            public String toString() {
398                return "JSpinnerOperator.scrollToMaximum.Action{description = " + getDescription() + '}';
399            }
400        }, "JSpinnerOperator.WholeScrollTimeout");
401    }
402
403    /**
404     * Scrolls to minimum value.
405     *
406     * @throws SpinnerModelException if an operator's model does not have a
407     * minimum value.
408     */
409    public void scrollToMinimum() {
410        produceTimeRestricted(new Action<Void, Void>() {
411            @Override
412            public Void launch(Void obj) {
413                driver.scrollToMinimum(JSpinnerOperator.this, SwingConstants.VERTICAL);
414                return null;
415            }
416
417            @Override
418            public String getDescription() {
419                return "Scrolling";
420            }
421
422            @Override
423            public String toString() {
424                return "JSpinnerOperator.scrollToMinimum.Action{description = " + getDescription() + '}';
425            }
426        }, "JSpinnerOperator.WholeScrollTimeout");
427    }
428
429    /**
430     * Scrolls to exact match of a spinner value to the specified value.
431     *
432     * @param value an value to scroll to.
433     * @param direction a scrolling direction - one of
434     * {@code ScrollAdjuster.*_SCROLL_DIRECTION} fields.
435     */
436    public void scrollToObject(Object value, int direction) {
437        scrollTo(new ExactScrollAdjuster(this, value, direction));
438    }
439
440    /**
441     * Scrolls to matching of <code>getValue().toString() with the pattern.
442     *
443     * @param pattern a pattern to compare with
444     * @param comparator a string comparision criteria
445     * @param direction a scrolling direction - one of
446     * {@code ScrollAdjuster.*_SCROLL_DIRECTION} fields.
447     */
448    public void scrollToString(String pattern, StringComparator comparator, int direction) {
449        scrollTo(new ToStringScrollAdjuster(this, pattern, comparator, direction));
450    }
451
452    /**
453     * Scrolls to matching of {@code getValue().toString()} with the
454     * pattern. Uses {@code StringComparator} assigned to the operator.
455     *
456     * @param pattern a pattern to compare with
457     * @param direction a scrolling direction - one of
458     * {@code ScrollAdjuster.*_SCROLL_DIRECTION} fields.
459     */
460    public void scrollToString(String pattern, int direction) {
461        scrollToString(pattern, getComparator(), direction);
462    }
463
464    /**
465     * Returns an operator for a button used for value increasing.
466     *
467     * @return an operator for a first <code>JButton<code> inside this spinner.
468     */
469    public JButtonOperator getIncreaseOperator() {
470        if (increaseOperator == null) {
471            increaseOperator = (JButtonOperator) createSubOperator(new JButtonOperator.JButtonFinder(), 0);
472            increaseOperator.copyEnvironment(this);
473            increaseOperator.setOutput(getOutput().createErrorOutput());
474        }
475        return increaseOperator;
476    }
477
478    /**
479     * Returns an operator for a button used for value decreasing.
480     *
481     * @return an operator for a second <code>JButton<code> inside this spinner.
482     */
483    public JButtonOperator getDecreaseOperator() {
484        if (decreaseOperator == null) {
485            decreaseOperator = (JButtonOperator) createSubOperator(new JButtonOperator.JButtonFinder(), 1);
486            decreaseOperator.copyEnvironment(this);
487            decreaseOperator.setOutput(getOutput().createErrorOutput());
488        }
489        return decreaseOperator;
490    }
491
492    /**
493     * Returns a minimal value. Returns null if model is not one of the
494     * following: {@code javax.swing.SpinnerDateModel},
495     * {@code javax.swing.SpinnerListModel},
496     * {@code javax.swing.SpinnerNumberModel}. Also, returns null if the
497     * model does not have a minimal value.
498     *
499     * @return a minimal value.
500     */
501    public Object getMinimum() {
502        SpinnerModel model = getModel();
503        if (model instanceof SpinnerNumberModel) {
504            return ((SpinnerNumberModel) model).getMinimum();
505        } else if (model instanceof SpinnerDateModel) {
506            return ((SpinnerDateModel) model).getEnd();
507        } else if (model instanceof SpinnerListModel) {
508            List<?> list = ((SpinnerListModel) model).getList();
509            return list.get(list.size() - 1);
510        } else {
511            return null;
512        }
513    }
514
515    /**
516     * Returns a maximal value. Returns null if model is not one of the
517     * following: {@code javax.swing.SpinnerDateModel},
518     * {@code javax.swing.SpinnerListModel},
519     * {@code javax.swing.SpinnerNumberModel}. Also, returns null if the
520     * model does not have a maximal value.
521     *
522     * @return a maximal value.
523     */
524    public Object getMaximum() {
525        SpinnerModel model = getModel();
526        if (model instanceof SpinnerNumberModel) {
527            return ((SpinnerNumberModel) model).getMaximum();
528        } else if (model instanceof SpinnerDateModel) {
529            return ((SpinnerDateModel) model).getEnd();
530        } else if (model instanceof SpinnerListModel) {
531            List<?> list = ((SpinnerListModel) model).getList();
532            return list.get(list.size() - 1);
533        } else {
534            return null;
535        }
536    }
537
538    @Override
539    public Hashtable<String, Object> getDump() {
540        Hashtable<String, Object> result = super.getDump();
541        result.put(VALUE_DPROP, ((JSpinner) getSource()).getValue().toString());
542        return result;
543    }
544
545    ////////////////////////////////////////////////////////
546    //Mapping                                             //
547    /**
548     * Maps {@code JSpinner.getValue()} through queue
549     */
550    public Object getValue() {
551        return (runMapping(new MapAction<Object>("getValue") {
552            @Override
553            public Object map() {
554                return ((JSpinner) getSource()).getValue();
555            }
556        }));
557    }
558
559    /**
560     * Maps {@code JSpinner.setValue(Object)} through queue
561     */
562    public void setValue(final Object object) {
563        runMapping(new MapVoidAction("setValue") {
564            @Override
565            public void map() {
566                ((JSpinner) getSource()).setValue(object);
567            }
568        });
569    }
570
571    /**
572     * Maps {@code JSpinner.getUI()} through queue
573     */
574    public SpinnerUI getUI() {
575        return (runMapping(new MapAction<SpinnerUI>("getUI") {
576            @Override
577            public SpinnerUI map() {
578                return ((JSpinner) getSource()).getUI();
579            }
580        }));
581    }
582
583    /**
584     * Maps {@code JSpinner.setUI(SpinnerUI)} through queue
585     */
586    public void setUI(final SpinnerUI spinnerUI) {
587        runMapping(new MapVoidAction("setUI") {
588            @Override
589            public void map() {
590                ((JSpinner) getSource()).setUI(spinnerUI);
591            }
592        });
593    }
594
595    /**
596     * Maps {@code JSpinner.setModel(SpinnerModel)} through queue
597     */
598    public void setModel(final SpinnerModel spinnerModel) {
599        runMapping(new MapVoidAction("setModel") {
600            @Override
601            public void map() {
602                ((JSpinner) getSource()).setModel(spinnerModel);
603            }
604        });
605    }
606
607    /**
608     * Maps {@code JSpinner.getModel()} through queue
609     */
610    public SpinnerModel getModel() {
611        return (runMapping(new MapAction<SpinnerModel>("getModel") {
612            @Override
613            public SpinnerModel map() {
614                return ((JSpinner) getSource()).getModel();
615            }
616        }));
617    }
618
619    /**
620     * Maps {@code JSpinner.getNextValue()} through queue
621     */
622    public Object getNextValue() {
623        return (runMapping(new MapAction<Object>("getNextValue") {
624            @Override
625            public Object map() {
626                return ((JSpinner) getSource()).getNextValue();
627            }
628        }));
629    }
630
631    /**
632     * Maps {@code JSpinner.addChangeListener(ChangeListener)} through queue
633     */
634    public void addChangeListener(final ChangeListener changeListener) {
635        runMapping(new MapVoidAction("addChangeListener") {
636            @Override
637            public void map() {
638                ((JSpinner) getSource()).addChangeListener(changeListener);
639            }
640        });
641    }
642
643    /**
644     * Maps {@code JSpinner.removeChangeListener(ChangeListener)} through queue
645     */
646    public void removeChangeListener(final ChangeListener changeListener) {
647        runMapping(new MapVoidAction("removeChangeListener") {
648            @Override
649            public void map() {
650                ((JSpinner) getSource()).removeChangeListener(changeListener);
651            }
652        });
653    }
654
655    /**
656     * Maps {@code JSpinner.getChangeListeners()} through queue
657     */
658    public ChangeListener[] getChangeListeners() {
659        return ((ChangeListener[]) runMapping(new MapAction<Object>("getChangeListeners") {
660            @Override
661            public Object map() {
662                return ((JSpinner) getSource()).getChangeListeners();
663            }
664        }));
665    }
666
667    /**
668     * Maps {@code JSpinner.getPreviousValue()} through queue
669     */
670    public Object getPreviousValue() {
671        return (runMapping(new MapAction<Object>("getPreviousValue") {
672            @Override
673            public Object map() {
674                return ((JSpinner) getSource()).getPreviousValue();
675            }
676        }));
677    }
678
679    /**
680     * Maps {@code JSpinner.setEditor(JComponent)} through queue
681     */
682    public void setEditor(final JComponent jComponent) {
683        runMapping(new MapVoidAction("setEditor") {
684            @Override
685            public void map() {
686                ((JSpinner) getSource()).setEditor(jComponent);
687            }
688        });
689    }
690
691    /**
692     * Maps {@code JSpinner.getEditor()} through queue
693     */
694    public JComponent getEditor() {
695        return (runMapping(new MapAction<JComponent>("getEditor") {
696            @Override
697            public JComponent map() {
698                return ((JSpinner) getSource()).getEditor();
699            }
700        }));
701    }
702
703    /**
704     * Maps {@code JSpinner.commitEdit()} through queue
705     */
706    public void commitEdit() {
707        runMapping(new MapVoidAction("commitEdit") {
708            @Override
709            public void map() throws ParseException {
710                ((JSpinner) getSource()).commitEdit();
711            }
712        });
713    }
714
715    //End of mapping                                      //
716    ////////////////////////////////////////////////////////
717    /**
718     * Allows to find component by text.
719     */
720    public static class JSpinnerByTextFinder implements ComponentChooser {
721
722        String label;
723        StringComparator comparator;
724
725        /**
726         * Constructs JSpinnerByTextFinder.
727         *
728         * @param lb a text pattern
729         * @param comparator specifies string comparision algorithm.
730         */
731        public JSpinnerByTextFinder(String lb, StringComparator comparator) {
732            label = lb;
733            this.comparator = comparator;
734        }
735
736        /**
737         * Constructs JSpinnerByTextFinder.
738         *
739         * @param lb a text pattern
740         */
741        public JSpinnerByTextFinder(String lb) {
742            this(lb, Operator.getDefaultStringComparator());
743        }
744
745        @Override
746        public boolean checkComponent(Component comp) {
747            if (comp instanceof JSpinner) {
748                if (((JSpinner) comp).getValue() != null) {
749                    return (comparator.equals(((JSpinner) comp).getValue().toString(),
750                            label));
751                }
752            }
753            return false;
754        }
755
756        @Override
757        public String getDescription() {
758            return "JSpinner with text \"" + label + "\"";
759        }
760
761        @Override
762        public String toString() {
763            return "JSpinnerByTextFinder{" + "label=" + label + ", comparator=" + comparator + '}';
764        }
765    }
766
767    /**
768     * Checks component type.
769     */
770    public static class JSpinnerFinder extends Finder {
771
772        /**
773         * Constructs JSpinnerFinder.
774         *
775         * @param sf other searching criteria.
776         */
777        public JSpinnerFinder(ComponentChooser sf) {
778            super(JSpinner.class, sf);
779        }
780
781        /**
782         * Constructs JSpinnerFinder.
783         */
784        public JSpinnerFinder() {
785            super(JSpinner.class);
786        }
787    }
788
789    /**
790     * A {@code ScrollAdjuster} to be used for {@code JSpinner}
791     * component having {@code SpinnerNumberModel} model.
792     *
793     * @see NumberSpinnerOperator
794     */
795    public static class NumberScrollAdjuster implements ScrollAdjuster {
796
797        SpinnerNumberModel model;
798        double value;
799
800        /**
801         * Constructs a {@code NumberScrollAdjuster} object.
802         *
803         * @param oper an operator to work with.
804         * @param value a value to scroll to.
805         */
806        public NumberScrollAdjuster(JSpinnerOperator oper, double value) {
807            this.value = value;
808            checkModel(oper, SpinnerNumberModel.class);
809            model = (SpinnerNumberModel) oper.getModel();
810        }
811
812        /**
813         * Constructs a {@code NumberScrollAdjuster} object.
814         *
815         * @param oper an operator to work with.
816         * @param value a value to scroll to.
817         */
818        public NumberScrollAdjuster(JSpinnerOperator oper, Number value) {
819            this(oper, value.doubleValue());
820        }
821
822        @Override
823        public int getScrollDirection() {
824            if (value > model.getNumber().doubleValue()) {
825                return ScrollAdjuster.INCREASE_SCROLL_DIRECTION;
826            } else if (value < model.getNumber().doubleValue()) {
827                return ScrollAdjuster.DECREASE_SCROLL_DIRECTION;
828            } else {
829                return ScrollAdjuster.DO_NOT_TOUCH_SCROLL_DIRECTION;
830            }
831        }
832
833        @Override
834        public int getScrollOrientation() {
835            return SwingConstants.VERTICAL;
836        }
837
838        @Override
839        public String getDescription() {
840            return "Spin to " + value + " value";
841        }
842
843        @Override
844        public String toString() {
845            return "NumberScrollAdjuster{" + "model=" + model + ", value=" + value + '}';
846        }
847    }
848
849    /**
850     * A {@code ScrollAdjuster} to be used for {@code JSpinner}
851     * component having {@code SpinnerListModel} model.
852     *
853     * @see ListSpinnerOperator
854     */
855    public static class ListScrollAdjuster implements ScrollAdjuster {
856
857        SpinnerListModel model;
858        int itemIndex;
859        List<?> elements;
860
861        private ListScrollAdjuster(JSpinnerOperator oper) {
862            checkModel(oper, SpinnerListModel.class);
863            model = (SpinnerListModel) oper.getModel();
864            elements = model.getList();
865        }
866
867        /**
868         * Constructs a {@code ListScrollAdjuster} object.
869         *
870         * @param oper an operator to work with.
871         * @param value a value to scroll to.
872         */
873        public ListScrollAdjuster(JSpinnerOperator oper, Object value) {
874            this(oper);
875            this.itemIndex = elements.indexOf(value);
876        }
877
878        /**
879         * Constructs a {@code ListScrollAdjuster} object.
880         *
881         * @param oper an operator to work with.
882         * @param itemIndex an item index to scroll to.
883         */
884        public ListScrollAdjuster(JSpinnerOperator oper, int itemIndex) {
885            this(oper);
886            this.itemIndex = itemIndex;
887        }
888
889        @Override
890        public int getScrollDirection() {
891            int curIndex = elements.indexOf(model.getValue());
892            if (itemIndex > curIndex) {
893                return ScrollAdjuster.INCREASE_SCROLL_DIRECTION;
894            } else if (itemIndex < curIndex) {
895                return ScrollAdjuster.DECREASE_SCROLL_DIRECTION;
896            } else {
897                return ScrollAdjuster.DO_NOT_TOUCH_SCROLL_DIRECTION;
898            }
899        }
900
901        @Override
902        public int getScrollOrientation() {
903            return SwingConstants.VERTICAL;
904        }
905
906        @Override
907        public String getDescription() {
908            return "Spin to " + Integer.toString(itemIndex) + "'th item";
909        }
910
911        @Override
912        public String toString() {
913            return "ListScrollAdjuster{" + "model=" + model + ", itemIndex=" + itemIndex + ", elements=" + elements + '}';
914        }
915    }
916
917    /**
918     * A {@code ScrollAdjuster} to be used for {@code JSpinner}
919     * component having {@code SpinnerDateModel} model.
920     *
921     * @see DateSpinnerOperator
922     */
923    public static class DateScrollAdjuster implements ScrollAdjuster {
924
925        Date date;
926        SpinnerDateModel model;
927
928        /**
929         * Constructs a {@code DateScrollAdjuster} object.
930         *
931         * @param oper an operator to work with.
932         * @param date a date to scroll to.
933         */
934        public DateScrollAdjuster(JSpinnerOperator oper, Date date) {
935            this.date = date;
936            checkModel(oper, SpinnerDateModel.class);
937            model = (SpinnerDateModel) oper.getModel();
938        }
939
940        @Override
941        public int getScrollDirection() {
942            if (date.after(model.getDate())) {
943                return ScrollAdjuster.INCREASE_SCROLL_DIRECTION;
944            } else if (date.before(model.getDate())) {
945                return ScrollAdjuster.DECREASE_SCROLL_DIRECTION;
946            } else {
947                return ScrollAdjuster.DO_NOT_TOUCH_SCROLL_DIRECTION;
948            }
949        }
950
951        @Override
952        public int getScrollOrientation() {
953            return SwingConstants.VERTICAL;
954        }
955
956        @Override
957        public String getDescription() {
958            return "Spin to " + date.toString() + " date";
959        }
960
961        @Override
962        public String toString() {
963            return "DateScrollAdjuster{" + "date=" + date + ", model=" + model + '}';
964        }
965    }
966
967    /**
968     * Abstract class for a scrolling of a spinner having unknown model type. A
969     * subclass needs to override {@code reached(Object)} method to specify a
970     * criteria of successful scrolling.
971     */
972    public abstract static class ObjectScrollAdjuster implements ScrollAdjuster {
973
974        SpinnerModel model;
975        int direction;
976
977        /**
978         * Constructs a {@code ObjectScrollAdjuster} object.
979         *
980         * @param oper an operator to work with.
981         * @param direction a scrolling direction - one of
982         * {@code ScrollAdjuster.*_SCROLL_DIRECTION} fields.
983         */
984        public ObjectScrollAdjuster(JSpinnerOperator oper, int direction) {
985            this.direction = direction;
986            model = oper.getModel();
987        }
988
989        @Override
990        public int getScrollDirection() {
991            if (reached(model.getValue())) {
992                return ScrollAdjuster.DO_NOT_TOUCH_SCROLL_DIRECTION;
993            } else if (direction == ScrollAdjuster.INCREASE_SCROLL_DIRECTION
994                    && model.getNextValue() != null
995                    || direction == ScrollAdjuster.DECREASE_SCROLL_DIRECTION
996                    && model.getPreviousValue() != null) {
997                return direction;
998            } else {
999                return ScrollAdjuster.DO_NOT_TOUCH_SCROLL_DIRECTION;
1000            }
1001        }
1002
1003        public abstract boolean reached(Object curvalue);
1004
1005        @Override
1006        public int getScrollOrientation() {
1007            return SwingConstants.VERTICAL;
1008        }
1009    }
1010
1011    /**
1012     * Class for a scrolling of a spinner having unknown model type. Checks
1013     * spinner value for exact equality with a specified value.
1014     */
1015    public static class ExactScrollAdjuster extends ObjectScrollAdjuster {
1016
1017        Object obj;
1018
1019        /**
1020         * Constructs a {@code ExactScrollAdjuster} object.
1021         *
1022         * @param oper an operator to work with.
1023         * @param direction a scrolling direction - one of
1024         * {@code ScrollAdjuster.*_SCROLL_DIRECTION} fields.
1025         */
1026        public ExactScrollAdjuster(JSpinnerOperator oper, Object obj, int direction) {
1027            super(oper, direction);
1028            this.obj = obj;
1029        }
1030
1031        @Override
1032        public boolean reached(Object curvalue) {
1033            return curvalue != null && curvalue.equals(obj);
1034        }
1035
1036        @Override
1037        public String getDescription() {
1038            return "Spin to " + obj.toString() + " value";
1039        }
1040
1041        @Override
1042        public String toString() {
1043            return "ExactScrollAdjuster{" + "obj=" + obj + '}';
1044        }
1045
1046        @Override
1047        public int getScrollOrientation() {
1048            return SwingConstants.VERTICAL;
1049        }
1050    }
1051
1052    /**
1053     * Class for a scrolling of a spinner having unknown model type. Checks
1054     * spinner value's toString() reprsentation to match a string pattern.
1055     */
1056    public static class ToStringScrollAdjuster extends ObjectScrollAdjuster {
1057
1058        String pattern;
1059        StringComparator comparator;
1060
1061        /**
1062         * Constructs a {@code ToStringScrollAdjuster} object.
1063         *
1064         * @param oper an operator to work with.
1065         * @param pattern a pattern to compare with
1066         * @param comparator specifies string comparision algorithm.
1067         * @param direction a scrolling direction - one of
1068         * {@code ScrollAdjuster.*_SCROLL_DIRECTION} fields.
1069         */
1070        public ToStringScrollAdjuster(JSpinnerOperator oper, String pattern, StringComparator comparator, int direction) {
1071            super(oper, direction);
1072            this.pattern = pattern;
1073            this.comparator = comparator;
1074        }
1075
1076        /**
1077         * Constructs a {@code ToStringScrollAdjuster} object. Uses
1078         * {@code StringComparator} assigned to the operator.
1079         *
1080         * @param oper an operator to work with.
1081         * @param pattern a pattern to compare with
1082         * @param direction a scrolling direction - one of
1083         * {@code ScrollAdjuster.*_SCROLL_DIRECTION} fields.
1084         */
1085        public ToStringScrollAdjuster(JSpinnerOperator oper, String pattern, int direction) {
1086            this(oper, pattern, oper.getComparator(), direction);
1087        }
1088
1089        @Override
1090        public boolean reached(Object curvalue) {
1091            return curvalue != null && comparator.equals(curvalue.toString(), pattern);
1092        }
1093
1094        @Override
1095        public String getDescription() {
1096            return "Spin to \"" + pattern + "\" value";
1097        }
1098
1099        @Override
1100        public String toString() {
1101            return "ToStringScrollAdjuster{" + "pattern=" + pattern + ", comparator=" + comparator + '}';
1102        }
1103
1104        @Override
1105        public int getScrollOrientation() {
1106            return SwingConstants.VERTICAL;
1107        }
1108    }
1109
1110    /**
1111     * Provides some specific functionality for {@code JSpinner} components
1112     * having {@code SpinnerNumberModel} model. Constructor of this object
1113     * is private - it cannot be received only from another JSpinnerOperator
1114     * instance.
1115     *
1116     * @see #getNumberSpinner
1117     */
1118    public static class NumberSpinnerOperator extends JSpinnerOperator {
1119
1120        private NumberSpinnerOperator(JSpinnerOperator spinner) {
1121            super((JSpinner) spinner.getSource());
1122            copyEnvironment(spinner);
1123            checkModel(this, SpinnerNumberModel.class);
1124        }
1125
1126        /**
1127         * Costs spinner's model to <code>SpinnerNumberModel<code>.
1128         *
1129         * @return a spinner model.
1130         */
1131        public SpinnerNumberModel getNumberModel() {
1132            return (SpinnerNumberModel) getModel();
1133        }
1134
1135        /**
1136         * Scrolls to a double value.
1137         *
1138         * @param value a value to scroll to.
1139         */
1140        public void scrollToValue(double value) {
1141            scrollTo(new NumberScrollAdjuster(this, value));
1142        }
1143
1144        /**
1145         * Scrolls to a number value.
1146         *
1147         * @param value a value to scroll to.
1148         */
1149        public void scrollToValue(Number value) {
1150            scrollTo(new NumberScrollAdjuster(this, value));
1151        }
1152    }
1153
1154    /**
1155     * Provides some specific functionality for {@code JSpinner} components
1156     * having {@code SpinnerListModel} model. Constructor of this object is
1157     * private - it cannot be received only from another JSpinnerOperator
1158     * instance.
1159     *
1160     * @see #getListSpinner
1161     */
1162    public static class ListSpinnerOperator extends JSpinnerOperator {
1163
1164        private ListSpinnerOperator(JSpinnerOperator spinner) {
1165            super((JSpinner) spinner.getSource());
1166            copyEnvironment(spinner);
1167            checkModel(this, SpinnerListModel.class);
1168        }
1169
1170        /**
1171         * Costs spinner's model to <code>SpinnerListModel<code>.
1172         *
1173         * @return a spinner model.
1174         */
1175        public SpinnerListModel getListModel() {
1176            return (SpinnerListModel) getModel();
1177        }
1178
1179        /**
1180         * Looks for an index of an item having {@code toString()} matching
1181         * a specified pattern.
1182         *
1183         * @param pattern a string pattern
1184         * @param comparator a string comparision criteria.
1185         */
1186        public int findItem(String pattern, StringComparator comparator) {
1187            List<?> list = getListModel().getList();
1188            for (int i = 0; i < list.size(); i++) {
1189                if (comparator.equals(list.get(i).toString(), pattern)) {
1190                    return i;
1191                }
1192            }
1193            return -1;
1194        }
1195
1196        /**
1197         * Looks for an index of an item having {@code toString()} matching
1198         * a specified pattern. Uses a {@code StringComparator} assigned to
1199         * the operator.
1200         *
1201         * @param pattern a string pattern
1202         */
1203        public int findItem(String pattern) {
1204            return findItem(pattern, getComparator());
1205        }
1206
1207        /**
1208         * Scrolls to an item having specified instance.
1209         *
1210         * @param index an index to scroll to.
1211         */
1212        public void scrollToIndex(int index) {
1213            scrollTo(new ListScrollAdjuster(this, index));
1214        }
1215
1216        /**
1217         * Scrolls to {@code getValue().toString()} match a specified
1218         * pattern.
1219         *
1220         * @param pattern a string pattern
1221         * @param comparator a string comparision criteria.
1222         */
1223        public void scrollToString(String pattern, StringComparator comparator) {
1224            int index = findItem(pattern, comparator);
1225            if (index != -1) {
1226                scrollToIndex(index);
1227            } else {
1228                throw (new JemmyException("No \"" + pattern + "\" item in JSpinner", getSource()));
1229            }
1230        }
1231
1232        /**
1233         * Scrolls to {@code getValue().toString()} match a specified
1234         * pattern. Uses a {@code StringComparator} assigned to the
1235         * operator.
1236         *
1237         * @param pattern a string pattern
1238         */
1239        public void scrollToString(String pattern) {
1240            scrollToString(pattern, getComparator());
1241        }
1242    }
1243
1244    /**
1245     * Provides some specific functionality for {@code JSpinner} components
1246     * having {@code SpinnerDateModel} model. Constructor of this object is
1247     * private - it cannot be received only from another JSpinnerOperator
1248     * instance.
1249     *
1250     * @see #getDateSpinner
1251     */
1252    public static class DateSpinnerOperator extends JSpinnerOperator {
1253
1254        private DateSpinnerOperator(JSpinnerOperator spinner) {
1255            super((JSpinner) spinner.getSource());
1256            copyEnvironment(spinner);
1257            checkModel(this, SpinnerDateModel.class);
1258        }
1259
1260        /**
1261         * Costs spinner's model to <code>SpinnerDateModel<code>.
1262         *
1263         * @return a spinner model.
1264         */
1265        public SpinnerDateModel getDateModel() {
1266            return (SpinnerDateModel) getModel();
1267        }
1268
1269        /**
1270         * Scrolls to a date.
1271         *
1272         * @param date a date to scroll to.
1273         */
1274        public void scrollToDate(Date date) {
1275            scrollTo(new DateScrollAdjuster(this, date));
1276        }
1277    }
1278
1279    /**
1280     * Exception is thown whenever spinner model is threated wrong.
1281     */
1282    public static class SpinnerModelException extends JemmyException {
1283
1284        private static final long serialVersionUID = 42L;
1285
1286        /**
1287         * Constructs a {@code SpinnerModelException} object.
1288         *
1289         * @param message error message.
1290         * @param comp a spinner which model cased the exception.
1291         */
1292        public SpinnerModelException(String message, Component comp) {
1293            super(message, comp);
1294        }
1295    }
1296}
1297