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  */
  23 package org.netbeans.jemmy.operators;
  24 
  25 import java.awt.Component;
  26 import java.awt.Container;
  27 import java.text.ParseException;
  28 import java.util.Date;
  29 import java.util.Hashtable;
  30 import java.util.List;
  31 
  32 import javax.swing.JComponent;
  33 import javax.swing.JSpinner;
  34 import javax.swing.SpinnerDateModel;
  35 import javax.swing.SpinnerListModel;
  36 import javax.swing.SpinnerModel;
  37 import javax.swing.SpinnerNumberModel;
  38 import javax.swing.SwingConstants;
  39 import javax.swing.event.ChangeListener;
  40 import javax.swing.plaf.SpinnerUI;
  41 
  42 import org.netbeans.jemmy.Action;
  43 import org.netbeans.jemmy.ComponentChooser;
  44 import org.netbeans.jemmy.ComponentSearcher;
  45 import org.netbeans.jemmy.JemmyException;
  46 import org.netbeans.jemmy.Outputable;
  47 import org.netbeans.jemmy.TestOut;
  48 import org.netbeans.jemmy.TimeoutExpiredException;
  49 import org.netbeans.jemmy.Timeoutable;
  50 import org.netbeans.jemmy.Timeouts;
  51 import org.netbeans.jemmy.drivers.DriverManager;
  52 import org.netbeans.jemmy.drivers.ScrollDriver;
  53 import 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  */
  65 public 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 }