Package org.fest.swing.driver

Source Code of org.fest.swing.driver.JTableDriver

/*
* Created on Feb 2, 2008
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright @2008-2013 the original author or authors.
*/
package org.fest.swing.driver;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.fest.swing.core.MouseButton.LEFT_BUTTON;
import static org.fest.swing.driver.ComponentPreconditions.checkEnabledAndShowing;
import static org.fest.swing.driver.JTableCellEditableQuery.isCellEditable;
import static org.fest.swing.driver.JTableColumnCountQuery.columnCountOf;
import static org.fest.swing.driver.JTableContentsQuery.tableContents;
import static org.fest.swing.driver.JTableHasSelectionQuery.hasSelection;
import static org.fest.swing.driver.JTableHeaderQuery.tableHeader;
import static org.fest.swing.driver.JTableMatchingCellQuery.cellWithValue;
import static org.fest.swing.driver.JTableSingleRowCellSelectedQuery.isCellSelected;
import static org.fest.swing.driver.TextAssert.verifyThat;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.fest.swing.exception.ActionFailedException.actionFailure;
import static org.fest.swing.query.JTableColumnByIdentifierQuery.columnIndexByIdentifier;
import static org.fest.swing.util.ArrayPreconditions.checkNotNullOrEmpty;
import static org.fest.swing.util.Arrays.equal;
import static org.fest.util.Arrays.format;
import static org.fest.util.Preconditions.checkNotNull;
import static org.fest.util.Preconditions.checkNotNullOrEmpty;
import static org.fest.util.Strings.concat;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Point;
import java.util.regex.Pattern;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.table.JTableHeader;

import org.fest.assertions.Description;
import org.fest.swing.annotation.RunsInCurrentThread;
import org.fest.swing.annotation.RunsInEDT;
import org.fest.swing.cell.JTableCellReader;
import org.fest.swing.cell.JTableCellWriter;
import org.fest.swing.core.MouseButton;
import org.fest.swing.core.Robot;
import org.fest.swing.data.TableCell;
import org.fest.swing.data.TableCellFinder;
import org.fest.swing.edt.GuiQuery;
import org.fest.swing.edt.GuiTask;
import org.fest.swing.exception.ActionFailedException;
import org.fest.swing.exception.ComponentLookupException;
import org.fest.swing.util.Arrays;
import org.fest.swing.util.Pair;
import org.fest.swing.util.PatternTextMatcher;
import org.fest.swing.util.StringTextMatcher;
import org.fest.util.InternalApi;
import org.fest.util.VisibleForTesting;

/**
* <p>
* Supports functional testing of {@code JTable}s.
* </p>
*
* <p>
* <b>Note:</b> This class is intended for internal use only. Please use the classes in the package
* {@link org.fest.swing.fixture} in your tests.
* </p>
*
* @author Yvonne Wang
* @author Alex Ruiz
*/
@InternalApi
public class JTableDriver extends JComponentDriver {
  private static final String CONTENTS_PROPERTY = "contents";
  private static final String EDITABLE_PROPERTY = "editable";
  private static final String SELECTED_ROWS_PROPERTY = "selectedRows";
  private static final String SELECTION_PROPERTY = "selection";
  private static final String VALUE_PROPERTY = "value";

  private final JTableLocation location = new JTableLocation();
  private JTableCellReader cellReader;
  private JTableCellWriter cellWriter;

  /**
   * Creates a new {@link JTableDriver}.
   *
   * @param robot the robot to use to simulate user events.
   */
  public JTableDriver(@Nonnull Robot robot) {
    super(robot);
    replaceCellReader(new BasicJTableCellReader());
    replaceCellWriter(new BasicJTableCellWriter(robot));
  }

  /**
   * Returns the {@code JTableHeader} of the given {@code JTable}.
   *
   * @param table the given {@code JTable}.
   * @return the {@code JTableHeader} of the given {@code JTable}.
   */
  @RunsInEDT
  public @Nullable JTableHeader tableHeaderOf(@Nonnull JTable table) {
    return tableHeader(table);
  }

  /**
   * Returns the {@code String} representation of the value of the selected cell, using this driver's
   * {@link JTableCellReader}.
   *
   * @param table the target {@code JTable}.
   * @return the {@code String} representation of the value of the selected cell.
   * @see #replaceCellReader(JTableCellReader)
   */
  @RunsInEDT
  public @Nullable String selectionValue(@Nonnull JTable table) {
    return selectionValue(table, cellReader());
  }

