All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.darcy.ui.api.elements.Table Maven / Gradle / Ivy

Go to download

Framework for writing page objects to automate interaction with graphical user interfaces.

The newest version!
/*
 Copyright 2014 Red Hat, Inc. and/or its affiliates.

 This file is part of darcy-ui.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .
 */

package com.redhat.darcy.ui.api.elements;

import static java.util.Spliterator.DISTINCT;
import static java.util.Spliterator.NONNULL;
import static java.util.Spliterator.ORDERED;
import static java.util.Spliterator.SORTED;

import com.redhat.darcy.ui.api.ViewElement;

import org.hamcrest.Matcher;

import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterators;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Tables are constructed of rows and columns. Cells can contain any arbitrary configuration of
 * elements, and it is assumed that every row in a column has a consistent element configuration (or
 * at least consistent logic to determine what is inside a cell within that column given a
 * particular row). Given this, the general "equation" of this interface and related interfaces is,
 * "table + row index + column = content of a cell", which should intuitively make sense.
 *
 * 

You can see this expressed in the interaction between each interface, specifically * {@link com.redhat.darcy.ui.api.elements.Table.Column}, which may also be thought of as * a function which accepts a table and a row index as an argument, and spits out the contents of * the cell within this column at the passed row index. The contents of the cells in that column are * arbitrary and may be typed whatever is appropriate for that table. Many column definitions will * return Strings or other element types, such as Checkboxes or Links. * *

Knowledge about how to retrieve particular content is contained with column implementations. * This makes sense: columns are typed by the content of cells in that column. If one column has * only text in it, and another has checkboxes, those are clearly different column implementations. * This means that defining a table within your View is not merely about implementing this * interface, but also necessarily implementing some * {@link com.redhat.darcy.ui.api.elements.Table.Column}s. Otherwise, no content in your table would * actually be retrievable. It is common, then, to implement table (whether directly or by extending * a base class), for each different table that would appear in your view. Then, within that class, * have appropriately visible instances of {@link com.redhat.darcy.ui.api.elements.Table.Column} * that can be used in cooperation with an instance of that table. * *

