001 /*
002 * Created on Feb 2, 2008
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005 * 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 is distributed on
010 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011 * specific language governing permissions and limitations under the License.
012 *
013 * Copyright @2008-2010 the original author or authors.
014 */
015 package org.fest.swing.driver;
016
017 import static org.fest.assertions.Assertions.assertThat;
018 import static org.fest.assertions.Fail.fail;
019 import static org.fest.swing.core.MouseButton.LEFT_BUTTON;
020 import static org.fest.swing.driver.CommonValidations.validateCellReader;
021 import static org.fest.swing.driver.CommonValidations.validateCellWriter;
022 import static org.fest.swing.driver.ComponentStateValidator.validateIsEnabledAndShowing;
023 import static org.fest.swing.driver.JTableCellEditableQuery.isCellEditable;
024 import static org.fest.swing.driver.JTableCellValidator.validateCellIndices;
025 import static org.fest.swing.driver.JTableCellValidator.validateIndices;
026 import static org.fest.swing.driver.JTableCellValidator.validateNotNull;
027 import static org.fest.swing.driver.JTableColumnCountQuery.columnCountOf;
028 import static org.fest.swing.driver.JTableContentsQuery.tableContents;
029 import static org.fest.swing.driver.JTableHasSelectionQuery.hasSelection;
030 import static org.fest.swing.driver.JTableHeaderQuery.tableHeader;
031 import static org.fest.swing.driver.JTableMatchingCellQuery.cellWithValue;
032 import static org.fest.swing.driver.JTableSingleRowCellSelectedQuery.isCellSelected;
033 import static org.fest.swing.driver.TextAssert.verifyThat;
034 import static org.fest.swing.edt.GuiActionRunner.execute;
035 import static org.fest.swing.exception.ActionFailedException.actionFailure;
036 import static org.fest.swing.query.JTableColumnByIdentifierQuery.columnIndexByIdentifier;
037 import static org.fest.swing.util.Arrays.equal;
038 import static org.fest.swing.util.Arrays.isEmptyIntArray;
039 import static org.fest.util.Arrays.format;
040 import static org.fest.util.Arrays.isEmpty;
041 import static org.fest.util.Strings.concat;
042 import static org.fest.util.Strings.quote;
043
044 import java.awt.Color;
045 import java.awt.Component;
046 import java.awt.Font;
047 import java.awt.Point;
048 import java.util.regex.Pattern;
049
050 import javax.swing.JPopupMenu;
051 import javax.swing.JTable;
052 import javax.swing.table.JTableHeader;
053
054 import org.fest.assertions.Description;
055 import org.fest.swing.annotation.RunsInCurrentThread;
056 import org.fest.swing.annotation.RunsInEDT;
057 import org.fest.swing.cell.JTableCellReader;
058 import org.fest.swing.cell.JTableCellWriter;
059 import org.fest.swing.core.MouseButton;
060 import org.fest.swing.core.Robot;
061 import org.fest.swing.data.TableCell;
062 import org.fest.swing.data.TableCellFinder;
063 import org.fest.swing.edt.GuiQuery;
064 import org.fest.swing.edt.GuiTask;
065 import org.fest.swing.exception.ActionFailedException;
066 import org.fest.swing.exception.ComponentLookupException;
067 import org.fest.swing.util.Arrays;
068 import org.fest.swing.util.Pair;
069 import org.fest.swing.util.PatternTextMatcher;
070 import org.fest.swing.util.StringTextMatcher;
071 import org.fest.util.VisibleForTesting;
072
073 /**
074 * Understands functional testing of <code>{@link JTable}</code>s:
075 * <ul>
076 * <li>user input simulation</li>
077 * <li>state verification</li>
078 * <li>property value query</li>
079 * </ul>
080 * This class is intended for internal use only. Please use the classes in the package
081 * <code>{@link org.fest.swing.fixture}</code> in your tests.
082 *
083 * @author Yvonne Wang
084 * @author Alex Ruiz
085 */
086 public class JTableDriver extends JComponentDriver {
087
088 private static final String CONTENTS_PROPERTY = "contents";
089 private static final String EDITABLE_PROPERTY = "editable";
090 private static final String SELECTED_ROWS_PROPERTY = "selectedRows";
091 private static final String SELECTION_PROPERTY = "selection";
092 private static final String VALUE_PROPERTY = "value";
093
094 private final JTableLocation location = new JTableLocation();
095 private JTableCellReader cellReader;
096 private JTableCellWriter cellWriter;
097
098 /**
099 * Creates a new </code>{@link JTableDriver}</code>.
100 * @param robot the robot to use to simulate user events.
101 */
102 public JTableDriver(Robot robot) {
103 super(robot);
104 cellReader(new BasicJTableCellReader());
105 cellWriter(new BasicJTableCellWriter(robot));
106 }
107
108 /**
109 * Returns the <code>{@link JTableHeader}</code> of the given <code>{@link JTable}</code>.
110 * @param table the given <code>JTable</code>.
111 * @return the <code>JTableHeader</code> of the given <code>JTable</code>.
112 */
113 @RunsInEDT
114 public JTableHeader tableHeaderOf(JTable table) {
115 return tableHeader(table);
116 }
117
118 /**
119 * Returns the <code>String</code> representation of the value of the selected cell, using this driver's
120 * <code>{@link JTableCellReader}</code>.
121 * @param table the target <code>JTable</code>.
122 * @return the <code>String</code> representation of the value of the selected cell.
123 * @see #cellReader(JTableCellReader)
124 */
125 @RunsInEDT
126 public String selectionValue(JTable table) {
127 return selectionValue(table, cellReader);
128 }
129
130 @RunsInEDT
131 private static String selectionValue(final JTable table, final JTableCellReader cellReader) {
132 return execute(new GuiQuery<String>() {
133 protected String executeInEDT() {
134 if (table.getSelectedRowCount() == 0) return null;
135 return cellReader.valueAt(table, table.getSelectedRow(), table.getSelectedColumn());
136 }
137 });
138 }
139
140 /**
141 * Returns a cell from the given <code>{@link JTable}</code> using the given cell finder.
142 * @param table the target <code>JTable</code>.
143 * @param cellFinder knows how to find a cell.
144 * @return the found cell, if any.
145 * @throws NullPointerException if <code>cellFinder</code> is <code>null</code>.
146 * @throws IndexOutOfBoundsException if the row or column indices in the found cell are out of bounds.
147 * @throws ActionFailedException if a matching cell could not be found.
148 */
149 @RunsInEDT
150 public TableCell cell(JTable table, TableCellFinder cellFinder) {
151 if (cellFinder == null) throw new NullPointerException("The cell finder to use should not be null");
152 TableCell cell = cellFinder.findCell(table, cellReader);
153 validateCellIndices(table, cell);
154 return cell;
155 }
156
157 /**
158 * Returns a cell from the given <code>{@link JTable}</code> whose value matches the given one.
159 * @param table the target <code>JTable</code>.
160 * @param value the value of the cell to look for. It can be a regular expression.
161 * @return a cell from the given <code>JTable</code> whose value matches the given one.
162 * @throws ActionFailedException if a cell with a matching value cannot be found.
163 */
164 @RunsInEDT
165 public TableCell cell(JTable table, String value) {
166 return cellWithValue(table, new StringTextMatcher(value), cellReader);
167 }
168
169 /**
170 * Returns a cell from the given <code>{@link JTable}</code> whose value matches the given regular expression pattern.
171 * @param table the target <code>JTable</code>.
172 * @param pattern the regular expression pattern to match
173 * @return a cell from the given <code>JTable</code> whose value matches the given one.
174 * @throws NullPointerException if the given regular expression is <code>null</code>.
175 * @throws ActionFailedException if a cell with a matching value cannot be found.
176 * @since 1.2
177 */
178 @RunsInEDT
179 public TableCell cell(JTable table, Pattern pattern) {
180 return cellWithValue(table, new PatternTextMatcher(pattern), cellReader);
181 }
182
183 /**
184 * Returns the <code>String</code> representation of the value at the given cell, using this driver's
185 * <code>{@link JTableCellReader}</code>.
186 * @param table the target <code>JTable</code>.
187 * @param cell the table cell.
188 * @return the <code>String</code> representation of the value at the given cell.
189 * @throws NullPointerException if the cell is <code>null</code>.
190 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
191 * @see #cellReader(JTableCellReader)
192 */
193 @RunsInEDT
194 public String value(JTable table, TableCell cell) {
195 validateNotNull(cell);
196 return cellValue(table, cell, cellReader);
197 }
198
199 @RunsInEDT
200 private static String cellValue(final JTable table, final TableCell cell, final JTableCellReader cellReader) {
201 return execute(new GuiQuery<String>() {
202 protected String executeInEDT() {
203 validateCellIndices(table, cell);
204 return cellReader.valueAt(table, cell.row, cell.column);
205 }
206 });
207 }
208
209 /**
210 * Returns the <code>String</code> representation of the value at the given row and column, using this driver's
211 * <code>{@link JTableCellReader}</code>.
212 * @param table the target <code>JTable</code>.
213 * @param row the given row.
214 * @param column the given column.
215 * @return the <code>String</code> representation of the value at the given row and column.
216 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
217 * @see #cellReader(JTableCellReader)
218 */
219 @RunsInEDT
220 public String value(JTable table, int row, int column) {
221 return cellValue(table, row, column, cellReader);
222 }
223
224 @RunsInEDT
225 private static String cellValue(final JTable table, final int row, final int column,
226 final JTableCellReader cellReader) {
227 return execute(new GuiQuery<String>() {
228 protected String executeInEDT() {
229 validateIndices(table, row, column);
230 return cellReader.valueAt(table, row, column);
231 }
232 });
233 }
234
235 /**
236 * Returns the font of the given table cell.
237 * @param table the target <code>JTable</code>.
238 * @param cell the table cell.
239 * @return the font of the given table cell.
240 * @throws NullPointerException if the cell is <code>null</code>.
241 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
242 */
243 @RunsInEDT
244 public Font font(JTable table, TableCell cell) {
245 validateNotNull(cell);
246 return cellFont(table, cell, cellReader);
247 }
248
249 @RunsInEDT
250 private static Font cellFont(final JTable table, final TableCell cell, final JTableCellReader cellReader) {
251 return execute(new GuiQuery<Font>() {
252 protected Font executeInEDT() {
253 validateCellIndices(table, cell);
254 return cellReader.fontAt(table, cell.row, cell.column);
255 }
256 });
257 }
258
259 /**
260 * Returns the background color of the given table cell.
261 * @param table the target <code>JTable</code>.
262 * @param cell the table cell.
263 * @return the background color of the given table cell.
264 * @throws ActionFailedException if the cell is <code>null</code>.
265 * @throws ActionFailedException if any of the indices (row and column) is out of bounds.
266 */
267 @RunsInEDT
268 public Color background(JTable table, TableCell cell) {
269 validateNotNull(cell);
270 return cellBackground(table, cell, cellReader);
271 }
272
273 @RunsInEDT
274 private static Color cellBackground(final JTable table, final TableCell cell, final JTableCellReader cellReader) {
275 return execute(new GuiQuery<Color>() {
276 protected Color executeInEDT() {
277 validateCellIndices(table, cell);
278 return cellReader.backgroundAt(table, cell.row, cell.column);
279 }
280 });
281 }
282
283 /**
284 * Returns the foreground color of the given table cell.
285 * @param table the target <code>JTable</code>.
286 * @param cell the table cell.
287 * @return the foreground color of the given table cell.
288 * @throws NullPointerException if the cell is <code>null</code>.
289 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
290 */
291 @RunsInEDT
292 public Color foreground(JTable table, TableCell cell) {
293 validateNotNull(cell);
294 return cellForeground(table, cell, cellReader);
295 }
296
297 @RunsInEDT
298 private static Color cellForeground(final JTable table, final TableCell cell, final JTableCellReader cellReader) {
299 return execute(new GuiQuery<Color>() {
300 protected Color executeInEDT() {
301 validateCellIndices(table, cell);
302 return cellReader.foregroundAt(table, cell.row, cell.column);
303 }
304 });
305 }
306
307 /**
308 * Selects the given cells of the <code>{@link JTable}</code>.
309 * @param table the target <code>JTable</code>.
310 * @param cells the cells to select.
311 * @throws NullPointerException if <code>cells</code> is <code>null</code> or empty.
312 * @throws IllegalArgumentException if <code>cells</code> is <code>null</code> or empty.
313 * @throws IllegalStateException if the <code>JTable</code> is disabled.
314 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
315 * @throws NullPointerException if any element in <code>cells</code> is <code>null</code>.
316 * @throws IndexOutOfBoundsException if any of the indices of any of the <code>cells</code> are out of bounds.
317 */
318 public void selectCells(final JTable table, final TableCell[] cells) {
319 validateCellsToSelect(cells);
320 new MultipleSelectionTemplate(robot) {
321 int elementCount() {
322 return cells.length;
323 }
324
325 void selectElement(int index) {
326 selectCell(table, cells[index]);
327 }
328 }.multiSelect();
329 }
330
331 private void validateCellsToSelect(final TableCell[] cells) {
332 if (cells == null) throw new NullPointerException("Array of table cells to select should not be null");
333 if (isEmpty(cells)) throw new IllegalArgumentException("Array of table cells to select should not be empty");
334 }
335
336 /**
337 * Verifies that the <code>{@link JTable}</code> does not have any selection.
338 * @param table the target <code>JTable</code>.
339 * @throws AssertionError is the <code>JTable</code> has a selection.
340 */
341 @RunsInEDT
342 public void requireNoSelection(JTable table) {
343 assertNoSelection(table);
344 }
345
346 @RunsInEDT
347 private static void assertNoSelection(final JTable table) {
348 execute(new GuiTask() {
349 protected void executeInEDT() {
350 if (!hasSelection(table)) return;
351 String message = concat("[", propertyName(table, SELECTION_PROPERTY).value(),
352 "] expected no selection but was:<rows=", format(selectedRowsOf(table)), ", columns=",
353 format(table.getSelectedColumns()), ">");
354 fail(message);
355 }
356 });
357 }
358
359 /**
360 * Selects the given cell, if it is not selected already.
361 * @param table the target <code>JTable</code>.
362 * @param cell the cell to select.
363 * @throws NullPointerException if the cell is <code>null</code>.
364 * @throws IllegalStateException if the <code>JTable</code> is disabled.
365 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
366 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
367 */
368 @RunsInEDT
369 public void selectCell(JTable table, TableCell cell) {
370 validateNotNull(cell);
371 selectCell(table, cell.row, cell.column);
372 }
373
374 /**
375 * Clicks the given cell, using the specified mouse button, the given number of times.
376 * @param table the target <code>JTable</code>.
377 * @param cell the table cell.
378 * @param mouseButton the mouse button to use.
379 * @param times the number of times to click the cell.
380 * @throws NullPointerException if the cell is <code>null</code>.
381 * @throws IllegalStateException if the <code>JTable</code> is disabled.
382 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
383 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
384 */
385 @RunsInEDT
386 public void click(JTable table, TableCell cell, MouseButton mouseButton, int times) {
387 if (times <= 0)
388 throw new IllegalArgumentException("The number of times to click a cell should be greater than zero");
389 Point pointAtCell = scrollToPointAtCell(table, cell, location);
390 robot.click(table, pointAtCell, mouseButton, times);
391 }
392
393 /**
394 * Starts a drag operation at the location of the given table cell.
395 * @param table the target <code>JTable</code>.
396 * @param cell the table cell.
397 * @throws NullPointerException if the cell is <code>null</code>.
398 * @throws IllegalStateException if the <code>JTable</code> is disabled.
399 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
400 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
401 */
402 @RunsInEDT
403 public void drag(JTable table, TableCell cell) {
404 Point pointAtCell = scrollToPointAtCell(table, cell, location);
405 drag(table, pointAtCell);
406 }
407
408 /**
409 * Starts a drop operation at the location of the given table cell.
410 * @param table the target <code>JTable</code>.
411 * @param cell the table cell.
412 * @throws NullPointerException if the cell is <code>null</code>.
413 * @throws IllegalStateException if the <code>JTable</code> is disabled.
414 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
415 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
416 */
417 @RunsInEDT
418 public void drop(JTable table, TableCell cell) {
419 Point pointAtCell = scrollToPointAtCell(table, cell, location);
420 drop(table, pointAtCell);
421 }
422
423 /**
424 * Shows a pop-up menu at the given table cell.
425 * @param table the target <code>JTable</code>.
426 * @param cell the table cell.
427 * @return the displayed pop-up menu.
428 * @throws NullPointerException if the cell is <code>null</code>.
429 * @throws IllegalStateException if the <code>JTable</code> is disabled.
430 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
431 * @throws ComponentLookupException if a pop-up menu cannot be found.
432 */
433 @RunsInEDT
434 public JPopupMenu showPopupMenuAt(JTable table, TableCell cell) {
435 Point pointAtCell = scrollToPointAtCell(table, cell, location);
436 return robot.showPopupMenu(table, pointAtCell);
437 }
438
439 @RunsInEDT
440 private static Point scrollToPointAtCell(final JTable table, final TableCell cell, final JTableLocation location) {
441 validateNotNull(cell);
442 return execute(new GuiQuery<Point>() {
443 protected Point executeInEDT() {
444 scrollToCell(table, cell, location);
445 return location.pointAt(table, cell.row, cell.column);
446 }
447 });
448 }
449
450 @RunsInCurrentThread
451 private static void scrollToCell(final JTable table, final TableCell cell, final JTableLocation location) {
452 validateIsEnabledAndShowing(table);
453 validateCellIndices(table, cell);
454 table.scrollRectToVisible(location.cellBounds(table, cell));
455 }
456
457 /**
458 * Converts the given table cell into a coordinate pair.
459 * @param table the target <code>JTable</code>.
460 * @param cell the table cell.
461 * @return the coordinates of the given row and column.
462 * @throws NullPointerException if the cell is <code>null</code>.
463 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
464 */
465 @RunsInEDT
466 public Point pointAt(JTable table, TableCell cell) {
467 return pointAtCell(table, cell, location);
468 }
469
470 @RunsInEDT
471 private static Point pointAtCell(final JTable table, final TableCell cell, final JTableLocation location) {
472 return execute(new GuiQuery<Point>() {
473 protected Point executeInEDT() {
474 validateCellIndices(table, cell);
475 return location.pointAt(table, cell.row, cell.column);
476 }
477 });
478 }
479
480 /**
481 * Asserts that the <code>String</code> representation of the cell values in the <code>{@link JTable}</code> is
482 * equal to the given <code>String</code> array. This method uses this driver's
483 * <code>{@link JTableCellReader}</code> to read the values of the table cells as <code>String</code>s.
484 * @param table the target <code>JTable</code>.
485 * @param contents the expected <code>String</code> representation of the cell values in the <code>JTable</code>.
486 * @see #cellReader(JTableCellReader)
487 */
488 @RunsInEDT
489 public void requireContents(JTable table, String[][] contents) {
490 String[][] actual = contents(table);
491
492 if (!equal(actual, contents))
493 failNotEqual(actual, contents, propertyName(table, CONTENTS_PROPERTY));
494 }
495
496 private static void failNotEqual(String[][] actual, String[][] expected, Description description) {
497 String descriptionValue = description != null ? description.value() : null;
498 String message = descriptionValue == null ? "" : concat("[", descriptionValue, "]");
499 fail(concat(message, " expected:<", Arrays.format(expected), "> but was:<", Arrays.format(actual), ">"));
500 }
501
502 /**
503 * Returns the <code>String</code> representation of the cells in the <code>{@link JTable}</code>, using this
504 * driver's <code>{@link JTableCellReader}</code>.
505 * @param table the target <code>JTable</code>.
506 * @return the <code>String</code> representation of the cells in the <code>JTable</code>.
507 * @see #cellReader(JTableCellReader)
508 */
509 @RunsInEDT
510 public String[][] contents(JTable table) {
511 return tableContents(table, cellReader);
512 }
513
514 /**
515 * Asserts that the value of the given cell matches the given value.
516 * @param table the target <code>JTable</code>.
517 * @param cell the given table cell.
518 * @param value the expected value. It can be a regular expression.
519 * @throws NullPointerException if the cell is <code>null</code>.
520 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
521 * @throws AssertionError if the value of the given cell does not match the given value.
522 */
523 @RunsInEDT
524 public void requireCellValue(JTable table, TableCell cell, String value) {
525 verifyThat(value(table, cell)).as(cellValueProperty(table, cell)).isEqualOrMatches(value);
526 }
527
528 /**
529 * Asserts that the value of the given cell matches the given regular expression pattern.
530 * @param table the target <code>JTable</code>.
531 * @param cell the given table cell.
532 * @param pattern the regular expression pattern to match.
533 * @throws NullPointerException if the cell is <code>null</code>.
534 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
535 * @throws NullPointerException if the given regular expression pattern is <code>null</code>.
536 * @throws AssertionError if the value of the given cell does not match the given regular expression pattern.
537 * @since 1.2
538 */
539 @RunsInEDT
540 public void requireCellValue(JTable table, TableCell cell, Pattern pattern) {
541 verifyThat(value(table, cell)).as(cellValueProperty(table, cell)).matches(pattern);
542 }
543
544 @RunsInEDT
545 private Description cellValueProperty(JTable table, TableCell cell) {
546 return cellProperty(table, concat(VALUE_PROPERTY, " ", cell));
547 }
548
549 /**
550 * Enters the given value in the given cell of the <code>{@link JTable}</code>, using this driver's
551 * <code>{@link JTableCellWriter}</code>.
552 * @param table the target <code>JTable</code>.
553 * @param cell the given cell.
554 * @param value the given value.
555 * @throws NullPointerException if the cell is <code>null</code>.
556 * @throws IllegalStateException if the <code>JTable</code> is disabled.
557 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
558 * @throws IllegalStateException if the <code>JTable</code> cell is not editable.
559 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
560 * @throws ActionFailedException if this driver's <code>JTableCellValueReader</code> is unable to enter the given
561 * value.
562 * @see #cellWriter(JTableCellWriter)
563 */
564 @RunsInEDT
565 public void enterValueInCell(JTable table, TableCell cell, String value) {
566 validateNotNull(cell);
567 cellWriter.enterValue(table, cell.row, cell.column, value);
568 }
569
570 /**
571 * Asserts that the given table cell is editable.
572 * @param table the target <code>JTable</code>.
573 * @param cell the given table cell.
574 * @throws NullPointerException if the cell is <code>null</code>.
575 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
576 * @throws AssertionError if the given table cell is not editable.
577 */
578 @RunsInEDT
579 public void requireEditable(JTable table, TableCell cell) {
580 requireEditableEqualTo(table, cell, true);
581 }
582
583 /**
584 * Asserts that the given table cell is not editable.
585 * @param table the target <code>JTable</code>.
586 * @param cell the given table cell.
587 * @throws NullPointerException if the cell is <code>null</code>.
588 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
589 * @throws AssertionError if the given table cell is editable.
590 */
591 @RunsInEDT
592 public void requireNotEditable(JTable table, TableCell cell) {
593 requireEditableEqualTo(table, cell, false);
594 }
595
596 @RunsInEDT
597 private static void requireEditableEqualTo(final JTable table, final TableCell cell, boolean editable) {
598 validateNotNull(cell);
599 boolean cellEditable = execute(new GuiQuery<Boolean>() {
600 protected Boolean executeInEDT() {
601 return isCellEditable(table, cell);
602 }
603 });
604 assertThat(cellEditable).as(cellProperty(table, concat(EDITABLE_PROPERTY, " ", cell))).isEqualTo(editable);
605 }
606
607 @RunsInEDT
608 private static Description cellProperty(JTable table, String propertyName) {
609 return propertyName(table, propertyName);
610 }
611
612 /**
613 * Returns the editor in the given cell of the <code>{@link JTable}</code>, using this driver's
614 * <code>{@link JTableCellWriter}</code>.
615 * @param table the target <code>JTable</code>.
616 * @param cell the given cell.
617 * @return the editor in the given cell of the <code>JTable</code>.
618 * @throws NullPointerException if the cell is <code>null</code>.
619 * @throws IllegalStateException if the <code>JTable</code> cell is not editable.
620 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
621 * @see #cellWriter(JTableCellWriter)
622 */
623 @RunsInEDT
624 public Component cellEditor(JTable table, TableCell cell) {
625 validateNotNull(cell);
626 return cellWriter.editorForCell(table, cell.row, cell.column);
627 }
628
629 /**
630 * Starts editing the given cell of the <code>{@link JTable}</code>, using this driver's
631 * <code>{@link JTableCellWriter}</code>. This method should be called before manipulating the
632 * <code>{@link Component}</code> returned by <code>{@link #cellEditor(JTable, TableCell)}</code>.
633 * @param table the target <code>JTable</code>.
634 * @param cell the given cell.
635 * @throws NullPointerException if the cell is <code>null</code>.
636 * @throws IllegalStateException if the <code>JTable</code> is disabled.
637 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
638 * @throws IllegalStateException if the <code>JTable</code> cell is not editable.
639 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
640 * @throws ActionFailedException if this writer is unable to handle the underlying cell editor.
641 * @see #cellWriter(JTableCellWriter)
642 */
643 @RunsInEDT
644 public void startCellEditing(JTable table, TableCell cell) {
645 validateNotNull(cell);
646 cellWriter.startCellEditing(table, cell.row, cell.column);
647 }
648
649 /**
650 * Stops editing the given cell of the <code>{@link JTable}</code>, using this driver's
651 * <code>{@link JTableCellWriter}</code>. This method should be called after manipulating the
652 * <code>{@link Component}</code> returned by <code>{@link #cellEditor(JTable, TableCell)}</code>.
653 * @param table the target <code>JTable</code>.
654 * @param cell the given cell.
655 * @throws NullPointerException if the cell is <code>null</code>.
656 * @throws IllegalStateException if the <code>JTable</code> is disabled.
657 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
658 * @throws IllegalStateException if the <code>JTable</code> cell is not editable.
659 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
660 * @throws ActionFailedException if this writer is unable to handle the underlying cell editor.
661 * @see #cellWriter(JTableCellWriter)
662 */
663 @RunsInEDT
664 public void stopCellEditing(JTable table, TableCell cell) {
665 validateNotNull(cell);
666 cellWriter.stopCellEditing(table, cell.row, cell.column);
667 }
668
669 /**
670 * Cancels editing the given cell of the <code>{@link JTable}</code>, using this driver's
671 * <code>{@link JTableCellWriter}</code>. This method should be called after manipulating the
672 * <code>{@link Component}</code> returned by <code>{@link #cellEditor(JTable, TableCell)}</code>.
673 * @param table the target <code>JTable</code>.
674 * @param cell the given cell.
675 * @throws NullPointerException if the cell is <code>null</code>.
676 * @throws IllegalStateException if the <code>JTable</code> is disabled.
677 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
678 * @throws IllegalStateException if the <code>JTable</code> cell is not editable.
679 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
680 * @throws ActionFailedException if this writer is unable to handle the underlying cell editor.
681 * @see #cellWriter(JTableCellWriter)
682 */
683 @RunsInEDT
684 public void cancelCellEditing(JTable table, TableCell cell) {
685 validateNotNull(cell);
686 cellWriter.cancelCellEditing(table, cell.row, cell.column);
687 }
688
689 /**
690 * Validates that the given table cell is non <code>null</code> and its indices are not out of bounds.
691 * @param table the target <code>JTable</code>.
692 * @param cell to validate.
693 * @throws NullPointerException if the cell is <code>null</code>.
694 * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
695 */
696 @RunsInEDT
697 public void validate(JTable table, TableCell cell) {
698 validateCellIndexBounds(table, cell);
699 }
700
701 private static void validateCellIndexBounds(final JTable table, final TableCell cell) {
702 execute(new GuiTask() {
703 protected void executeInEDT() {
704 validateCellIndices(table, cell);
705 }
706 });
707 }
708
709 /**
710 * Updates the implementation of <code>{@link JTableCellReader}</code> to use when comparing internal values of a
711 * <code>{@link JTable}</code> and the values expected in a test.
712 * @param newCellReader the new <code>JTableCellValueReader</code> to use.
713 * @throws NullPointerException if <code>newCellReader</code> is <code>null</code>.
714 */
715 public void cellReader(JTableCellReader newCellReader) {
716 validateCellReader(newCellReader);
717 cellReader = newCellReader;
718 }
719
720 /**
721 * Updates the implementation of <code>{@link JTableCellWriter}</code> to use to edit cell values in a
722 * <code>{@link JTable}</code>.
723 * @param newCellWriter the new <code>JTableCellWriter</code> to use.
724 * @throws NullPointerException if <code>newCellWriter</code> is <code>null</code>.
725 */
726 public void cellWriter(JTableCellWriter newCellWriter) {
727 validateCellWriter(newCellWriter);
728 cellWriter = newCellWriter;
729 }
730
731 /**
732 * Returns the number of rows that can be shown in the given <code>{@link JTable}</code>, given unlimited space.
733 * @param table the target <code>JTable</code>.
734 * @return the number of rows shown in the given <code>JTable</code>.
735 * @see JTable#getRowCount()
736 */
737 @RunsInEDT
738 public int rowCountOf(JTable table) {
739 return JTableRowCountQuery.rowCountOf(table);
740 }
741
742 /**
743 * Returns the index of the column in the given <code>{@link JTable}</code> whose id matches the given one.
744 * @param table the target <code>JTable</code>.
745 * @param columnId the id of the column to look for.
746 * @return the index of the column whose id matches the given one.
747 * @throws ActionFailedException if a column with a matching id could not be found.
748 */
749 @RunsInEDT
750 public int columnIndex(JTable table, Object columnId) {
751 return findColumnIndex(table, columnId);
752 }
753
754 @RunsInEDT
755 private static int findColumnIndex(final JTable table, final Object columnId) {
756 return execute(new GuiQuery<Integer>() {
757 protected Integer executeInEDT() {
758 int index = columnIndexByIdentifier(table, columnId);
759 if (index < 0) failColumnIndexNotFound(columnId);
760 return index;
761 }
762 });
763 }
764
765 private static ActionFailedException failColumnIndexNotFound(Object columnId) {
766 throw actionFailure(concat("Unable to find a column with id ", quote(columnId)));
767 }
768
769 /**
770 * Asserts that the given <code>{@link JTable}</code> has the given number of rows.
771 * @param table the target <code>JTable</code>.
772 * @param rowCount the expected number of rows.
773 * @throws AssertionError if the given <code>JTable</code> does not have the given number of rows.
774 */
775 @RunsInEDT
776 public void requireRowCount(JTable table, int rowCount) {
777 assertThat(rowCountOf(table)).as(propertyName(table, "rowCount")).isEqualTo(rowCount);
778 }
779
780 /**
781 * Asserts that the given <code>{@link JTable}</code> has the given number of columns.
782 * @param table the target <code>JTable</code>.
783 * @param columnCount the expected number of columns.
784 * @throws AssertionError if the given <code>JTable</code> does not have the given number of columns.
785 */
786 @RunsInEDT
787 public void requireColumnCount(JTable table, int columnCount) {
788 assertThat(columnCountOf(table)).as(propertyName(table, "columnCount")).isEqualTo(columnCount);
789 }
790
791 /**
792 * Simulates a user selecting the given rows in the given <code>{@link JTable}</code>.
793 * @param table the target <code>JTable</code>.
794 * @param rows the indices of the row to select.
795 * @throws NullPointerException if the given array of indices is <code>null</code>.
796 * @throws IllegalArgumentException if the given array of indices is empty.
797 * @throws IllegalStateException if the <code>JTable</code> is disabled.
798 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
799 * @throws IndexOutOfBoundsException if any of the given indices is negative, or equal to or greater than the number
800 * of rows in the <code>JTable</code>.
801 * @since 1.2
802 */
803 @RunsInEDT
804 public void selectRows(final JTable table, final int... rows) {
805 if (rows == null) throw new NullPointerException("The array of row indices should not be null");
806 if (isEmptyIntArray(rows)) throw new IllegalArgumentException("The array of row indices should not be empty");
807 new MultipleSelectionTemplate(robot) {
808 int elementCount() {
809 return rows.length;
810 }
811
812 void selectElement(int index) {
813 selectCell(table, rows[index], 0);
814 }
815 }.multiSelect();
816 }
817
818 @RunsInEDT
819 private void selectCell(JTable table, int row, int column) {
820 Pair<Boolean, Point> cellSelectionInfo = cellSelectionInfo(table, row, column, location);
821 if (cellSelectionInfo.i) return; // cell already selected
822 robot.click(table, cellSelectionInfo.ii, LEFT_BUTTON, 1);
823 }
824
825 @RunsInEDT
826 private static Pair<Boolean, Point> cellSelectionInfo(final JTable table, final int row, final int column,
827 final JTableLocation location) {
828 return execute(new GuiQuery<Pair<Boolean, Point>>() {
829 protected Pair<Boolean, Point> executeInEDT() {
830 if (isCellSelected(table, row, column)) return new Pair<Boolean, Point>(true, null);
831 scrollToCell(table, row, column, location);
832 Point pointAtCell = location.pointAt(table, row, column);
833 return new Pair<Boolean, Point>(false, pointAtCell);
834 }
835 });
836 }
837
838 @RunsInCurrentThread
839 private static void scrollToCell(final JTable table, final int row, final int column, final JTableLocation location) {
840 validateIsEnabledAndShowing(table);
841 validateIndices(table, row, column);
842 table.scrollRectToVisible(location.cellBounds(table, row, column));
843 }
844
845 /**
846 * Asserts that the set of selected rows in the given <code>{@link JTable}</code> contains to the given row indices.
847 * @param table the target <code>JTable</code>.
848 * @param rows the indices of the rows expected to be selected.
849 * @throws AssertionError if the sets of selected rows in the given <code>JTable</code> (if any) do not contain the
850 * given row indices.
851 * @since 1.2
852 */
853 @RunsInEDT
854 public void requireSelectedRows(JTable table, int... rows) {
855 int[] selectedRows = selectedRowsOf(table);
856 assertThat(selectedRows).as(propertyName(table, SELECTED_ROWS_PROPERTY)).contains(rows);
857 }
858
859 @RunsInEDT
860 private static int[] selectedRowsOf(final JTable table) {
861 return execute(new GuiQuery<int[]>() {
862 protected int[] executeInEDT() {
863 return table.getSelectedRows();
864 }
865 });
866 }
867
868 @VisibleForTesting
869 JTableCellReader cellReader() { return cellReader; }
870 }