  @RunsInEDT
  private static @Nullable String selectionValue(final @Nonnull JTable table,
      final @Nonnull JTableCellReader cellReader) {
    return execute(new GuiQuery<String>() {
      @Override
      protected String executeInEDT() {
        if (table.getSelectedRowCount() == 0) {
          return null;
        }
        return cellReader.valueAt(table, table.getSelectedRow(), table.getSelectedColumn());
      }
    });
  }

  /**
   * Returns a cell from the given {@code JTable} using the given cell finder.
   *
   * @param table the target {@code JTable}.
   * @param cellFinder knows how to find a cell.
   * @return the found cell, if any.
   * @throws NullPointerException if {@code cellFinder} is {@code null}.
   * @throws IndexOutOfBoundsException if the row or column indices in the found cell are out of bounds.
   * @throws ActionFailedException if a matching cell could not be found.
   */
  @RunsInEDT
  public @Nonnull TableCell cell(@Nonnull JTable table, @Nonnull TableCellFinder cellFinder) {
    checkNotNull(cellFinder);
    TableCell cell = cellFinder.findCell(table, cellReader());
    checkCellIndicesInBounds(table, cell);
    return cell;
  }

  /**
   * Returns a cell from the given {@code JTable} whose value matches the given one.
   *
   * @param table the target {@code JTable}.
   * @param value the value of the cell to look for. It can be a regular expression.
   * @return a cell from the given {@code JTable} whose value matches the given one.
   * @throws ActionFailedException if a cell with a matching value cannot be found.
   */
  @RunsInEDT
  public @Nonnull TableCell cell(@Nonnull JTable table, @Nullable String value) {
    return cellWithValue(table, new StringTextMatcher(value), cellReader());
  }

  /**
   * Returns a cell from the given {@code JTable} whose value matches the given regular expression pattern.
   *
   * @param table the target {@code JTable}.
   * @param pattern the regular expression pattern to match
   * @return a cell from the given {@code JTable} whose value matches the given one.
   * @throws NullPointerException if the given regular expression is {@code null}.
   * @throws ActionFailedException if a cell with a matching value cannot be found.
   * @since 1.2
   */
  @RunsInEDT
  public @Nonnull TableCell cell(@Nonnull JTable table, @Nonnull Pattern pattern) {
    return cellWithValue(table, new PatternTextMatcher(pattern), cellReader());
  }

  /**
   * Returns the {@code String} representation of the value at the given cell, using this driver's
   * {@link JTableCellReader}.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @return the {@code String} representation of the value at the given cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @see #replaceCellReader(JTableCellReader)
   */
  @RunsInEDT
  public @Nullable String value(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    return cellValue(table, cell, cellReader());
  }

  @RunsInEDT
  private static @Nullable String cellValue(final @Nonnull JTable table, final @Nonnull TableCell cell,
      final @Nonnull JTableCellReader cellReader) {
    return execute(new GuiQuery<String>() {
      @Override
      protected String executeInEDT() {
        JTableCellPreconditions.checkCellIndicesInBounds(table, cell);
        return cellReader.valueAt(table, cell.row, cell.column);
      }
    });
  }

  /**
   * Returns the {@code String} representation of the value at the given row and column, using this driver's
   * {@link JTableCellReader}.
   *
   * @param table the target {@code JTable}.
   * @param row the given row.
   * @param column the given column.
   * @return the {@code String} representation of the value at the given row and column.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @see #replaceCellReader(JTableCellReader)
   */
  @RunsInEDT
  public @Nullable String value(@Nonnull JTable table, int row, int column) {
    return cellValue(table, row, column, cellReader());
  }

  @RunsInEDT
  private static @Nullable String cellValue(final @Nonnull JTable table, final int row, final int column,
      final @Nonnull JTableCellReader cellReader) {
    return execute(new GuiQuery<String>() {
      @Override
      protected String executeInEDT() {
        JTableCellPreconditions.checkCellIndicesInBounds(table, row, column);
        return cellReader.valueAt(table, row, column);
      }
    });
  }

