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 }