Your instinct may be to consider a table an element, however tables are really * {@link com.redhat.darcy.ui.api.ViewElement ViewElements}—they may behave as a * single element since they are found by a locator within some element context, however they are * themselves containers of many more elements (their cells and their cells' contents), which means * they are also views. * * @param The type of the subclass of this table. Allows the parent interface to refer to the * subclass's specific type. */ public interface Table> extends ViewElement { /** * The currently visible number of rows shown within the table. */ int getRowCount(); /** * Equivalent to {@link #getRowCount()} == 0, however from an implementation point of view, * different tables often have special indications for when there is no data to display within * the table. */ boolean isEmpty(); /** * An iterable for all of the currently visible rows, which means the amount of items in the * iterable should match {@link #getRowCount()}. * *

The iterable iterates over {@link com.redhat.darcy.ui.api.elements.Table.Row} objects that * are tied to this table. */ default Iterable> rows() { return () -> new Iterator>() { private int cursor = 0; @Override public boolean hasNext() { return cursor + 1 <= getRowCount(); } @SuppressWarnings("unchecked") @Override public Row next() { return new Row((T) Table.this, ++cursor); } }; } /** * @return A specific {@link com.redhat.darcy.ui.api.elements.Table.Row} for this table. */ @SuppressWarnings("unchecked") default Row getRow(int rowIndex) { return new Row<>((T) this, rowIndex); } /** * @return A specific {@link com.redhat.darcy.ui.api.elements.Table.TableColumn} for this table. */ @SuppressWarnings("unchecked") default TableColumn, T, U> getColumn(Column column) { return new TableColumn<>((T) this, column); } /** * @return A specific {@link com.redhat.darcy.ui.api.elements.Table.HeadedTableColumn} for this * table. */ @SuppressWarnings("unchecked") default HeadedTableColumn, T, U, E> getColumn( ColumnWithHeader column) { return new HeadedTableColumn<>((T) this, column); } /** * @return A specific cell's contents within this table, as determined by the specified * {@link com.redhat.darcy.ui.api.elements.Table.Column} and row index. */ @SuppressWarnings("unchecked") default U getCell(Column column, int rowIndex) { return column.getCell((T) this, rowIndex); } /** * @return header A specific column's header's contents within this table, as determined by the * specified {@link com.redhat.darcy.ui.api.elements.Table.Header}. * @param The type of contents within the header. */ @SuppressWarnings("unchecked") default U getHeader(Header header) { return header.getHeader((T) this); } /** * @return A filtered {@link java.util.stream.Stream} of the rows in this table where the * contents of a particular column match the specified {@link java.util.function.Predicate}. */ default Stream> getRowsWhere(Column column, Predicate predicate) { return StreamSupport.stream( Spliterators.spliterator(rows().iterator(), getRowCount(), DISTINCT | ORDERED | SORTED | NONNULL), false) .filter(r -> predicate.test(r.getCell(column))); } /** * @return The first filtered {@link java.util.Optional} of the rows in this table where the * contents of a particular column match the specified {@link java.util.function.Predicate}. */ default Optional> getFirstRowWhere(Column column, Predicate predicate) { return getRowsWhere(column, predicate).findFirst(); } /** * @return A filtered {@link java.util.stream.Stream} of the rows in this table where the * contents of a particular column match the specified Hamcrest {@link org.hamcrest.Matcher}. */ default Stream> getRowsWhere(Column column, Matcher matcher) { return getRowsWhere(column, matcher::matches); } /** * @return The first filtered {@link java.util.Optional} of the rows in this table where the * contents of a particular column match the specified Hamcrest {@link org.hamcrest.Matcher}. */ default Optional> getFirstRowWhere(Column column, Matcher matcher) { return getRowsWhere(column, matcher::matches).findFirst(); } /** * @return Like * {@link #getRowsWhere(com.redhat.darcy.ui.api.elements.Table.Column, java.util.function.Predicate)}, * except instead of returning a stream of rows, returns a stream of the exact cells that * matched the specified predicate within the specified column. Useful if you want the contents * of cells that match some range of possible values, and you would like to inspect the actual * values. */ default Stream getCellsWhere(Column column, Predicate predicate) { return getRowsWhere(column, predicate) .map(r -> r.getCell(column)); } /** * @return Like * {@link #getRowsWhere(com.redhat.darcy.ui.api.elements.Table.Column, org.hamcrest.Matcher)}, * except instead of returning a stream of rows, returns a stream of the exact cells that * matched the specified matcher within the specified column. Useful if you want the contents * of cells that match some range of possible values, and you would like to inspect the actual * values. */ default Stream getCellsWhere(Column column, Matcher matcher) { return getCellsWhere(column, matcher::matches); } /** * A column definition does the actual work of returning some useful contents within the table, * limited to a particular column. Each column is expected to have consistent cell contents. If * these contents are variable, consider returning instances of a custom class which can handle * the various contents, or implementing different column definitions, despite actually * referring to the same visual column. * * @param The class of table with which this column refers to. This will be your specific * table implementation, or one of the common ones provided in implementations of darcy-ui. * @param The class that models the contents within cells of this column. */ interface Column, U> { U getCell(T table, int rowIndex); } /** * Like a {@link com.redhat.darcy.ui.api.elements.Table.Column}, but contains the * knowledge for locating the header of a particular column. Typically, a column definition * will implement {@link com.redhat.darcy.ui.api.elements.Table.Column} alone at the * very least, and if that column has a header, additionally implement * {@link com.redhat.darcy.ui.api.elements.Table.Header}. * * @param The class of table with which this header refers to. This will be your specific * table implementation, or one of the common ones provided in implementations of darcy-ui. * @param The class that models the contents within the header for this column. */ interface Header, U> { U getHeader(T table); } interface ColumnWithHeader, U, E> extends Column, Header {} /** * Represents a concrete column within a specific table instance. Given this and a row index, * you will be able to look up the contents of a specific cell. * * @param The type of the definition for this column. * @param The type of table this column is within. * @param The type of the cell content within this column. */ final class TableColumn, U extends Table, E> { private final U table; private final T column; public TableColumn(U table, T column) { this.table = Objects.requireNonNull(table, "table"); this.column = Objects.requireNonNull(column, "column"); } public E getCell(int rowIndex) { return column.getCell(table, rowIndex); } public U getTable() { return table; } @Override public String toString() { return "TableColumn: {table: " + table + ", column: " + column + "}"; } @Override public int hashCode() { return Objects.hash(table, column); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (!(o instanceof TableColumn)) { return false; } TableColumn other = (TableColumn) o; return other.table.equals(table) && other.column.equals(column); } } /** * Represents a concrete column within a specific table instance. Given this and a row index, * you will be able to look up the contents of a specific cell. * *

This column also contains a header of some type. * * @param The type of the definition for this column. * @param The type of table this column is within. * @param The type of the cell content within this column. */ final class HeadedTableColumn & Header, U extends Table, E, V> { private final U table; private final T column; public HeadedTableColumn(U table, T column) { this.table = Objects.requireNonNull(table, "table"); this.column = Objects.requireNonNull(column, "column"); } public V getHeader() { return column.getHeader(table); } public E getCell(int rowIndex) { return column.getCell(table, rowIndex); } public U getTable() { return table; } @Override public String toString() { return "HeadedTableColumn: {table: " + table + ", column: " + column + "}"; } @Override public int hashCode() { return Objects.hash(table, column); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (!(o instanceof HeadedTableColumn)) { return false; } HeadedTableColumn other = (HeadedTableColumn) o; return other.table.equals(table) && other.column.equals(column); } } /** * Represents a concrete row within a specific table instance. Given this and a column * definition that corresponds to this row's table type (T), you will be able to look up the * contents of a specific cell. * * @param The type of table this row is within. */ final class Row> implements Comparable> { private final T table; private final int index; public Row(T table, int index) { this.table = Objects.requireNonNull(table, "table"); this.index = Objects.requireNonNull(index, "index"); } public U getCell(Column column) { return column.getCell(getTable(), getIndex()); } public T getTable() { return table; } public int getIndex() { return index; } @Override public String toString() { return "Row: {table: " + table + ", index: " + index + "}"; } @Override public int compareTo(Row o) { Objects.requireNonNull(o, "o"); return index - o.index; } @Override public int hashCode() { return Objects.hash(table, index); } @Override public boolean equals(Object o) { if (o == null) { return false; } if (!(o instanceof Row)) { return false; } Row other = (Row) o; return other.index == index && other.table.equals(table); } } }