  /**
   * Returns the font of the given table cell.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @return the font of the given table cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public @Nullable Font font(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    return cellFont(table, cell, cellReader());
  }

  @RunsInEDT
  private static @Nullable Font cellFont(final @Nonnull JTable table, final @Nonnull TableCell cell,
      final @Nonnull JTableCellReader cellReader) {
    return execute(new GuiQuery<Font>() {
      @Override
      protected Font executeInEDT() {
        JTableCellPreconditions.checkCellIndicesInBounds(table, cell);
        return cellReader.fontAt(table, cell.row, cell.column);
      }
    });
  }

  /**
   * Returns the background color of the given table cell.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @return the background color of the given table cell.
   * @throws ActionFailedException if the cell is {@code null}.
   * @throws ActionFailedException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public Color background(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    return cellBackground(table, cell, cellReader());
  }

  @RunsInEDT
  private static @Nullable Color cellBackground(final @Nonnull JTable table, final @Nonnull TableCell cell,
      final @Nonnull JTableCellReader cellReader) {
    return execute(new GuiQuery<Color>() {
      @Override
      protected Color executeInEDT() {
        JTableCellPreconditions.checkCellIndicesInBounds(table, cell);
        return cellReader.backgroundAt(table, cell.row, cell.column);
      }
    });
  }

  /**
   * Returns the foreground color of the given table cell.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @return the foreground color of the given table cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public @Nullable Color foreground(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    return cellForeground(table, cell, cellReader());
  }

  @RunsInEDT
  private static @Nullable Color cellForeground(final @Nonnull JTable table, final @Nonnull TableCell cell,
      final @Nonnull JTableCellReader cellReader) {
    return execute(new GuiQuery<Color>() {
      @Override
      protected Color executeInEDT() {
        JTableCellPreconditions.checkCellIndicesInBounds(table, cell);
        return cellReader.foregroundAt(table, cell.row, cell.column);
      }
    });
  }

  /**
   * Selects the given cells of the {@code JTable}.
   *
   * @param table the target {@code JTable}.
   * @param cells the cells to select.
   * @throws NullPointerException if {@code cells} is {@code null} or empty.
   * @throws IllegalArgumentException if {@code cells} is {@code null} or empty.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws NullPointerException if any element in {@code cells} is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices of any of the {@code cells} are out of bounds.
   */
  public void selectCells(final @Nonnull JTable table, final @Nonnull TableCell[] cells) {
    checkNotNullOrEmpty(cells);
    new MultipleSelectionTemplate(robot) {
      @Override
      int elementCount() {
        return cells.length;
      }

      @Override
      void selectElement(int index) {
        selectCell(table, checkNotNull(cells[index]));
      }
    }.multiSelect();
  }

  /**
   * Verifies that the {@code JTable} does not have any selection.
   *
   * @param table the target {@code JTable}.
   * @throws AssertionError is the {@code JTable} has a selection.
   */
  @RunsInEDT
  public void requireNoSelection(@Nonnull JTable table) {
    assertNoSelection(table);
  }

  @RunsInEDT
  private static void assertNoSelection(final @Nonnull JTable table) {
    execute(new GuiTask() {
      @Override
      protected void executeInEDT() {
        if (!hasSelection(table)) {
          return;
        }
        String format = "[%s] expected no selection but was:<rows=%s, columns=%s>";
        String msg = String.format(format, propertyName(table, SELECTION_PROPERTY).value(),
            format(selectedRowsOf(table)), format(table.getSelectedColumns()));
        fail(msg);
      }
    });
  }

  /**
   * Selects the given cell, if it is not selected already.
   *
   * @param table the target {@code JTable}.
   * @param cell the cell to select.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public void selectCell(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    selectCell(table, cell.row, cell.column);
  }

  /**
   * Clicks the given cell, using the specified mouse button, the given number of times.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @param mouseButton the mouse button to use.
   * @param times the number of times to click the cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public void click(@Nonnull JTable table, @Nonnull TableCell cell, @Nonnull MouseButton mouseButton,
      @Nonnegative int times) {
    if (times <= 0) {
      throw new IllegalArgumentException("The number of times to click a cell should be greater than zero");
    }
    Point pointAtCell = scrollToPointAtCell(table, cell, location());
    robot.click(table, pointAtCell, mouseButton, times);
  }

  /**
   * Starts a drag operation at the location of the given table cell.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public void drag(@Nonnull JTable table, @Nonnull TableCell cell) {
    Point pointAtCell = scrollToPointAtCell(table, cell, location());
    drag(table, pointAtCell);
  }

  /**
   * Starts a drop operation at the location of the given table cell.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public void drop(@Nonnull JTable table, @Nonnull TableCell cell) {
    Point pointAtCell = scrollToPointAtCell(table, cell, location());
    drop(table, pointAtCell);
  }

  /**
   * Shows a pop-up menu at the given table cell.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @return the displayed pop-up menu.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws ComponentLookupException if a pop-up menu cannot be found.
   */
  @RunsInEDT
  public @Nonnull JPopupMenu showPopupMenuAt(@Nonnull JTable table, @Nonnull TableCell cell) {
    Point pointAtCell = scrollToPointAtCell(table, cell, location());
    return robot.showPopupMenu(table, pointAtCell);
  }

