001    /*
002     * Created on Jan 19, 2008
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License
010     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011     * or implied. See the License for the specific language governing permissions and limitations under
012     * the License.
013     *
014     * Copyright @2008-2010 the original author or authors.
015     */
016    package org.fest.swing.driver;
017    
018    import static java.awt.event.KeyEvent.VK_SHIFT;
019    import static java.util.Arrays.sort;
020    import static org.fest.assertions.Assertions.assertThat;
021    import static org.fest.assertions.Fail.fail;
022    import static org.fest.swing.awt.AWT.visibleCenterOf;
023    import static org.fest.swing.core.MouseButton.LEFT_BUTTON;
024    import static org.fest.swing.driver.CommonValidations.validateCellReader;
025    import static org.fest.swing.driver.JListContentQuery.contents;
026    import static org.fest.swing.driver.JListItemCountQuery.itemCountIn;
027    import static org.fest.swing.driver.JListItemIndexValidator.validateIndices;
028    import static org.fest.swing.driver.JListItemValueQuery.itemValue;
029    import static org.fest.swing.driver.JListMatchingItemQuery.centerOfMatchingItemCell;
030    import static org.fest.swing.driver.JListMatchingItemQuery.matchingItemIndex;
031    import static org.fest.swing.driver.JListMatchingItemQuery.matchingItemIndices;
032    import static org.fest.swing.driver.JListMatchingItemQuery.matchingItemValues;
033    import static org.fest.swing.driver.JListScrollToItemTask.ITEM_NOT_FOUND;
034    import static org.fest.swing.driver.JListScrollToItemTask.scrollToItem;
035    import static org.fest.swing.driver.JListScrollToItemTask.scrollToItemIfNotSelectedYet;
036    import static org.fest.swing.driver.JListSelectedIndexQuery.selectedIndexOf;
037    import static org.fest.swing.driver.JListSelectionIndicesQuery.selectedIndices;
038    import static org.fest.swing.driver.JListSelectionValueQuery.NO_SELECTION_VALUE;
039    import static org.fest.swing.driver.JListSelectionValueQuery.singleSelectionValue;
040    import static org.fest.swing.driver.JListSelectionValuesQuery.selectionValues;
041    import static org.fest.swing.driver.TextAssert.verifyThat;
042    import static org.fest.swing.edt.GuiActionRunner.execute;
043    import static org.fest.swing.util.Arrays.isEmptyIntArray;
044    import static org.fest.util.Arrays.format;
045    import static org.fest.util.Strings.concat;
046    
047    import java.awt.Point;
048    import java.util.List;
049    import java.util.regex.Pattern;
050    
051    import javax.swing.JList;
052    import javax.swing.JPopupMenu;
053    
054    import org.fest.assertions.Description;
055    import org.fest.swing.annotation.RunsInEDT;
056    import org.fest.swing.cell.JListCellReader;
057    import org.fest.swing.core.MouseButton;
058    import org.fest.swing.core.Robot;
059    import org.fest.swing.edt.GuiQuery;
060    import org.fest.swing.edt.GuiTask;
061    import org.fest.swing.exception.ActionFailedException;
062    import org.fest.swing.exception.ComponentLookupException;
063    import org.fest.swing.exception.LocationUnavailableException;
064    import org.fest.swing.util.Pair;
065    import org.fest.swing.util.PatternTextMatcher;
066    import org.fest.swing.util.StringTextMatcher;
067    import org.fest.swing.util.TextMatcher;
068    import org.fest.swing.util.Range.From;
069    import org.fest.swing.util.Range.To;
070    
071    /**
072     * Understands functional testing of <code>{@link JList}</code>s:
073     * <ul>
074     * <li>user input simulation</li>
075     * <li>state verification</li>
076     * <li>property value query</li>
077     * </ul>
078     * This class is intended for internal use only. Please use the classes in the package
079     * <code>{@link org.fest.swing.fixture}</code> in your tests.
080     *
081     * @author Alex Ruiz
082     * @author Yvonne Wang
083     */
084    public class JListDriver extends JComponentDriver {
085    
086      private static final String SELECTED_INDICES_PROPERTY = "selectedIndices";
087      private static final String SELECTED_INDEX_PROPERTY = "selectedIndex";
088    
089      private JListCellReader cellReader;
090    
091      /**
092       * Creates a new </code>{@link JListDriver}</code>.
093       * @param robot the robot to use to simulate user input.
094       */
095      public JListDriver(Robot robot) {
096        super(robot);
097        cellReader(new BasicJListCellReader());
098      }
099    
100      /**
101       * Returns an array of <code>String</code>s that represents the contents of the given <code>{@link JList}</code>,
102       * using this driver's <code>{@link JListCellReader}</code>.
103       * @param list the target <code>JList</code>.
104       * @return an array of <code>String</code>s that represents the contents of the given <code>JList</code>.
105       * @see #cellReader(JListCellReader)
106       */
107      @RunsInEDT
108      public String[] contentsOf(JList list) {
109        return contents(list, cellReader);
110      }
111    
112      /**
113       * Selects the items matching the given values.
114       * @param list the target <code>JList</code>.
115       * @param values the values to match. Each <code>String</code> can be a regular expression.
116       * @throws NullPointerException if the given array is <code>null</code>.
117       * @throws IllegalArgumentException if the given array is empty.
118       * @throws IllegalStateException if the <code>JList</code> is disabled.
119       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
120       * @throws LocationUnavailableException if an element matching the any of the given values cannot be found.
121       */
122      @RunsInEDT
123      public void selectItems(JList list, String[] values) {
124        selectItems(list, new StringTextMatcher(values));
125      }
126    
127      /**
128       * Selects the items matching the given regular expression patterns.
129       * @param list the target <code>JList</code>.
130       * @param patterns the regular expression patterns to match.
131       * @throws NullPointerException if the given array is <code>null</code>.
132       * @throws NullPointerException if any of the regular expression patterns is <code>null</code>.
133       * @throws IllegalArgumentException if the given array is empty.
134       * @throws IllegalStateException if the <code>JList</code> is disabled.
135       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
136       * @throws LocationUnavailableException if an element matching the any of the given regular expression patterns cannot
137       * be found.
138       * @since 1.2
139       */
140      @RunsInEDT
141      public void selectItems(JList list, Pattern[] patterns) {
142        selectItems(list, new PatternTextMatcher(patterns));
143      }
144    
145      @RunsInEDT
146      private void selectItems(final JList list, final TextMatcher matcher) {
147        final List<Integer> indices = matchingItemIndices(list, matcher, cellReader);
148        if (indices.isEmpty()) throw failMatchingNotFound(list, matcher);
149        clearSelection(list);
150        new MultipleSelectionTemplate(robot) {
151          int elementCount() { return indices.size(); }
152          void selectElement(int index) { selectItem(list, indices.get(index)); }
153        }.multiSelect();
154      }
155    
156      /**
157       * Selects the item in the given <code>{@link JList}</code> whose value matches the given one.
158       * @param list the target <code>JList</code>.
159       * @param value the value to match.
160       * @throws IllegalStateException if the <code>JList</code> is disabled.
161       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
162       * @throws LocationUnavailableException if an element matching the given value cannot be found.
163       */
164      @RunsInEDT
165      public void selectItem(JList list, String value) {
166        selectItem(list, new StringTextMatcher(value));
167      }
168    
169      /**
170       * Selects the item in the given <code>{@link JList}</code> whose value matches the given regular expression pattern.
171       * @param list the target <code>JList</code>.
172       * @param pattern the regular expression to match.
173       * @throws IllegalStateException if the <code>JList</code> is disabled.
174       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
175       * @throws LocationUnavailableException if an element matching the given value cannot be found.
176       * @throws NullPointerException if the given regular expression pattern is <code>null</code>.
177       * @since 1.2
178       */
179      @RunsInEDT
180      public void selectItem(JList list, Pattern pattern) {
181        selectItem(list, new PatternTextMatcher(pattern));
182      }
183    
184      @RunsInEDT
185      private void selectItem(JList list, TextMatcher matcher) {
186        Pair<Integer, Point> scrollInfo = scrollToItemIfNotSelectedYet(list, matcher, cellReader);
187        robot.waitForIdle();
188        verify(list, scrollInfo, matcher);
189        if (scrollInfo.ii == null) return; // already selected cell.
190        robot.click(list, cellCenterIn(scrollInfo));
191      }
192    
193      /**
194       * Clicks the first item matching the given value, using the specified mouse button, the given number times.
195       * @param list the target <code>JList</code>.
196       * @param value the value to match.
197       * @param button the button to use.
198       * @param times the number of times to click.
199       * @throws IllegalStateException if the <code>JList</code> is disabled.
200       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
201       * @throws LocationUnavailableException if an element matching the given value cannot be found.
202       */
203      public void clickItem(JList list, String value, MouseButton button, int times) {
204        clickItem(list, new StringTextMatcher(value), button, times);
205      }
206    
207      /**
208       * Clicks the first item matching the given regular expression pattern, using the specified mouse button, the given
209       * number times.
210       * @param list the target <code>JList</code>.
211       * @param pattern the regular expression pattern to match.
212       * @param button the button to use.
213       * @param times the number of times to click.
214       * @throws IllegalStateException if the <code>JList</code> is disabled.
215       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
216       * @throws NullPointerException if the given regular expression pattern is <code>null</code>.
217       * @throws LocationUnavailableException if an element matching the given regular expression pattern cannot be found.
218       * @since 1.2
219       */
220      public void clickItem(JList list, Pattern pattern, MouseButton button, int times) {
221        clickItem(list, new PatternTextMatcher(pattern), button, times);
222      }
223    
224      private void clickItem(JList list, TextMatcher matcher, MouseButton button, int times) {
225        Pair<Integer, Point> scrollInfo = scrollToItem(list, matcher, cellReader);
226        robot.waitForIdle();
227        verify(list, scrollInfo, matcher);
228        robot.click(list, cellCenterIn(scrollInfo), button, times);
229      }
230    
231      /**
232       * Selects the items under the given indices.
233       * @param list the target <code>JList</code>.
234       * @param indices the indices of the items to select.
235       * @throws NullPointerException if the given array is <code>null</code>.
236       * @throws IllegalArgumentException if the given array is empty.
237       * @throws IllegalStateException if the <code>JList</code> is disabled.
238       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
239       * @throws IndexOutOfBoundsException if any of the indices is negative or greater than the index of the last item in
240       * the <code>JList</code>.
241       */
242      public void selectItems(final JList list, final int[] indices) {
243        validateArrayOfIndices(indices);
244        clearSelection(list);
245        new MultipleSelectionTemplate(robot) {
246          int elementCount() { return indices.length; }
247          void selectElement(int index) { selectItem(list, indices[index]); }
248        }.multiSelect();
249      }
250    
251      /**
252       * Clears the selection in the given <code>{@link JList}</code>. Since this method does not simulate user input, it
253       * does not verifies that the <code>JList</code> is enabled and showing.
254       * @param list the target <code>JList</code>.
255       * @since 1.2
256       */
257      public void clearSelection(JList list) {
258        clearSelectionOf(list);
259        robot.waitForIdle();
260      }
261    
262      @RunsInEDT
263      private static void clearSelectionOf(final JList list) {
264        execute(new GuiTask() {
265          protected void executeInEDT() {
266            list.clearSelection();
267          }
268        });
269      }
270    
271      /**
272       * Selects the items in the specified range.
273       * @param list the target <code>JList</code>.
274       * @param from the starting point of the selection.
275       * @param to the last item to select.
276       * @throws IllegalStateException if the <code>JList</code> is disabled.
277       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
278       * @throws IndexOutOfBoundsException if the any index is negative or greater than the index of the last item in the
279       * <code>JList</code>.
280       */
281      @RunsInEDT
282      public void selectItems(JList list, From from, To to) {
283        selectItems(list, from.value, to.value);
284      }
285    
286      /**
287       * Selects the items in the specified range.
288       * @param list the target <code>JList</code>.
289       * @param start the starting point of the selection.
290       * @param end the last item to select (inclusive.)
291       * @throws IllegalStateException if the <code>JList</code> is disabled.
292       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
293       * @throws IndexOutOfBoundsException if the any index is negative or greater than the index of the last item in the
294       * <code>JList</code>.
295       */
296      @RunsInEDT
297      public void selectItems(JList list, int start, int end) {
298        validateIndicesAndClearSelection(list, start, end);
299        selectItem(list, start);
300        robot.pressKey(VK_SHIFT);
301        try {
302          clickItem(list, end, LEFT_BUTTON, 1);
303        } finally {
304          robot.releaseKey(VK_SHIFT);
305        }
306      }
307    
308      @RunsInEDT
309      private static void validateIndicesAndClearSelection(final JList list, final int...indices) {
310        execute(new GuiTask() {
311          protected void executeInEDT() {
312            validateIndices(list, indices);
313            list.clearSelection();
314          }
315        });
316      }
317    
318      /**
319       * Selects the item under the given index using left mouse button once.
320       * @param list the target <code>JList</code>.
321       * @param index the index of the item to click.
322       * @throws IllegalStateException if the <code>JList</code> is disabled.
323       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
324       * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the
325       * <code>JList</code>.
326       */
327      @RunsInEDT
328      public void selectItem(JList list, int index) {
329        Point cellCenter = scrollToItemIfNotSelectedYet(list, index);
330        robot.waitForIdle();
331        if (cellCenter == null) return; // cell already selected
332        robot.click(list, cellCenter);
333      }
334    
335      /**
336       * Clicks the item under the given index, using the specified mouse button, the given number times.
337       * @param list the target <code>JList</code>.
338       * @param index the index of the item to click.
339       * @param button the button to use.
340       * @param times the number of times to click.
341       * @throws IllegalStateException if the <code>JList</code> is disabled.
342       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
343       * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the
344       * <code>JList</code>.
345       */
346      @RunsInEDT
347      public void clickItem(JList list, int index, MouseButton button, int times) {
348        Point cellCenter = scrollToItem(list, index);
349        robot.waitForIdle();
350        robot.click(list, cellCenter, button, times);
351      }
352    
353      /**
354       * Verifies that the selected item in the <code>{@link JList}</code> matches the given value.
355       * @param list the target <code>JList</code>.
356       * @param value the value to match. It can be a regular expression pattern.
357       * @throws AssertionError if the selected item does not match the value.
358       * @see #cellReader(JListCellReader)
359       */
360      @RunsInEDT
361      public void requireSelection(final JList list, String value) {
362        String selection = requiredSelection(list);
363        verifyThat(selection).as(selectedIndexProperty(list)).isEqualOrMatches(value);
364      }
365    
366      /**
367       * Verifies that the selected item in the <code>{@link JList}</code> matches the given regular expression pattern.
368       * @param list the target <code>JList</code>.
369       * @param pattern the regular expression pattern to match.
370       * @throws AssertionError if the selected item does not match the given regular expression pattern.
371       * @throws NullPointerException if the given regular expression pattern is <code>null</code>.
372       * @see #cellReader(JListCellReader)
373       * @since 1.2
374       */
375      @RunsInEDT
376      public void requireSelection(JList list, Pattern pattern) {
377        String selection = requiredSelection(list);
378        verifyThat(selection).as(selectedIndexProperty(list)).matches(pattern);
379      }
380    
381      private String requiredSelection(final JList list) {
382        Object selection = singleSelectionValue(list, cellReader);
383        if (NO_SELECTION_VALUE == selection) failNoSelection(list);
384        return (String)selection;
385      }
386    
387      /**
388       * Verifies that the selected index in the <code>{@link JList}</code> matches the given value.
389       * @param list the target <code>JList</code>.
390       * @param index the selection index to match.
391       * @throws AssertionError if the selected index does not match the value.
392       * @since 1.2
393       */
394      @RunsInEDT
395      public void requireSelection(final JList list, int index) {
396        int selectedIndex = selectedIndexOf(list);
397        if (selectedIndex == -1) failNoSelection(list);
398        assertThat(selectedIndex).as(selectedIndexProperty(list)).isEqualTo(index);
399      }
400    
401      /**
402       * Returns an array of <code>String</code>s that represents the selection in the given <code>{@link JList}</code>,
403       * using this driver's <code>{@link JListCellReader}</code>.
404       * @param list the target <code>JList</code>.
405       * @return an array of <code>String</code>s that represents the selection in the given <code>JList</code>.
406       * @see #cellReader(JListCellReader)
407       */
408      @RunsInEDT
409      public String[] selectionOf(JList list) {
410        List<String> selection = selectionValues(list, cellReader);
411        return selection.toArray(new String[selection.size()]);
412      }
413    
414      /**
415       * Verifies that the selected items in the <code>{@link JList}</code> match the given values.
416       * @param list the target <code>JList</code>.
417       * @param items the values to match. Each value can be a regular expression pattern.
418       * @throws NullPointerException if the given array is <code>null</code>.
419       * @throws IllegalArgumentException if the given array is empty.
420       * @throws AssertionError if the selected items do not match the given values.
421       */
422      @RunsInEDT
423      public void requireSelectedItems(JList list, String... items) {
424        requireSelectedItems(list, new StringTextMatcher(items));
425      }
426    
427      /**
428       * Verifies that the selected items in the <code>{@link JList}</code> match the given regular expression patterns.
429       * @param list the target <code>JList</code>.
430       * @param patterns the regular expression patterns to match.
431       * @throws NullPointerException if the given array is <code>null</code>.
432       * @throws IllegalArgumentException if the given array is empty.
433       * @throws NullPointerException if any of the patterns in the array is <code>null</code>.
434       * @throws AssertionError if the selected items do not match the given values.
435       * @see #cellReader(JListCellReader)
436       * @since 1.2
437       */
438      @RunsInEDT
439      public void requireSelectedItems(JList list, Pattern... patterns) {
440        requireSelectedItems(list, new PatternTextMatcher(patterns));
441      }
442    
443      @RunsInEDT
444      private void requireSelectedItems(JList list, TextMatcher matcher) {
445        List<String> matchingValues = matchingItemValues(list, matcher, cellReader);
446        assertThat(selectionValues(list, cellReader)).as(propertyName(list, SELECTED_INDICES_PROPERTY)).isEqualTo(matchingValues);
447      }
448    
449      /**
450       * Verifies that the given item indices are selected in the <code>{@link JList}</code>.
451       * @param list the target <code>JList</code>.
452       * @param indices the expected indices of the selected items.
453       * @throws NullPointerException if the given array is <code>null</code>.
454       * @throws IllegalArgumentException if the given array is empty.
455       * @throws AssertionError if the selection in the <code>JList</code> does not match the given one.
456       */
457      @RunsInEDT
458      public void requireSelectedItems(JList list, int... indices) {
459        validateArrayOfIndices(indices);
460        sort(indices);
461        assertThat(selectedIndices(list)).as(propertyName(list, SELECTED_INDICES_PROPERTY)).isEqualTo(indices);
462      }
463    
464      private void validateArrayOfIndices(int[] indices) {
465        if (indices == null) throw new NullPointerException("The array of indices should not be null");
466        if (isEmptyIntArray(indices)) throw new IllegalArgumentException("The array of indices should not be empty");
467      }
468    
469      /**
470       * Verifies that the <code>{@link JList}</code> does not have a selection.
471       * @param list the target <code>JList</code>.
472       * @throws AssertionError if the <code>JList</code> has a selection.
473       */
474      @RunsInEDT
475      public void requireNoSelection(JList list) {
476        assertThat(selectedIndexOf(list)).as(selectedIndexProperty(list)).isEqualTo(-1);
477      }
478    
479      @RunsInEDT
480      private void failNoSelection(JList list) {
481        fail(concat("[", selectedIndexProperty(list).value(), "] No selection"));
482      }
483    
484      @RunsInEDT
485      private Description selectedIndexProperty(JList list) {
486        return propertyName(list, SELECTED_INDEX_PROPERTY);
487      }
488    
489      /**
490       * Starts a drag operation at the location of the first item matching the given value.
491       * @param list the target <code>JList</code>.
492       * @param value the value to match. It can be a regular expression.
493       * @throws IllegalStateException if the <code>JList</code> is disabled.
494       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
495       * @throws LocationUnavailableException if an element matching the given value cannot be found.
496       * @see #cellReader(JListCellReader)
497       */
498      @RunsInEDT
499      public void drag(JList list, String value) {
500        drag(list, new StringTextMatcher(value));
501      }
502    
503      /**
504       * Starts a drag operation at the location of the first item matching the given regular expression pattern.
505       * @param list the target <code>JList</code>.
506       * @param pattern the regular expression pattern to match.
507       * @throws IllegalStateException if the <code>JList</code> is disabled.
508       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
509       * @throws NullPointerException if the regular expression pattern is <code>null</code>.
510       * @throws LocationUnavailableException if an element matching the given regular expression pattern cannot be found.
511       * @see #cellReader(JListCellReader)
512       * @since 1.2
513       */
514      @RunsInEDT
515      public void drag(JList list, Pattern pattern) {
516        drag(list, new PatternTextMatcher(pattern));
517      }
518    
519      private void drag(JList list, TextMatcher matcher) {
520        Pair<Integer, Point> scrollInfo = scrollToItem(list, matcher, cellReader);
521        robot.waitForIdle();
522        verify(list, scrollInfo, matcher);
523        super.drag(list, cellCenterIn(scrollInfo));
524      }
525    
526      /**
527       * Ends a drag operation at the location of the first item matching the given value.
528       * @param list the target <code>JList</code>.
529       * @param value the value to match. It can be a regular expression.
530       * @throws IllegalStateException if the <code>JList</code> is disabled.
531       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
532       * @throws LocationUnavailableException if an element matching the given value cannot be found.
533       * @throws ActionFailedException if there is no drag action in effect.
534       */
535      @RunsInEDT
536      public void drop(JList list, String value) {
537        drop(list, new StringTextMatcher(value));
538      }
539    
540      /**
541       * Ends a drag operation at the location of the first item matching the given regular expression pattern.
542       * @param list the target <code>JList</code>.
543       * @param pattern the regular expression pattern to match.
544       * @throws IllegalStateException if the <code>JList</code> is disabled.
545       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
546       * @throws NullPointerException if the given regular expression pattern is <code>null</code>.
547       * @throws LocationUnavailableException if an element matching the given value cannot be found.
548       * @throws ActionFailedException if there is no drag action in effect.
549       * @since 1.2
550       */
551      public void drop(JList list, Pattern pattern) {
552        drop(list, new PatternTextMatcher(pattern));
553      }
554    
555      private void drop(JList list, TextMatcher matcher) {
556        Pair<Integer, Point> scrollInfo = scrollToItem(list, matcher, cellReader);
557        robot.waitForIdle();
558        verify(list, scrollInfo, matcher);
559        super.drop(list, cellCenterIn(scrollInfo));
560      }
561    
562      private void verify(JList list, Pair<Integer, Point> scrollInfo, TextMatcher matcher) {
563        if (ITEM_NOT_FOUND.equals(scrollInfo)) throw failMatchingNotFound(list, matcher);
564      }
565    
566      /**
567       * Starts a drag operation at the location of the given index.
568       * @param list the target <code>JList</code>.
569       * @param index the given index.
570       * @throws IllegalStateException if the <code>JList</code> is disabled.
571       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
572       * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the
573       * <code>JList</code>.
574       */
575      @RunsInEDT
576      public void drag(JList list, int index) {
577        Point cellCenter = scrollToItem(list, index);
578        robot.waitForIdle();
579        super.drag(list, cellCenter);
580      }
581    
582      /**
583       * Ends a drag operation at the location of the given index.
584       * @param list the target <code>JList</code>.
585       * @param index the given index.
586       * @throws IllegalStateException if the <code>JList</code> is disabled.
587       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
588       * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the
589       * <code>JList</code>.
590       * @throws ActionFailedException if there is no drag action in effect.
591       */
592      @RunsInEDT
593      public void drop(JList list, int index) {
594        Point cellCenter = scrollToItem(list, index);
595        robot.waitForIdle();
596        super.drop(list, cellCenter);
597      }
598    
599    
600      /**
601       * Ends a drag operation at the center of the <code>{@link JList}</code>.
602       * @param list the target <code>JList</code>.
603       * @throws IllegalStateException if the <code>JList</code> is disabled.
604       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
605       * @throws ActionFailedException if there is no drag action in effect.
606       */
607      @RunsInEDT
608      public void drop(JList list) {
609        assertIsEnabledAndShowing(list);
610        super.drop(list, visibleCenterOf(list));
611      }
612    
613      /**
614       * Shows a pop-up menu at the location of the specified item in the <code>{@link JList}</code>.
615       * @param list the target <code>JList</code>.
616       * @param value the value to match. It can be a regular expression pattern.
617       * @return a fixture that manages the displayed pop-up menu.
618       * @throws IllegalStateException if the <code>JList</code> is disabled.
619       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
620       * @throws ComponentLookupException if a pop-up menu cannot be found.
621       * @throws LocationUnavailableException if an element matching the given value cannot be found.
622       */
623      @RunsInEDT
624      public JPopupMenu showPopupMenu(JList list, String value) {
625        return showPopupMenu(list, new StringTextMatcher(value));
626      }
627    
628      /**
629       * Shows a pop-up menu at the location of the specified item in the <code>{@link JList}</code>.
630       * @param list the target <code>JList</code>.
631       * @param pattern the regular expression pattern to match.
632       * @return a fixture that manages the displayed pop-up menu.
633       * @throws IllegalStateException if the <code>JList</code> is disabled.
634       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
635       * @throws NullPointerException if the regular expression pattern is <code>null</code>.
636       * @throws ComponentLookupException if a pop-up menu cannot be found.
637       * @throws LocationUnavailableException if an element matching the given value cannot be found.
638       * @since 1.2
639       */
640      @RunsInEDT
641      public JPopupMenu showPopupMenu(JList list, Pattern pattern) {
642        return showPopupMenu(list, new PatternTextMatcher(pattern));
643      }
644    
645      @RunsInEDT
646      private JPopupMenu showPopupMenu(JList list, TextMatcher matcher) {
647        Pair<Integer, Point> scrollInfo = scrollToItem(list, matcher, cellReader);
648        robot.waitForIdle();
649        verify(list, scrollInfo, matcher);
650        return robot.showPopupMenu(list, cellCenterIn(scrollInfo));
651      }
652    
653      private Point cellCenterIn(Pair<Integer, Point> scrollInfo) {
654        return scrollInfo.ii;
655      }
656    
657      /**
658       * Shows a pop-up menu at the location of the specified item in the <code>{@link JList}</code>.
659       * @param list the target <code>JList</code>.
660       * @param index the index of the item.
661       * @return a driver that manages the displayed pop-up menu.
662       * @throws IllegalStateException if the <code>JList</code> is disabled.
663       * @throws IllegalStateException if the <code>JList</code> is not showing on the screen.
664       * @throws ComponentLookupException if a pop-up menu cannot be found.
665       * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the
666       * <code>JList</code>.
667       */
668      @RunsInEDT
669      public JPopupMenu showPopupMenu(JList list, int index) {
670        Point cellCenter = scrollToItem(list, index);
671        robot.waitForIdle();
672        return robot.showPopupMenu(list, cellCenter);
673      }
674    
675      /**
676       * Returns the coordinates of the first item matching the given value.
677       * @param list the target <code>JList</code>.
678       * @param value the value to match.
679       * @return the coordinates of the item at the given item.
680       * @throws LocationUnavailableException if an element matching the given value cannot be found.
681       */
682      @RunsInEDT
683      public Point pointAt(JList list, String value) {
684        return centerOfMatchingItemCell(list, value, cellReader);
685      }
686    
687      /**
688       * Returns the index of the first item matching the given value.
689       * @param list the target <code>JList</code>
690       * @param value the value to match. It can be a regular expression.
691       * @return the index of the first item matching the given value.
692       * @throws LocationUnavailableException if an element matching the given value cannot be found.
693       */
694      @RunsInEDT
695      public int indexOf(JList list, String value) {
696        return indexOf(list, new StringTextMatcher(value));
697      }
698    
699      /**
700       * Returns the index of the first item matching the given regular expression pattern.
701       * @param list the target <code>JList</code>.
702       * @param pattern the regular expression pattern to match.
703       * @return the index of the first item matching the given regular expression pattern.
704       * @throws LocationUnavailableException if an element matching the given value cannot be found.
705       * @throws NullPointerException if the given regular expression pattern is <code>null</code>.
706       * @since 1.2
707       */
708      @RunsInEDT
709      public int indexOf(JList list, Pattern pattern) {
710        return indexOf(list, new PatternTextMatcher(pattern));
711      }
712    
713      @RunsInEDT
714      private int indexOf(JList list, TextMatcher matcher) {
715        int index = itemIndex(list, matcher, cellReader);
716        if (index >= 0) return index;
717        throw failMatchingNotFound(list, matcher);
718      }
719    
720      @RunsInEDT
721      private static int itemIndex(final JList list, final TextMatcher matcher, final JListCellReader cellReader) {
722        return execute(new GuiQuery<Integer>() {
723          protected Integer executeInEDT() {
724            return matchingItemIndex(list, matcher, cellReader);
725          }
726        });
727      }
728    
729      private LocationUnavailableException failMatchingNotFound(JList list, TextMatcher matcher) {
730        throw new LocationUnavailableException(concat(
731            "Unable to find item matching the ", matcher.description(), " ", matcher.formattedValues(),
732            " among the JList contents ", format(contents(list, cellReader))));
733      }
734    
735      /**
736       * Returns the <code>String</code> representation of the element under the given index, using this driver's
737       * <code>{@link JListCellReader}</code>.
738       * @param list the target <code>JList</code>.
739       * @param index the given index.
740       * @return the value of the element under the given index.
741       * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the
742       * <code>JList</code>.
743       * @see #cellReader(JListCellReader)
744       */
745      @RunsInEDT
746      public String value(JList list, int index) {
747        return itemValue(list, index, cellReader);
748      }
749    
750      /**
751       * Updates the implementation of <code>{@link JListCellReader}</code> to use when comparing internal values of a
752       * <code>{@link JList}</code> and the values expected in a test.
753       * @param newCellReader the new <code>JListCellValueReader</code> to use.
754       * @throws NullPointerException if <code>newCellReader</code> is <code>null</code>.
755       */
756      public void cellReader(JListCellReader newCellReader) {
757        validateCellReader(newCellReader);
758        cellReader = newCellReader;
759      }
760    
761      /**
762       * Verifies that number of items in the given <code>{@link JList}</code> is equal to the expected one.
763       * @param list the target <code>JList</code>.
764       * @param expected the expected number of items.
765       * @throws AssertionError if the number of items in the given <code>{@link JList}</code> is not equal to the expected
766       * one.
767       * @since 1.2
768       */
769      @RunsInEDT
770      public void requireItemCount(JList list, int expected) {
771        int actual = itemCountIn(list);
772        assertThat(actual).as(propertyName(list, "itemCount")).isEqualTo(expected);
773      }
774    }