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