  @RunsInEDT
  private static @Nonnull Point scrollToPointAtCell(final @Nonnull JTable table, final @Nonnull TableCell cell,
      final @Nonnull JTableLocation location) {
    checkNotNull(cell);
    Point result = execute(new GuiQuery<Point>() {
      @Override
      protected Point executeInEDT() {
        scrollToCell(table, cell, location);
        return location.pointAt(table, cell.row, cell.column);
      }
    });
    return checkNotNull(result);
  }

  @RunsInCurrentThread
  private static void scrollToCell(@Nonnull JTable table, @Nonnull TableCell cell, @Nonnull JTableLocation location) {
    checkEnabledAndShowing(table);
    JTableCellPreconditions.checkCellIndicesInBounds(table, cell);
    table.scrollRectToVisible(location.cellBounds(table, cell));
  }

  /**
   * Converts the given table cell into a coordinate pair.
   *
   * @param table the target {@code JTable}.
   * @param cell the table cell.
   * @return the coordinates of the given row and column.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public @Nonnull Point pointAt(@Nonnull JTable table, @Nonnull TableCell cell) {
    return pointAtCell(table, cell, location());
  }

  @RunsInEDT
  private static @Nonnull Point pointAtCell(final @Nonnull JTable table, final @Nonnull TableCell cell,
      final @Nonnull JTableLocation location) {
    Point result = execute(new GuiQuery<Point>() {
      @Override
      protected Point executeInEDT() {
        JTableCellPreconditions.checkCellIndicesInBounds(table, cell);
        return location.pointAt(table, cell.row, cell.column);
      }
    });
    return checkNotNull(result);
  }

  /**
   * Asserts that the {@code String} representation of the cell values in the {@code JTable} is equal to the given
   * {@code String} array. This method uses this driver's {@link JTableCellReader} to read the values of the table cells
   * as {@code String}s.
   *
   * @param table the target {@code JTable}.
   * @param contents the expected {@code String} representation of the cell values in the {@code JTable}.
   * @see #replaceCellReader(JTableCellReader)
   */
  @RunsInEDT
  public void requireContents(@Nonnull JTable table, @Nonnull String[][] contents) {
    String[][] actual = contents(table);
    if (!equal(actual, contents)) {
      failNotEqual(actual, contents, propertyName(table, CONTENTS_PROPERTY));
    }
  }

  private static void failNotEqual(@Nonnull String[][] actual, @Nonnull String[][] expected,
      @Nullable Description description) {
    String descriptionValue = description != null ? description.value() : null;
    String message = descriptionValue == null ? "" : String.format("[%s] ", descriptionValue);
    fail(message + String.format("expected:<%s> but was<%s>", Arrays.format(expected), Arrays.format(actual)));
  }

  /**
   * Returns the {@code String} representation of the cells in the {@code JTable}, using this driver's
   * {@link JTableCellReader}.
   *
   * @param table the target {@code JTable}.
   * @return the {@code String} representation of the cells in the {@code JTable}.
   * @see #replaceCellReader(JTableCellReader)
   */
  @RunsInEDT
  public @Nonnull String[][] contents(@Nonnull JTable table) {
    return tableContents(table, cellReader());
  }

  /**
   * Asserts that the value of the given cell matches the given value.
   *
   * @param table the target {@code JTable}.
   * @param cell the given table cell.
   * @param value the expected value. It can be a regular expression.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws AssertionError if the value of the given cell does not match the given value.
   */
  @RunsInEDT
  public void requireCellValue(@Nonnull JTable table, @Nonnull TableCell cell, @Nullable String value) {
    verifyThat(value(table, cell)).as(cellValueProperty(table, cell)).isEqualOrMatches(value);
  }

  /**
   * Asserts that the value of the given cell matches the given regular expression pattern.
   *
   * @param table the target {@code JTable}.
   * @param cell the given table cell.
   * @param pattern the regular expression pattern to match.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws NullPointerException if the given regular expression pattern is {@code null}.
   * @throws AssertionError if the value of the given cell does not match the given regular expression pattern.
   * @since 1.2
   */
  @RunsInEDT
  public void requireCellValue(@Nonnull JTable table, @Nonnull TableCell cell, @Nonnull Pattern pattern) {
    verifyThat(value(table, cell)).as(cellValueProperty(table, cell)).matches(pattern);
  }

  @RunsInEDT
  private @Nonnull Description cellValueProperty(@Nonnull JTable table, @Nonnull TableCell cell) {
    return cellProperty(table, concat(VALUE_PROPERTY, " ", cell));
  }

  /**
   * Enters the given value in the given cell of the {@code JTable}, using this driver's {@link JTableCellWriter}.
   *
   * @param table the target {@code JTable}.
   * @param cell the given cell.
   * @param value the given value.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IllegalStateException if the {@code JTable} cell is not editable.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws ActionFailedException if this driver's {@code JTableCellValueReader} is unable to enter the given value.
   * @see #replaceCellWriter(JTableCellWriter)
   */
  @RunsInEDT
  public void enterValueInCell(@Nonnull JTable table, @Nonnull TableCell cell, @Nonnull String value) {
    checkNotNull(cell);
    cellWriter.enterValue(table, cell.row, cell.column, value);
  }

  /**
   * Asserts that the given table cell is editable.
   *
   * @param table the target {@code JTable}.
   * @param cell the given table cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws AssertionError if the given table cell is not editable.
   */
  @RunsInEDT
  public void requireEditable(@Nonnull JTable table, @Nonnull TableCell cell) {
    requireEditableEqualTo(table, cell, true);
  }

  /**
   * Asserts that the given table cell is not editable.
   *
   * @param table the target {@code JTable}.
   * @param cell the given table cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws AssertionError if the given table cell is editable.
   */
  @RunsInEDT
  public void requireNotEditable(@Nonnull JTable table, @Nonnull TableCell cell) {
    requireEditableEqualTo(table, cell, false);
  }

  @RunsInEDT
  private static void requireEditableEqualTo(final @Nonnull JTable table, final @Nonnull TableCell cell,
      boolean editable) {
    checkNotNull(cell);
    boolean cellEditable = checkNotNull(execute(new GuiQuery<Boolean>() {
      @Override
      protected Boolean executeInEDT() {
        return isCellEditable(table, cell);
      }
    }));
    assertThat(cellEditable).as(cellProperty(table, concat(EDITABLE_PROPERTY, " ", cell))).isEqualTo(editable);
  }

  @RunsInEDT
  private static @Nonnull Description cellProperty(@Nonnull JTable table, @Nonnull String propertyName) {
    return propertyName(table, propertyName);
  }

  /**
   * Returns the editor in the given cell of the {@code JTable}, using this driver's {@link JTableCellWriter}.
   *
   * @param table the target {@code JTable}.
   * @param cell the given cell.
   * @return the editor in the given cell of the {@code JTable}.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} cell is not editable.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @see #replaceCellWriter(JTableCellWriter)
   */
  @RunsInEDT
  public Component cellEditor(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    return cellWriter.editorForCell(table, cell.row, cell.column);
  }

  /**
   * Starts editing the given cell of the {@code JTable}, using this driver's {@link JTableCellWriter}. This method
   * should be called before manipulating the {@code Component} returned by {@link #cellEditor(JTable, TableCell)}.
   *
   * @param table the target {@code JTable}.
   * @param cell the given cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IllegalStateException if the {@code JTable} cell is not editable.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws ActionFailedException if this writer is unable to handle the underlying cell editor.
   * @see #replaceCellWriter(JTableCellWriter)
   */
  @RunsInEDT
  public void startCellEditing(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    cellWriter.startCellEditing(table, cell.row, cell.column);
  }

  /**
   * Stops editing the given cell of the {@code JTable}, using this driver's {@link JTableCellWriter}. This method
   * should be called after manipulating the {@code Component} returned by {@link #cellEditor(JTable, TableCell)}.
   *
   * @param table the target {@code JTable}.
   * @param cell the given cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IllegalStateException if the {@code JTable} cell is not editable.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws ActionFailedException if this writer is unable to handle the underlying cell editor.
   * @see #replaceCellWriter(JTableCellWriter)
   */
  @RunsInEDT
  public void stopCellEditing(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    cellWriter.stopCellEditing(table, cell.row, cell.column);
  }

  /**
   * Cancels editing the given cell of the {@code JTable}, using this driver's {@link JTableCellWriter}. This method
   * should be called after manipulating the {@code Component} returned by {@link #cellEditor(JTable, TableCell)}.
   *
   * @param table the target {@code JTable}.
   * @param cell the given cell.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IllegalStateException if the {@code JTable} cell is not editable.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   * @throws ActionFailedException if this writer is unable to handle the underlying cell editor.
   * @see #replaceCellWriter(JTableCellWriter)
   */
  @RunsInEDT
  public void cancelCellEditing(@Nonnull JTable table, @Nonnull TableCell cell) {
    checkNotNull(cell);
    cellWriter.cancelCellEditing(table, cell.row, cell.column);
  }

  /**
   * Validates that the given table cell is non {@code null} and its indices are not out of bounds.
   *
   * @param table the target {@code JTable}.
   * @param cell to validate.
   * @throws NullPointerException if the cell is {@code null}.
   * @throws IndexOutOfBoundsException if any of the indices (row and column) is out of bounds.
   */
  @RunsInEDT
  public void checkCellIndicesInBounds(final @Nonnull JTable table, final @Nonnull TableCell cell) {
    execute(new GuiTask() {
      @Override
      protected void executeInEDT() {
        JTableCellPreconditions.checkCellIndicesInBounds(table, cell);
      }
    });
  }

  /**
   * Updates the implementation of {@link JTableCellReader} to use when comparing internal values of a {@code JTable}
   * and the values expected in a test.
   *
   * @param newCellReader the new {@code JTableCellValueReader} to use.
   * @throws NullPointerException if {@code newCellReader} is {@code null}.
   */
  public void replaceCellReader(@Nonnull JTableCellReader newCellReader) {
    cellReader = checkNotNull(newCellReader);
  }

  /**
   * Updates the implementation of {@link JTableCellWriter} to use to edit cell values in a {@code JTable}.
   *
   * @param newCellWriter the new {@code JTableCellWriter} to use.
   * @throws NullPointerException if {@code newCellWriter} is {@code null}.
   */
  public void replaceCellWriter(JTableCellWriter newCellWriter) {
    cellWriter = checkNotNull(newCellWriter);
  }

  /**
   * Returns the number of rows that can be shown in the given {@code JTable}, given unlimited space.
   *
   * @param table the target {@code JTable}.
   * @return the number of rows shown in the given {@code JTable}.
   * @see JTable#getRowCount()
   */
  @RunsInEDT
  public int rowCountOf(@Nonnull JTable table) {
    return JTableRowCountQuery.rowCountOf(table);
  }

  /**
   * Returns the index of the column in the given {@code JTable} whose id matches the given one.
   *
   * @param table the target {@code JTable}.
   * @param columnId the id of the column to look for.
   * @return the index of the column whose id matches the given one.
   * @throws ActionFailedException if a column with a matching id could not be found.
   */
  @RunsInEDT
  public int columnIndex(@Nonnull JTable table, @Nonnull Object columnId) {
    return findColumnIndex(table, columnId);
  }

  @RunsInEDT
  private static int findColumnIndex(final @Nonnull JTable table, final @Nonnull Object columnId) {
    Integer result = execute(new GuiQuery<Integer>() {
      @Override
      protected Integer executeInEDT() {
        int index = columnIndexByIdentifier(table, columnId);
        if (index < 0) {
          failColumnIndexNotFound(columnId);
        }
        return index;
      }
    });
    return checkNotNull(result);
  }

  private static @Nonnull ActionFailedException failColumnIndexNotFound(@Nonnull Object columnId) {
    throw actionFailure(String.format("Unable to find a column with id '%s'", columnId.toString()));
  }

  /**
   * Asserts that the given {@code JTable} has the given number of rows.
   *
   * @param table the target {@code JTable}.
   * @param rowCount the expected number of rows.
   * @throws AssertionError if the given {@code JTable} does not have the given number of rows.
   */
  @RunsInEDT
  public void requireRowCount(@Nonnull JTable table, int rowCount) {
    assertThat(rowCountOf(table)).as(propertyName(table, "rowCount")).isEqualTo(rowCount);
  }

  /**
   * Asserts that the given {@code JTable} has the given number of columns.
   *
   * @param table the target {@code JTable}.
   * @param columnCount the expected number of columns.
   * @throws AssertionError if the given {@code JTable} does not have the given number of columns.
   */
  @RunsInEDT
  public void requireColumnCount(@Nonnull JTable table, int columnCount) {
    assertThat(columnCountOf(table)).as(propertyName(table, "columnCount")).isEqualTo(columnCount);
  }

  /**
   * Simulates a user selecting the given rows in the given {@code JTable}.
   *
   * @param table the target {@code JTable}.
   * @param rows the indices of the row to select.
   * @throws NullPointerException if the given array of indices is {@code null}.
   * @throws IllegalArgumentException if the given array of indices is empty.
   * @throws IllegalStateException if the {@code JTable} is disabled.
   * @throws IllegalStateException if the {@code JTable} is not showing on the screen.
   * @throws IndexOutOfBoundsException if any of the given indices is negative, or equal to or greater than the number
   *           of rows in the {@code JTable}.
   * @since 1.2
   */
  @RunsInEDT
  public void selectRows(final @Nonnull JTable table, final @Nonnull int... rows) {
    checkNotNullOrEmpty(rows);
    new MultipleSelectionTemplate(robot) {
      @Override
      int elementCount() {
        return rows.length;
      }

      @Override
      void selectElement(int index) {
        selectCell(table, rows[index], 0);
      }
    }.multiSelect();
  }

  @RunsInEDT
  private void selectCell(@Nonnull JTable table, int row, int column) {
    Pair<Boolean, Point> cellSelectionInfo = cellSelectionInfo(table, row, column, location);
    if (cellSelectionInfo.first) {
      return; // cell already selected
    }
    robot.click(table, checkNotNull(cellSelectionInfo.second), LEFT_BUTTON, 1);
  }

  @RunsInEDT
  private static @Nonnull Pair<Boolean, Point> cellSelectionInfo(final @Nonnull JTable table, final int row, final int column,
      final @Nonnull JTableLocation location) {
    Pair<Boolean, Point> result = execute(new GuiQuery<Pair<Boolean, Point>>() {
      @Override
      protected Pair<Boolean, Point> executeInEDT() {
        if (isCellSelected(table, row, column)) {
          return Pair.of(true, null);
        }
        scrollToCell(table, row, column, location);
        Point pointAtCell = location.pointAt(table, row, column);
        return Pair.of(false, pointAtCell);
      }
    });
    return checkNotNull(result);
  }

  @RunsInCurrentThread
  private static void scrollToCell(final @Nonnull JTable table, final int row, final int column,
      final @Nonnull JTableLocation location) {
    checkEnabledAndShowing(table);
    JTableCellPreconditions.checkCellIndicesInBounds(table, row, column);
    table.scrollRectToVisible(location.cellBounds(table, row, column));
  }

  /**
   * Asserts that the set of selected rows in the given {@code JTable} contains to the given row indices.
   *
   * @param table the target {@code JTable}.
   * @param rows the indices of the rows expected to be selected.
   * @throws AssertionError if the sets of selected rows in the given {@code JTable} (if any) do not contain the given
   *           row indices.
   * @since 1.2
   */
  @RunsInEDT
  public void requireSelectedRows(@Nonnull JTable table, @Nonnull int... rows) {
    int[] selectedRows = selectedRowsOf(table);
    assertThat(selectedRows).as(propertyName(table, SELECTED_ROWS_PROPERTY)).contains(rows);
  }

  @RunsInEDT
  private static @Nonnull int[] selectedRowsOf(final @Nonnull JTable table) {
    int[] result = execute(new GuiQuery<int[]>() {
      @Override
      protected int[] executeInEDT() {
        return table.getSelectedRows();
      }
    });
    return checkNotNull(result);
  }

  @VisibleForTesting
  @Nonnull JTableCellReader cellReader() {
    return cellReader;
  }

  private @Nonnull JTableLocation location() {
    return location;
  }
}
TOP

Related Classes of org.fest.swing.driver.JTableDriver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.