From fdd1a06f990e5214b0f7bbb3dc1946bac2962e27 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Mon, 10 Oct 2022 01:51:58 +0300 Subject: [PATCH 01/94] update copyright --- .idea/copyright/GNU_AGPLv3.xml | 2 +- pom.xml | 2 +- .../org/spacious_team/table_wrapper/api/AbstractReportPage.java | 2 +- .../spacious_team/table_wrapper/api/AbstractReportPageRow.java | 2 +- .../java/org/spacious_team/table_wrapper/api/AbstractTable.java | 2 +- .../org/spacious_team/table_wrapper/api/AbstractTableCell.java | 2 +- .../spacious_team/table_wrapper/api/AbstractTableFactory.java | 2 +- .../org/spacious_team/table_wrapper/api/AnyOfTableColumn.java | 2 +- .../spacious_team/table_wrapper/api/CellDataAccessObject.java | 2 +- .../table_wrapper/api/ConstantPositionTableColumn.java | 2 +- .../spacious_team/table_wrapper/api/MultiLineTableColumn.java | 2 +- .../org/spacious_team/table_wrapper/api/MutableTableRow.java | 2 +- .../spacious_team/table_wrapper/api/OptionalTableColumn.java | 2 +- .../table_wrapper/api/RelativePositionTableColumn.java | 2 +- .../java/org/spacious_team/table_wrapper/api/ReportPage.java | 2 +- .../org/spacious_team/table_wrapper/api/ReportPageHelper.java | 2 +- .../java/org/spacious_team/table_wrapper/api/ReportPageRow.java | 2 +- src/main/java/org/spacious_team/table_wrapper/api/Table.java | 2 +- .../java/org/spacious_team/table_wrapper/api/TableCell.java | 2 +- .../org/spacious_team/table_wrapper/api/TableCellAddress.java | 2 +- .../org/spacious_team/table_wrapper/api/TableCellRange.java | 2 +- .../java/org/spacious_team/table_wrapper/api/TableColumn.java | 2 +- .../spacious_team/table_wrapper/api/TableColumnDescription.java | 2 +- .../org/spacious_team/table_wrapper/api/TableColumnImpl.java | 2 +- .../java/org/spacious_team/table_wrapper/api/TableFactory.java | 2 +- .../spacious_team/table_wrapper/api/TableFactoryRegistry.java | 2 +- src/main/java/org/spacious_team/table_wrapper/api/TableRow.java | 2 +- 27 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.idea/copyright/GNU_AGPLv3.xml b/.idea/copyright/GNU_AGPLv3.xml index d427976..1029333 100644 --- a/.idea/copyright/GNU_AGPLv3.xml +++ b/.idea/copyright/GNU_AGPLv3.xml @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5464af8..7f01766 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ + diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java index 13999ff..af03268 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java @@ -18,11 +18,14 @@ package org.spacious_team.table_wrapper.api; +import javax.annotation.Nullable; + /** * {@link ReportPage} with specified {@link ReportPageRow} */ public abstract class AbstractReportPage implements ReportPage { + @Nullable @Override public abstract T getRow(int i); diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java index 8a14b0b..1e7f4ee 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java @@ -22,6 +22,7 @@ import lombok.ToString; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -157,7 +158,7 @@ public List getDataCollection(Object report, Function List getDataCollection(Object report, BiConsumer> rowHandler) { List data = new ArrayList<>(); - for (TableRow row : this) { + for (@Nullable TableRow row : this) { if (row != null) { try { rowHandler.accept(row, data); @@ -174,7 +175,7 @@ public static void addWithEqualityChecker(T element, Collection collection, BiPredicate equalityChecker, BiFunction> duplicatesMerger) { - T equalsObject = null; + @Nullable T equalsObject = null; for (T e : collection) { if (equalityChecker.test(e, element)) { equalsObject = e; @@ -192,7 +193,7 @@ public static void addWithEqualityChecker(T element, /** * {@link TableRow} impl is mutable. * For performance issue same object with changed state is provided in each loop cycle. - * Call {@link TableRow#clone()} if you want use row object outside stream() block. + * Call {@link TableRow#clone()} if you want to use row object outside stream() block. */ @Override public Stream stream() { @@ -202,7 +203,7 @@ public Stream stream() { /** * {@link TableRow} impl is mutable. * For performance issue same object with changed state is provided in each loop cycle. - * Call {@link TableRow#clone()} if you want use row object outside iterator() block. + * Call {@link TableRow#clone()} if you want to use row object outside iterator() block. */ @Override public Iterator iterator() { @@ -222,7 +223,7 @@ public boolean hasNext() { @Override public TableRow next() { - R row; + @Nullable R row; do { row = getRow(tableRange.getFirstRow() + (i++)); } while (row == null && hasNext()); @@ -231,23 +232,27 @@ public TableRow next() { } } + @Nullable @Override public R getRow(int i) { return reportPage.getRow(i); } + @Nullable @Override public TableRow findRow(Object value) { TableCellAddress address = reportPage.find(value); return getMutableTableRow(address); } + @Nullable @Override public TableRow findRowByPrefix(String prefix) { TableCellAddress address = reportPage.findByPrefix(prefix); return getMutableTableRow(address); } + @Nullable private MutableTableRow getMutableTableRow(TableCellAddress address) { if (tableRange.contains(address)) { MutableTableRow tableRow = new MutableTableRow<>(this, getCellDataAccessObject()); diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java index aa7c38f..74e9a07 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java @@ -22,6 +22,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; @@ -33,6 +34,7 @@ public abstract class AbstractTableCell implements TableCell { private final T cell; private final CellDataAccessObject dao; + @Nullable @Override public Object getValue() { return dao.getValue(cell); diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java index 95263c9..5281b0b 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java @@ -18,6 +18,7 @@ package org.spacious_team.table_wrapper.api; +import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; @@ -25,6 +26,8 @@ import java.util.Objects; import java.util.regex.Pattern; +import static java.util.Objects.requireNonNull; + /** * @apiNote Impl may have parameters that affect how the value is parsed, * for example DataTimeFormat that changes behavior of date time value parser. @@ -35,8 +38,10 @@ public interface CellDataAccessObject { Pattern spacePattern = Pattern.compile("\\s"); String NO_CELL_VALUE_EXCEPTION_MESSAGE = "Cell doesn't contains value"; + @Nullable C getCell(R row, Integer cellIndex); + @Nullable Object getValue(C cell); /** @@ -50,7 +55,7 @@ default int getIntValue(C cell) { * @throws RuntimeException if method can't extract long value */ default long getLongValue(C cell) { - Object value = getValue(cell); + @Nullable Object value = getValue(cell); if (value instanceof Number) { return ((Number) value).longValue(); } else if (value != null) { @@ -64,7 +69,7 @@ default long getLongValue(C cell) { * @throws RuntimeException if method can't extract Double value */ default double getDoubleValue(C cell) { - Object value = getValue(cell); + @Nullable Object value = getValue(cell); if (value instanceof Number) { return ((Number) value).doubleValue(); } else if (value != null) { @@ -100,7 +105,7 @@ default BigDecimal getBigDecimalValue(C cell) { * @throws RuntimeException if method can't extract string value */ default String getStringValue(C cell) { - return getValue(cell).toString(); + return requireNonNull(getValue(cell), "Not a string").toString(); } /** @@ -117,16 +122,17 @@ default LocalDateTime getLocalDateTimeValue(C cell) { .toLocalDateTime(); } + @Nullable default Object getValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); - return getValue(cell); + @Nullable C cell = getCell(row, cellIndex); + return (cell == null) ? null : getValue(cell); } /** * @throws RuntimeException if method can't extract int value */ default int getIntValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); + C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getIntValue(cell); } @@ -134,7 +140,7 @@ default int getIntValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract long value */ default long getLongValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); + C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getLongValue(cell); } @@ -142,7 +148,7 @@ default long getLongValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract Double value */ default double getDoubleValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); + C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getDoubleValue(cell); } @@ -150,7 +156,7 @@ default double getDoubleValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract BigDecimal value */ default BigDecimal getBigDecimalValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); + C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getBigDecimalValue(cell); } @@ -158,7 +164,7 @@ default BigDecimal getBigDecimalValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract string value */ default String getStringValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); + C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getStringValue(cell); } @@ -166,7 +172,7 @@ default String getStringValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract instant value */ default Instant getInstantValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); + C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getInstantValue(cell); } @@ -174,8 +180,7 @@ default Instant getInstantValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract local date time value */ default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex) { - C cell = getCell(row, cellIndex); + C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getLocalDateTimeValue(cell); } - } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java index 890e8a8..d378725 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java @@ -22,6 +22,7 @@ import lombok.Data; import lombok.Setter; +import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; @@ -42,6 +43,7 @@ class MutableTableRow implements TableRow { @Setter(AccessLevel.PACKAGE) private volatile T row; + @Nullable public TableCell getCell(TableColumnDescription column) { return getCell(getCellIndex(column)); } @@ -76,6 +78,7 @@ public Iterator iterator() { return row.iterator(); } + @Nullable public Object getCellValue(TableColumnDescription column) { return dao.getValue(row, getCellIndex(column)); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/NonNullByDefault.java b/src/main/java/org/spacious_team/table_wrapper/api/NonNullByDefault.java new file mode 100644 index 0000000..665b2e6 --- /dev/null +++ b/src/main/java/org/spacious_team/table_wrapper/api/NonNullByDefault.java @@ -0,0 +1,51 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This annotation can be applied to a package, class or method to indicate that the class fields, + * method return types and parameters in that element are not null by default unless there is: + * The method overrides a method in a superclass (in which + * case the annotation of the corresponding parameter in the superclass applies) there is a + * default parameter annotation applied to a more tightly nested element. + * + * @see Explanation and linked to this article + * implementation + */ +@Documented +@Nonnull +@TypeQualifierDefault({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.LOCAL_VARIABLE, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.PARAMETER, + ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NonNullByDefault { +} \ No newline at end of file diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java index 5b00822..3e41d30 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java @@ -18,8 +18,10 @@ package org.spacious_team.table_wrapper.api; +import javax.annotation.Nullable; import java.util.function.Predicate; +import static java.util.Objects.requireNonNull; import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate; @SuppressWarnings("unused") @@ -151,7 +153,7 @@ default TableCellAddress findByPrefix(String prefix, int startRow, int endRow) { * @param endColumn search columns excluding this, can handle values greater than real columns count * @return cell address or {@link TableCellAddress#NOT_FOUND} */ - default TableCellAddress findByPrefix(String prefix, int startRow, int endRow, int startColumn, int endColumn) { + default TableCellAddress findByPrefix(@Nullable String prefix, int startRow, int endRow, int startColumn, int endColumn) { return prefix == null ? TableCellAddress.NOT_FOUND : find(startRow, endRow, startColumn, endColumn, getCellStringValueIgnoreCasePrefixPredicate(prefix)); @@ -160,13 +162,17 @@ default TableCellAddress findByPrefix(String prefix, int startRow, int endRow, i /** * For vertical table of key-value records (table with two columns), search and return value for requested key. */ + @Nullable default Object getNextColumnValue(String firstColumnValuePrefix) { TableCellAddress address = findByPrefix(firstColumnValuePrefix); - for (TableCell cell : getRow(address.getRow())) { - if (cell != null && cell.getColumnIndex() > address.getColumn()) { - Object value = cell.getValue(); - if (value != null && (!(value instanceof String) || !((String) value).isBlank())) { - return value; + @Nullable ReportPageRow row = getRow(address.getRow()); + if (row != null) { + for (@Nullable TableCell cell : row) { + if (cell != null && cell.getColumnIndex() > address.getColumn()) { + @Nullable Object value = cell.getValue(); + if (value != null && (!(value instanceof String) || !((String) value).isBlank())) { + return value; + } } } } @@ -178,6 +184,7 @@ default Object getNextColumnValue(String firstColumnValuePrefix) { * @return row object or null is row does not exist * @apiNote Method impl should return {@link CellDataAccessObject} aware {@link ReportPageRow} impl */ + @Nullable ReportPageRow getRow(int i); /** @@ -185,15 +192,19 @@ default Object getNextColumnValue(String firstColumnValuePrefix) { */ int getLastRowNum(); + @Nullable default TableCell getCell(TableCellAddress address) { - return getRow(address.getRow()).getCell(address.getColumn()); + @Nullable ReportPageRow row = getRow(address.getRow()); + return (row == null) ? null : row.getCell(address.getColumn()); } /** * Returns table range. Table's first row starts with 'firstRowPrefix' prefix in one of the cells * and table ends with predefined prefix in one of the last row cells. */ - default TableCellRange getTableCellRange(String firstRowPrefix, int headersRowCount, String lastRowPrefix) { + default TableCellRange getTableCellRange(@Nullable String firstRowPrefix, + int headersRowCount, + @Nullable String lastRowPrefix) { if (firstRowPrefix == null || lastRowPrefix == null) { return TableCellRange.EMPTY_RANGE; } @@ -206,9 +217,9 @@ default TableCellRange getTableCellRange(String firstRowPrefix, int headersRowCo /** * Returns table range. First and last row will be found by predicate. */ - default TableCellRange getTableCellRange(Predicate firstRowFinder, + default TableCellRange getTableCellRange(@Nullable Predicate firstRowFinder, int headersRowCount, - Predicate lastRowFinder) { + @Nullable Predicate lastRowFinder) { if (firstRowFinder == null || lastRowFinder == null) { return TableCellRange.EMPTY_RANGE; } @@ -216,22 +227,24 @@ default TableCellRange getTableCellRange(Predicate firstRowFinder, if (startAddress.equals(TableCellAddress.NOT_FOUND)) { return TableCellRange.EMPTY_RANGE; } + ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row not found"); TableCellAddress endAddress = find(startAddress.getRow() + headersRowCount + 1, lastRowFinder); if (endAddress.equals(TableCellAddress.NOT_FOUND)) { return TableCellRange.EMPTY_RANGE; } + ReportPageRow lastRow = requireNonNull(getRow(endAddress.getRow()), "Row not found"); return new TableCellRange( startAddress.getRow(), endAddress.getRow(), - getRow(startAddress.getRow()).getFirstCellNum(), - getRow(endAddress.getRow()).getLastCellNum()); + firstRow.getFirstCellNum(), + lastRow.getLastCellNum()); } /** * Returns table range. First row starts with 'firstRowPrefix' prefix in one of the cells, * range ends with empty row or last row of report page. */ - default TableCellRange getTableCellRange(String firstRowPrefix, int headersRowCount) { + default TableCellRange getTableCellRange(@Nullable String firstRowPrefix, int headersRowCount) { if (firstRowPrefix == null) { return TableCellRange.EMPTY_RANGE; } @@ -243,7 +256,7 @@ default TableCellRange getTableCellRange(String firstRowPrefix, int headersRowCo /** * Returns table range. First row will be found by predicate, range ends with empty row or last row of report page. */ - default TableCellRange getTableCellRange(Predicate firstRowFinder, int headersRowCount) { + default TableCellRange getTableCellRange(@Nullable Predicate firstRowFinder, int headersRowCount) { if (firstRowFinder == null) { return TableCellRange.EMPTY_RANGE; } @@ -251,6 +264,7 @@ default TableCellRange getTableCellRange(Predicate firstRowFinder, int h if (startAddress.equals(TableCellAddress.NOT_FOUND)) { return TableCellRange.EMPTY_RANGE; } + ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row not found"); int lastRowNum = findEmptyRow(startAddress.getRow() + headersRowCount + 1); if (lastRowNum == -1) { lastRowNum = getLastRowNum(); // empty row not found @@ -260,11 +274,12 @@ default TableCellRange getTableCellRange(Predicate firstRowFinder, int h if (lastRowNum < startAddress.getRow()) { lastRowNum = startAddress.getRow(); } + ReportPageRow lastRow = requireNonNull(getRow(lastRowNum), "Row not found"); return new TableCellRange( startAddress.getRow(), lastRowNum, - getRow(startAddress.getRow()).getFirstCellNum(), - getRow(lastRowNum).getLastCellNum()); + firstRow.getFirstCellNum(), + lastRow.getLastCellNum()); } /** @@ -278,12 +293,12 @@ default int findEmptyRow(int startRow) { int lastRowNum = startRow; LAST_ROW: for (int n = getLastRowNum(); lastRowNum <= n; lastRowNum++) { - ReportPageRow row = getRow(lastRowNum); + @Nullable ReportPageRow row = getRow(lastRowNum); if (row == null || row.getLastCellNum() == -1) { return lastRowNum; // all row cells blank } - for (TableCell cell : row) { - Object value; + for (@Nullable TableCell cell : row) { + @Nullable Object value; if (!(cell == null || ((value = cell.getValue()) == null) || (value instanceof String) && (value.toString().isEmpty()))) { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java index fe3d9d0..e2d5484 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java @@ -18,12 +18,15 @@ package org.spacious_team.table_wrapper.api; +import javax.annotation.Nullable; + public interface ReportPageRow extends Iterable { /** * @param i zero-based cell number - * @return cell ot null if cell does not exists + * @return cell ot null if cell does not exist */ + @Nullable TableCell getCell(int i); /** diff --git a/src/main/java/org/spacious_team/table_wrapper/api/Table.java b/src/main/java/org/spacious_team/table_wrapper/api/Table.java index ae838a9..7f03224 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/Table.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/Table.java @@ -18,6 +18,7 @@ package org.spacious_team.table_wrapper.api; +import javax.annotation.Nullable; import java.util.Collection; import java.util.List; import java.util.Map; @@ -68,16 +69,19 @@ List getDataCollection(Object report, Function> r * @return row object or null is row does not exist * @apiNote Method impl should return {@link CellDataAccessObject} aware {@link ReportPageRow} impl */ + @Nullable ReportPageRow getRow(int i); /** * @return row containing cell with exact value or null if not found */ + @Nullable TableRow findRow(Object value); /** * @return row containing cell starting with prefix or null if not found */ + @Nullable TableRow findRowByPrefix(String prefix); Map getHeaderDescription(); diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java index 6b3a6e6..0d6adb8 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java @@ -18,6 +18,7 @@ package org.spacious_team.table_wrapper.api; +import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; @@ -30,6 +31,7 @@ public interface TableCell { */ int getColumnIndex(); + @Nullable Object getValue(); /** @@ -70,7 +72,8 @@ public interface TableCell { /** * @return return cell value or defaultValue if the cell is missing or the type does not match the expected */ - default Object getValueOrDefault(Object defaultValue) { + @Nullable + default Object getValueOrDefault(@Nullable Object defaultValue) { try { return getValue(); } catch (Exception e) { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java b/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java index 6c9eb3c..65cd7a0 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java @@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; +import javax.annotation.Nullable; import java.util.Arrays; @ToString @@ -44,9 +45,10 @@ private TableColumnImpl(String... words) { public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) { for (ReportPageRow header : headerRows) { next_cell: - for (TableCell cell : header) { - Object value; - if (cell != null && cell.getColumnIndex() >= firstColumnForSearch && ((value = cell.getValue()) instanceof String)) { + for (@Nullable TableCell cell : header) { + @Nullable Object value; + if (cell != null && cell.getColumnIndex() >= firstColumnForSearch && + ((value = cell.getValue()) instanceof String)) { String colName = value.toString().toLowerCase(); for (String word : words) { if (!containsWord(colName, word)) { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java index 8976325..0dcc06d 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java @@ -126,8 +126,13 @@ default Table create(ReportPage reportPage, if (!range.equals(TableCellRange.EMPTY_RANGE)) { TableCellAddress tableNameCell = reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder); - tableName = tableNameCell.equals(TableCellAddress.NOT_FOUND) ? "" : - reportPage.getCell(tableNameCell).getStringValue(); + if (!tableNameCell.equals(TableCellAddress.NOT_FOUND)) { + try { + //noinspection ConstantConditions + tableName = reportPage.getCell(tableNameCell).getStringValue(); + } catch (Exception ignore) { + } + } } return create(reportPage, tableName, range, headerDescription, headersRowCount); } @@ -148,8 +153,13 @@ default Table create(ReportPage reportPage, if (!range.equals(TableCellRange.EMPTY_RANGE)) { TableCellAddress tableNameCell = reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder); - tableName = tableNameCell.equals(TableCellAddress.NOT_FOUND) ? "" : - reportPage.getCell(tableNameCell).getStringValue(); + if (!tableNameCell.equals(TableCellAddress.NOT_FOUND)) { + try { + //noinspection ConstantConditions + tableName = reportPage.getCell(tableNameCell).getStringValue(); + } catch (Exception ignore) { + } + } } return create(reportPage, tableName, range, headerDescription, headersRowCount); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java index 898ccb9..bc5b8b3 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java @@ -18,6 +18,7 @@ package org.spacious_team.table_wrapper.api; +import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; @@ -30,11 +31,13 @@ public interface TableRow extends ReportPageRow, Cloneable { Table getTable(); + @Nullable TableCell getCell(TableColumnDescription column); /** * Returns cell's native value */ + @Nullable Object getCellValue(TableColumnDescription column); /** @@ -75,7 +78,8 @@ public interface TableRow extends ReportPageRow, Cloneable { /** * @return return cell value or defaultValue if the cell is missing or the type does not match the expected */ - default Object getCellValueOrDefault(TableColumnDescription column, Object defaultValue) { + @Nullable + default Object getCellValueOrDefault(TableColumnDescription column, @Nullable Object defaultValue) { try { return getCellValue(column); } catch (Exception e) { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/package-info.java b/src/main/java/org/spacious_team/table_wrapper/api/package-info.java new file mode 100644 index 0000000..313c22b --- /dev/null +++ b/src/main/java/org/spacious_team/table_wrapper/api/package-info.java @@ -0,0 +1,20 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@NonNullByDefault +package org.spacious_team.table_wrapper.api; From e8a6c7d2a629f0828ee36068a12aaf5a9d021ed0 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Thu, 20 Oct 2022 01:20:18 +0300 Subject: [PATCH 07/94] add early fail on null object --- .../java/org/spacious_team/table_wrapper/api/AbstractTable.java | 2 ++ .../java/org/spacious_team/table_wrapper/api/ReportPageRow.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java index 1e7f4ee..daa904d 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java @@ -31,6 +31,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.BiPredicate; @@ -227,6 +228,7 @@ public TableRow next() { do { row = getRow(tableRange.getFirstRow() + (i++)); } while (row == null && hasNext()); + Objects.requireNonNull(row, "Last row is empty"); tableRow.setRow(row); return tableRow; } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java index e2d5484..87293f1 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java @@ -35,7 +35,7 @@ public interface ReportPageRow extends Iterable { int getRowNum(); /** - * Zero-based cell number + * Zero-based cell number or -1 if row doesn't contain cells */ int getFirstCellNum(); From d4e4303c62d13d1ede3ddd8fec70124d65364bb2 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Thu, 20 Oct 2022 03:48:46 +0300 Subject: [PATCH 08/94] remove one NPE from AbstractTable --- .../table_wrapper/api/AbstractTable.java | 8 +- .../table_wrapper/api/EmptyTableRow.java | 117 ++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java index daa904d..ad066e2 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java @@ -224,11 +224,15 @@ public boolean hasNext() { @Override public TableRow next() { + int rowNum; @Nullable R row; do { - row = getRow(tableRange.getFirstRow() + (i++)); + rowNum = tableRange.getFirstRow() + (i++); + row = getRow(rowNum); } while (row == null && hasNext()); - Objects.requireNonNull(row, "Last row is empty"); + if (row == null) { // Last row is empty + return new EmptyTableRow(AbstractTable.this, rowNum); + } tableRow.setRow(row); return tableRow; } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java new file mode 100644 index 0000000..1df8d2c --- /dev/null +++ b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java @@ -0,0 +1,117 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import lombok.Data; +import javax.annotation.Nullable; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Iterator; + +import static java.util.Collections.emptyIterator; + +@Data +class EmptyTableRow implements TableRow { + private final Table table; + private final int rowNum; + + @Nullable + @Override + public TableCell getCell(TableColumnDescription column) { + return null; + } + + @Nullable + @Override + public TableCell getCell(int i) { + return null; + } + + @Override + public int getFirstCellNum() { + return -1; + } + + @Override + public int getLastCellNum() { + return -1; + } + + @Override + public boolean rowContains(Object expected) { + return false; + } + + @Override + public Iterator iterator() { + return emptyIterator(); + } + + @Nullable + @Override + public Object getCellValue(TableColumnDescription column) { + return null; + } + + @Override + public int getIntCellValue(TableColumnDescription column) { + throw new NullPointerException("Cell not found"); + } + + @Override + public long getLongCellValue(TableColumnDescription column) { + throw new NullPointerException("Cell not found"); + } + + @Override + public double getDoubleCellValue(TableColumnDescription column) { + throw new NullPointerException("Cell not found"); + } + + @Override + public BigDecimal getBigDecimalCellValue(TableColumnDescription column) { + throw new NullPointerException("Cell not found"); + } + + @Override + public String getStringCellValue(TableColumnDescription column) { + throw new NullPointerException("Cell not found"); + } + + @Override + public Instant getInstantCellValue(TableColumnDescription column) { + throw new NullPointerException("Cell not found"); + } + + @Override + public LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column) { + throw new NullPointerException("Cell not found"); + } + + @Override + public TableRow clone() { + try { + return (TableRow) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Can't clone " + this.getClass().getName()); + } + } +} From 73a0fd50f9bf8a9e2218ba5231b97170b91dd273 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Thu, 20 Oct 2022 22:17:20 +0300 Subject: [PATCH 09/94] switch to check framework --- jdk.astub | 8 +++ pom.xml | 48 +++++++++++++++-- .../table_wrapper/api/AbstractReportPage.java | 2 +- .../table_wrapper/api/AbstractTable.java | 38 +++++++++----- .../table_wrapper/api/AbstractTableCell.java | 2 +- .../api/CellDataAccessObject.java | 14 ++++- .../table_wrapper/api/EmptyTableRow.java | 2 +- .../table_wrapper/api/MutableTableRow.java | 8 ++- .../table_wrapper/api/NonNullByDefault.java | 51 ------------------- .../table_wrapper/api/ReportPage.java | 10 ++-- .../table_wrapper/api/ReportPageRow.java | 2 +- .../table_wrapper/api/Table.java | 2 +- .../table_wrapper/api/TableCell.java | 2 +- .../table_wrapper/api/TableColumnImpl.java | 13 ++--- .../table_wrapper/api/TableFactory.java | 12 +++-- .../table_wrapper/api/TableRow.java | 2 +- .../table_wrapper/api/package-info.java | 5 +- 17 files changed, 128 insertions(+), 93 deletions(-) create mode 100644 jdk.astub delete mode 100644 src/main/java/org/spacious_team/table_wrapper/api/NonNullByDefault.java diff --git a/jdk.astub b/jdk.astub new file mode 100644 index 0000000..04a9da9 --- /dev/null +++ b/jdk.astub @@ -0,0 +1,8 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + +package java.util; + +public class Objects { + public static T requireNonNull(@Nullable T obj); + public static T requireNonNull(@Nullable T obj, String message); +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 20bee43..1bdb0ed 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,8 @@ 11 UTF-8 UTF-8 + 1.18.24 + 3.26.0 @@ -67,15 +69,55 @@ provided - com.google.code.findbugs - jsr305 - 3.0.2 + org.checkerframework + checker-qual + ${checkerframework.version} provided + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + true + true + true + + + org.projectlombok + lombok + ${lombok.version} + + + org.checkerframework + checker + ${checkerframework.version} + + + + + lombok.launch.AnnotationProcessorHider$AnnotationProcessor + + + org.checkerframework.checker.nullness.NullnessChecker + + + + -Awarns + -Astubs=jdk.astub + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + + + org.apache.maven.plugins maven-source-plugin diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java index af03268..c948e84 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java @@ -18,7 +18,7 @@ package org.spacious_team.table_wrapper.api; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; /** * {@link ReportPage} with specified {@link ReportPageRow} diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java index ad066e2..df0f0f2 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java @@ -21,8 +21,8 @@ import lombok.Getter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,7 +31,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.BiPredicate; @@ -40,6 +39,8 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static java.util.Objects.requireNonNull; + @Slf4j @ToString(of = {"tableName"}) public abstract class AbstractTable implements Table { @@ -108,8 +109,12 @@ private static Map getHeaderDescription(AbstractReportPage Map columnIndices = new HashMap<>(); ReportPageRow[] headerRows = new ReportPageRow[headersRowCount]; for (int i = 0; i < headersRowCount; i++) { - headerRows[i] = reportPage.getRow(tableRange.getFirstRow() + 1 + i); + @Nullable ReportPageRow row = reportPage.getRow(tableRange.getFirstRow() + 1 + i); + @SuppressWarnings({"nullness"}) + ReportPageRow notNullRow = requireNonNull(row, "Header row is absent"); + headerRows[i] = notNullRow; } + @SuppressWarnings("nullness") TableColumn[] columns = Arrays.stream(headerDescription.getEnumConstants()) .map(TableColumnDescription::getColumn) .toArray(TableColumn[]::new); @@ -126,29 +131,29 @@ private static IntStream getColumnIndices(Map headerDescri .filter(i -> i != TableColumn.NOCOLUMN_INDEX); } - public List getData(Object report, Function rowExtractor) { + public List getData(Object report, Function rowExtractor) { return getDataCollection(report, (row, data) -> { - T result = rowExtractor.apply(row); + @Nullable T result = rowExtractor.apply(row); if (result != null) { data.add(result); } }); } - public List getDataCollection(Object report, Function> rowExtractor) { + public List getDataCollection(Object report, Function> rowExtractor) { return getDataCollection(report, (row, data) -> { - Collection result = rowExtractor.apply(row); + @Nullable Collection result = rowExtractor.apply(row); if (result != null) { data.addAll(result); } }); } - public List getDataCollection(Object report, Function> rowExtractor, + public List getDataCollection(Object report, Function> rowExtractor, BiPredicate equalityChecker, - BiFunction> mergeDuplicates) { + BiFunction> mergeDuplicates) { return getDataCollection(report, (row, data) -> { - Collection result = rowExtractor.apply(row); + @Nullable Collection result = rowExtractor.apply(row); if (result != null) { for (T r : result) { addWithEqualityChecker(r, data, equalityChecker, mergeDuplicates); @@ -159,7 +164,7 @@ public List getDataCollection(Object report, Function List getDataCollection(Object report, BiConsumer> rowHandler) { List data = new ArrayList<>(); - for (@Nullable TableRow row : this) { + for (@SuppressWarnings("NullableProblems") @Nullable TableRow row : this) { if (row != null) { try { rowHandler.accept(row, data); @@ -175,7 +180,7 @@ private List getDataCollection(Object report, BiConsumer void addWithEqualityChecker(T element, Collection collection, BiPredicate equalityChecker, - BiFunction> duplicatesMerger) { + BiFunction> duplicatesMerger) { @Nullable T equalsObject = null; for (T e : collection) { if (equalityChecker.test(e, element)) { @@ -185,7 +190,10 @@ public static void addWithEqualityChecker(T element, } if (equalsObject != null) { collection.remove(equalsObject); - collection.addAll(duplicatesMerger.apply(equalsObject, element)); + @Nullable Collection mergedCollection = duplicatesMerger.apply(equalsObject, element); + if (mergedCollection != null) { + collection.addAll(mergedCollection); + } } else { collection.add(element); } @@ -262,7 +270,9 @@ public TableRow findRowByPrefix(String prefix) { private MutableTableRow getMutableTableRow(TableCellAddress address) { if (tableRange.contains(address)) { MutableTableRow tableRow = new MutableTableRow<>(this, getCellDataAccessObject()); - tableRow.setRow(getRow(address.getRow())); + @SuppressWarnings({"nullness", "ConstantConditions"}) + R row = requireNonNull(getRow(address.getRow()), "Row is empty"); + tableRow.setRow(row); return tableRow; } return null; diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java index 74e9a07..452cbfb 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java @@ -22,7 +22,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java index 5281b0b..1dc2d17 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java @@ -18,7 +18,7 @@ package org.spacious_team.table_wrapper.api; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; @@ -104,8 +104,11 @@ default BigDecimal getBigDecimalValue(C cell) { /** * @throws RuntimeException if method can't extract string value */ + default String getStringValue(C cell) { - return requireNonNull(getValue(cell), "Not a string").toString(); + @SuppressWarnings({"nullness", "ConstantConditions"}) + Object value = requireNonNull(getValue(cell), "Not a string"); + return value.toString(); } /** @@ -132,6 +135,7 @@ default Object getValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract int value */ default int getIntValue(R row, Integer cellIndex) { + @SuppressWarnings({"nullness", "ConstantConditions"}) C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getIntValue(cell); } @@ -140,6 +144,7 @@ default int getIntValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract long value */ default long getLongValue(R row, Integer cellIndex) { + @SuppressWarnings({"nullness", "ConstantConditions"}) C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getLongValue(cell); } @@ -148,6 +153,7 @@ default long getLongValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract Double value */ default double getDoubleValue(R row, Integer cellIndex) { + @SuppressWarnings({"nullness", "ConstantConditions"}) C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getDoubleValue(cell); } @@ -156,6 +162,7 @@ default double getDoubleValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract BigDecimal value */ default BigDecimal getBigDecimalValue(R row, Integer cellIndex) { + @SuppressWarnings({"nullness", "ConstantConditions"}) C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getBigDecimalValue(cell); } @@ -164,6 +171,7 @@ default BigDecimal getBigDecimalValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract string value */ default String getStringValue(R row, Integer cellIndex) { + @SuppressWarnings({"nullness", "ConstantConditions"}) C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getStringValue(cell); } @@ -172,6 +180,7 @@ default String getStringValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract instant value */ default Instant getInstantValue(R row, Integer cellIndex) { + @SuppressWarnings({"nullness", "ConstantConditions"}) C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getInstantValue(cell); } @@ -180,6 +189,7 @@ default Instant getInstantValue(R row, Integer cellIndex) { * @throws RuntimeException if method can't extract local date time value */ default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex) { + @SuppressWarnings({"nullness", "ConstantConditions"}) C cell = requireNonNull(getCell(row, cellIndex), "Cell not found"); return getLocalDateTimeValue(cell); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java index 1df8d2c..44ed72c 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java @@ -19,7 +19,7 @@ package org.spacious_team.table_wrapper.api; import lombok.Data; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; import java.time.Instant; diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java index d378725..f446004 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java @@ -21,13 +21,15 @@ import lombok.AccessLevel; import lombok.Data; import lombok.Setter; +import org.checkerframework.checker.nullness.qual.Nullable; -import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; import java.util.Iterator; +import static java.util.Objects.requireNonNull; + /** * Mutable implementation. Used by {@link AbstractTable#iterator()} and {@link AbstractTable#stream()} to eliminate * heap pollution. On each iteration {@link #row} field is updated. Call {@link #clone()} instead of using this object @@ -48,6 +50,7 @@ public TableCell getCell(TableColumnDescription column) { return getCell(getCellIndex(column)); } + @Nullable @Override public TableCell getCell(int i) { return row.getCell(i); @@ -112,8 +115,9 @@ public LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column) { } private Integer getCellIndex(TableColumnDescription column) { - return table.getHeaderDescription() + @Nullable Integer cellIndex = table.getHeaderDescription() .get(column.getColumn()); + return requireNonNull(cellIndex, "Cell not found"); } /** diff --git a/src/main/java/org/spacious_team/table_wrapper/api/NonNullByDefault.java b/src/main/java/org/spacious_team/table_wrapper/api/NonNullByDefault.java deleted file mode 100644 index 665b2e6..0000000 --- a/src/main/java/org/spacious_team/table_wrapper/api/NonNullByDefault.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Table Wrapper API - * Copyright (C) 2022 Spacious Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.spacious_team.table_wrapper.api; - -import javax.annotation.Nonnull; -import javax.annotation.meta.TypeQualifierDefault; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * This annotation can be applied to a package, class or method to indicate that the class fields, - * method return types and parameters in that element are not null by default unless there is: - * The method overrides a method in a superclass (in which - * case the annotation of the corresponding parameter in the superclass applies) there is a - * default parameter annotation applied to a more tightly nested element. - * - * @see Explanation and linked to this article - * implementation - */ -@Documented -@Nonnull -@TypeQualifierDefault({ - ElementType.ANNOTATION_TYPE, - ElementType.CONSTRUCTOR, - ElementType.FIELD, - ElementType.LOCAL_VARIABLE, - ElementType.METHOD, - ElementType.PACKAGE, - ElementType.PARAMETER, - ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface NonNullByDefault { -} \ No newline at end of file diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java index 3e41d30..9b4f6ba 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java @@ -18,7 +18,7 @@ package org.spacious_team.table_wrapper.api; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.function.Predicate; import static java.util.Objects.requireNonNull; @@ -167,7 +167,7 @@ default Object getNextColumnValue(String firstColumnValuePrefix) { TableCellAddress address = findByPrefix(firstColumnValuePrefix); @Nullable ReportPageRow row = getRow(address.getRow()); if (row != null) { - for (@Nullable TableCell cell : row) { + for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : row) { if (cell != null && cell.getColumnIndex() > address.getColumn()) { @Nullable Object value = cell.getValue(); if (value != null && (!(value instanceof String) || !((String) value).isBlank())) { @@ -227,11 +227,13 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin if (startAddress.equals(TableCellAddress.NOT_FOUND)) { return TableCellRange.EMPTY_RANGE; } + @SuppressWarnings({"nullness", "ConstantConditions"}) ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row not found"); TableCellAddress endAddress = find(startAddress.getRow() + headersRowCount + 1, lastRowFinder); if (endAddress.equals(TableCellAddress.NOT_FOUND)) { return TableCellRange.EMPTY_RANGE; } + @SuppressWarnings({"nullness", "ConstantConditions"}) ReportPageRow lastRow = requireNonNull(getRow(endAddress.getRow()), "Row not found"); return new TableCellRange( startAddress.getRow(), @@ -264,6 +266,7 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin if (startAddress.equals(TableCellAddress.NOT_FOUND)) { return TableCellRange.EMPTY_RANGE; } + @SuppressWarnings({"nullness", "ConstantConditions"}) ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row not found"); int lastRowNum = findEmptyRow(startAddress.getRow() + headersRowCount + 1); if (lastRowNum == -1) { @@ -274,6 +277,7 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin if (lastRowNum < startAddress.getRow()) { lastRowNum = startAddress.getRow(); } + @SuppressWarnings({"nullness", "ConstantConditions"}) ReportPageRow lastRow = requireNonNull(getRow(lastRowNum), "Row not found"); return new TableCellRange( startAddress.getRow(), @@ -297,7 +301,7 @@ default int findEmptyRow(int startRow) { if (row == null || row.getLastCellNum() == -1) { return lastRowNum; // all row cells blank } - for (@Nullable TableCell cell : row) { + for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : row) { @Nullable Object value; if (!(cell == null || ((value = cell.getValue()) == null) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java index 87293f1..15d2efc 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java @@ -18,7 +18,7 @@ package org.spacious_team.table_wrapper.api; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; public interface ReportPageRow extends Iterable { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/Table.java b/src/main/java/org/spacious_team/table_wrapper/api/Table.java index 7f03224..9db7993 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/Table.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/Table.java @@ -18,7 +18,7 @@ package org.spacious_team.table_wrapper.api; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collection; import java.util.List; import java.util.Map; diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java index 0d6adb8..94b0a9d 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java @@ -18,7 +18,7 @@ package org.spacious_team.table_wrapper.api; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java b/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java index 65cd7a0..d63bf5c 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java @@ -20,8 +20,8 @@ import lombok.EqualsAndHashCode; import lombok.ToString; +import org.checkerframework.checker.nullness.qual.Nullable; -import javax.annotation.Nullable; import java.util.Arrays; @ToString @@ -30,6 +30,7 @@ public class TableColumnImpl implements TableColumn { private final String[] words; public static TableColumn of(String... words) { + //noinspection ConstantConditions if (words.length == 0 || (words.length == 1 && (words[0] == null || words[0].isEmpty()))) { return LEFTMOST_COLUMN; } @@ -45,10 +46,10 @@ private TableColumnImpl(String... words) { public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) { for (ReportPageRow header : headerRows) { next_cell: - for (@Nullable TableCell cell : header) { + for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : header) { @Nullable Object value; - if (cell != null && cell.getColumnIndex() >= firstColumnForSearch && - ((value = cell.getValue()) instanceof String)) { + if ((cell != null) && (cell.getColumnIndex() >= firstColumnForSearch) && + (((value = cell.getValue()) != null) && (value instanceof String))) { String colName = value.toString().toLowerCase(); for (String word : words) { if (!containsWord(colName, word)) { @@ -62,7 +63,7 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) throw new RuntimeException("Не обнаружен заголовок таблицы, включающий слова: " + String.join(", ", words)); } - private boolean containsWord(String text, String word) { - return text.matches("(^|(.|\\n)*\\b|(.|\\n)*\\s)" + word + "(\\b(.|\\n)*|\\s(.|\\n)*|$)"); + private boolean containsWord(String text, @Nullable String word) { + return word != null && text.matches("(^|(.|\\n)*\\b|(.|\\n)*\\s)" + word + "(\\b(.|\\n)*|\\s(.|\\n)*|$)"); } } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java index 0dcc06d..69da2ee 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java @@ -20,6 +20,8 @@ import java.util.function.Predicate; +import static java.util.Objects.requireNonNull; + public interface TableFactory { boolean canHandle(ReportPage reportPage); @@ -128,8 +130,9 @@ default Table create(ReportPage reportPage, reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder); if (!tableNameCell.equals(TableCellAddress.NOT_FOUND)) { try { - //noinspection ConstantConditions - tableName = reportPage.getCell(tableNameCell).getStringValue(); + @SuppressWarnings({"nullness", "ConstantConditions"}) + TableCell cell = requireNonNull(reportPage.getCell(tableNameCell)); + tableName = cell.getStringValue(); } catch (Exception ignore) { } } @@ -155,8 +158,9 @@ default Table create(ReportPage reportPage, reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder); if (!tableNameCell.equals(TableCellAddress.NOT_FOUND)) { try { - //noinspection ConstantConditions - tableName = reportPage.getCell(tableNameCell).getStringValue(); + @SuppressWarnings({"nullness", "ConstantConditions"}) + TableCell cell = requireNonNull(reportPage.getCell(tableNameCell)); + tableName = cell.getStringValue(); } catch (Exception ignore) { } } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java index bc5b8b3..cf839f1 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java @@ -18,7 +18,7 @@ package org.spacious_team.table_wrapper.api; -import javax.annotation.Nullable; +import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; diff --git a/src/main/java/org/spacious_team/table_wrapper/api/package-info.java b/src/main/java/org/spacious_team/table_wrapper/api/package-info.java index 313c22b..65b6edd 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/package-info.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/package-info.java @@ -16,5 +16,8 @@ * along with this program. If not, see . */ -@NonNullByDefault +@DefaultQualifier(NonNull.class) package org.spacious_team.table_wrapper.api; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; From c4f4c25d8d9be5ebb876ee43ea405575b7008625 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Thu, 20 Oct 2022 23:56:12 +0300 Subject: [PATCH 10/94] turn checker framework warns to errors --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1bdb0ed..299e5a2 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ - -Awarns + -Astubs=jdk.astub -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED From 841013c591b51e94c110e3ebbae07dd46b93a8e4 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Fri, 21 Oct 2022 01:18:09 +0300 Subject: [PATCH 11/94] add unit tests GitHub action --- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++ .github/workflows/unit-tests.yml | 33 ++++ README.md | 1 + pom.xml | 39 +++++ .../table_wrapper/api/MutableTableRow.java | 4 +- .../table_wrapper/api/EmptyTableRowTest.java | 160 ++++++++++++++++++ 7 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/unit-tests.yml create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..ef22662 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Отчет об ошибке +about: Создайте отчет об ошибке, чтобы улучшить приложение +title: '' +labels: bug +assignees: '' + +--- + +**Опишите ошибку** +Ясное и краткое описание ошибки. + +**Последовательность действий для воспроизведения** +Шаги по воспроизведению ошибки. + +**Ожидаемый результат** +Ясное и краткое описание того, что вы ожидали. + +**Скриншоты** +Если возможно, добавьте скриншоты, чтобы объяснить проблему. + +**Окружение:** + - ОС: [например windows 10] + - Версия Java [например 19] + - Версия приложения[например 2020.1] + +**Дополнительный контекст** +Любой другой контекст проблемы. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..be84300 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Запрос новой функциональности +about: Предложите идею для этого проекта +title: '' +labels: feature +assignees: '' + +--- + +**Связан ли ваш запрос новой функциональности с проблемой? Пожалуйста, опишите.** +Четкое и краткое описание проблемы. Например, я всегда расстраиваюсь, когда [...] + +**Опишите желаемое решение** +Четкое и краткое описание того, что вы хотите сделать. + +**Опишите альтернативы, которые вы рассмотрели** +Четкое и краткое описание любых рассмотренных вами альтернативных решений или функций. + +**Дополнительный контекст** +Любой другой контекст. diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..e22b110 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,33 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Unit tests + +on: + workflow_dispatch: + pull_request: + branches: + - 'master' + - 'develop' + push: + branches: + - 'master' + - 'develop' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '18' + distribution: 'liberica' + cache: maven + - name: Maven tests + run: mvn -B clean test + - name: Test coverage + uses: codecov/codecov-action@v3 diff --git a/README.md b/README.md index 9d2634b..3b18d33 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ![java-version](https://img.shields.io/badge/Java-11-brightgreen?style=flat-square) ![jitpack-last-release](https://jitpack.io/v/spacious-team/table-wrapper-api.svg?style=flat-square) +[![Unit tests](https://github.com/spacious-team/table-wrapper-api/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/spacious-team/table-wrapper-api/actions/workflows/unit-tests.yml) #### Оглавление - [Назначение](#назначение) diff --git a/pom.xml b/pom.xml index 299e5a2..ed5aa4e 100644 --- a/pom.xml +++ b/pom.xml @@ -74,9 +74,29 @@ ${checkerframework.version} provided + + org.junit.jupiter + junit-jupiter + 5.9.0 + test + + + org.mockito + mockito-junit-jupiter + 4.8.1 + test + + + + + maven-surefire-plugin + 2.22.2 + + + org.apache.maven.plugins @@ -118,6 +138,25 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + prepare-agent + + + + report + test + + report + + + + org.apache.maven.plugins maven-source-plugin diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java index f446004..da41100 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java @@ -117,7 +117,9 @@ public LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column) { private Integer getCellIndex(TableColumnDescription column) { @Nullable Integer cellIndex = table.getHeaderDescription() .get(column.getColumn()); - return requireNonNull(cellIndex, "Cell not found"); + @SuppressWarnings("nullness") + Integer cell = requireNonNull(cellIndex, "Cell not found"); + return cell; } /** diff --git a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java new file mode 100644 index 0000000..b927c79 --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java @@ -0,0 +1,160 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import lombok.Getter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; + + +@Getter +@SuppressWarnings("nullness") // TODO exclude test sources entirely +@ExtendWith(MockitoExtension.class) +class EmptyTableRowTest { + + @Mock + Table table; + @Mock + TableColumnDescription column; + EmptyTableRow row; + + @BeforeEach + void beforeEach() { + row = new EmptyTableRow(table, 0); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 1000_000}) + void getCell(int col) { + assertNull(getRow().getCell(col)); + } + + @Test + void testGetCell() { + assertNull(getRow().getCell(getColumn())); + } + + @Test + void getFirstCellNum() { + assertEquals(-1, getRow().getFirstCellNum()); + } + + @Test + void getLastCellNum() { + assertEquals(-1, getRow().getLastCellNum()); + } + + @ParameterizedTest + @ValueSource(strings = {"1", "abc"}) + void rowContains(String value) { + assertFalse(getRow().rowContains(value)); + } + + @Test + void iterator() { + assertFalse(getRow().iterator().hasNext()); + } + + @Test + void getCellValue() { + assertNull(getRow().getCellValue(getColumn())); + } + + @Test + void getIntCellValue() { + assertThrows(NullPointerException.class, + () -> getRow().getIntCellValue(getColumn())); + } + + @Test + void getLongCellValue() { + assertThrows(NullPointerException.class, + () -> getRow().getLongCellValue(getColumn())); + } + + @Test + void getDoubleCellValue() { + assertThrows(NullPointerException.class, + () -> getRow().getDoubleCellValue(getColumn())); + } + + @Test + void getBigDecimalCellValue() { + assertThrows(NullPointerException.class, + () -> getRow().getBigDecimalCellValue(getColumn())); + } + + @Test + void getStringCellValue() { + assertThrows(NullPointerException.class, + () -> getRow().getStringCellValue(getColumn())); + } + + @Test + void getInstantCellValue() { + assertThrows(NullPointerException.class, + () -> getRow().getInstantCellValue(getColumn())); + } + + @Test + void getLocalDateTimeCellValue() { + assertThrows(NullPointerException.class, + () -> getRow().getLocalDateTimeCellValue(getColumn())); + } + + @Test + void testClone() { + assertEquals(getRow(), getRow().clone()); + } + + @Test + void testGetTable() { + assertEquals(getTable(), getRow().getTable()); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 1000_000}) + void getRowNum(int rowNum) { + assertEquals(rowNum, new EmptyTableRow(table, rowNum).getRowNum()); + } + + @Test + void testEquals() { + assertEquals(new EmptyTableRow(getTable(), 0), getRow()); + assertNotEquals(new EmptyTableRow(getTable(), 1), getRow()); + } + + @Test + void testHashCode() { + assertEquals(new EmptyTableRow(getTable(), 0).hashCode(), getRow().hashCode()); + assertNotEquals(new EmptyTableRow(getTable(), 1).hashCode(), getRow().hashCode()); + } + + @Test + void testToString() { + assertNotNull(getRow().toString()); + } +} \ No newline at end of file From 8764063c97f61284540ee69e6bcd5456ec2ca9d7 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 22 Oct 2022 18:57:40 +0300 Subject: [PATCH 12/94] add sonarcloud scan --- .github/workflows/unit-tests.yml | 28 ++++++++++++++++++++-------- pom.xml | 1 + 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e22b110..9c61d32 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -1,7 +1,4 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Unit tests +name: Unit Tests on: workflow_dispatch: @@ -15,19 +12,34 @@ on: - 'develop' jobs: - build: + tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up JDK uses: actions/setup-java@v3 with: java-version: '18' distribution: 'liberica' cache: maven - - name: Maven tests - run: mvn -B clean test - - name: Test coverage + + - name: Maven Tests + run: mvn --batch-mode clean test + + - name: Test Coverage uses: codecov/codecov-action@v3 + + - name: SonarCloud Analyze + run: > + mvn --batch-mode sonar:sonar + -Dsonar.projectKey=spacious-team_table-wrapper-api + -Dsonar.organization=spacious-team + -Dsonar.host.url=https://sonarcloud.io + -Dsonar.login=$SONAR_TOKEN + -Dsonar.coverage.jacoco.xmlReportPaths=./target/site/jacoco/jacoco.xml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/pom.xml b/pom.xml index ed5aa4e..d297da8 100644 --- a/pom.xml +++ b/pom.xml @@ -144,6 +144,7 @@ 0.8.8 + prepare-agent prepare-agent From 4ef80ee4223ce3dfd94e64c042a27115f9ca7acf Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 22 Oct 2022 20:20:21 +0300 Subject: [PATCH 13/94] made shield flat square --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3b18d33..8003c55 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ![java-version](https://img.shields.io/badge/Java-11-brightgreen?style=flat-square) -![jitpack-last-release](https://jitpack.io/v/spacious-team/table-wrapper-api.svg?style=flat-square) -[![Unit tests](https://github.com/spacious-team/table-wrapper-api/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/spacious-team/table-wrapper-api/actions/workflows/unit-tests.yml) +[![jitpack-last-release](https://jitpack.io/v/spacious-team/table-wrapper-api.svg?style=flat-square)]( +https://jitpack.io/#spacious-team/table-wrapper-api) +[![Unit tests](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fspacious-team%2Ftable-wrapper-api%2Fbadge%3Fref%3Ddevelop&style=flat-square&label=Test&logo=none)]( +https://github.com/spacious-team/table-wrapper-api/actions/workflows/unit-tests.yml) #### Оглавление - [Назначение](#назначение) From 5382cf55640fcb8479462c4df086e190bd1c842b Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 22 Oct 2022 20:27:25 +0300 Subject: [PATCH 14/94] fix checker framework warnings --- .../table_wrapper/api/AbstractReportPage.java | 3 +-- .../table_wrapper/api/AbstractTable.java | 12 ++++-------- .../table_wrapper/api/AbstractTableCell.java | 3 +-- .../table_wrapper/api/CellDataAccessObject.java | 3 +-- .../table_wrapper/api/EmptyTableRow.java | 9 +++------ .../table_wrapper/api/MutableTableRow.java | 9 +++------ .../spacious_team/table_wrapper/api/ReportPage.java | 6 ++---- .../spacious_team/table_wrapper/api/TableCell.java | 3 +-- .../spacious_team/table_wrapper/api/TableRow.java | 3 +-- 9 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java index c948e84..e881178 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPage.java @@ -25,8 +25,7 @@ */ public abstract class AbstractReportPage implements ReportPage { - @Nullable @Override - public abstract T getRow(int i); + public abstract @Nullable T getRow(int i); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java index df0f0f2..51447b1 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java @@ -246,28 +246,24 @@ public TableRow next() { } } - @Nullable @Override - public R getRow(int i) { + public @Nullable R getRow(int i) { return reportPage.getRow(i); } - @Nullable @Override - public TableRow findRow(Object value) { + public @Nullable TableRow findRow(Object value) { TableCellAddress address = reportPage.find(value); return getMutableTableRow(address); } - @Nullable @Override - public TableRow findRowByPrefix(String prefix) { + public @Nullable TableRow findRowByPrefix(String prefix) { TableCellAddress address = reportPage.findByPrefix(prefix); return getMutableTableRow(address); } - @Nullable - private MutableTableRow getMutableTableRow(TableCellAddress address) { + private @Nullable MutableTableRow getMutableTableRow(TableCellAddress address) { if (tableRange.contains(address)) { MutableTableRow tableRow = new MutableTableRow<>(this, getCellDataAccessObject()); @SuppressWarnings({"nullness", "ConstantConditions"}) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java index 452cbfb..b0f129c 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java @@ -34,9 +34,8 @@ public abstract class AbstractTableCell implements TableCell { private final T cell; private final CellDataAccessObject dao; - @Nullable @Override - public Object getValue() { + public @Nullable Object getValue() { return dao.getValue(cell); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java index 1dc2d17..0a29ca9 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java @@ -125,8 +125,7 @@ default LocalDateTime getLocalDateTimeValue(C cell) { .toLocalDateTime(); } - @Nullable - default Object getValue(R row, Integer cellIndex) { + default @Nullable Object getValue(R row, Integer cellIndex) { @Nullable C cell = getCell(row, cellIndex); return (cell == null) ? null : getValue(cell); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java index 44ed72c..9d707d0 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java @@ -33,15 +33,13 @@ class EmptyTableRow implements TableRow { private final Table table; private final int rowNum; - @Nullable @Override - public TableCell getCell(TableColumnDescription column) { + public @Nullable TableCell getCell(TableColumnDescription column) { return null; } - @Nullable @Override - public TableCell getCell(int i) { + public @Nullable TableCell getCell(int i) { return null; } @@ -65,9 +63,8 @@ public Iterator iterator() { return emptyIterator(); } - @Nullable @Override - public Object getCellValue(TableColumnDescription column) { + public @Nullable Object getCellValue(TableColumnDescription column) { return null; } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java index da41100..362c6fb 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java @@ -45,14 +45,12 @@ class MutableTableRow implements TableRow { @Setter(AccessLevel.PACKAGE) private volatile T row; - @Nullable - public TableCell getCell(TableColumnDescription column) { + public @Nullable TableCell getCell(TableColumnDescription column) { return getCell(getCellIndex(column)); } - @Nullable @Override - public TableCell getCell(int i) { + public @Nullable TableCell getCell(int i) { return row.getCell(i); } @@ -81,8 +79,7 @@ public Iterator iterator() { return row.iterator(); } - @Nullable - public Object getCellValue(TableColumnDescription column) { + public @Nullable Object getCellValue(TableColumnDescription column) { return dao.getValue(row, getCellIndex(column)); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java index 9b4f6ba..0d6b69d 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java @@ -162,8 +162,7 @@ default TableCellAddress findByPrefix(@Nullable String prefix, int startRow, int /** * For vertical table of key-value records (table with two columns), search and return value for requested key. */ - @Nullable - default Object getNextColumnValue(String firstColumnValuePrefix) { + default @Nullable Object getNextColumnValue(String firstColumnValuePrefix) { TableCellAddress address = findByPrefix(firstColumnValuePrefix); @Nullable ReportPageRow row = getRow(address.getRow()); if (row != null) { @@ -192,8 +191,7 @@ default Object getNextColumnValue(String firstColumnValuePrefix) { */ int getLastRowNum(); - @Nullable - default TableCell getCell(TableCellAddress address) { + default @Nullable TableCell getCell(TableCellAddress address) { @Nullable ReportPageRow row = getRow(address.getRow()); return (row == null) ? null : row.getCell(address.getColumn()); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java index 94b0a9d..7245598 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java @@ -72,8 +72,7 @@ public interface TableCell { /** * @return return cell value or defaultValue if the cell is missing or the type does not match the expected */ - @Nullable - default Object getValueOrDefault(@Nullable Object defaultValue) { + default @Nullable Object getValueOrDefault(@Nullable Object defaultValue) { try { return getValue(); } catch (Exception e) { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java index cf839f1..5e080b5 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java @@ -78,8 +78,7 @@ public interface TableRow extends ReportPageRow, Cloneable { /** * @return return cell value or defaultValue if the cell is missing or the type does not match the expected */ - @Nullable - default Object getCellValueOrDefault(TableColumnDescription column, @Nullable Object defaultValue) { + default @Nullable Object getCellValueOrDefault(TableColumnDescription column, @Nullable Object defaultValue) { try { return getCellValue(column); } catch (Exception e) { From 359992bae99da644470359aed55d489e068ef719 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 22 Oct 2022 20:36:59 +0300 Subject: [PATCH 15/94] remove code duplicate --- .../table_wrapper/api/TableFactory.java | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java index 69da2ee..4ca4292 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java @@ -122,21 +122,8 @@ default Table create(ReportPage reportPage, Predicate lastRowFinder, Class headerDescription, int headersRowCount) { - String tableName = ""; TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount, lastRowFinder); - //noinspection DuplicatedCode - if (!range.equals(TableCellRange.EMPTY_RANGE)) { - TableCellAddress tableNameCell = - reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder); - if (!tableNameCell.equals(TableCellAddress.NOT_FOUND)) { - try { - @SuppressWarnings({"nullness", "ConstantConditions"}) - TableCell cell = requireNonNull(reportPage.getCell(tableNameCell)); - tableName = cell.getStringValue(); - } catch (Exception ignore) { - } - } - } + String tableName = getTableName(reportPage, tableNameFinder, range); return create(reportPage, tableName, range, headerDescription, headersRowCount); } @@ -150,9 +137,12 @@ default Table create(ReportPage reportPage, Predicate tableNameFinder, Class headerDescription, int headersRowCount) { - String tableName = ""; TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount); - //noinspection DuplicatedCode + String tableName = getTableName(reportPage, tableNameFinder, range); + return create(reportPage, tableName, range, headerDescription, headersRowCount); + } + + private static String getTableName(ReportPage reportPage, Predicate tableNameFinder, TableCellRange range) { if (!range.equals(TableCellRange.EMPTY_RANGE)) { TableCellAddress tableNameCell = reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder); @@ -160,12 +150,13 @@ default Table create(ReportPage reportPage, try { @SuppressWarnings({"nullness", "ConstantConditions"}) TableCell cell = requireNonNull(reportPage.getCell(tableNameCell)); - tableName = cell.getStringValue(); + return cell.getStringValue(); } catch (Exception ignore) { + return ""; } } } - return create(reportPage, tableName, range, headerDescription, headersRowCount); + return ""; } /** From e5a660a6046100f074df23cdac0136dbb6f7fb1d Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 22 Oct 2022 20:49:19 +0300 Subject: [PATCH 16/94] fix assertThrows(a, () m1().m2()) code smell --- .../table_wrapper/api/EmptyTableRowTest.java | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java index b927c79..9101192 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java @@ -18,7 +18,6 @@ package org.spacious_team.table_wrapper.api; -import lombok.Getter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -29,8 +28,6 @@ import static org.junit.jupiter.api.Assertions.*; - -@Getter @SuppressWarnings("nullness") // TODO exclude test sources entirely @ExtendWith(MockitoExtension.class) class EmptyTableRowTest { @@ -49,90 +46,90 @@ void beforeEach() { @ParameterizedTest @ValueSource(ints = {0, 1, 1000_000}) void getCell(int col) { - assertNull(getRow().getCell(col)); + assertNull(row.getCell(col)); } @Test void testGetCell() { - assertNull(getRow().getCell(getColumn())); + assertNull(row.getCell(column)); } @Test void getFirstCellNum() { - assertEquals(-1, getRow().getFirstCellNum()); + assertEquals(-1, row.getFirstCellNum()); } @Test void getLastCellNum() { - assertEquals(-1, getRow().getLastCellNum()); + assertEquals(-1, row.getLastCellNum()); } @ParameterizedTest @ValueSource(strings = {"1", "abc"}) void rowContains(String value) { - assertFalse(getRow().rowContains(value)); + assertFalse(row.rowContains(value)); } @Test void iterator() { - assertFalse(getRow().iterator().hasNext()); + assertFalse(row.iterator().hasNext()); } @Test void getCellValue() { - assertNull(getRow().getCellValue(getColumn())); + assertNull(row.getCellValue(column)); } @Test void getIntCellValue() { assertThrows(NullPointerException.class, - () -> getRow().getIntCellValue(getColumn())); + () -> row.getIntCellValue(column)); } @Test void getLongCellValue() { assertThrows(NullPointerException.class, - () -> getRow().getLongCellValue(getColumn())); + () -> row.getLongCellValue(column)); } @Test void getDoubleCellValue() { assertThrows(NullPointerException.class, - () -> getRow().getDoubleCellValue(getColumn())); + () -> row.getDoubleCellValue(column)); } @Test void getBigDecimalCellValue() { assertThrows(NullPointerException.class, - () -> getRow().getBigDecimalCellValue(getColumn())); + () -> row.getBigDecimalCellValue(column)); } @Test void getStringCellValue() { assertThrows(NullPointerException.class, - () -> getRow().getStringCellValue(getColumn())); + () -> row.getStringCellValue(column)); } @Test void getInstantCellValue() { assertThrows(NullPointerException.class, - () -> getRow().getInstantCellValue(getColumn())); + () -> row.getInstantCellValue(column)); } @Test void getLocalDateTimeCellValue() { assertThrows(NullPointerException.class, - () -> getRow().getLocalDateTimeCellValue(getColumn())); + () -> row.getLocalDateTimeCellValue(column)); } @Test void testClone() { - assertEquals(getRow(), getRow().clone()); + assertEquals(row, row.clone()); } @Test void testGetTable() { - assertEquals(getTable(), getRow().getTable()); + assertEquals(table, row.getTable()); } @ParameterizedTest @@ -143,18 +140,18 @@ void getRowNum(int rowNum) { @Test void testEquals() { - assertEquals(new EmptyTableRow(getTable(), 0), getRow()); - assertNotEquals(new EmptyTableRow(getTable(), 1), getRow()); + assertEquals(new EmptyTableRow(table, 0), row); + assertNotEquals(new EmptyTableRow(table, 1), row); } @Test void testHashCode() { - assertEquals(new EmptyTableRow(getTable(), 0).hashCode(), getRow().hashCode()); - assertNotEquals(new EmptyTableRow(getTable(), 1).hashCode(), getRow().hashCode()); + assertEquals(new EmptyTableRow(table, 0).hashCode(), row.hashCode()); + assertNotEquals(new EmptyTableRow(table, 1).hashCode(), row.hashCode()); } @Test void testToString() { - assertNotNull(getRow().toString()); + assertNotNull(row.toString()); } } \ No newline at end of file From 181e7abcdd994afd0117bc9320223a20aa5978fc Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 22 Oct 2022 21:01:37 +0300 Subject: [PATCH 17/94] disable checker framework for test classes --- pom.xml | 1 + .../org/spacious_team/table_wrapper/api/EmptyTableRowTest.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d297da8..11feb3f 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,7 @@ -Astubs=jdk.astub + -AskipDefs=.*Test -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED -J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED diff --git a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java index 9101192..97e2447 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java @@ -28,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.*; -@SuppressWarnings("nullness") // TODO exclude test sources entirely @ExtendWith(MockitoExtension.class) class EmptyTableRowTest { From fc1a4f4290f2c10c58cd52dad73f97e3304cd810 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 22 Oct 2022 21:15:47 +0300 Subject: [PATCH 18/94] fix some code smells --- .../table_wrapper/api/EmptyTableRow.java | 23 +++++++++---------- .../table_wrapper/api/MutableTableRow.java | 18 +++++++++++---- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java index 9d707d0..e27f7f2 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java @@ -19,6 +19,7 @@ package org.spacious_team.table_wrapper.api; import lombok.Data; +import lombok.SneakyThrows; import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; @@ -30,6 +31,7 @@ @Data class EmptyTableRow implements TableRow { + private static final String CELL_NOT_FOUND = "Cell not found"; private final Table table; private final int rowNum; @@ -70,45 +72,42 @@ public Iterator iterator() { @Override public int getIntCellValue(TableColumnDescription column) { - throw new NullPointerException("Cell not found"); + throw new NullPointerException(CELL_NOT_FOUND); } @Override public long getLongCellValue(TableColumnDescription column) { - throw new NullPointerException("Cell not found"); + throw new NullPointerException(CELL_NOT_FOUND); } @Override public double getDoubleCellValue(TableColumnDescription column) { - throw new NullPointerException("Cell not found"); + throw new NullPointerException(CELL_NOT_FOUND); } @Override public BigDecimal getBigDecimalCellValue(TableColumnDescription column) { - throw new NullPointerException("Cell not found"); + throw new NullPointerException(CELL_NOT_FOUND); } @Override public String getStringCellValue(TableColumnDescription column) { - throw new NullPointerException("Cell not found"); + throw new NullPointerException(CELL_NOT_FOUND); } @Override public Instant getInstantCellValue(TableColumnDescription column) { - throw new NullPointerException("Cell not found"); + throw new NullPointerException(CELL_NOT_FOUND); } @Override public LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column) { - throw new NullPointerException("Cell not found"); + throw new NullPointerException(CELL_NOT_FOUND); } @Override + @SneakyThrows public TableRow clone() { - try { - return (TableRow) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException("Can't clone " + this.getClass().getName()); - } + return (TableRow) super.clone(); } } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java index 362c6fb..7cebb20 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java @@ -21,6 +21,7 @@ import lombok.AccessLevel; import lombok.Data; import lombok.Setter; +import lombok.SneakyThrows; import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; @@ -45,6 +46,7 @@ class MutableTableRow implements TableRow { @Setter(AccessLevel.PACKAGE) private volatile T row; + @Override public @Nullable TableCell getCell(TableColumnDescription column) { return getCell(getCellIndex(column)); } @@ -79,34 +81,42 @@ public Iterator iterator() { return row.iterator(); } + @Override public @Nullable Object getCellValue(TableColumnDescription column) { return dao.getValue(row, getCellIndex(column)); } + @Override public int getIntCellValue(TableColumnDescription column) { return dao.getIntValue(row, getCellIndex(column)); } + @Override public long getLongCellValue(TableColumnDescription column) { return dao.getLongValue(row, getCellIndex(column)); } + @Override public double getDoubleCellValue(TableColumnDescription column) { return dao.getDoubleValue(row, getCellIndex(column)); } + @Override public BigDecimal getBigDecimalCellValue(TableColumnDescription column) { return dao.getBigDecimalValue(row, getCellIndex(column)); } + @Override public String getStringCellValue(TableColumnDescription column) { return dao.getStringValue(row, getCellIndex(column)); } + @Override public Instant getInstantCellValue(TableColumnDescription column) { return dao.getInstantValue(row, getCellIndex(column)); } + @Override public LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column) { return dao.getLocalDateTimeValue(row, getCellIndex(column)); } @@ -125,12 +135,10 @@ private Integer getCellIndex(TableColumnDescription column) { * Cloned object is safe use everywhere, this object should be used oly inside of one iteration * of {@link Table#iterator()} or {@link Table#stream()} */ + @Override + @SneakyThrows @SuppressWarnings("unchecked") public MutableTableRow clone() { - try { - return (MutableTableRow) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException("Can't clone " + this.getClass().getName()); - } + return (MutableTableRow) super.clone(); } } From 791d437bdfd4fd05ece408b09be70e63f2a6317e Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sun, 23 Oct 2022 01:15:55 +0300 Subject: [PATCH 19/94] fix pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11feb3f..dd2a524 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ org.projectlombok lombok - 1.18.24 + ${lombok.version} provided true From da965948106e822df140f135f7f88c1b4a1e4bd4 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sun, 23 Oct 2022 02:04:39 +0300 Subject: [PATCH 20/94] add coverage shield --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8003c55..09c4809 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ https://jitpack.io/#spacious-team/table-wrapper-api) [![Unit tests](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fspacious-team%2Ftable-wrapper-api%2Fbadge%3Fref%3Ddevelop&style=flat-square&label=Test&logo=none)]( https://github.com/spacious-team/table-wrapper-api/actions/workflows/unit-tests.yml) +[![Coverage](https://img.shields.io/codecov/c/github/spacious-team/table-wrapper-api/develop?style=flat-square&token=SIOIDt0TcY)]( +https://codecov.io/gh/spacious-team/table-wrapper-api) #### Оглавление - [Назначение](#назначение) From 569a9317e49b88fda99b0eeac0b05c4579b509ee Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sun, 23 Oct 2022 02:25:36 +0300 Subject: [PATCH 21/94] fix shield --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09c4809..3392d92 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ https://jitpack.io/#spacious-team/table-wrapper-api) [![Unit tests](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fspacious-team%2Ftable-wrapper-api%2Fbadge%3Fref%3Ddevelop&style=flat-square&label=Test&logo=none)]( https://github.com/spacious-team/table-wrapper-api/actions/workflows/unit-tests.yml) -[![Coverage](https://img.shields.io/codecov/c/github/spacious-team/table-wrapper-api/develop?style=flat-square&token=SIOIDt0TcY)]( +[![Coverage](https://img.shields.io/codecov/c/github/spacious-team/table-wrapper-api/develop?label=Coverage&style=flat-square&token=SIOIDt0TcY)]( https://codecov.io/gh/spacious-team/table-wrapper-api) #### Оглавление From afc92f808064e6c72b219b15c0a904c4b46254bc Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sun, 23 Oct 2022 03:14:17 +0300 Subject: [PATCH 22/94] fix iterator --- .../org/spacious_team/table_wrapper/api/AbstractTable.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java index 51447b1..69758d5 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java @@ -31,6 +31,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.BiPredicate; @@ -232,6 +233,9 @@ public boolean hasNext() { @Override public TableRow next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } int rowNum; @Nullable R row; do { From 00c6e4991f748522aafcafd764665326195f3e2f Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sun, 23 Oct 2022 03:19:40 +0300 Subject: [PATCH 23/94] refactor ReportPageHelper --- .../org/spacious_team/table_wrapper/api/ReportPageHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java index 14385d2..e1301bb 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java @@ -24,7 +24,7 @@ class ReportPageHelper { static Predicate getCellStringValueIgnoreCasePrefixPredicate(String prefix) { String lowercasePrefix = prefix.trim().toLowerCase(); - return (cell) -> (cell instanceof String) && + return cell -> (cell instanceof String) && ((String) cell).trim().toLowerCase().startsWith(lowercasePrefix); } } From c75bc4927090b2018f55aaa708505fe845ffe22d Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sun, 23 Oct 2022 04:15:20 +0300 Subject: [PATCH 24/94] add AbstractReportPageRowTest --- .../api/AbstractReportPageRowTest.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java new file mode 100644 index 0000000..d96c1ca --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java @@ -0,0 +1,71 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.spacious_team.table_wrapper.api.AbstractReportPageRow.ReportPageRowIterator; + +import java.util.Iterator; +import java.util.function.Function; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AbstractReportPageRowTest { + + @Mock + Iterator innerIterator; + @Mock + Function converter; + @InjectMocks + ReportPageRowIterator iterator; + @Mock + AbstractReportPageRow row; + + + @BeforeEach + void beforeEach() { + when(row.iterator()).thenReturn(iterator); + when(innerIterator.next()).thenReturn(IteratorElement.INSTANCE); + } + + static class IteratorElement { + @SuppressWarnings("InstantiationOfUtilityClass") + static final IteratorElement INSTANCE = new IteratorElement(); + } + + @Test + @SuppressWarnings("ResultOfMethodCallIgnored") + void testIterator() { + Iterator it = row.iterator(); + + it.hasNext(); + verify(innerIterator).hasNext(); + + it.next(); + verify(innerIterator).next(); + verify(converter).apply(IteratorElement.INSTANCE); + } +} \ No newline at end of file From 39c71a26baf925f62e9cc4a85fd2e12122b0fe9e Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sun, 23 Oct 2022 16:13:36 +0300 Subject: [PATCH 25/94] add AbstractTableTest --- .../table_wrapper/api/AbstractTableTest.java | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java new file mode 100644 index 0000000..b756e56 --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java @@ -0,0 +1,207 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AbstractTableTest { + + @Mock + AbstractReportPage report; + @Mock + TableCellRange tableRange; + @Mock + TableColumnDescription headerDescription; + AbstractTable table; + + @BeforeEach + void beforeEach() { + table = spy(new TableImpl(report, "table name", tableRange, headerDescription.getClass(), 1)); + } + + @Test + void getData() { + TableRow sourceRow = mock(TableRow.class); + EmptyTableRow resultRow = mock(EmptyTableRow.class); + Iterator iterator = Arrays.asList(sourceRow, null).iterator(); + when(table.iterator()).thenReturn(iterator); + @SuppressWarnings("unchecked") + Function extractor = mock(Function.class); + when(extractor.apply(sourceRow)).thenReturn(resultRow); + + Collection result = table.getData(report, extractor); + + assertEquals(List.of(resultRow), result); + verify(extractor).apply(sourceRow); + } + + @Test + void getDataWithException() { + TableRow sourceRow = mock(TableRow.class); + Iterator iterator = Arrays.asList(sourceRow, null).iterator(); + when(table.iterator()).thenReturn(iterator); + Function extractor = srcRow -> { + throw new RuntimeException(); + }; + + Collection result = table.getData(report, extractor); + + assertEquals(emptyList(), result); + } + + @Test + void getDataCollection() { + TableRow sourceRow = mock(TableRow.class); + EmptyTableRow resultRow = mock(EmptyTableRow.class); + Iterator iterator = Arrays.asList(sourceRow, null).iterator(); + when(table.iterator()).thenReturn(iterator); + @SuppressWarnings("unchecked") + Function> rowExtractor = mock(Function.class); + when(rowExtractor.apply(sourceRow)).thenReturn(List.of(resultRow)); + + Collection result = table.getDataCollection(report, rowExtractor); + + assertEquals(List.of(resultRow), result); + verify(rowExtractor).apply(sourceRow); + } + + @Test + @SuppressWarnings("unchecked") + void testGetDataCollection() { + TableRow sourceRow = mock(TableRow.class); + EmptyTableRow internalRow = mock(EmptyTableRow.class); + EmptyTableRow resultRow = mock(EmptyTableRow.class); + Iterator iterator = Arrays.asList(sourceRow, null).iterator(); + when(table.iterator()).thenReturn(iterator); + Function> rowExtractor = mock(Function.class); + when(rowExtractor.apply(sourceRow)).thenReturn(List.of(internalRow, internalRow)); // more than one object + BiPredicate equalityChecker = mock(BiPredicate.class); + when(equalityChecker.test(internalRow, internalRow)).thenReturn(true); + BiFunction> mergeDuplicates = mock(BiFunction.class); + when(mergeDuplicates.apply(internalRow, internalRow)).thenReturn(singleton(resultRow)); + + Collection result = table.getDataCollection(report, rowExtractor, equalityChecker, mergeDuplicates); + + assertEquals(List.of(resultRow), result); + verify(rowExtractor).apply(sourceRow); + verify(equalityChecker).test(internalRow, internalRow); + verify(mergeDuplicates).apply(internalRow, internalRow); + } + + @Test + void stream() { + TableRow row = mock(TableRow.class); + Iterator iterator = Arrays.asList(row, null).iterator(); + when(table.iterator()).thenReturn(iterator); + + Stream stream = table.stream(); + + assertEquals(Arrays.asList(row, null), stream.collect(Collectors.toList())); + } + + @Test + void iterator() { + Iterator iterator = table.iterator(); + assertEquals(AbstractTable.TableIterator.class, iterator.getClass()); + } + + @Test + void getRow() { + } + + @Test + void findRow() { + } + + @Test + void findRowByPrefix() { + } + + @Test + void getCellDataAccessObject() { + } + + @Test + void testToString() { + } + + @Test + void getReportPage() { + } + + @Test + void getTableRange() { + } + + @Test + void getHeaderDescription() { + } + + @Test + void isEmpty() { + } + + static class TableImpl extends AbstractTable { + + @Mock + CellDataAccessObject dao; + + protected TableImpl(AbstractReportPage reportPage, + String tableName, + TableCellRange tableRange, + Class headerDescription, + int headersRowCount) { + super(reportPage, tableName, tableRange, headerDescription, headersRowCount); + } + + public TableImpl(AbstractTable table, int appendDataRowsToTop, int appendDataRowsToBottom) { + super(table, appendDataRowsToTop, appendDataRowsToBottom); + } + + @Override + protected CellDataAccessObject getCellDataAccessObject() { + return dao; + } + + @Override + public Table subTable(int topRows, int bottomRows) { + throw new UnsupportedOperationException(); + } + } +} \ No newline at end of file From 9af12704e9ee30ad387103570cdf29ae31cf9902 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Tue, 25 Oct 2022 11:15:43 +0300 Subject: [PATCH 26/94] add some tests to AbstractTableTest --- .../table_wrapper/api/AbstractTableTest.java | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java index b756e56..c0e4dcc 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java @@ -36,7 +36,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singleton; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @@ -48,6 +48,8 @@ class AbstractTableTest { TableCellRange tableRange; @Mock TableColumnDescription headerDescription; + @Mock + CellDataAccessObject dao; AbstractTable table; @BeforeEach @@ -143,14 +145,47 @@ void iterator() { @Test void getRow() { + table.getRow(1); + verify(report).getRow(1); + } + + @Test + void findRowNotFound() { + when(tableRange.contains(any())).thenReturn(false); + assertNull(table.findRow("row value")); } @Test + void findRowFoundEmptyRow() { + when(tableRange.contains(any())).thenReturn(true); + assertThrows(NullPointerException.class, () -> table.findRow("row value")); + } + + @Test + @SuppressWarnings("ConstantConditions") void findRow() { + TableCellAddress address = new TableCellAddress(1, 0); + EmptyTableRow row = new EmptyTableRow(table, address.getRow()); + MutableTableRow mutableRow = new MutableTableRow<>(table, dao); + mutableRow.setRow(row); + when(report.find("row value")).thenReturn(address); + when(tableRange.contains(address)).thenReturn(true); + when(table.getRow(address.getRow())).thenReturn(row); + + assertEquals(mutableRow, table.findRow("row value")); + verify(report).find("row value"); + verify(tableRange).contains(address); + verify(table).getRow(address.getRow()); } @Test void findRowByPrefix() { + TableCellAddress address = new TableCellAddress(1, 0); + when(report.findByPrefix("row value")).thenReturn(address); + + table.findRowByPrefix("row value"); + + verify(report).findByPrefix("row value"); } @Test @@ -177,10 +212,7 @@ void getHeaderDescription() { void isEmpty() { } - static class TableImpl extends AbstractTable { - - @Mock - CellDataAccessObject dao; + class TableImpl extends AbstractTable { protected TableImpl(AbstractReportPage reportPage, String tableName, From e6623a29a91a34f3066d031e68f02f0161276e7a Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Wed, 26 Oct 2022 23:24:55 +0300 Subject: [PATCH 27/94] impl all AbstractTableTest tests --- .../table_wrapper/api/TableCellRange.java | 2 +- .../table_wrapper/api/AbstractTableTest.java | 156 ++++++++++++++---- 2 files changed, 126 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java index 3afdb2a..f1759a4 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java @@ -27,8 +27,8 @@ * Zero-based table cell range */ @Getter -@EqualsAndHashCode @ToString +@EqualsAndHashCode @RequiredArgsConstructor public class TableCellRange { public static final TableCellRange EMPTY_RANGE = new EmptyTableCellRange(); diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java index c0e4dcc..171225a 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java @@ -18,6 +18,8 @@ package org.spacious_team.table_wrapper.api; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -28,6 +30,8 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Function; @@ -38,6 +42,7 @@ import static java.util.Collections.singleton; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; @ExtendWith(MockitoExtension.class) class AbstractTableTest { @@ -46,15 +51,87 @@ class AbstractTableTest { AbstractReportPage report; @Mock TableCellRange tableRange; - @Mock - TableColumnDescription headerDescription; + Class headerDescription = Columns.class; @Mock CellDataAccessObject dao; AbstractTable table; @BeforeEach void beforeEach() { - table = spy(new TableImpl(report, "table name", tableRange, headerDescription.getClass(), 1)); + table = spy(new TableImpl(report, "table name", tableRange, headerDescription, 1)); + } + + @Test + void testEmptyRangeConstructor() { + AbstractTable table = getEmptyTable(); + + assertEquals(report, table.getReportPage()); + assertEquals(new TableCellRange(2, 3, 0, 100), table.getTableRange()); + assertTrue(table.getHeaderDescription().isEmpty()); + assertTrue(table.isEmpty()); + } + + /** + * Builds not empty table of 2 columns, row #2 contains table name, row #3 - header, no data rows + */ + private AbstractTable getEmptyTable() { + TableCellRange tableRange = new TableCellRange(2, 3, 0, 100); + return new TableImpl(report, "table name", tableRange, headerDescription, 1); + } + + @Test + void testNotEmptyRangeConstructor() { + AbstractTable table = getNotEmptyTable(); + TableCellRange range = new TableCellRange(2, 6, 0, 1); + ReportPageRow[] headerRows = new EmptyTableRow[0]; // mock + Map headerDescriptionMap = Map.of( + Columns.FIRST.getColumn(), + Columns.FIRST.getColumn().getColumnIndex(headerRows), + Columns.SECOND.getColumn(), + Columns.SECOND.getColumn().getColumnIndex(headerRows)); + + assertEquals(report, table.getReportPage()); + assertEquals(range, table.getTableRange()); + assertEquals(headerDescriptionMap, table.getHeaderDescription()); + assertFalse(table.isEmpty()); + } + + /** + * Builds not empty table of 2 columns, row #2 contains table name, row #3 and #4 - header, row #5 - data, + * row #6 - data with is equals to null + */ + @SuppressWarnings("ConstantConditions") + private AbstractTable getNotEmptyTable() { + // 2-th row - table name, 3-st and 4-nd rows - table header + TableCellRange tableRange = new TableCellRange(2, 6, 0, 100); + when(report.getRow(3)).thenReturn(new EmptyTableRow(table, 3)); + when(report.getRow(4)).thenReturn(new EmptyTableRow(table, 4)); + return new TableImpl(report, "table name", tableRange, headerDescription, 2); + } + + @Test + void testEmptyRangeConstructor2() { + AbstractTable originalTable = getNotEmptyTable(); + AbstractTable table = new TableImpl(originalTable, -1, -1); + TableCellRange range = new TableCellRange(3, 5, 0, 1); + + assertEquals(report, table.getReportPage()); + assertEquals(range, table.getTableRange()); + assertEquals(originalTable.getHeaderDescription(), table.getHeaderDescription()); + assertTrue(table.isEmpty()); + } + + @Test + void testNotEmptyRangeConstructor2() { + AbstractTable originalTable = getEmptyTable(); + AbstractTable table = new TableImpl(originalTable, 1, 2); + TableCellRange range = new TableCellRange(1, 5, + table.getTableRange().getFirstColumn(), table.getTableRange().getLastColumn()); + + assertEquals(report, table.getReportPage()); + assertEquals(range, table.getTableRange()); + assertEquals(originalTable.getHeaderDescription(), table.getHeaderDescription()); + assertFalse(table.isEmpty()); } @Test @@ -107,23 +184,26 @@ void getDataCollection() { @SuppressWarnings("unchecked") void testGetDataCollection() { TableRow sourceRow = mock(TableRow.class); - EmptyTableRow internalRow = mock(EmptyTableRow.class); - EmptyTableRow resultRow = mock(EmptyTableRow.class); + EmptyTableRow internalRow1 = mock(EmptyTableRow.class); + EmptyTableRow internalRow2 = mock(EmptyTableRow.class); + EmptyTableRow mergedRow = mock(EmptyTableRow.class); Iterator iterator = Arrays.asList(sourceRow, null).iterator(); when(table.iterator()).thenReturn(iterator); Function> rowExtractor = mock(Function.class); - when(rowExtractor.apply(sourceRow)).thenReturn(List.of(internalRow, internalRow)); // more than one object + when(rowExtractor.apply(sourceRow)).thenReturn(List.of(internalRow1, internalRow1, internalRow2)); // has duplicated BiPredicate equalityChecker = mock(BiPredicate.class); - when(equalityChecker.test(internalRow, internalRow)).thenReturn(true); + when(equalityChecker.test(internalRow1, internalRow1)).thenReturn(true); + when(equalityChecker.test(mergedRow, internalRow2)).thenReturn(false); BiFunction> mergeDuplicates = mock(BiFunction.class); - when(mergeDuplicates.apply(internalRow, internalRow)).thenReturn(singleton(resultRow)); + when(mergeDuplicates.apply(internalRow1, internalRow1)).thenReturn(singleton(mergedRow)); Collection result = table.getDataCollection(report, rowExtractor, equalityChecker, mergeDuplicates); - assertEquals(List.of(resultRow), result); + assertEquals(List.of(mergedRow, internalRow2), result); verify(rowExtractor).apply(sourceRow); - verify(equalityChecker).test(internalRow, internalRow); - verify(mergeDuplicates).apply(internalRow, internalRow); + verify(equalityChecker).test(internalRow1, internalRow1); + verify(equalityChecker).test(mergedRow, internalRow2); + verify(mergeDuplicates).apply(internalRow1, internalRow1); } @Test @@ -143,6 +223,31 @@ void iterator() { assertEquals(AbstractTable.TableIterator.class, iterator.getClass()); } + @Test + void testIteration() { + table = getNotEmptyTable(); + //noinspection ConstantConditions + when(report.getRow(5)).thenReturn(new EmptyTableRow(table, 5)); + // report.getRow(6) == null + + Iterator iterator = table.iterator(); + boolean nextA = iterator.hasNext(); + TableRow rowA = iterator.next(); + boolean nextB = iterator.hasNext(); + TableRow rowB = iterator.next(); + boolean nextC = iterator.hasNext(); + + assertEquals(AbstractTable.TableIterator.class, iterator.getClass()); + assertTrue(nextA); + assertTrue(nextB); + assertFalse(nextC); + assertEquals(MutableTableRow.class, rowA.getClass()); + assertEquals(EmptyTableRow.class, rowB.getClass()); + assertThrows(NoSuchElementException.class, iterator::next); + assertEquals(5, rowA.getRowNum()); + assertEquals(6, rowB.getRowNum()); + } + @Test void getRow() { table.getRow(1); @@ -188,28 +293,9 @@ void findRowByPrefix() { verify(report).findByPrefix("row value"); } - @Test - void getCellDataAccessObject() { - } - @Test void testToString() { - } - - @Test - void getReportPage() { - } - - @Test - void getTableRange() { - } - - @Test - void getHeaderDescription() { - } - - @Test - void isEmpty() { + assertEquals("AbstractTable(tableName=table name)", table.toString()); } class TableImpl extends AbstractTable { @@ -236,4 +322,12 @@ public Table subTable(int topRows, int bottomRows) { throw new UnsupportedOperationException(); } } + + @Getter + @RequiredArgsConstructor + enum Columns implements TableColumnDescription { + FIRST(LEFTMOST_COLUMN), + SECOND(ConstantPositionTableColumn.of(1)); + private final TableColumn column; + } } \ No newline at end of file From 2926bc827c09ebf313701a2e8e1aab4b9e2f3668 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Fri, 28 Oct 2022 00:15:21 +0300 Subject: [PATCH 28/94] impl AbstractTableCell tests --- .../table_wrapper/api/AbstractTable.java | 5 +- .../table_wrapper/api/AbstractTableCell.java | 6 +- .../table_wrapper/api/Table.java | 9 +- .../api/AbstractTableCellTest.java | 137 ++++++++++++++++++ .../table_wrapper/api/AbstractTableTest.java | 12 ++ 5 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java index 69758d5..2d2b6b1 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java @@ -18,6 +18,7 @@ package org.spacious_team.table_wrapper.api; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; @@ -43,6 +44,7 @@ import static java.util.Objects.requireNonNull; @Slf4j +@EqualsAndHashCode @ToString(of = {"tableName"}) public abstract class AbstractTable implements Table { @@ -96,8 +98,7 @@ protected AbstractTable(AbstractTable table, int appendDataRowsToTop, int app } private static boolean isEmpty(TableCellRange tableRange, int dataRowOffset) { - return tableRange.equals(TableCellRange.EMPTY_RANGE) || - (getNumberOfTableRows(tableRange) - dataRowOffset) <= 0; + return getNumberOfTableRows(tableRange) <= dataRowOffset; } private static int getNumberOfTableRows(TableCellRange tableRange) { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java index b0f129c..8227d1f 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableCell.java @@ -19,14 +19,18 @@ package org.spacious_team.table_wrapper.api; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; - +import lombok.ToString; import org.checkerframework.checker.nullness.qual.Nullable; + import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; +@ToString +@EqualsAndHashCode @Getter(AccessLevel.PROTECTED) @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public abstract class AbstractTableCell implements TableCell { diff --git a/src/main/java/org/spacious_team/table_wrapper/api/Table.java b/src/main/java/org/spacious_team/table_wrapper/api/Table.java index 9db7993..ce19564 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/Table.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/Table.java @@ -42,7 +42,7 @@ default List getData(Function rowExtractor) { return getData("unknown", rowExtractor); } - List getData(Object report, Function rowExtractor); + List getData(Object report, Function rowExtractor); /** * Extracts objects from table without duplicate objects handling (duplicated row are both will be returned) @@ -51,14 +51,14 @@ default List getDataCollection(Function> rowExtra return getDataCollection("unknown", rowExtractor); } - List getDataCollection(Object report, Function> rowExtractor); + List getDataCollection(Object report, Function> rowExtractor); /** * Extracts objects from table with duplicate objects handling logic */ - List getDataCollection(Object report, Function> rowExtractor, + List getDataCollection(Object report, Function> rowExtractor, BiPredicate equalityChecker, - BiFunction> mergeDuplicates); + BiFunction> mergeDuplicates); boolean isEmpty(); @@ -82,6 +82,7 @@ List getDataCollection(Object report, Function> r * @return row containing cell starting with prefix or null if not found */ @Nullable + @SuppressWarnings("UnusedReturnValue") TableRow findRowByPrefix(String prefix); Map getHeaderDescription(); diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java new file mode 100644 index 0000000..b5276ac --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java @@ -0,0 +1,137 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +@ExtendWith(MockitoExtension.class) +class AbstractTableCellTest { + + @Mock + CellDataAccessObject dao; + @Mock + Object cellValue; + TableCellImpl cell; + + @BeforeEach + void setUp() { + cell = new TableCellImpl(cellValue, dao); + } + + @Test + void getValue() { + cell.getValue(); + Mockito.verify(dao).getValue(cellValue); + } + + @Test + void getIntValue() { + cell.getIntValue(); + Mockito.verify(dao).getIntValue(cellValue); + } + + @Test + void getLongValue() { + cell.getLongValue(); + Mockito.verify(dao).getLongValue(cellValue); + } + + @Test + void getDoubleValue() { + cell.getDoubleValue(); + Mockito.verify(dao).getDoubleValue(cellValue); + } + + @Test + void getBigDecimalValue() { + cell.getBigDecimalValue(); + Mockito.verify(dao).getBigDecimalValue(cellValue); + } + + @Test + void getStringValue() { + cell.getStringValue(); + Mockito.verify(dao).getStringValue(cellValue); + } + + @Test + void getInstantValue() { + cell.getInstantValue(); + Mockito.verify(dao).getInstantValue(cellValue); + } + + @Test + void getLocalDateTimeValue() { + cell.getLocalDateTimeValue(); + Mockito.verify(dao).getLocalDateTimeValue(cellValue); + } + + @Test + void getCell() { + assertEquals(cellValue, cell.getCell()); + } + + @Test + void getDao() { + assertEquals(dao, cell.getDao()); + } + + @Test + void testEquals() { + TableCellImpl expected = new TableCellImpl(cellValue, dao); + TableCellImpl notExpected = new TableCellImpl(new Object(), dao); + + assertEquals(expected, cell); + assertNotEquals(notExpected, cell); + } + + @Test + void testHashCode() { + TableCellImpl expected = new TableCellImpl(cellValue, dao); + TableCellImpl notExpected = new TableCellImpl(new Object(), dao); + + assertEquals(expected.hashCode(), cell.hashCode()); + assertNotEquals(notExpected.hashCode(), cell.hashCode()); + } + + @Test + void testToString() { + assertEquals("AbstractTableCell(cell=cellValue, dao=dao)", cell.toString()); + } + + static class TableCellImpl extends AbstractTableCell { + + protected TableCellImpl(Object cell, CellDataAccessObject dao) { + super(cell, dao); + } + + @Override + public int getColumnIndex() { + throw new UnsupportedOperationException(); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java index 171225a..5ef368b 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java @@ -298,6 +298,18 @@ void testToString() { assertEquals("AbstractTable(tableName=table name)", table.toString()); } + @Test + void testEquals() { + assertEquals(getNotEmptyTable(), getNotEmptyTable()); + assertNotEquals(getNotEmptyTable(), getEmptyTable()); + } + + @Test + void testHashCode() { + assertEquals(getNotEmptyTable().hashCode(), getNotEmptyTable().hashCode()); + assertNotEquals(getNotEmptyTable().hashCode(), getEmptyTable().hashCode()); + } + class TableImpl extends AbstractTable { protected TableImpl(AbstractReportPage reportPage, From 4b078a77aa71685260fc8cbaad855a02398f2e9b Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Fri, 28 Oct 2022 00:47:20 +0300 Subject: [PATCH 29/94] impl AbstractTableFactory tests --- .../api/AbstractTableFactory.java | 4 + .../api/AbstractTableCellTest.java | 16 ++-- .../api/AbstractTableFactoryTest.java | 82 +++++++++++++++++++ .../table_wrapper/api/AbstractTableTest.java | 24 +++--- 4 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableFactory.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableFactory.java index ae10b63..42b1902 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableFactory.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTableFactory.java @@ -19,12 +19,16 @@ package org.spacious_team.table_wrapper.api; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; +import lombok.ToString; /** * {@link TableFactory} factory with specified {@link ReportPage} * @param the factory supported {@link ReportPage} type and subtypes */ +@ToString +@EqualsAndHashCode @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public abstract class AbstractTableFactory implements TableFactory { diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java index b5276ac..317f0c0 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java @@ -35,11 +35,11 @@ class AbstractTableCellTest { CellDataAccessObject dao; @Mock Object cellValue; - TableCellImpl cell; + TableCellTestImpl cell; @BeforeEach void setUp() { - cell = new TableCellImpl(cellValue, dao); + cell = new TableCellTestImpl(cellValue, dao); } @Test @@ -102,8 +102,8 @@ void getDao() { @Test void testEquals() { - TableCellImpl expected = new TableCellImpl(cellValue, dao); - TableCellImpl notExpected = new TableCellImpl(new Object(), dao); + TableCellTestImpl expected = new TableCellTestImpl(cellValue, dao); + TableCellTestImpl notExpected = new TableCellTestImpl(new Object(), dao); assertEquals(expected, cell); assertNotEquals(notExpected, cell); @@ -111,8 +111,8 @@ void testEquals() { @Test void testHashCode() { - TableCellImpl expected = new TableCellImpl(cellValue, dao); - TableCellImpl notExpected = new TableCellImpl(new Object(), dao); + TableCellTestImpl expected = new TableCellTestImpl(cellValue, dao); + TableCellTestImpl notExpected = new TableCellTestImpl(new Object(), dao); assertEquals(expected.hashCode(), cell.hashCode()); assertNotEquals(notExpected.hashCode(), cell.hashCode()); @@ -123,9 +123,9 @@ void testToString() { assertEquals("AbstractTableCell(cell=cellValue, dao=dao)", cell.toString()); } - static class TableCellImpl extends AbstractTableCell { + static class TableCellTestImpl extends AbstractTableCell { - protected TableCellImpl(Object cell, CellDataAccessObject dao) { + protected TableCellTestImpl(Object cell, CellDataAccessObject dao) { super(cell, dao); } diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java new file mode 100644 index 0000000..ca028a4 --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java @@ -0,0 +1,82 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +@ExtendWith(MockitoExtension.class) +class AbstractTableFactoryTest { + + @Mock + AbstractReportPage reportPage; + AbstractTableFactory> factory; + + @BeforeEach + void setUp() { + //noinspection unchecked + factory = new TableFactoryTestImpl((Class>) reportPage.getClass()); + } + + @Test + void canHandle() { + assertTrue(factory.canHandle(reportPage)); + assertFalse(factory.canHandle(mock(ReportPage.class))); + } + + @Test + void cast() { + assertEquals(reportPage, factory.cast(reportPage)); + } + + @Test + @SuppressWarnings("unchecked") + void testEquals() { + TableFactoryTestImpl expected = new TableFactoryTestImpl( + (Class>) reportPage.getClass()); + assertEquals(expected, factory); + } + + @Test + @SuppressWarnings("unchecked") + void testHashCode() { + TableFactoryTestImpl expected = new TableFactoryTestImpl( + (Class>) reportPage.getClass()); + assertEquals(expected.hashCode(), factory.hashCode()); + } + + static class TableFactoryTestImpl extends AbstractTableFactory> { + + protected TableFactoryTestImpl(Class> reportPageType) { + super(reportPageType); + } + + @Override + public Table create(ReportPage reportPage, String tableName, TableCellRange tableRange, + Class headerDescription, int headersRowCount) { + throw new UnsupportedOperationException(); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java index 5ef368b..7596b63 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java @@ -58,7 +58,7 @@ class AbstractTableTest { @BeforeEach void beforeEach() { - table = spy(new TableImpl(report, "table name", tableRange, headerDescription, 1)); + table = spy(new TableTestImpl(report, "table name", tableRange, headerDescription, 1)); } @Test @@ -76,7 +76,7 @@ void testEmptyRangeConstructor() { */ private AbstractTable getEmptyTable() { TableCellRange tableRange = new TableCellRange(2, 3, 0, 100); - return new TableImpl(report, "table name", tableRange, headerDescription, 1); + return new TableTestImpl(report, "table name", tableRange, headerDescription, 1); } @Test @@ -106,13 +106,13 @@ private AbstractTable getNotEmptyTable() { TableCellRange tableRange = new TableCellRange(2, 6, 0, 100); when(report.getRow(3)).thenReturn(new EmptyTableRow(table, 3)); when(report.getRow(4)).thenReturn(new EmptyTableRow(table, 4)); - return new TableImpl(report, "table name", tableRange, headerDescription, 2); + return new TableTestImpl(report, "table name", tableRange, headerDescription, 2); } @Test void testEmptyRangeConstructor2() { AbstractTable originalTable = getNotEmptyTable(); - AbstractTable table = new TableImpl(originalTable, -1, -1); + AbstractTable table = new TableTestImpl(originalTable, -1, -1); TableCellRange range = new TableCellRange(3, 5, 0, 1); assertEquals(report, table.getReportPage()); @@ -124,7 +124,7 @@ void testEmptyRangeConstructor2() { @Test void testNotEmptyRangeConstructor2() { AbstractTable originalTable = getEmptyTable(); - AbstractTable table = new TableImpl(originalTable, 1, 2); + AbstractTable table = new TableTestImpl(originalTable, 1, 2); TableCellRange range = new TableCellRange(1, 5, table.getTableRange().getFirstColumn(), table.getTableRange().getLastColumn()); @@ -310,17 +310,17 @@ void testHashCode() { assertNotEquals(getNotEmptyTable().hashCode(), getEmptyTable().hashCode()); } - class TableImpl extends AbstractTable { + class TableTestImpl extends AbstractTable { - protected TableImpl(AbstractReportPage reportPage, - String tableName, - TableCellRange tableRange, - Class headerDescription, - int headersRowCount) { + protected TableTestImpl(AbstractReportPage reportPage, + String tableName, + TableCellRange tableRange, + Class headerDescription, + int headersRowCount) { super(reportPage, tableName, tableRange, headerDescription, headersRowCount); } - public TableImpl(AbstractTable table, int appendDataRowsToTop, int appendDataRowsToBottom) { + public TableTestImpl(AbstractTable table, int appendDataRowsToTop, int appendDataRowsToBottom) { super(table, appendDataRowsToTop, appendDataRowsToBottom); } From 4d081c48033a392a30141928a96b7138fbd73275 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 01:24:28 +0300 Subject: [PATCH 30/94] impl AnyOfTableColumnTest and TableColumnImpl tests --- .../api/AnyOfTableColumnTest.java | 66 ++++++++++ .../api/TableColumnImplTest.java | 117 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableColumnImplTest.java diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java new file mode 100644 index 0000000..cfc7ef2 --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java @@ -0,0 +1,66 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; + +class AnyOfTableColumnTest { + + TableColumn NOT_FOUND = (x, y) -> { + throw new RuntimeException(); + }; + + @Test + void getColumnIndex() { + TableColumn column1 = AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND); + TableColumn column2 = AnyOfTableColumn.of(NOT_FOUND, LEFTMOST_COLUMN); + TableColumn column3 = AnyOfTableColumn.of(NOT_FOUND, NOT_FOUND); + + assertEquals(0, column1.getColumnIndex()); + assertEquals(10, column1.getColumnIndex(10)); + assertEquals(0, column2.getColumnIndex()); + assertEquals(10, column2.getColumnIndex(10)); + assertThrows(RuntimeException.class, column3::getColumnIndex); + assertThrows(RuntimeException.class, () -> column3.getColumnIndex(10)); + } + + @Test + void testEquals() { + assertEquals( + AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND), + AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND)); + } + + @Test + void testHashCode() { + assertEquals( + AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND).hashCode(), + AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND).hashCode()); + } + + @Test + void testToString() { + assertEquals("AnyOfTableColumn(columns=[TableColumnImpl(words=[test word])])", + AnyOfTableColumn.of(TableColumnImpl.of("test word")).toString()); + } +} \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnImplTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnImplTest.java new file mode 100644 index 0000000..f882b3d --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnImplTest.java @@ -0,0 +1,117 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import lombok.Getter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; + +class TableColumnImplTest { + + @Mock + CellDataAccessObject dao; + ReportPageRow row; + + @BeforeEach + @SuppressWarnings("ConstantConditions") + void setUp() { + row = mock(ReportPageRow.class); + Collection cells = List.of( + new TableCellTestImpl(null, 1), + new TableCellTestImpl(123, 2), + new TableCellTestImpl(1.23, 3), + new TableCellTestImpl(BigDecimal.valueOf(1), 4), + new TableCellTestImpl("", 5), + new TableCellTestImpl( " ", 6), + new TableCellTestImpl("test word", 9), + new TableCellTestImpl("This Is Sparta", 10), + new TableCellTestImpl("London\nis the\ncapital\nof Great Britain", 20), + new TableCellTestImpl("The Mac\rnew line", 21), + new TableCellTestImpl("The Windows\r\nnew line", 22)); + when(row.iterator()).then($ -> cells.iterator()); + } + + @Test + @SuppressWarnings("ConstantConditions") + void testZeroArg() { + assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of()); + //assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of((String[]) null)); + assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of(new String[]{null})); + //assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of(null, null)); + } + + @Test + void getColumnIndex() { + assertEquals(0, TableColumnImpl.of().getColumnIndex(row)); + assertEquals(9, TableColumnImpl.of("test").getColumnIndex(row)); + assertEquals(9, TableColumnImpl.of("WORD").getColumnIndex(row)); + assertEquals(10, TableColumnImpl.of("is\\s*sparta").getColumnIndex(row)); + assertEquals(20, TableColumnImpl.of("is the").getColumnIndex(row)); + assertEquals(20, TableColumnImpl.of("(old|Gr..t)").getColumnIndex(row)); + assertEquals(20, TableColumnImpl.of("of").getColumnIndex(row)); + assertEquals(21, TableColumnImpl.of("mac").getColumnIndex(row)); + assertEquals(22, TableColumnImpl.of("windows").getColumnIndex(row)); + assertThrows(RuntimeException.class, () -> TableColumnImpl.of("not found").getColumnIndex(row)); + } + + @Test + void testEquals() { + TableColumn expected = TableColumnImpl.of("test"); + TableColumn notExpected = TableColumnImpl.of("abc"); + + assertEquals(expected, TableColumnImpl.of("test")); + assertNotEquals(notExpected, TableColumnImpl.of("test")); + } + + @Test + void testHashCode() { + TableColumn expected = TableColumnImpl.of("test"); + TableColumn notExpected = TableColumnImpl.of("abc"); + + assertEquals(expected.hashCode(), TableColumnImpl.of("test").hashCode()); + assertNotEquals(notExpected.hashCode(), TableColumnImpl.of("test").hashCode()); + } + + @Test + void testToString() { + assertEquals("TableColumnImpl(words=[test])", TableColumnImpl.of("test").toString()); + } + + @Getter + class TableCellTestImpl extends AbstractTableCell { + private final Object value; + private final int columnIndex; + + TableCellTestImpl(Object value, int columnIndex) { + super(value, dao); + this.value = value; + this.columnIndex = columnIndex; + } + } +} \ No newline at end of file From fb8b7a3083ee9e14aedb817991913013233a2825 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 01:26:59 +0300 Subject: [PATCH 31/94] rename TableColumnImpl to PatternTableColumn --- .../api/MultiLineTableColumn.java | 2 +- ...olumnImpl.java => PatternTableColumn.java} | 6 +-- .../api/AnyOfTableColumnTest.java | 2 +- ...lTest.java => PatternTableColumnTest.java} | 48 +++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) rename src/main/java/org/spacious_team/table_wrapper/api/{TableColumnImpl.java => PatternTableColumn.java} (94%) rename src/test/java/org/spacious_team/table_wrapper/api/{TableColumnImplTest.java => PatternTableColumnTest.java} (61%) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java index 9bcd115..a8c9db4 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java @@ -46,7 +46,7 @@ public static MultiLineTableColumn of(TableColumn... rowDescriptors) { public static MultiLineTableColumn of(String... rowDescriptors) { return new MultiLineTableColumn(Arrays.stream(rowDescriptors) - .map(TableColumnImpl::of) + .map(PatternTableColumn::of) .toArray(TableColumn[]::new)); } diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java similarity index 94% rename from src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java rename to src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java index d63bf5c..b918835 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnImpl.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java @@ -26,7 +26,7 @@ @ToString @EqualsAndHashCode -public class TableColumnImpl implements TableColumn { +public class PatternTableColumn implements TableColumn { private final String[] words; public static TableColumn of(String... words) { @@ -34,10 +34,10 @@ public static TableColumn of(String... words) { if (words.length == 0 || (words.length == 1 && (words[0] == null || words[0].isEmpty()))) { return LEFTMOST_COLUMN; } - return new TableColumnImpl(words); + return new PatternTableColumn(words); } - private TableColumnImpl(String... words) { + private PatternTableColumn(String... words) { this.words = Arrays.stream(words) .map(String::toLowerCase) .toArray(String[]::new); diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java index cfc7ef2..620112e 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java @@ -61,6 +61,6 @@ void testHashCode() { @Test void testToString() { assertEquals("AnyOfTableColumn(columns=[TableColumnImpl(words=[test word])])", - AnyOfTableColumn.of(TableColumnImpl.of("test word")).toString()); + AnyOfTableColumn.of(PatternTableColumn.of("test word")).toString()); } } \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnImplTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java similarity index 61% rename from src/test/java/org/spacious_team/table_wrapper/api/TableColumnImplTest.java rename to src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index f882b3d..a1b0101 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnImplTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -32,7 +32,7 @@ import static org.mockito.Mockito.when; import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; -class TableColumnImplTest { +class PatternTableColumnTest { @Mock CellDataAccessObject dao; @@ -60,47 +60,47 @@ void setUp() { @Test @SuppressWarnings("ConstantConditions") void testZeroArg() { - assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of()); - //assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of((String[]) null)); - assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of(new String[]{null})); - //assertEquals(LEFTMOST_COLUMN, TableColumnImpl.of(null, null)); + assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of()); + //assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of((String[]) null)); + assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of(new String[]{null})); + //assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of(null, null)); } @Test void getColumnIndex() { - assertEquals(0, TableColumnImpl.of().getColumnIndex(row)); - assertEquals(9, TableColumnImpl.of("test").getColumnIndex(row)); - assertEquals(9, TableColumnImpl.of("WORD").getColumnIndex(row)); - assertEquals(10, TableColumnImpl.of("is\\s*sparta").getColumnIndex(row)); - assertEquals(20, TableColumnImpl.of("is the").getColumnIndex(row)); - assertEquals(20, TableColumnImpl.of("(old|Gr..t)").getColumnIndex(row)); - assertEquals(20, TableColumnImpl.of("of").getColumnIndex(row)); - assertEquals(21, TableColumnImpl.of("mac").getColumnIndex(row)); - assertEquals(22, TableColumnImpl.of("windows").getColumnIndex(row)); - assertThrows(RuntimeException.class, () -> TableColumnImpl.of("not found").getColumnIndex(row)); + assertEquals(0, PatternTableColumn.of().getColumnIndex(row)); + assertEquals(9, PatternTableColumn.of("test").getColumnIndex(row)); + assertEquals(9, PatternTableColumn.of("WORD").getColumnIndex(row)); + assertEquals(10, PatternTableColumn.of("is\\s*sparta").getColumnIndex(row)); + assertEquals(20, PatternTableColumn.of("is the").getColumnIndex(row)); + assertEquals(20, PatternTableColumn.of("(old|Gr..t)").getColumnIndex(row)); + assertEquals(20, PatternTableColumn.of("of").getColumnIndex(row)); + assertEquals(21, PatternTableColumn.of("mac").getColumnIndex(row)); + assertEquals(22, PatternTableColumn.of("windows").getColumnIndex(row)); + assertThrows(RuntimeException.class, () -> PatternTableColumn.of("not found").getColumnIndex(row)); } @Test void testEquals() { - TableColumn expected = TableColumnImpl.of("test"); - TableColumn notExpected = TableColumnImpl.of("abc"); + TableColumn expected = PatternTableColumn.of("test"); + TableColumn notExpected = PatternTableColumn.of("abc"); - assertEquals(expected, TableColumnImpl.of("test")); - assertNotEquals(notExpected, TableColumnImpl.of("test")); + assertEquals(expected, PatternTableColumn.of("test")); + assertNotEquals(notExpected, PatternTableColumn.of("test")); } @Test void testHashCode() { - TableColumn expected = TableColumnImpl.of("test"); - TableColumn notExpected = TableColumnImpl.of("abc"); + TableColumn expected = PatternTableColumn.of("test"); + TableColumn notExpected = PatternTableColumn.of("abc"); - assertEquals(expected.hashCode(), TableColumnImpl.of("test").hashCode()); - assertNotEquals(notExpected.hashCode(), TableColumnImpl.of("test").hashCode()); + assertEquals(expected.hashCode(), PatternTableColumn.of("test").hashCode()); + assertNotEquals(notExpected.hashCode(), PatternTableColumn.of("test").hashCode()); } @Test void testToString() { - assertEquals("TableColumnImpl(words=[test])", TableColumnImpl.of("test").toString()); + assertEquals("PatternTableColumn(words=[test])", PatternTableColumn.of("test").toString()); } @Getter From 1d63f87ef88d47216e522892b4a41e51629cbcef Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 01:34:19 +0300 Subject: [PATCH 32/94] add PatternTableColumn tests --- .../table_wrapper/api/AnyOfTableColumnTest.java | 2 +- .../api/PatternTableColumnTest.java | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java index 620112e..deb23fe 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java @@ -60,7 +60,7 @@ void testHashCode() { @Test void testToString() { - assertEquals("AnyOfTableColumn(columns=[TableColumnImpl(words=[test word])])", + assertEquals("AnyOfTableColumn(columns=[PatternTableColumn(words=[test word])])", AnyOfTableColumn.of(PatternTableColumn.of("test word")).toString()); } } \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index a1b0101..d4d3ea2 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -24,8 +24,8 @@ import org.mockito.Mock; import java.math.BigDecimal; +import java.util.Arrays; import java.util.Collection; -import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -42,13 +42,14 @@ class PatternTableColumnTest { @SuppressWarnings("ConstantConditions") void setUp() { row = mock(ReportPageRow.class); - Collection cells = List.of( + Collection cells = Arrays.asList( + null, new TableCellTestImpl(null, 1), new TableCellTestImpl(123, 2), new TableCellTestImpl(1.23, 3), new TableCellTestImpl(BigDecimal.valueOf(1), 4), new TableCellTestImpl("", 5), - new TableCellTestImpl( " ", 6), + new TableCellTestImpl(" ", 6), new TableCellTestImpl("test word", 9), new TableCellTestImpl("This Is Sparta", 10), new TableCellTestImpl("London\nis the\ncapital\nof Great Britain", 20), @@ -61,6 +62,7 @@ void setUp() { @SuppressWarnings("ConstantConditions") void testZeroArg() { assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of()); + assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of("")); //assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of((String[]) null)); assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of(new String[]{null})); //assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of(null, null)); @@ -71,13 +73,20 @@ void getColumnIndex() { assertEquals(0, PatternTableColumn.of().getColumnIndex(row)); assertEquals(9, PatternTableColumn.of("test").getColumnIndex(row)); assertEquals(9, PatternTableColumn.of("WORD").getColumnIndex(row)); - assertEquals(10, PatternTableColumn.of("is\\s*sparta").getColumnIndex(row)); + assertEquals(10, PatternTableColumn.of("this is\\s*sparta").getColumnIndex(row)); + assertEquals(10, PatternTableColumn.of("this is").getColumnIndex(row)); + assertEquals(10, PatternTableColumn.of("is sparta").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("is the").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("(old|Gr..t)").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("of").getColumnIndex(row)); assertEquals(21, PatternTableColumn.of("mac").getColumnIndex(row)); assertEquals(22, PatternTableColumn.of("windows").getColumnIndex(row)); + assertEquals(22, PatternTableColumn.of("windows").getColumnIndex(21, row)); + assertThrows(RuntimeException.class, () -> PatternTableColumn.of("windows").getColumnIndex(23, row)); assertThrows(RuntimeException.class, () -> PatternTableColumn.of("not found").getColumnIndex(row)); + assertThrows(RuntimeException.class, () -> PatternTableColumn.of("london is").getColumnIndex(row)); + assertThrows(RuntimeException.class, () -> PatternTableColumn.of("mac new").getColumnIndex(row)); + assertThrows(RuntimeException.class, () -> PatternTableColumn.of("windows new").getColumnIndex(row)); } @Test From f8132120bfc71e4f1d00850350e3aff7215430f6 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 01:54:51 +0300 Subject: [PATCH 33/94] refactor PatternTableColumn --- .../table_wrapper/api/PatternTableColumn.java | 49 +++++++++++++------ .../api/PatternTableColumnTest.java | 6 ++- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java index b918835..372bd60 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java @@ -19,28 +19,41 @@ package org.spacious_team.table_wrapper.api; import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; import lombok.ToString; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Arrays; +import java.util.Objects; +import java.util.regex.Pattern; -@ToString -@EqualsAndHashCode +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static java.util.regex.Pattern.UNICODE_CASE; + +@ToString(of = "words") +@RequiredArgsConstructor +@EqualsAndHashCode(of = "words") public class PatternTableColumn implements TableColumn { + private final Pattern[] patterns; private final String[] words; - public static TableColumn of(String... words) { + public static TableColumn of(@Nullable String... words) { //noinspection ConstantConditions - if (words.length == 0 || (words.length == 1 && (words[0] == null || words[0].isEmpty()))) { + if (words == null) { return LEFTMOST_COLUMN; } - return new PatternTableColumn(words); - } - - private PatternTableColumn(String... words) { - this.words = Arrays.stream(words) - .map(String::toLowerCase) + @SuppressWarnings("nullness") + String[] nonNullWords = Arrays.stream(words) + .filter(Objects::nonNull) + .filter(s -> !s.isEmpty()) .toArray(String[]::new); + Pattern[] patterns = Arrays.stream(nonNullWords) + .map(PatternTableColumn::toPattern) + .toArray(Pattern[]::new); + if (patterns.length == 0) { + return LEFTMOST_COLUMN; + } + return new PatternTableColumn(patterns, nonNullWords); } public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) { @@ -50,9 +63,9 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) @Nullable Object value; if ((cell != null) && (cell.getColumnIndex() >= firstColumnForSearch) && (((value = cell.getValue()) != null) && (value instanceof String))) { - String colName = value.toString().toLowerCase(); - for (String word : words) { - if (!containsWord(colName, word)) { + String colName = value.toString(); + for (Pattern pattern : patterns) { + if (!containsWord(colName, pattern)) { continue next_cell; } } @@ -63,7 +76,13 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) throw new RuntimeException("Не обнаружен заголовок таблицы, включающий слова: " + String.join(", ", words)); } - private boolean containsWord(String text, @Nullable String word) { - return word != null && text.matches("(^|(.|\\n)*\\b|(.|\\n)*\\s)" + word + "(\\b(.|\\n)*|\\s(.|\\n)*|$)"); + private boolean containsWord(String text, Pattern pattern) { + return pattern.matcher(text).matches(); + } + + private static Pattern toPattern(String pattern) { + String leftBoundary = "(^|(.|\\n)*\\b|(.|\\n)*\\s)"; // '.' not matches new lines + String rightBoundary = "(\\b(.|\\n)*|\\s(.|\\n)*|$)"; + return Pattern.compile(leftBoundary + pattern + rightBoundary, CASE_INSENSITIVE | UNICODE_CASE); } } diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index d4d3ea2..923ca18 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -63,9 +63,9 @@ void setUp() { void testZeroArg() { assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of()); assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of("")); - //assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of((String[]) null)); + assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of((String[]) null)); assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of(new String[]{null})); - //assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of(null, null)); + assertEquals(LEFTMOST_COLUMN, PatternTableColumn.of(null, null)); } @Test @@ -77,6 +77,8 @@ void getColumnIndex() { assertEquals(10, PatternTableColumn.of("this is").getColumnIndex(row)); assertEquals(10, PatternTableColumn.of("is sparta").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("is the").getColumnIndex(row)); + assertEquals(20, PatternTableColumn.of("is", "the").getColumnIndex(row)); + assertEquals(20, PatternTableColumn.of("is", null, "the").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("(old|Gr..t)").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("of").getColumnIndex(row)); assertEquals(21, PatternTableColumn.of("mac").getColumnIndex(row)); From f25d44344e709a9458fa0b57c362f58345212e4a Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 02:13:22 +0300 Subject: [PATCH 34/94] simplify PatternTableColumnTest --- .../api/PatternTableColumnTest.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index 923ca18..00354d5 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -19,9 +19,7 @@ package org.spacious_team.table_wrapper.api; import lombok.Getter; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mock; import java.math.BigDecimal; import java.util.Arrays; @@ -34,14 +32,9 @@ class PatternTableColumnTest { - @Mock - CellDataAccessObject dao; - ReportPageRow row; - - @BeforeEach @SuppressWarnings("ConstantConditions") - void setUp() { - row = mock(ReportPageRow.class); + ReportPageRow getRow() { + ReportPageRow row = mock(ReportPageRow.class); Collection cells = Arrays.asList( null, new TableCellTestImpl(null, 1), @@ -56,6 +49,7 @@ void setUp() { new TableCellTestImpl("The Mac\rnew line", 21), new TableCellTestImpl("The Windows\r\nnew line", 22)); when(row.iterator()).then($ -> cells.iterator()); + return row; } @Test @@ -70,6 +64,7 @@ void testZeroArg() { @Test void getColumnIndex() { + ReportPageRow row = getRow(); assertEquals(0, PatternTableColumn.of().getColumnIndex(row)); assertEquals(9, PatternTableColumn.of("test").getColumnIndex(row)); assertEquals(9, PatternTableColumn.of("WORD").getColumnIndex(row)); @@ -115,12 +110,12 @@ void testToString() { } @Getter - class TableCellTestImpl extends AbstractTableCell { + static class TableCellTestImpl extends AbstractTableCell { private final Object value; private final int columnIndex; TableCellTestImpl(Object value, int columnIndex) { - super(value, dao); + super(value, null); this.value = value; this.columnIndex = columnIndex; } From 0cb8aee3a020baa774fd03251fb14caaa4ef6056 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 02:13:37 +0300 Subject: [PATCH 35/94] refactor PatternTableColumn --- .../table_wrapper/api/PatternTableColumn.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java index 372bd60..7dc051b 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java @@ -30,6 +30,9 @@ import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.UNICODE_CASE; +/** + * Finds cell column index by case-insensitive checks by all the predefined regexp patterns. + */ @ToString(of = "words") @RequiredArgsConstructor @EqualsAndHashCode(of = "words") @@ -37,6 +40,9 @@ public class PatternTableColumn implements TableColumn { private final Pattern[] patterns; private final String[] words; + /** + * Cell text should match to all regexp patterns. + */ public static TableColumn of(@Nullable String... words) { //noinspection ConstantConditions if (words == null) { @@ -63,9 +69,9 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) @Nullable Object value; if ((cell != null) && (cell.getColumnIndex() >= firstColumnForSearch) && (((value = cell.getValue()) != null) && (value instanceof String))) { - String colName = value.toString(); + String cellText = value.toString(); for (Pattern pattern : patterns) { - if (!containsWord(colName, pattern)) { + if (!pattern.matcher(cellText).find()) { continue next_cell; } } @@ -76,13 +82,7 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) throw new RuntimeException("Не обнаружен заголовок таблицы, включающий слова: " + String.join(", ", words)); } - private boolean containsWord(String text, Pattern pattern) { - return pattern.matcher(text).matches(); - } - private static Pattern toPattern(String pattern) { - String leftBoundary = "(^|(.|\\n)*\\b|(.|\\n)*\\s)"; // '.' not matches new lines - String rightBoundary = "(\\b(.|\\n)*|\\s(.|\\n)*|$)"; - return Pattern.compile(leftBoundary + pattern + rightBoundary, CASE_INSENSITIVE | UNICODE_CASE); + return Pattern.compile(pattern, CASE_INSENSITIVE | UNICODE_CASE); } } From 700d106c15d2b850ced832383c48863d16379b7b Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 02:34:08 +0300 Subject: [PATCH 36/94] args order is not required for PatternTableColumn equality --- .../table_wrapper/api/PatternTableColumn.java | 10 ++++++---- .../table_wrapper/api/PatternTableColumnTest.java | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java index 7dc051b..317401f 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java @@ -25,7 +25,9 @@ import java.util.Arrays; import java.util.Objects; +import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.UNICODE_CASE; @@ -38,7 +40,7 @@ @EqualsAndHashCode(of = "words") public class PatternTableColumn implements TableColumn { private final Pattern[] patterns; - private final String[] words; + private final Set words; /** * Cell text should match to all regexp patterns. @@ -49,11 +51,11 @@ public static TableColumn of(@Nullable String... words) { return LEFTMOST_COLUMN; } @SuppressWarnings("nullness") - String[] nonNullWords = Arrays.stream(words) + Set nonNullWords = Arrays.stream(words) .filter(Objects::nonNull) .filter(s -> !s.isEmpty()) - .toArray(String[]::new); - Pattern[] patterns = Arrays.stream(nonNullWords) + .collect(Collectors.toUnmodifiableSet()); + Pattern[] patterns = nonNullWords.stream() .map(PatternTableColumn::toPattern) .toArray(Pattern[]::new); if (patterns.length == 0) { diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index 00354d5..e90e498 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -97,10 +97,10 @@ void testEquals() { @Test void testHashCode() { - TableColumn expected = PatternTableColumn.of("test"); + TableColumn expected = PatternTableColumn.of("test", "word"); TableColumn notExpected = PatternTableColumn.of("abc"); - assertEquals(expected.hashCode(), PatternTableColumn.of("test").hashCode()); + assertEquals(expected.hashCode(), PatternTableColumn.of("word", "test").hashCode()); assertNotEquals(notExpected.hashCode(), PatternTableColumn.of("test").hashCode()); } From b625a4183e4039c449400d8344b653b8b95e67bd Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 02:57:49 +0300 Subject: [PATCH 37/94] refactor PatternTableColumn --- .../table_wrapper/api/PatternTableColumn.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java index 317401f..35a894a 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java @@ -66,17 +66,13 @@ public static TableColumn of(@Nullable String... words) { public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) { for (ReportPageRow header : headerRows) { - next_cell: for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : header) { @Nullable Object value; - if ((cell != null) && (cell.getColumnIndex() >= firstColumnForSearch) && - (((value = cell.getValue()) != null) && (value instanceof String))) { - String cellText = value.toString(); - for (Pattern pattern : patterns) { - if (!pattern.matcher(cellText).find()) { - continue next_cell; - } - } + if (cell != null && + cell.getColumnIndex() >= firstColumnForSearch && + (value = cell.getValue()) != null && + value instanceof CharSequence && + matches((CharSequence) value)) { return cell.getColumnIndex(); } } @@ -84,6 +80,16 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) throw new RuntimeException("Не обнаружен заголовок таблицы, включающий слова: " + String.join(", ", words)); } + private boolean matches(CharSequence cellText) { + for (Pattern pattern : patterns) { + if (!pattern.matcher(cellText).find()) { + return false; + } + } + return true; + } + + private static Pattern toPattern(String pattern) { return Pattern.compile(pattern, CASE_INSENSITIVE | UNICODE_CASE); } From 1ec8f68866d4c0bc6085c4e3c15d7a72c93898cd Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 03:05:11 +0300 Subject: [PATCH 38/94] fix PatternTableColumn code smells --- .../api/PatternTableColumnTest.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index e90e498..93eea7b 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -75,15 +75,21 @@ void getColumnIndex() { assertEquals(20, PatternTableColumn.of("is", "the").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("is", null, "the").getColumnIndex(row)); assertEquals(20, PatternTableColumn.of("(old|Gr..t)").getColumnIndex(row)); - assertEquals(20, PatternTableColumn.of("of").getColumnIndex(row)); + assertEquals(20, PatternTableColumn.of("of").getColumnIndex(row)); assertEquals(21, PatternTableColumn.of("mac").getColumnIndex(row)); assertEquals(22, PatternTableColumn.of("windows").getColumnIndex(row)); assertEquals(22, PatternTableColumn.of("windows").getColumnIndex(21, row)); - assertThrows(RuntimeException.class, () -> PatternTableColumn.of("windows").getColumnIndex(23, row)); - assertThrows(RuntimeException.class, () -> PatternTableColumn.of("not found").getColumnIndex(row)); - assertThrows(RuntimeException.class, () -> PatternTableColumn.of("london is").getColumnIndex(row)); - assertThrows(RuntimeException.class, () -> PatternTableColumn.of("mac new").getColumnIndex(row)); - assertThrows(RuntimeException.class, () -> PatternTableColumn.of("windows new").getColumnIndex(row)); + + TableColumn column1 = PatternTableColumn.of("windows"); + assertThrows(RuntimeException.class, () -> column1.getColumnIndex(23, row)); + TableColumn column2 = PatternTableColumn.of("not found"); + assertThrows(RuntimeException.class, () -> column2.getColumnIndex(row)); + TableColumn column3 = PatternTableColumn.of("london is"); + assertThrows(RuntimeException.class, () -> column3.getColumnIndex(row)); + TableColumn column4 = PatternTableColumn.of("mac new"); + assertThrows(RuntimeException.class, () -> column4.getColumnIndex(row)); + TableColumn column5 = PatternTableColumn.of("windows new"); + assertThrows(RuntimeException.class, () -> column5.getColumnIndex(row)); } @Test From 8faedd87c688a4f4afffc8d0bc7efc1fc0c90527 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 03:14:46 +0300 Subject: [PATCH 39/94] fix README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3392d92..7c24be1 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ enum ProductTableHeader implements TableColumnDescription { } ProductTableHeader(String... words) { - this.column = TableColumnImpl.of(words); + this.column = PatternTableColumn.of(words); } public TableColumn getColumn() { @@ -65,7 +65,7 @@ enum ProductTableHeader implements TableColumnDescription { } } -enum CellTableHeader implements TableColumnDescription { +enum SalesTableHeader implements TableColumnDescription { BUYER_COUNTRY(MultiLineTableColumn.of("покупатель", "страна")), BUYER_COMPANY(MultiLineTableColumn.of("покупатель", "компания")), TYPE(MultiLineTableColumn.of("категория", "покупателя")), @@ -93,18 +93,18 @@ ReportPage reportPage = new ExcelSheet(book.getSheetAt(0)); // select first E Используем API для доступа к данным таблиц ```java // finding row with "таблица товаров" content, parsing next row as header and -// counting next rows as table till empty line -Table productTable = reportPage.create("таблица товаров", null, ProductTableHeader.class); +// counting next rows as table data rows till empty line +Table productTable = reportPage.create("таблица товаров", ProductTableHeader.class); // finding row with "таблица продаж" content, parsing next 2 rows as header and -// counting next rows as table till row containing "итого" in any cell -Table cellTable = reportPage.create("таблица продаж", "итого", CellTableHeader.class, 2); +// counting next rows as table data rows till row containing "итого" in any cell +Table salesTable = reportPage.create("таблица продаж", "итого", SalesTableHeader.class, 2); for (TableRow row : productTable) { String product = row.getStringCellValueOrDefault(PRICE_TRADE, "Неизвестный товар"); BigDecimal price = row.getBigDecimalCellValue(PRICE_TRADE); } -Set countries = cellTable.stream() +Set countries = salesTable.stream() .map(row -> row.getStringCelValueOrDefault(BUYER_COUNTRY, "unknown")) .collect(toSet()) ``` From e49582e571f51dc72b53d7afd2d7768ab2d94397 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 20:03:12 +0300 Subject: [PATCH 40/94] use equalsverifier --- pom.xml | 6 +++++ .../api/AbstractTableCellTest.java | 23 +++++----------- .../api/AbstractTableFactoryTest.java | 20 +++++--------- .../table_wrapper/api/AbstractTableTest.java | 20 +++++++------- .../api/AnyOfTableColumnTest.java | 18 +++++-------- .../table_wrapper/api/EmptyTableRowTest.java | 16 +++++------ .../api/PatternTableColumnTest.java | 27 ++++++++----------- 7 files changed, 54 insertions(+), 76 deletions(-) diff --git a/pom.xml b/pom.xml index dd2a524..23a7e72 100644 --- a/pom.xml +++ b/pom.xml @@ -86,6 +86,12 @@ 4.8.1 test + + nl.jqno.equalsverifier + equalsverifier + 3.10.1 + test + diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java index 317f0c0..d993658 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableCellTest.java @@ -18,6 +18,7 @@ package org.spacious_team.table_wrapper.api; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,8 +26,8 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; @ExtendWith(MockitoExtension.class) class AbstractTableCellTest { @@ -101,21 +102,11 @@ void getDao() { } @Test - void testEquals() { - TableCellTestImpl expected = new TableCellTestImpl(cellValue, dao); - TableCellTestImpl notExpected = new TableCellTestImpl(new Object(), dao); - - assertEquals(expected, cell); - assertNotEquals(notExpected, cell); - } - - @Test - void testHashCode() { - TableCellTestImpl expected = new TableCellTestImpl(cellValue, dao); - TableCellTestImpl notExpected = new TableCellTestImpl(new Object(), dao); - - assertEquals(expected.hashCode(), cell.hashCode()); - assertNotEquals(notExpected.hashCode(), cell.hashCode()); + void testEqualsAndHashCode() { + EqualsVerifier + .forClass(AbstractTableCell.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .verify(); } @Test diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java index ca028a4..a577a08 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java @@ -18,12 +18,14 @@ package org.spacious_team.table_wrapper.api; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -52,19 +54,11 @@ void cast() { } @Test - @SuppressWarnings("unchecked") - void testEquals() { - TableFactoryTestImpl expected = new TableFactoryTestImpl( - (Class>) reportPage.getClass()); - assertEquals(expected, factory); - } - - @Test - @SuppressWarnings("unchecked") - void testHashCode() { - TableFactoryTestImpl expected = new TableFactoryTestImpl( - (Class>) reportPage.getClass()); - assertEquals(expected.hashCode(), factory.hashCode()); + void testEqualsAndHashCode() { + EqualsVerifier + .forClass(AbstractTableFactory.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .verify(); } static class TableFactoryTestImpl extends AbstractTableFactory> { diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java index 7596b63..b7b91dc 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java @@ -20,6 +20,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -40,6 +41,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singleton; +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; @@ -294,20 +296,16 @@ void findRowByPrefix() { } @Test - void testToString() { - assertEquals("AbstractTable(tableName=table name)", table.toString()); + void testEqualsAndHashCode() { + EqualsVerifier + .forClass(AbstractTable.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .verify(); } @Test - void testEquals() { - assertEquals(getNotEmptyTable(), getNotEmptyTable()); - assertNotEquals(getNotEmptyTable(), getEmptyTable()); - } - - @Test - void testHashCode() { - assertEquals(getNotEmptyTable().hashCode(), getNotEmptyTable().hashCode()); - assertNotEquals(getNotEmptyTable().hashCode(), getEmptyTable().hashCode()); + void testToString() { + assertEquals("AbstractTable(tableName=table name)", table.toString()); } class TableTestImpl extends AbstractTable { diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java index deb23fe..d12ffa9 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java @@ -18,8 +18,10 @@ package org.spacious_team.table_wrapper.api; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.Test; +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; @@ -45,17 +47,11 @@ void getColumnIndex() { } @Test - void testEquals() { - assertEquals( - AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND), - AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND)); - } - - @Test - void testHashCode() { - assertEquals( - AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND).hashCode(), - AnyOfTableColumn.of(LEFTMOST_COLUMN, NOT_FOUND).hashCode()); + void testEqualsAndHashCode() { + EqualsVerifier + .forClass(AnyOfTableColumn.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .verify(); } @Test diff --git a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java index 97e2447..86c4ef8 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java @@ -18,6 +18,7 @@ package org.spacious_team.table_wrapper.api; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,6 +27,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) @@ -138,15 +140,11 @@ void getRowNum(int rowNum) { } @Test - void testEquals() { - assertEquals(new EmptyTableRow(table, 0), row); - assertNotEquals(new EmptyTableRow(table, 1), row); - } - - @Test - void testHashCode() { - assertEquals(new EmptyTableRow(table, 0).hashCode(), row.hashCode()); - assertNotEquals(new EmptyTableRow(table, 1).hashCode(), row.hashCode()); + void testEqualsAndHashCode() { + EqualsVerifier + .forClass(EmptyTableRow.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .verify(); } @Test diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index 93eea7b..7afd4be 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -19,13 +19,17 @@ package org.spacious_team.table_wrapper.api; import lombok.Getter; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collection; -import static org.junit.jupiter.api.Assertions.*; +import static nl.jqno.equalsverifier.Warning.ALL_FIELDS_SHOULD_BE_USED; +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; @@ -93,21 +97,12 @@ void getColumnIndex() { } @Test - void testEquals() { - TableColumn expected = PatternTableColumn.of("test"); - TableColumn notExpected = PatternTableColumn.of("abc"); - - assertEquals(expected, PatternTableColumn.of("test")); - assertNotEquals(notExpected, PatternTableColumn.of("test")); - } - - @Test - void testHashCode() { - TableColumn expected = PatternTableColumn.of("test", "word"); - TableColumn notExpected = PatternTableColumn.of("abc"); - - assertEquals(expected.hashCode(), PatternTableColumn.of("word", "test").hashCode()); - assertNotEquals(notExpected.hashCode(), PatternTableColumn.of("test").hashCode()); + void testEqualsAndHashCode() { + EqualsVerifier + .forClass(PatternTableColumn.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .suppress(ALL_FIELDS_SHOULD_BE_USED) + .verify(); } @Test From a747de664827e1326c3199f3f1a1ed241e814614 Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 20:09:46 +0300 Subject: [PATCH 41/94] impl ConstantPositionTableColumn --- .../api/ConstantPositionTableColumn.java | 2 + .../api/ConstantPositionTableColumnTest.java | 54 +++++++++++++++++++ .../api/PatternTableColumnTest.java | 28 +--------- .../api/TableColumnHelperTest.java | 49 +++++++++++++++++ 4 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumn.java index 1f867b8..dcf778b 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumn.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumn.java @@ -18,10 +18,12 @@ package org.spacious_team.table_wrapper.api; +import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; @ToString +@EqualsAndHashCode @RequiredArgsConstructor(staticName = "of") public class ConstantPositionTableColumn implements TableColumn { private final int columnIndex; diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java new file mode 100644 index 0000000..c854ab6 --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java @@ -0,0 +1,54 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ConstantPositionTableColumnTest { + + @ParameterizedTest + @ValueSource(ints = {0, 1, 10, 20, 1_000_000}) + void getColumnIndex(int columnNum) { + ReportPageRow row = TableColumnHelperTest.getRow(); + ConstantPositionTableColumn column = ConstantPositionTableColumn.of(columnNum); + + assertEquals(columnNum, column.getColumnIndex(row)); + } + + @Test + void testEqualsAndHashCode() { + EqualsVerifier + .forClass(ConstantPositionTableColumn.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .verify(); + } + + @Test + void testToString() { + assertEquals( + "ConstantPositionTableColumn(columnIndex=10)", + ConstantPositionTableColumn.of(10).toString()); + } +} \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index 7afd4be..aa681e3 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -22,40 +22,14 @@ import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.Test; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.Collection; - import static nl.jqno.equalsverifier.Warning.ALL_FIELDS_SHOULD_BE_USED; import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN; class PatternTableColumnTest { - @SuppressWarnings("ConstantConditions") - ReportPageRow getRow() { - ReportPageRow row = mock(ReportPageRow.class); - Collection cells = Arrays.asList( - null, - new TableCellTestImpl(null, 1), - new TableCellTestImpl(123, 2), - new TableCellTestImpl(1.23, 3), - new TableCellTestImpl(BigDecimal.valueOf(1), 4), - new TableCellTestImpl("", 5), - new TableCellTestImpl(" ", 6), - new TableCellTestImpl("test word", 9), - new TableCellTestImpl("This Is Sparta", 10), - new TableCellTestImpl("London\nis the\ncapital\nof Great Britain", 20), - new TableCellTestImpl("The Mac\rnew line", 21), - new TableCellTestImpl("The Windows\r\nnew line", 22)); - when(row.iterator()).then($ -> cells.iterator()); - return row; - } - @Test @SuppressWarnings("ConstantConditions") void testZeroArg() { @@ -68,7 +42,7 @@ void testZeroArg() { @Test void getColumnIndex() { - ReportPageRow row = getRow(); + ReportPageRow row = TableColumnHelperTest.getRow(); assertEquals(0, PatternTableColumn.of().getColumnIndex(row)); assertEquals(9, PatternTableColumn.of("test").getColumnIndex(row)); assertEquals(9, PatternTableColumn.of("WORD").getColumnIndex(row)); diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java new file mode 100644 index 0000000..23dafc2 --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java @@ -0,0 +1,49 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collection; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TableColumnHelperTest { + + @SuppressWarnings("ConstantConditions") + static ReportPageRow getRow() { + ReportPageRow row = mock(ReportPageRow.class); + Collection cells = Arrays.asList( + null, + new PatternTableColumnTest.TableCellTestImpl(null, 1), + new PatternTableColumnTest.TableCellTestImpl(123, 2), + new PatternTableColumnTest.TableCellTestImpl(1.23, 3), + new PatternTableColumnTest.TableCellTestImpl(BigDecimal.valueOf(1), 4), + new PatternTableColumnTest.TableCellTestImpl("", 5), + new PatternTableColumnTest.TableCellTestImpl(" ", 6), + new PatternTableColumnTest.TableCellTestImpl("test word", 9), + new PatternTableColumnTest.TableCellTestImpl("This Is Sparta", 10), + new PatternTableColumnTest.TableCellTestImpl("London\nis the\ncapital\nof Great Britain", 20), + new PatternTableColumnTest.TableCellTestImpl("The Mac\rnew line", 21), + new PatternTableColumnTest.TableCellTestImpl("The Windows\r\nnew line", 22)); + when(row.iterator()).then($ -> cells.iterator()); + return row; + } +} From 5e63c9c6e3b1e4955f061bdfae681556a3dcaffe Mon Sep 17 00:00:00 2001 From: Vitalii Ananev Date: Sat, 29 Oct 2022 20:43:03 +0300 Subject: [PATCH 42/94] impl MultiLineTableColumn --- .../api/MultiLineTableColumn.java | 9 +- .../api/MultiLineTableColumnTest.java | 61 ++++++++++++ .../api/PatternTableColumnTest.java | 13 --- .../api/TableColumnHelperTest.java | 94 ++++++++++++++++--- 4 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java index a8c9db4..745d5d0 100644 --- a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java +++ b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java @@ -18,10 +18,12 @@ package org.spacious_team.table_wrapper.api; +import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; import java.util.Arrays; +import java.util.Objects; /** * Implements table header kind of @@ -33,6 +35,7 @@ * Can find index for (Two -> a3 -> b1) column */ @ToString +@EqualsAndHashCode @RequiredArgsConstructor public class MultiLineTableColumn implements TableColumn { private final TableColumn[] rowDescriptors; @@ -45,9 +48,11 @@ public static MultiLineTableColumn of(TableColumn... rowDescriptors) { } public static MultiLineTableColumn of(String... rowDescriptors) { - return new MultiLineTableColumn(Arrays.stream(rowDescriptors) + TableColumn[] descriptors = Arrays.stream(rowDescriptors) + .map(Objects::requireNonNull) .map(PatternTableColumn::of) - .toArray(TableColumn[]::new)); + .toArray(TableColumn[]::new); + return new MultiLineTableColumn(descriptors); } /** diff --git a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java new file mode 100644 index 0000000..02a265a --- /dev/null +++ b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java @@ -0,0 +1,61 @@ +/* + * Table Wrapper API + * Copyright (C) 2022 Spacious Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.spacious_team.table_wrapper.api; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MultiLineTableColumnTest { + + @Test + void getColumnIndex() { + ReportPageRow[] rows = TableColumnHelperTest.getThreeRowsHeader(); + assertEquals(2, MultiLineTableColumn.of("one", "a1", "b2").getColumnIndex(rows)); + assertEquals(5, MultiLineTableColumn.of("one", "a2", "b1").getColumnIndex(rows)); + assertEquals(26, MultiLineTableColumn.of("TWO", "A2", "b2").getColumnIndex(rows)); + + TableColumn column1 = MultiLineTableColumn.of("not", "fo", "und"); + assertThrows(RuntimeException.class, () -> column1.getColumnIndex(rows)); + TableColumn column2 = MultiLineTableColumn.of("one"); + assertThrows(RuntimeException.class, () -> column2.getColumnIndex(rows)); + TableColumn column3 = MultiLineTableColumn.of("Three", "a2"); + assertThrows(RuntimeException.class, () -> column3.getColumnIndex(rows)); + TableColumn column4 = MultiLineTableColumn.of("one", "a1", "b2"); + assertThrows(RuntimeException.class, () -> column4.getColumnIndex(3, rows)); + } + + @Test + void equalsAndHashCode() { + EqualsVerifier + .forClass(MultiLineTableColumn.class) + .suppress(STRICT_INHERITANCE) // no subclass for test + .verify(); + } + + @Test + void restToString() { + assertEquals( + "MultiLineTableColumn(rowDescriptors=[PatternTableColumn(words=[a]), PatternTableColumn(words=[b])])", + MultiLineTableColumn.of("a", "b").toString()); + } +} \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java index aa681e3..4049aeb 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java @@ -18,7 +18,6 @@ package org.spacious_team.table_wrapper.api; -import lombok.Getter; import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.Test; @@ -83,16 +82,4 @@ void testEqualsAndHashCode() { void testToString() { assertEquals("PatternTableColumn(words=[test])", PatternTableColumn.of("test").toString()); } - - @Getter - static class TableCellTestImpl extends AbstractTableCell { - private final Object value; - private final int columnIndex; - - TableCellTestImpl(Object value, int columnIndex) { - super(value, null); - this.value = value; - this.columnIndex = columnIndex; - } - } } \ No newline at end of file diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java index 23dafc2..0a09399 100644 --- a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java +++ b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java @@ -18,6 +18,8 @@ package org.spacious_team.table_wrapper.api; +import lombok.Getter; + import java.math.BigDecimal; import java.util.Arrays; import java.util.Collection; @@ -32,18 +34,88 @@ static ReportPageRow getRow() { ReportPageRow row = mock(ReportPageRow.class); Collection cells = Arrays.asList( null, - new PatternTableColumnTest.TableCellTestImpl(null, 1), - new PatternTableColumnTest.TableCellTestImpl(123, 2), - new PatternTableColumnTest.TableCellTestImpl(1.23, 3), - new PatternTableColumnTest.TableCellTestImpl(BigDecimal.valueOf(1), 4), - new PatternTableColumnTest.TableCellTestImpl("", 5), - new PatternTableColumnTest.TableCellTestImpl(" ", 6), - new PatternTableColumnTest.TableCellTestImpl("test word", 9), - new PatternTableColumnTest.TableCellTestImpl("This Is Sparta", 10), - new PatternTableColumnTest.TableCellTestImpl("London\nis the\ncapital\nof Great Britain", 20), - new PatternTableColumnTest.TableCellTestImpl("The Mac\rnew line", 21), - new PatternTableColumnTest.TableCellTestImpl("The Windows\r\nnew line", 22)); + cell(null, 1), + cell(123, 2), + cell(1.23, 3), + cell(BigDecimal.valueOf(1), 4), + cell("", 5), + cell(" ", 6), + cell("test word", 9), + cell("This Is Sparta", 10), + cell("London\nis the\ncapital\nof Great Britain", 20), + cell("The Mac\rnew line", 21), + cell("The Windows\r\nnew line", 22)); when(row.iterator()).then($ -> cells.iterator()); return row; } + + /** + * Returns 3 rows + *
+     * Number: |  0   | 1  | 2  | 5  | 6  | 11 | 12 | 15 | 16 | 21 | 22 | 25 | 26 |
+     *
+     * Row 0:  | null |        One        |        Two        |        Three      |
+     * Row 1:  | null |   a1    |   a2    |    a1   |   a2    |    a1   |   a2    |
+     * Row 2:  | null | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 |
+     * 
+     */
+    static ReportPageRow[] getThreeRowsHeader() {
+        ReportPageRow[] rows = new ReportPageRow[3];
+
+        rows[0] = mock(ReportPageRow.class);
+        when(rows[0].getRowNum()).thenReturn(0);
+        Collection cells0 = Arrays.asList(
+                null,
+                cell("One", 1),
+                cell("Two", 20),
+                cell("Three", 30));
+        when(rows[0].iterator()).then($ -> cells0.iterator());
+
+        rows[1] = mock(ReportPageRow.class);
+        when(rows[1].getRowNum()).thenReturn(1);
+        Collection cells1 = Arrays.asList(
+                null,
+                cell("a1", 1),
+                cell("a2", 5),
+                cell("a1", 11),
+                cell("a2", 15),
+                cell("a1", 21),
+                cell("a2", 25));
+        when(rows[1].iterator()).then($ -> cells1.iterator());
+
+        rows[2] = mock(ReportPageRow.class);
+        when(rows[2].getRowNum()).thenReturn(2);
+        Collection cells2 = Arrays.asList(
+                null,
+                cell("b1", 1),
+                cell("b2", 2),
+                cell("b1", 5),
+                cell("b2", 6),
+                cell("b1", 11),
+                cell("b2", 12),
+                cell("b1", 15),
+                cell("b2", 16),
+                cell("b1", 21),
+                cell("b2", 22),
+                cell("b1", 25),
+                cell("b2", 26));
+        when(rows[2].iterator()).then($ -> cells2.iterator());
+        return rows;
+    }
+
+    private static TableCellTestImpl cell(Object value, int columnIndex) {
+        return new TableCellTestImpl(value, columnIndex);
+    }
+
+    @Getter
+    private static class TableCellTestImpl extends AbstractTableCell {
+        private final Object value;
+        private final int columnIndex;
+
+        TableCellTestImpl(Object value, int columnIndex) {
+            super(value, null);
+            this.value = value;
+            this.columnIndex = columnIndex;
+        }
+    }
 }

From 6603e31da76a685c694029cfad992cfb32c64ac1 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Sun, 30 Oct 2022 02:52:35 +0300
Subject: [PATCH 43/94] add OptionalTableColumn and RelativePositionTableColumn
 tests

---
 .../table_wrapper/api/AnyOfTableColumn.java   |  6 +-
 .../api/MultiLineTableColumn.java             |  4 +-
 .../api/OptionalTableColumn.java              | 24 ++++----
 .../table_wrapper/api/PatternTableColumn.java |  3 +-
 .../api/RelativePositionTableColumn.java      |  8 ++-
 .../api/OptionalTableColumnTest.java          | 54 ++++++++++++++++++
 .../api/RelativePositionTableColumnTest.java  | 57 +++++++++++++++++++
 7 files changed, 139 insertions(+), 17 deletions(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
index 1adc9f8..18ad26b 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
@@ -24,14 +24,16 @@
 
 import java.util.Arrays;
 
+import static lombok.AccessLevel.PRIVATE;
+
 @ToString
 @EqualsAndHashCode
-@RequiredArgsConstructor
+@RequiredArgsConstructor(access = PRIVATE)
 public class AnyOfTableColumn implements TableColumn {
 
     private final TableColumn[] columns;
 
-    public static TableColumn of(TableColumn... columns) {
+    public static AnyOfTableColumn of(TableColumn... columns) {
         return new AnyOfTableColumn(columns);
     }
 
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
index 745d5d0..87f5347 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
@@ -25,6 +25,8 @@
 import java.util.Arrays;
 import java.util.Objects;
 
+import static lombok.AccessLevel.PRIVATE;
+
 /**
  * Implements table header kind of
  * 
@@ -36,7 +38,7 @@
  */
 @ToString
 @EqualsAndHashCode
-@RequiredArgsConstructor
+@RequiredArgsConstructor(access = PRIVATE)
 public class MultiLineTableColumn implements TableColumn {
     private final TableColumn[] rowDescriptors;
 
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java
index bf8c1d0..3b0c423 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java
@@ -18,21 +18,25 @@
 
 package org.spacious_team.table_wrapper.api;
 
-@SuppressWarnings("unused")
-public class OptionalTableColumn implements TableColumn {
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
 
-    public static TableColumn of(TableColumn column) {
-        return AnyOfTableColumn.of(column, TableColumn.NOCOLUMN);
-    }
+import static lombok.AccessLevel.PRIVATE;
+
+@ToString(of = "column")
+@EqualsAndHashCode(of = "column")
+@RequiredArgsConstructor(access = PRIVATE)
+public class OptionalTableColumn implements TableColumn {
+    private final TableColumn column;
+    private final AnyOfTableColumn anyOfTableColumn;
 
-    private OptionalTableColumn() {
+    public static OptionalTableColumn of(TableColumn column) {
+        return new OptionalTableColumn(column, AnyOfTableColumn.of(column, TableColumn.NOCOLUMN));
     }
 
-    /**
-     * Should never be called
-     */
     @Override
     public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) {
-        return TableColumn.NOCOLUMN_INDEX;
+        return anyOfTableColumn.getColumnIndex(firstColumnForSearch, headerRows);
     }
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
index 35a894a..d0ccc95 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
@@ -31,13 +31,14 @@
 
 import static java.util.regex.Pattern.CASE_INSENSITIVE;
 import static java.util.regex.Pattern.UNICODE_CASE;
+import static lombok.AccessLevel.PRIVATE;
 
 /**
  * Finds cell column index by case-insensitive checks by all the predefined regexp patterns.
  */
 @ToString(of = "words")
-@RequiredArgsConstructor
 @EqualsAndHashCode(of = "words")
+@RequiredArgsConstructor(access = PRIVATE)
 public class PatternTableColumn implements TableColumn {
     private final Pattern[] patterns;
     private final Set words;
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumn.java
index 49b65ce..064c680 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumn.java
@@ -19,17 +19,19 @@
 package org.spacious_team.table_wrapper.api;
 
 
+import lombok.EqualsAndHashCode;
 import lombok.RequiredArgsConstructor;
 import lombok.ToString;
 
 @ToString
+@EqualsAndHashCode
 @RequiredArgsConstructor(staticName = "of")
 public class RelativePositionTableColumn implements TableColumn {
-    private final TableColumn relatedTableColumn;
-    private final int relatedOffset;
+    private final TableColumn column;
+    private final int positionOffset;
 
     @Override
     public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) {
-        return relatedTableColumn.getColumnIndex(firstColumnForSearch, headerRows) + relatedOffset;
+        return column.getColumnIndex(firstColumnForSearch, headerRows) + positionOffset;
     }
 }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
new file mode 100644
index 0000000..32cb14b
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
@@ -0,0 +1,54 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+import static nl.jqno.equalsverifier.Warning.ALL_FIELDS_SHOULD_BE_USED;
+import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
+import static org.junit.jupiter.api.Assertions.*;
+
+class OptionalTableColumnTest {
+
+    @Test
+    void getColumnIndex() {
+        ReportPageRow row = TableColumnHelperTest.getRow();
+        assertEquals(0, OptionalTableColumn.of(PatternTableColumn.of()).getColumnIndex(row));
+        assertEquals(9, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(row));
+        assertEquals(-1, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(10, row));
+        assertEquals(-1, OptionalTableColumn.of(PatternTableColumn.of("not found")).getColumnIndex(row));
+    }
+
+    @Test
+    void testEqualsAndHashCode() {
+        EqualsVerifier
+                .forClass(OptionalTableColumn.class)
+                .suppress(STRICT_INHERITANCE) // no subclass for test
+                .suppress(ALL_FIELDS_SHOULD_BE_USED)
+                .verify();
+    }
+
+    @Test
+    void testToString() {
+        assertEquals(
+                "OptionalTableColumn(column=PatternTableColumn(words=[test]))",
+                OptionalTableColumn.of(PatternTableColumn.of("test")).toString());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
new file mode 100644
index 0000000..b73308e
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
@@ -0,0 +1,57 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class RelativePositionTableColumnTest {
+
+    @Test
+    void getColumnIndex() {
+        ReportPageRow row = TableColumnHelperTest.getRow();
+        assertEquals(2, RelativePositionTableColumn.of(PatternTableColumn.of(), 2).getColumnIndex(row));
+        assertEquals(7, RelativePositionTableColumn.of(PatternTableColumn.of("test"), -2).getColumnIndex(row));
+
+        TableColumn column1 = RelativePositionTableColumn.of(PatternTableColumn.of("test"), 2);
+        assertThrows(RuntimeException.class, () -> column1.getColumnIndex(10, row));
+
+        TableColumn column2 = RelativePositionTableColumn.of(PatternTableColumn.of("not found"), 2);
+        assertThrows(RuntimeException.class, () -> column2.getColumnIndex(row));
+    }
+
+    @Test
+    void testEqualsAndHashCode() {
+        EqualsVerifier
+                .forClass(RelativePositionTableColumn.class)
+                .suppress(STRICT_INHERITANCE) // no subclass for test
+                .verify();
+    }
+
+    @Test
+    void testToString() {
+        assertEquals(
+                "RelativePositionTableColumn(column=PatternTableColumn(words=[test]), positionOffset=2)",
+                RelativePositionTableColumn.of(PatternTableColumn.of("test"), 2).toString());
+    }
+}
\ No newline at end of file

From 352aefa1e5dba239d1dc2ec88f10e6bb0be24066 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Thu, 3 Nov 2022 02:31:35 +0300
Subject: [PATCH 44/94] impl some CellDataAccessObject tests

---
 .../api/CellDataAccessObject.java             |   4 +-
 .../api/CellDataAccessObjectTest.java         | 157 ++++++++++++++++++
 2 files changed, 160 insertions(+), 1 deletion(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
index 0a29ca9..9076412 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
@@ -19,6 +19,7 @@
 package org.spacious_team.table_wrapper.api;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
+
 import java.math.BigDecimal;
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -96,8 +97,9 @@ default double getDoubleValue(C cell) {
      */
     default BigDecimal getBigDecimalValue(C cell) {
         String number = getStringValue(cell);
+        number = spacePattern.matcher(number).replaceAll("");
         number = number.replace(',', '.');
-        return (Objects.equals(number, "0") || Objects.equals(number, "0.0")) ?
+        return (Objects.equals(number, "0") || Objects.equals(number, "0.0") || Objects.equals(number, "0.00")) ?
                 BigDecimal.ZERO : new BigDecimal(number);
     }
 
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
new file mode 100644
index 0000000..2a42cc6
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
@@ -0,0 +1,157 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class CellDataAccessObjectTest {
+
+    CellDataAccessObject dao;
+
+    @BeforeEach
+    @SuppressWarnings("unchecked")
+    void setUp() {
+        dao = spy(CellDataAccessObject.class);
+        when(dao.getValue(any())).then(invocation -> invocation.getArguments()[0]);
+    }
+
+    @Test
+    void getIntValue() {
+        Object cell = "10";
+        dao.getIntValue(cell);
+        verify(dao).getLongValue(cell);
+    }
+
+    @Test
+    @SuppressWarnings("ConstantConditions")
+    void getLongValueException() {
+        assertThrows(NullPointerException.class, () -> dao.getLongValue(null));
+        assertThrows(NumberFormatException.class, () -> dao.getLongValue("10.0"));
+        assertThrows(NumberFormatException.class, () -> dao.getLongValue("abc"));
+        assertThrows(NumberFormatException.class, () -> dao.getLongValue("0xFF"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("longFactory")
+    void getLongValue(long expected, Object value) {
+        assertEquals(expected, dao.getLongValue(value));
+    }
+
+    static Object[][] longFactory() {
+        return new Object[][]{
+                {10L, (byte) 10},
+                {10L, (short) 10},
+                {10L, 10},
+                {10L, 10L},
+                {10L, BigInteger.valueOf(10)},
+                {10L, BigDecimal.valueOf(10.1)},
+                {10L, 10.0},
+                {10L, 10.1},
+                {10L, 10.9},
+                {10L, "10"},
+        };
+    }
+
+    @Test
+    @SuppressWarnings("ConstantConditions")
+    void getDoubleValueException() {
+        assertThrows(NullPointerException.class, () -> dao.getDoubleValue(null));
+        assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("abc"));
+        assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("0xFF"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("doubleFactory")
+    void getDoubleValue(double expected, Object value) {
+        assertEquals(expected, dao.getDoubleValue(value));
+    }
+
+    static Object[][] doubleFactory() {
+        return new Object[][]{
+                {10d, (byte) 10},
+                {10d, (short) 10},
+                {10d, 10},
+                {10d, 10L},
+                {10d, BigInteger.valueOf(10)},
+                {10.1d, BigDecimal.valueOf(10.1)},
+                {10d, 10.0},
+                {10.1d, 10.1},
+                {10.9d, 10.9},
+                {10d, "10"},
+                {10.1d, "10.1"},
+                {10.1d, "10,1"},
+                {1000.1d, "1 000.1"},
+        };
+    }
+
+    @Test
+    @SuppressWarnings({"ConstantConditions"})
+    void getBigDecimalValueSpecialCases() {
+        assertThrows(NullPointerException.class, () -> dao.getDoubleValue(null));
+        assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("abc"));
+        assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("0xFF"));
+        assertNotEquals(BigDecimal.valueOf(10), dao.getBigDecimalValue("10.0"));
+        assertNotEquals(BigDecimal.valueOf(10.0), dao.getBigDecimalValue("10"));
+        assertSame(BigDecimal.ZERO, dao.getBigDecimalValue("0"));
+        assertSame(BigDecimal.ZERO, dao.getBigDecimalValue("0.0"));
+        assertSame(BigDecimal.ZERO, dao.getBigDecimalValue("0.00"));
+        assertNotSame(BigDecimal.ZERO, dao.getBigDecimalValue("0.000"));
+        assertEquals(BigDecimal.valueOf(0, 3), dao.getBigDecimalValue("0.000"));
+        assertNotEquals(BigDecimal.valueOf(0, 4), dao.getBigDecimalValue("0.000"));
+        assertNotEquals(BigDecimal.valueOf(0.0), dao.getBigDecimalValue("0.000"));
+    }
+
+    @ParameterizedTest
+    @MethodSource("bigDecimalFactory")
+    void getBigDecimalValue(BigDecimal expected, Object value) {
+        assertEquals(expected, dao.getBigDecimalValue(value));
+    }
+
+    static Object[][] bigDecimalFactory() {
+        return new Object[][]{
+                {BigDecimal.valueOf(10), (byte) 10},
+                {BigDecimal.valueOf(10), (short) 10},
+                {BigDecimal.valueOf(10), 10},
+                {BigDecimal.valueOf(10), 10L},
+                {BigDecimal.valueOf(10), BigInteger.valueOf(10)},
+                {BigDecimal.valueOf(10.1), BigDecimal.valueOf(10.1)},
+                {BigDecimal.valueOf(10.0), 10.0},
+                {BigDecimal.valueOf(10.1), 10.1},
+                {BigDecimal.valueOf(10.9), 10.9},
+                {BigDecimal.valueOf(10.0), "10.0"},
+                {BigDecimal.valueOf(10), "10"},
+                {BigDecimal.valueOf(10.1), "10.1"},
+                {BigDecimal.valueOf(10.1), "10,1"},
+                {BigDecimal.valueOf(1000.1), "1 000.1"},
+        };
+    }
+}
\ No newline at end of file

From be15b1508c00f9f79d68f9c34b0f6dc2cecc10b9 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Thu, 3 Nov 2022 10:26:38 +0300
Subject: [PATCH 45/94] impl all CellDataAccessObject tests

---
 .../api/CellDataAccessObject.java             |  15 +--
 .../api/CellDataAccessObjectHelper.java       |  34 +++++
 .../api/CellDataAccessObjectTest.java         | 120 +++++++++++++++++-
 3 files changed, 158 insertions(+), 11 deletions(-)
 create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectHelper.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
index 9076412..cb63d18 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
@@ -23,11 +23,10 @@
 import java.math.BigDecimal;
 import java.time.Instant;
 import java.time.LocalDateTime;
-import java.time.ZoneId;
 import java.util.Objects;
-import java.util.regex.Pattern;
 
 import static java.util.Objects.requireNonNull;
+import static org.spacious_team.table_wrapper.api.CellDataAccessObjectHelper.*;
 
 /**
  * @apiNote Impl may have parameters that affect how the value is parsed,
@@ -35,10 +34,6 @@
  */
 public interface CellDataAccessObject {
 
-    ZoneId defaultZoneId = ZoneId.systemDefault();
-    Pattern spacePattern = Pattern.compile("\\s");
-    String NO_CELL_VALUE_EXCEPTION_MESSAGE = "Cell doesn't contains value";
-
     @Nullable
     C getCell(R row, Integer cellIndex);
 
@@ -60,7 +55,8 @@ default long getLongValue(C cell) {
         if (value instanceof Number) {
             return ((Number) value).longValue();
         } else if (value != null) {
-            return Long.parseLong(spacePattern.matcher((CharSequence) value).replaceAll(""));
+            String str = spacePattern.matcher(value.toString()).replaceAll("");
+            return Long.parseLong(str);
         } else {
             throw new NullPointerException(NO_CELL_VALUE_EXCEPTION_MESSAGE);
         }
@@ -74,7 +70,7 @@ default double getDoubleValue(C cell) {
         if (value instanceof Number) {
             return ((Number) value).doubleValue();
         } else if (value != null) {
-            String str = spacePattern.matcher((CharSequence) value).replaceAll("");
+            String str = spacePattern.matcher(value.toString()).replaceAll("");
             try {
                 return Double.parseDouble(str);
             } catch (NumberFormatException e) {
@@ -119,6 +115,9 @@ default String getStringValue(C cell) {
     Instant getInstantValue(C cell);
 
     /**
+     * Returns local date time at default time zone.
+     * To return date time at other timezone user {@link #getInstantValue(C)}.
+     *
      * @throws RuntimeException if method can't extract local date time value
      */
     default LocalDateTime getLocalDateTimeValue(C cell) {
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectHelper.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectHelper.java
new file mode 100644
index 0000000..b75a579
--- /dev/null
+++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectHelper.java
@@ -0,0 +1,34 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import lombok.NoArgsConstructor;
+
+import java.time.ZoneId;
+import java.util.regex.Pattern;
+
+import static lombok.AccessLevel.PRIVATE;
+
+@NoArgsConstructor(access = PRIVATE)
+final class CellDataAccessObjectHelper {
+
+    static final ZoneId defaultZoneId = ZoneId.systemDefault();
+    static final Pattern spacePattern = Pattern.compile("\\s");
+    static final String NO_CELL_VALUE_EXCEPTION_MESSAGE = "Cell doesn't contains value";
+}
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
index 2a42cc6..bb0c40f 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
@@ -18,16 +18,19 @@
 
 package org.spacious_team.table_wrapper.api;
 
-import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -35,13 +38,21 @@
 @ExtendWith(MockitoExtension.class)
 class CellDataAccessObjectTest {
 
+    final int EXISTS_CELL_INDEX = 0;
+    final int NOT_EXISTS_CELL_INDEX = 1_000_000;
+    @Mock
+    ReportPageRow row;
+    @Mock
+    Object cell;
     CellDataAccessObject dao;
 
     @BeforeEach
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({"unchecked", "ConstantConditions"})
     void setUp() {
         dao = spy(CellDataAccessObject.class);
-        when(dao.getValue(any())).then(invocation -> invocation.getArguments()[0]);
+        lenient().when(dao.getValue(any())).then(invocation -> invocation.getArguments()[0]);
+        lenient().when(dao.getCell(row, EXISTS_CELL_INDEX)).thenReturn(cell);
+        lenient().when(dao.getCell(row, NOT_EXISTS_CELL_INDEX)).thenReturn(null);
     }
 
     @Test
@@ -86,6 +97,7 @@ static Object[][] longFactory() {
     void getDoubleValueException() {
         assertThrows(NullPointerException.class, () -> dao.getDoubleValue(null));
         assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("abc"));
+        assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("a.bc"));
         assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("0xFF"));
     }
 
@@ -154,4 +166,106 @@ static Object[][] bigDecimalFactory() {
                 {BigDecimal.valueOf(1000.1), "1 000.1"},
         };
     }
+
+    @Test
+    void getStringValue() {
+        //noinspection ConstantConditions
+        assertThrows(NullPointerException.class, () -> dao.getStringValue(null));
+        assertEquals("Abc", dao.getStringValue("Abc"));
+        assertEquals("12", dao.getStringValue(12));
+    }
+
+    @Test
+    void getLocalDateTimeValue() {
+        LocalDateTime expected = LocalDateTime.of(2000, 1, 2, 3, 4, 5);
+        Instant instant = expected
+                .atZone(ZoneId.systemDefault())
+                .toInstant();
+        when(dao.getInstantValue(any())).thenReturn(instant);
+        assertEquals(expected, dao.getLocalDateTimeValue(instant));
+    }
+
+    @Test
+    void getValueNull() {
+        assertNull(dao.getValue(row, NOT_EXISTS_CELL_INDEX));
+        verify(dao, never()).getValue(any());
+    }
+
+    @Test
+    void getValue() {
+        dao.getValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getCell(row, EXISTS_CELL_INDEX);
+        verify(dao).getValue(cell);
+    }
+
+    @Test
+    void testGetIntValue() {
+        doReturn(10).when(dao).getIntValue(cell);
+
+        dao.getIntValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getIntValue(cell);
+        assertThrows(NullPointerException.class, () -> dao.getIntValue(row, NOT_EXISTS_CELL_INDEX));
+    }
+
+    @Test
+    void testGetLongValue() {
+        doReturn(10L).when(dao).getLongValue(cell);
+
+        dao.getLongValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getLongValue(cell);
+        assertThrows(NullPointerException.class, () -> dao.getLongValue(row, NOT_EXISTS_CELL_INDEX));
+    }
+
+    @Test
+    void testGetDoubleValue() {
+        doReturn(10.0).when(dao).getDoubleValue(cell);
+
+        dao.getDoubleValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getDoubleValue(cell);
+        assertThrows(NullPointerException.class, () -> dao.getDoubleValue(row, NOT_EXISTS_CELL_INDEX));
+    }
+
+    @Test
+    void testGetBigDecimalValue() {
+        doReturn(null).when(dao).getBigDecimalValue(cell);
+
+        dao.getBigDecimalValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getBigDecimalValue(cell);
+        assertThrows(NullPointerException.class, () -> dao.getBigDecimalValue(row, NOT_EXISTS_CELL_INDEX));
+    }
+
+    @Test
+    void testGetStringValue() {
+        doReturn(null).when(dao).getStringValue(cell);
+
+        dao.getStringValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getStringValue(cell);
+        assertThrows(NullPointerException.class, () -> dao.getStringValue(row, NOT_EXISTS_CELL_INDEX));
+    }
+
+    @Test
+    void getInstantValue() {
+        doReturn(null).when(dao).getInstantValue(cell);
+
+        dao.getInstantValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getInstantValue(cell);
+        assertThrows(NullPointerException.class, () -> dao.getInstantValue(row, NOT_EXISTS_CELL_INDEX));
+    }
+
+    @Test
+    void testGetLocalDateTimeValue() {
+        doReturn(null).when(dao).getLocalDateTimeValue(cell);
+
+        dao.getLocalDateTimeValue(row, EXISTS_CELL_INDEX);
+
+        verify(dao).getLocalDateTimeValue(cell);
+        assertThrows(NullPointerException.class, () -> dao.getLocalDateTimeValue(row, NOT_EXISTS_CELL_INDEX));
+    }
 }
\ No newline at end of file

From 9eda1f1a4ceae98f5e4278195235c91429ec9ddd Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Thu, 3 Nov 2022 22:31:50 +0300
Subject: [PATCH 46/94] add local date time methods to CellDataAccessObject

---
 .../api/CellDataAccessObject.java             | 23 ++++++++++++++++++-
 .../api/CellDataAccessObjectTest.java         | 22 ++++++++++++++++++
 2 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
index cb63d18..885fcbe 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
@@ -23,6 +23,7 @@
 import java.math.BigDecimal;
 import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.Objects;
 
 import static java.util.Objects.requireNonNull;
@@ -116,7 +117,6 @@ default String getStringValue(C cell) {
 
     /**
      * Returns local date time at default time zone.
-     * To return date time at other timezone user {@link #getInstantValue(C)}.
      *
      * @throws RuntimeException if method can't extract local date time value
      */
@@ -126,6 +126,15 @@ default LocalDateTime getLocalDateTimeValue(C cell) {
                 .toLocalDateTime();
     }
 
+    /**
+     * @throws RuntimeException if method can't extract local date time value
+     */
+    default LocalDateTime getLocalDateTimeValue(C cell, ZoneId zoneId) {
+        return getInstantValue(cell)
+                .atZone(zoneId)
+                .toLocalDateTime();
+    }
+
     default @Nullable Object getValue(R row, Integer cellIndex) {
         @Nullable C cell = getCell(row, cellIndex);
         return (cell == null) ? null : getValue(cell);
@@ -186,6 +195,8 @@ default Instant getInstantValue(R row, Integer cellIndex) {
     }
 
     /**
+     * Returns local date time at default time zone.
+     *
      * @throws RuntimeException if method can't extract local date time value
      */
     default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex) {
@@ -193,4 +204,14 @@ default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex) {
         C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
         return getLocalDateTimeValue(cell);
     }
+
+    /**
+     * @throws RuntimeException if method can't extract local date time value
+     */
+    @SuppressWarnings("UnusedReturnValue")
+    default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex, ZoneId zoneId) {
+        @SuppressWarnings({"nullness", "ConstantConditions"})
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        return getLocalDateTimeValue(cell, zoneId);
+    }
 }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
index bb0c40f..6886532 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
@@ -31,6 +31,7 @@
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -185,6 +186,17 @@ void getLocalDateTimeValue() {
         assertEquals(expected, dao.getLocalDateTimeValue(instant));
     }
 
+    @Test
+    void getLocalDateTimeValueAtTimeZone() {
+        int zoneOffset = 3;
+        Instant instant = LocalDateTime.of(2000, 1, 2, zoneOffset, 4, 5)
+                .atZone(ZoneOffset.ofHours(zoneOffset))
+                .toInstant();
+        LocalDateTime expectedAtUtc = LocalDateTime.of(2000, 1, 2, 0, 4, 5);
+        when(dao.getInstantValue(any())).thenReturn(instant);
+        assertEquals(expectedAtUtc, dao.getLocalDateTimeValue(instant, ZoneOffset.UTC));
+    }
+
     @Test
     void getValueNull() {
         assertNull(dao.getValue(row, NOT_EXISTS_CELL_INDEX));
@@ -268,4 +280,14 @@ void testGetLocalDateTimeValue() {
         verify(dao).getLocalDateTimeValue(cell);
         assertThrows(NullPointerException.class, () -> dao.getLocalDateTimeValue(row, NOT_EXISTS_CELL_INDEX));
     }
+
+    @Test
+    void testGetLocalDateTimeValueAtTimeZone() {
+        doReturn(null).when(dao).getLocalDateTimeValue(cell, ZoneOffset.UTC);
+
+        dao.getLocalDateTimeValue(row, EXISTS_CELL_INDEX, ZoneOffset.UTC);
+
+        verify(dao).getLocalDateTimeValue(cell, ZoneOffset.UTC);
+        assertThrows(NullPointerException.class, () -> dao.getLocalDateTimeValue(row, NOT_EXISTS_CELL_INDEX, ZoneOffset.UTC));
+    }
 }
\ No newline at end of file

From 698a332058c08c3dac9617155a671b8632c05413 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Tue, 8 Nov 2022 10:40:32 +0300
Subject: [PATCH 47/94] impl MutableTableRow tests, rename
 TableColumnDescription to TableHeaderColumn

---
 .../table_wrapper/api/AbstractTable.java      |   6 +-
 .../table_wrapper/api/EmptyTableRow.java      |  18 +-
 .../table_wrapper/api/MutableTableRow.java    |  22 +--
 .../table_wrapper/api/ReportPage.java         |  32 +--
 .../table_wrapper/api/Table.java              |   7 +-
 .../table_wrapper/api/TableFactory.java       |  34 ++--
 ...escription.java => TableHeaderColumn.java} |   5 +-
 .../table_wrapper/api/TableRow.java           |  34 ++--
 .../api/AbstractTableFactoryTest.java         |   2 +-
 .../table_wrapper/api/AbstractTableTest.java  |   4 +-
 .../table_wrapper/api/EmptyTableRowTest.java  |   2 +-
 .../api/MutableTableRowTest.java              | 185 ++++++++++++++++++
 12 files changed, 270 insertions(+), 81 deletions(-)
 rename src/main/java/org/spacious_team/table_wrapper/api/{TableColumnDescription.java => TableHeaderColumn.java} (90%)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/MutableTableRowTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
index 2d2b6b1..9f22d27 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
@@ -69,7 +69,7 @@ public abstract class AbstractTable implements Table {
     protected AbstractTable(AbstractReportPage reportPage,
                             String tableName,
                             TableCellRange tableRange,
-                            Class headerDescription,
+                            Class headerDescription,
                             int headersRowCount) {
         this.reportPage = reportPage;
         this.tableName = tableName;
@@ -106,7 +106,7 @@ private static int getNumberOfTableRows(TableCellRange tableRange) {
     }
 
     private static Map getHeaderDescription(AbstractReportPage reportPage, TableCellRange tableRange,
-                                                                  Class headerDescription,
+                                                                  Class headerDescription,
                                                                   int headersRowCount) {
         Map columnIndices = new HashMap<>();
         ReportPageRow[] headerRows = new ReportPageRow[headersRowCount];
@@ -118,7 +118,7 @@ private static Map getHeaderDescription(AbstractReportPage
         }
         @SuppressWarnings("nullness")
         TableColumn[] columns = Arrays.stream(headerDescription.getEnumConstants())
-                .map(TableColumnDescription::getColumn)
+                .map(TableHeaderColumn::getColumn)
                 .toArray(TableColumn[]::new);
         for (TableColumn column : columns) {
             columnIndices.put(column, column.getColumnIndex(headerRows));
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java
index e27f7f2..ac6bfb1 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableRow.java
@@ -36,7 +36,7 @@ class EmptyTableRow implements TableRow {
     private final int rowNum;
 
     @Override
-    public @Nullable TableCell getCell(TableColumnDescription column) {
+    public @Nullable TableCell getCell(TableHeaderColumn column) {
         return null;
     }
 
@@ -66,42 +66,42 @@ public Iterator iterator() {
     }
 
     @Override
-    public @Nullable Object getCellValue(TableColumnDescription column) {
+    public @Nullable Object getCellValue(TableHeaderColumn column) {
         return null;
     }
 
     @Override
-    public int getIntCellValue(TableColumnDescription column) {
+    public int getIntCellValue(TableHeaderColumn column) {
         throw new NullPointerException(CELL_NOT_FOUND);
     }
 
     @Override
-    public long getLongCellValue(TableColumnDescription column) {
+    public long getLongCellValue(TableHeaderColumn column) {
         throw new NullPointerException(CELL_NOT_FOUND);
     }
 
     @Override
-    public double getDoubleCellValue(TableColumnDescription column) {
+    public double getDoubleCellValue(TableHeaderColumn column) {
         throw new NullPointerException(CELL_NOT_FOUND);
     }
 
     @Override
-    public BigDecimal getBigDecimalCellValue(TableColumnDescription column) {
+    public BigDecimal getBigDecimalCellValue(TableHeaderColumn column) {
         throw new NullPointerException(CELL_NOT_FOUND);
     }
 
     @Override
-    public String getStringCellValue(TableColumnDescription column) {
+    public String getStringCellValue(TableHeaderColumn column) {
         throw new NullPointerException(CELL_NOT_FOUND);
     }
 
     @Override
-    public Instant getInstantCellValue(TableColumnDescription column) {
+    public Instant getInstantCellValue(TableHeaderColumn column) {
         throw new NullPointerException(CELL_NOT_FOUND);
     }
 
     @Override
-    public LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column) {
+    public LocalDateTime getLocalDateTimeCellValue(TableHeaderColumn column) {
         throw new NullPointerException(CELL_NOT_FOUND);
     }
 
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
index 7cebb20..998a4c2 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
@@ -47,7 +47,7 @@ class MutableTableRow implements TableRow {
     private volatile T row;
 
     @Override
-    public @Nullable TableCell getCell(TableColumnDescription column) {
+    public @Nullable TableCell getCell(TableHeaderColumn column) {
         return getCell(getCellIndex(column));
     }
 
@@ -82,50 +82,50 @@ public Iterator iterator() {
     }
 
     @Override
-    public @Nullable Object getCellValue(TableColumnDescription column) {
+    public @Nullable Object getCellValue(TableHeaderColumn column) {
         return dao.getValue(row, getCellIndex(column));
     }
 
     @Override
-    public int getIntCellValue(TableColumnDescription column) {
+    public int getIntCellValue(TableHeaderColumn column) {
         return dao.getIntValue(row, getCellIndex(column));
     }
 
     @Override
-    public long getLongCellValue(TableColumnDescription column) {
+    public long getLongCellValue(TableHeaderColumn column) {
         return dao.getLongValue(row, getCellIndex(column));
     }
 
     @Override
-    public double getDoubleCellValue(TableColumnDescription column) {
+    public double getDoubleCellValue(TableHeaderColumn column) {
         return dao.getDoubleValue(row, getCellIndex(column));
     }
 
     @Override
-    public BigDecimal getBigDecimalCellValue(TableColumnDescription column) {
+    public BigDecimal getBigDecimalCellValue(TableHeaderColumn column) {
         return dao.getBigDecimalValue(row, getCellIndex(column));
     }
 
     @Override
-    public String getStringCellValue(TableColumnDescription column) {
+    public String getStringCellValue(TableHeaderColumn column) {
         return dao.getStringValue(row, getCellIndex(column));
     }
 
     @Override
-    public Instant getInstantCellValue(TableColumnDescription column) {
+    public Instant getInstantCellValue(TableHeaderColumn column) {
         return dao.getInstantValue(row, getCellIndex(column));
     }
 
     @Override
-    public LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column) {
+    public LocalDateTime getLocalDateTimeCellValue(TableHeaderColumn column) {
         return dao.getLocalDateTimeValue(row, getCellIndex(column));
     }
 
-    private Integer getCellIndex(TableColumnDescription column) {
+    private int getCellIndex(TableHeaderColumn column) {
         @Nullable Integer cellIndex = table.getHeaderDescription()
                 .get(column.getColumn());
         @SuppressWarnings("nullness")
-        Integer cell = requireNonNull(cellIndex, "Cell not found");
+        int cell = requireNonNull(cellIndex, "Cell not found");
         return cell;
     }
 
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
index 0d6b69d..1984099 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
@@ -315,27 +315,27 @@ default int findEmptyRow(int startRow) {
 
     default Table create(String tableName,
                          String tableFooterString,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, tableFooterString, headerDescription);
     }
 
     default Table create(String tableName,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, headerDescription);
     }
 
     default Table create(String tableName,
                          String tableFooterString,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, tableFooterString, headerDescription, headersRowCount);
     }
 
     default Table create(String tableName,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, headerDescription, headersRowCount);
@@ -343,27 +343,27 @@ default Table create(String tableName,
 
     default Table create(Predicate tableNameFinder,
                          Predicate tableFooterFinder,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, tableFooterFinder, headerDescription);
     }
 
     default Table create(Predicate tableNameFinder,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, headerDescription);
     }
 
     default Table create(Predicate tableNameFinder,
                          Predicate tableFooterFinder,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, tableFooterFinder, headerDescription, headersRowCount);
     }
 
     default Table create(Predicate tableNameFinder,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, headerDescription, headersRowCount);
@@ -371,13 +371,13 @@ default Table create(Predicate tableNameFinder,
 
     default Table createNameless(String firstLineText,
                                  String lastRowString,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineText, lastRowString, headerDescription);
     }
 
     default Table createNameless(String firstLineText,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineText, headerDescription);
     }
@@ -385,7 +385,7 @@ default Table createNameless(String firstLineText,
     default Table createNameless(String providedTableName,
                                  String firstLineText,
                                  String lastRowString,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineText, lastRowString, headerDescription, headersRowCount);
@@ -393,7 +393,7 @@ default Table createNameless(String providedTableName,
 
     default Table createNameless(String providedTableName,
                                  String firstLineText,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineText, headerDescription, headersRowCount);
@@ -401,13 +401,13 @@ default Table createNameless(String providedTableName,
 
     default Table createNameless(Predicate firstLineFinder,
                                  Predicate lastRowFinder,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineFinder, lastRowFinder, headerDescription);
     }
 
     default Table createNameless(Predicate firstLineFinder,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineFinder, headerDescription);
     }
@@ -415,7 +415,7 @@ default Table createNameless(Predicate firstLineFinder,
     default Table createNameless(String providedTableName,
                                  Predicate firstLineFinder,
                                  Predicate lastRowFinder,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineFinder, lastRowFinder, headerDescription, headersRowCount);
@@ -423,7 +423,7 @@ default Table createNameless(String providedTableName,
 
     default Table createNameless(String providedTableName,
                                  Predicate firstLineFinder,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineFinder, headerDescription, headersRowCount);
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/Table.java b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
index ce19564..38ccfb5 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/Table.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
@@ -19,6 +19,7 @@
 package org.spacious_team.table_wrapper.api;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
+
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -81,9 +82,8 @@  List getDataCollection(Object report, Function getHeaderDescription();
 
@@ -100,7 +100,8 @@ default Table excludeTotalRow() {
      *     subTable(0, -1)
      * 
      * for exclude last "Total" row from iterator or stream.
-     * @param topRows positive value for inclusion, negative for exclusion
+     *
+     * @param topRows    positive value for inclusion, negative for exclusion
      * @param bottomRows positive value for inclusion, negative for exclusion
      */
     Table subTable(int topRows, int bottomRows);
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
index 4ca4292..156a067 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
@@ -36,7 +36,7 @@ public interface TableFactory {
     default Table create(ReportPage reportPage,
                          String tableName,
                          String lastRowString,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return create(reportPage, tableName, lastRowString, headerDescription, 1);
     }
 
@@ -47,7 +47,7 @@ default Table create(ReportPage reportPage,
      */
     default Table create(ReportPage reportPage,
                          String tableName,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return create(reportPage, tableName, headerDescription, 1);
     }
 
@@ -61,7 +61,7 @@ default Table create(ReportPage reportPage,
     default Table create(ReportPage reportPage,
                          String tableName,
                          String lastRowString,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         return create(reportPage,
                 tableName,
@@ -77,7 +77,7 @@ default Table create(ReportPage reportPage,
      */
     default Table create(ReportPage reportPage,
                          String tableName,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         return create(reportPage,
                 tableName,
@@ -95,7 +95,7 @@ default Table create(ReportPage reportPage,
     default Table create(ReportPage reportPage,
                          Predicate tableNameFinder,
                          Predicate lastRowFinder,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return create(reportPage, tableNameFinder, lastRowFinder, headerDescription, 1);
     }
 
@@ -107,7 +107,7 @@ default Table create(ReportPage reportPage,
      */
     default Table create(ReportPage reportPage,
                          Predicate tableNameFinder,
-                         Class headerDescription) {
+                         Class headerDescription) {
         return create(reportPage, tableNameFinder, headerDescription, 1);
     }
 
@@ -120,7 +120,7 @@ default Table create(ReportPage reportPage,
     default Table create(ReportPage reportPage,
                          Predicate tableNameFinder,
                          Predicate lastRowFinder,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount, lastRowFinder);
         String tableName = getTableName(reportPage, tableNameFinder, range);
@@ -135,7 +135,7 @@ default Table create(ReportPage reportPage,
      */
     default Table create(ReportPage reportPage,
                          Predicate tableNameFinder,
-                         Class headerDescription,
+                         Class headerDescription,
                          int headersRowCount) {
         TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount);
         String tableName = getTableName(reportPage, tableNameFinder, range);
@@ -169,7 +169,7 @@ private static String getTableName(ReportPage reportPage, Predicate tabl
     default Table createNameless(ReportPage reportPage,
                                  String firstRowString,
                                  String lastRowString,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowString, lastRowString, headerDescription, 1);
     }
 
@@ -180,7 +180,7 @@ default Table createNameless(ReportPage reportPage,
      */
     default Table createNameless(ReportPage reportPage,
                                  String firstRowString,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowString, headerDescription, 1);
     }
 
@@ -196,7 +196,7 @@ default Table createNameless(ReportPage reportPage,
                                  String providedTableName,
                                  String firstRowString,
                                  String lastRowString,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return create(reportPage,
                 providedTableName,
@@ -215,7 +215,7 @@ default Table createNameless(ReportPage reportPage,
     default Table createNameless(ReportPage reportPage,
                                  String providedTableName,
                                  String firstRowString,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return create(reportPage,
                 providedTableName,
@@ -234,7 +234,7 @@ default Table createNameless(ReportPage reportPage,
     default Table createNameless(ReportPage reportPage,
                                  Predicate firstRowFinder,
                                  Predicate lastRowFinder,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowFinder, lastRowFinder, headerDescription, 1);
     }
 
@@ -246,7 +246,7 @@ default Table createNameless(ReportPage reportPage,
      */
     default Table createNameless(ReportPage reportPage,
                                  Predicate firstRowFinder,
-                                 Class headerDescription) {
+                                 Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowFinder, headerDescription, 1);
     }
 
@@ -261,7 +261,7 @@ default Table createNameless(ReportPage reportPage,
                                  String providedTableName,
                                  Predicate firstRowFinder,
                                  Predicate lastRowFinder,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return create(reportPage,
                 providedTableName,
@@ -281,7 +281,7 @@ default Table createNameless(ReportPage reportPage,
     default Table createNameless(ReportPage reportPage,
                                  String providedTableName,
                                  Predicate firstRowFinder,
-                                 Class headerDescription,
+                                 Class headerDescription,
                                  int headersRowCount) {
         return create(reportPage,
                 providedTableName,
@@ -295,6 +295,6 @@ default Table createNameless(ReportPage reportPage,
     Table create(ReportPage reportPage,
                  String tableName,
                  TableCellRange tableRange,
-                 Class headerDescription,
+                 Class headerDescription,
                  int headersRowCount);
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnDescription.java b/src/main/java/org/spacious_team/table_wrapper/api/TableHeaderColumn.java
similarity index 90%
rename from src/main/java/org/spacious_team/table_wrapper/api/TableColumnDescription.java
rename to src/main/java/org/spacious_team/table_wrapper/api/TableHeaderColumn.java
index 4e3670c..07de83d 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnDescription.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableHeaderColumn.java
@@ -18,6 +18,9 @@
 
 package org.spacious_team.table_wrapper.api;
 
-public interface TableColumnDescription {
+/**
+ * Use to create enum of table header columns
+ */
+public interface TableHeaderColumn {
     TableColumn getColumn();
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java
index 5e080b5..0d3d02c 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java
@@ -32,53 +32,53 @@ public interface TableRow extends ReportPageRow, Cloneable {
     Table getTable();
 
     @Nullable
-    TableCell getCell(TableColumnDescription column);
+    TableCell getCell(TableHeaderColumn column);
 
     /**
      * Returns cell's native value
      */
     @Nullable
-    Object getCellValue(TableColumnDescription column);
+    Object getCellValue(TableHeaderColumn column);
 
     /**
      * @throws RuntimeException if method can't extract int value
      */
-    int getIntCellValue(TableColumnDescription column);
+    int getIntCellValue(TableHeaderColumn column);
 
     /**
      * @throws RuntimeException if method can't extract long value
      */
-    long getLongCellValue(TableColumnDescription column);
+    long getLongCellValue(TableHeaderColumn column);
 
     /**
      * @throws RuntimeException if method can't extract Double value
      */
-    double getDoubleCellValue(TableColumnDescription column);
+    double getDoubleCellValue(TableHeaderColumn column);
 
     /**
      * @throws RuntimeException if method can't extract BigDecimal value
      */
-    BigDecimal getBigDecimalCellValue(TableColumnDescription column);
+    BigDecimal getBigDecimalCellValue(TableHeaderColumn column);
 
     /**
      * @throws RuntimeException if method can't extract string value
      */
-    String getStringCellValue(TableColumnDescription column);
+    String getStringCellValue(TableHeaderColumn column);
 
     /**
      * @throws RuntimeException if method can't extract instant value
      */
-    Instant getInstantCellValue(TableColumnDescription column);
+    Instant getInstantCellValue(TableHeaderColumn column);
 
     /**
      * @throws RuntimeException if method can't extract local date time value
      */
-    LocalDateTime getLocalDateTimeCellValue(TableColumnDescription column);
+    LocalDateTime getLocalDateTimeCellValue(TableHeaderColumn column);
 
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default @Nullable Object getCellValueOrDefault(TableColumnDescription column, @Nullable Object defaultValue) {
+    default @Nullable Object getCellValueOrDefault(TableHeaderColumn column, @Nullable Object defaultValue) {
         try {
             return getCellValue(column);
         } catch (Exception e) {
@@ -89,7 +89,7 @@ public interface TableRow extends ReportPageRow, Cloneable {
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default int getIntCellValueOrDefault(TableColumnDescription column, int defaultValue) {
+    default int getIntCellValueOrDefault(TableHeaderColumn column, int defaultValue) {
         try {
             return getIntCellValue(column);
         } catch (Exception e) {
@@ -100,7 +100,7 @@ default int getIntCellValueOrDefault(TableColumnDescription column, int defaultV
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default long getLongCellValueOrDefault(TableColumnDescription column, long defaultValue) {
+    default long getLongCellValueOrDefault(TableHeaderColumn column, long defaultValue) {
         try {
             return getLongCellValue(column);
         } catch (Exception e) {
@@ -111,7 +111,7 @@ default long getLongCellValueOrDefault(TableColumnDescription column, long defau
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default double getDoubleCellValue(TableColumnDescription column, double defaultValue) {
+    default double getDoubleCellValue(TableHeaderColumn column, double defaultValue) {
         try {
             return getDoubleCellValue(column);
         } catch (Exception e) {
@@ -122,7 +122,7 @@ default double getDoubleCellValue(TableColumnDescription column, double defaultV
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default BigDecimal getBigDecimalCellValueOrDefault(TableColumnDescription column, BigDecimal defaultValue) {
+    default BigDecimal getBigDecimalCellValueOrDefault(TableHeaderColumn column, BigDecimal defaultValue) {
         try {
             return getBigDecimalCellValue(column);
         } catch (Exception e) {
@@ -133,7 +133,7 @@ default BigDecimal getBigDecimalCellValueOrDefault(TableColumnDescription column
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default String getStringCellValueOrDefault(TableColumnDescription column, String defaultValue) {
+    default String getStringCellValueOrDefault(TableHeaderColumn column, String defaultValue) {
         try {
             return getStringCellValue(column);
         } catch (Exception e) {
@@ -144,7 +144,7 @@ default String getStringCellValueOrDefault(TableColumnDescription column, String
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default Instant getInstantCellValueOrDefault(TableColumnDescription column, Instant defaultValue) {
+    default Instant getInstantCellValueOrDefault(TableHeaderColumn column, Instant defaultValue) {
         try {
             return getInstantCellValue(column);
         } catch (Exception e) {
@@ -155,7 +155,7 @@ default Instant getInstantCellValueOrDefault(TableColumnDescription column, Inst
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default LocalDateTime getLocalDateTimeCellValueOrDefault(TableColumnDescription column, LocalDateTime defaultValue) {
+    default LocalDateTime getLocalDateTimeCellValueOrDefault(TableHeaderColumn column, LocalDateTime defaultValue) {
         try {
             return getLocalDateTimeCellValue(column);
         } catch (Exception e) {
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
index a577a08..73f0d84 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
@@ -69,7 +69,7 @@ protected TableFactoryTestImpl(Class> reportPa
 
         @Override
         public Table create(ReportPage reportPage, String tableName, TableCellRange tableRange,
-                            Class headerDescription, int headersRowCount) {
+                            Class headerDescription, int headersRowCount) {
             throw new UnsupportedOperationException();
         }
     }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
index b7b91dc..e8d46df 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
@@ -313,7 +313,7 @@ class TableTestImpl extends AbstractTable {
         protected TableTestImpl(AbstractReportPage reportPage,
                                 String tableName,
                                 TableCellRange tableRange,
-                                Class headerDescription,
+                                Class headerDescription,
                                 int headersRowCount) {
             super(reportPage, tableName, tableRange, headerDescription, headersRowCount);
         }
@@ -335,7 +335,7 @@ public Table subTable(int topRows, int bottomRows) {
 
     @Getter
     @RequiredArgsConstructor
-    enum Columns implements TableColumnDescription {
+    enum Columns implements TableHeaderColumn {
         FIRST(LEFTMOST_COLUMN),
         SECOND(ConstantPositionTableColumn.of(1));
         private final TableColumn column;
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java
index 86c4ef8..5f96c79 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableRowTest.java
@@ -36,7 +36,7 @@ class EmptyTableRowTest {
     @Mock
     Table table;
     @Mock
-    TableColumnDescription column;
+    TableHeaderColumn column;
     EmptyTableRow row;
 
     @BeforeEach
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/MutableTableRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/MutableTableRowTest.java
new file mode 100644
index 0000000..a2b3396
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/MutableTableRowTest.java
@@ -0,0 +1,185 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Map;
+
+import static nl.jqno.equalsverifier.Warning.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class MutableTableRowTest {
+
+    @Mock
+    Table table;
+    @Mock
+    CellDataAccessObject dao;
+    @Mock
+    TableHeaderColumn headerColumn;
+    @Mock
+    TableColumn column;
+    final int COLUMN_INDEX = 10;
+    @Mock
+    ReportPageRow wrappedRow;
+    MutableTableRow row;
+
+    @BeforeEach
+    void setUp() {
+        row = spy(new MutableTableRow<>(table, dao));
+        row.setRow(wrappedRow);
+        lenient().when(headerColumn.getColumn()).thenReturn(column);
+        lenient().when(table.getHeaderDescription()).thenReturn(Map.of(column, COLUMN_INDEX));
+    }
+
+    @Test
+    void getCell() {
+        row.getCell(headerColumn);
+        verify(row).getCell(COLUMN_INDEX);
+    }
+
+    @Test
+    void testGetCell() {
+        row.getCell(COLUMN_INDEX);
+        verify(wrappedRow).getCell(COLUMN_INDEX);
+    }
+
+    @Test
+    void getRowNum() {
+        row.getRowNum();
+        verify(wrappedRow).getRowNum();
+    }
+
+    @Test
+    void getFirstCellNum() {
+        row.getFirstCellNum();
+        verify(wrappedRow).getFirstCellNum();
+    }
+
+    @Test
+    void getLastCellNum() {
+        row.getLastCellNum();
+        verify(wrappedRow).getLastCellNum();
+    }
+
+    @Test
+    void rowContains() {
+        Object object = new Object();
+        row.rowContains(object);
+        verify(wrappedRow).rowContains(object);
+    }
+
+    @Test
+    void iterator() {
+        row.iterator();
+        verify(wrappedRow).iterator();
+    }
+
+    @Test
+    void getCellValue() {
+        row.getCellValue(headerColumn);
+        verify(dao).getValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void getIntCellValue() {
+        row.getIntCellValue(headerColumn);
+        verify(dao).getIntValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void getLongCellValue() {
+        row.getLongCellValue(headerColumn);
+        verify(dao).getLongValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void getDoubleCellValue() {
+        row.getDoubleCellValue(headerColumn);
+        verify(dao).getDoubleValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void getBigDecimalCellValue() {
+        row.getBigDecimalCellValue(headerColumn);
+        verify(dao).getBigDecimalValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void getStringCellValue() {
+        row.getStringCellValue(headerColumn);
+        verify(dao).getStringValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void getInstantCellValue() {
+        row.getInstantCellValue(headerColumn);
+        verify(dao).getInstantValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void getLocalDateTimeCellValue() {
+        row.getLocalDateTimeCellValue(headerColumn);
+        verify(dao).getLocalDateTimeValue(wrappedRow, COLUMN_INDEX);
+    }
+
+    @Test
+    void testClone() {
+        MutableTableRow row = new MutableTableRow<>(table, dao);
+        row.setRow(wrappedRow);
+        assertEquals(row, row.clone());
+    }
+
+    @Test
+    void getTable() {
+        assertSame(table, row.getTable());
+    }
+
+    @Test
+    void getDao() {
+        assertSame(dao, row.getDao());
+    }
+
+    @Test
+    void getRow() {
+        assertSame(wrappedRow, row.getRow());
+    }
+
+    @Test
+    void testEquals() {
+        EqualsVerifier
+                .forClass(MutableTableRow.class)
+                .suppress(STRICT_INHERITANCE) // no subclass for test
+                .suppress(NONFINAL_FIELDS)
+                .verify();
+    }
+
+    @Test
+    void testToString() {
+        assertEquals("MutableTableRow(table=table, dao=dao, row=wrappedRow)", row.toString());
+    }
+}
\ No newline at end of file

From d1668cf0e79f7a6cfa7a08b3fd09b047e8eaa2a1 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Tue, 8 Nov 2022 11:22:42 +0300
Subject: [PATCH 48/94] impl stricter headerDescription type check

---
 .../table_wrapper/api/AbstractTable.java      |  21 ++-
 .../table_wrapper/api/ReportPage.java         | 121 ++++++++------
 .../table_wrapper/api/TableFactory.java       | 155 ++++++++++--------
 .../api/AbstractTableFactoryTest.java         |   5 +-
 .../table_wrapper/api/AbstractTableTest.java  |   5 +-
 5 files changed, 173 insertions(+), 134 deletions(-)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
index 9f22d27..d63cb57 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
@@ -41,6 +41,7 @@
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
+import static java.util.Collections.unmodifiableMap;
 import static java.util.Objects.requireNonNull;
 
 @Slf4j
@@ -66,11 +67,12 @@ public abstract class AbstractTable implements Table {
      * @param tableRange only first and last row numbers matters
      */
     @SuppressWarnings("unused")
-    protected AbstractTable(AbstractReportPage reportPage,
-                            String tableName,
-                            TableCellRange tableRange,
-                            Class headerDescription,
-                            int headersRowCount) {
+    protected  & TableHeaderColumn>
+    AbstractTable(AbstractReportPage reportPage,
+                  String tableName,
+                  TableCellRange tableRange,
+                  Class headerDescription,
+                  int headersRowCount) {
         this.reportPage = reportPage;
         this.tableName = tableName;
         this.dataRowOffset = 1 + headersRowCount; // table_name + headersRowCount
@@ -105,9 +107,10 @@ private static int getNumberOfTableRows(TableCellRange tableRange) {
         return tableRange.getLastRow() - tableRange.getFirstRow() + 1;
     }
 
-    private static Map getHeaderDescription(AbstractReportPage reportPage, TableCellRange tableRange,
-                                                                  Class headerDescription,
-                                                                  int headersRowCount) {
+    private static  & TableHeaderColumn>
+    Map getHeaderDescription(AbstractReportPage reportPage, TableCellRange tableRange,
+                                                   Class headerDescription,
+                                                   int headersRowCount) {
         Map columnIndices = new HashMap<>();
         ReportPageRow[] headerRows = new ReportPageRow[headersRowCount];
         for (int i = 0; i < headersRowCount; i++) {
@@ -123,7 +126,7 @@ private static Map getHeaderDescription(AbstractReportPage
         for (TableColumn column : columns) {
             columnIndices.put(column, column.getColumnIndex(headerRows));
         }
-        return Collections.unmodifiableMap(columnIndices);
+        return unmodifiableMap(columnIndices);
     }
 
     private static IntStream getColumnIndices(Map headerDescription) {
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
index 1984099..67dfcfa 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
@@ -19,6 +19,7 @@
 package org.spacious_team.table_wrapper.api;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
+
 import java.util.function.Predicate;
 
 import static java.util.Objects.requireNonNull;
@@ -313,118 +314,134 @@ default int findEmptyRow(int startRow) {
         return -1;
     }
 
-    default Table create(String tableName,
-                         String tableFooterString,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(String tableName,
+                 String tableFooterString,
+                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, tableFooterString, headerDescription);
     }
 
-    default Table create(String tableName,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(String tableName,
+                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, headerDescription);
     }
 
-    default Table create(String tableName,
-                         String tableFooterString,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(String tableName,
+                 String tableFooterString,
+                 Class headerDescription,
+                 int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, tableFooterString, headerDescription, headersRowCount);
     }
 
-    default Table create(String tableName,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(String tableName,
+                 Class headerDescription,
+                 int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableName, headerDescription, headersRowCount);
     }
 
-    default Table create(Predicate tableNameFinder,
-                         Predicate tableFooterFinder,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(Predicate tableNameFinder,
+                 Predicate tableFooterFinder,
+                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, tableFooterFinder, headerDescription);
     }
 
-    default Table create(Predicate tableNameFinder,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(Predicate tableNameFinder,
+                 Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, headerDescription);
     }
 
-    default Table create(Predicate tableNameFinder,
-                         Predicate tableFooterFinder,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(Predicate tableNameFinder,
+                 Predicate tableFooterFinder,
+                 Class headerDescription,
+                 int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, tableFooterFinder, headerDescription, headersRowCount);
     }
 
-    default Table create(Predicate tableNameFinder,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(Predicate tableNameFinder,
+                 Class headerDescription,
+                 int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .create(this, tableNameFinder, headerDescription, headersRowCount);
     }
 
-    default Table createNameless(String firstLineText,
-                                 String lastRowString,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(String firstLineText,
+                         String lastRowString,
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineText, lastRowString, headerDescription);
     }
 
-    default Table createNameless(String firstLineText,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(String firstLineText,
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineText, headerDescription);
     }
 
-    default Table createNameless(String providedTableName,
-                                 String firstLineText,
-                                 String lastRowString,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(String providedTableName,
+                         String firstLineText,
+                         String lastRowString,
+                         Class headerDescription,
+                         int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineText, lastRowString, headerDescription, headersRowCount);
     }
 
-    default Table createNameless(String providedTableName,
-                                 String firstLineText,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(String providedTableName,
+                         String firstLineText,
+                         Class headerDescription,
+                         int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineText, headerDescription, headersRowCount);
     }
 
-    default Table createNameless(Predicate firstLineFinder,
-                                 Predicate lastRowFinder,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(Predicate firstLineFinder,
+                         Predicate lastRowFinder,
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineFinder, lastRowFinder, headerDescription);
     }
 
-    default Table createNameless(Predicate firstLineFinder,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(Predicate firstLineFinder,
+                         Class headerDescription) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, firstLineFinder, headerDescription);
     }
 
-    default Table createNameless(String providedTableName,
-                                 Predicate firstLineFinder,
-                                 Predicate lastRowFinder,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(String providedTableName,
+                         Predicate firstLineFinder,
+                         Predicate lastRowFinder,
+                         Class headerDescription,
+                         int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineFinder, lastRowFinder, headerDescription, headersRowCount);
     }
 
-    default Table createNameless(String providedTableName,
-                                 Predicate firstLineFinder,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(String providedTableName,
+                         Predicate firstLineFinder,
+                         Class headerDescription,
+                         int headersRowCount) {
         return TableFactoryRegistry.get(this)
                 .createNameless(this, providedTableName, firstLineFinder, headerDescription, headersRowCount);
     }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
index 156a067..f561fc6 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
@@ -33,10 +33,11 @@ public interface TableFactory {
      * @param tableName     table name's row should contain cell which starts with given text
      * @param lastRowString table's last row should contain cell which starts with given text
      */
-    default Table create(ReportPage reportPage,
-                         String tableName,
-                         String lastRowString,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 String tableName,
+                 String lastRowString,
+                 Class headerDescription) {
         return create(reportPage, tableName, lastRowString, headerDescription, 1);
     }
 
@@ -45,9 +46,10 @@ default Table create(ReportPage reportPage,
      *
      * @param tableName table name's row should contain cell which starts with given text
      */
-    default Table create(ReportPage reportPage,
-                         String tableName,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 String tableName,
+                 Class headerDescription) {
         return create(reportPage, tableName, headerDescription, 1);
     }
 
@@ -58,11 +60,12 @@ default Table create(ReportPage reportPage,
      * @param tableName     table name's row should contain cell which starts with given text
      * @param lastRowString table's last row should contain cell which starts with given text
      */
-    default Table create(ReportPage reportPage,
-                         String tableName,
-                         String lastRowString,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 String tableName,
+                 String lastRowString,
+                 Class headerDescription,
+                 int headersRowCount) {
         return create(reportPage,
                 tableName,
                 reportPage.getTableCellRange(tableName, headersRowCount, lastRowString),
@@ -75,10 +78,11 @@ default Table create(ReportPage reportPage,
      *
      * @param tableName table name's row should contain cell which starts with given text
      */
-    default Table create(ReportPage reportPage,
-                         String tableName,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 String tableName,
+                 Class headerDescription,
+                 int headersRowCount) {
         return create(reportPage,
                 tableName,
                 reportPage.getTableCellRange(tableName, headersRowCount),
@@ -92,10 +96,11 @@ default Table create(ReportPage reportPage,
      * @param tableNameFinder table name containing row should contain cell satisfying predicate
      * @param lastRowFinder   table's last row should contain cell satisfying predicate
      */
-    default Table create(ReportPage reportPage,
-                         Predicate tableNameFinder,
-                         Predicate lastRowFinder,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 Predicate tableNameFinder,
+                 Predicate lastRowFinder,
+                 Class headerDescription) {
         return create(reportPage, tableNameFinder, lastRowFinder, headerDescription, 1);
     }
 
@@ -105,9 +110,10 @@ default Table create(ReportPage reportPage,
      *
      * @param tableNameFinder table name containing row should contain cell satisfying predicate
      */
-    default Table create(ReportPage reportPage,
-                         Predicate tableNameFinder,
-                         Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 Predicate tableNameFinder,
+                 Class headerDescription) {
         return create(reportPage, tableNameFinder, headerDescription, 1);
     }
 
@@ -117,11 +123,12 @@ default Table create(ReportPage reportPage,
      * @param tableNameFinder table name containing row should contain cell satisfying predicate
      * @param lastRowFinder   table's last row should contain cell satisfying predicate
      */
-    default Table create(ReportPage reportPage,
-                         Predicate tableNameFinder,
-                         Predicate lastRowFinder,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 Predicate tableNameFinder,
+                 Predicate lastRowFinder,
+                 Class headerDescription,
+                 int headersRowCount) {
         TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount, lastRowFinder);
         String tableName = getTableName(reportPage, tableNameFinder, range);
         return create(reportPage, tableName, range, headerDescription, headersRowCount);
@@ -133,10 +140,11 @@ default Table create(ReportPage reportPage,
      *
      * @param tableNameFinder table name containing row should contain cell satisfying predicate
      */
-    default Table create(ReportPage reportPage,
-                         Predicate tableNameFinder,
-                         Class headerDescription,
-                         int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table create(ReportPage reportPage,
+                 Predicate tableNameFinder,
+                 Class headerDescription,
+                 int headersRowCount) {
         TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount);
         String tableName = getTableName(reportPage, tableNameFinder, range);
         return create(reportPage, tableName, range, headerDescription, headersRowCount);
@@ -166,10 +174,11 @@ private static String getTableName(ReportPage reportPage, Predicate tabl
      * @param firstRowString table first row should contain cell which starts with given text
      * @param lastRowString  table's last row should contain cell which starts with given text
      */
-    default Table createNameless(ReportPage reportPage,
-                                 String firstRowString,
-                                 String lastRowString,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         String firstRowString,
+                         String lastRowString,
+                         Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowString, lastRowString, headerDescription, 1);
     }
 
@@ -178,9 +187,10 @@ default Table createNameless(ReportPage reportPage,
      *
      * @param firstRowString table first row should contain cell which starts with given text
      */
-    default Table createNameless(ReportPage reportPage,
-                                 String firstRowString,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         String firstRowString,
+                         Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowString, headerDescription, 1);
     }
 
@@ -192,12 +202,13 @@ default Table createNameless(ReportPage reportPage,
      * @param firstRowString    table first row should contain cell which starts with given text
      * @param lastRowString     table's last row should contain cell which starts with given text
      */
-    default Table createNameless(ReportPage reportPage,
-                                 String providedTableName,
-                                 String firstRowString,
-                                 String lastRowString,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         String providedTableName,
+                         String firstRowString,
+                         String lastRowString,
+                         Class headerDescription,
+                         int headersRowCount) {
         return create(reportPage,
                 providedTableName,
                 reportPage.getTableCellRange(firstRowString, headersRowCount, lastRowString)
@@ -212,11 +223,12 @@ default Table createNameless(ReportPage reportPage,
      * @param providedTableName predefined (not existing in reportPage) table name
      * @param firstRowString    table first row should contain cell which starts with given text
      */
-    default Table createNameless(ReportPage reportPage,
-                                 String providedTableName,
-                                 String firstRowString,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         String providedTableName,
+                         String firstRowString,
+                         Class headerDescription,
+                         int headersRowCount) {
         return create(reportPage,
                 providedTableName,
                 reportPage.getTableCellRange(firstRowString, headersRowCount)
@@ -231,10 +243,11 @@ default Table createNameless(ReportPage reportPage,
      * @param firstRowFinder table first row should contain cell satisfying predicate
      * @param lastRowFinder  table's last row should contain cell satisfying predicate
      */
-    default Table createNameless(ReportPage reportPage,
-                                 Predicate firstRowFinder,
-                                 Predicate lastRowFinder,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         Predicate firstRowFinder,
+                         Predicate lastRowFinder,
+                         Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowFinder, lastRowFinder, headerDescription, 1);
     }
 
@@ -244,9 +257,10 @@ default Table createNameless(ReportPage reportPage,
      *
      * @param firstRowFinder table first row should contain cell satisfying predicate
      */
-    default Table createNameless(ReportPage reportPage,
-                                 Predicate firstRowFinder,
-                                 Class headerDescription) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         Predicate firstRowFinder,
+                         Class headerDescription) {
         return createNameless(reportPage, "undefined", firstRowFinder, headerDescription, 1);
     }
 
@@ -257,12 +271,13 @@ default Table createNameless(ReportPage reportPage,
      * @param firstRowFinder    table first row should contain cell satisfying predicate
      * @param lastRowFinder     table's last row should contain cell satisfying predicate
      */
-    default Table createNameless(ReportPage reportPage,
-                                 String providedTableName,
-                                 Predicate firstRowFinder,
-                                 Predicate lastRowFinder,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         String providedTableName,
+                         Predicate firstRowFinder,
+                         Predicate lastRowFinder,
+                         Class headerDescription,
+                         int headersRowCount) {
         return create(reportPage,
                 providedTableName,
                 reportPage.getTableCellRange(firstRowFinder, headersRowCount, lastRowFinder)
@@ -278,11 +293,12 @@ default Table createNameless(ReportPage reportPage,
      * @param providedTableName predefined (not existing in reportPage) table name
      * @param firstRowFinder    table first row should contain cell satisfying predicate
      */
-    default Table createNameless(ReportPage reportPage,
-                                 String providedTableName,
-                                 Predicate firstRowFinder,
-                                 Class headerDescription,
-                                 int headersRowCount) {
+    default  & TableHeaderColumn>
+    Table createNameless(ReportPage reportPage,
+                         String providedTableName,
+                         Predicate firstRowFinder,
+                         Class headerDescription,
+                         int headersRowCount) {
         return create(reportPage,
                 providedTableName,
                 reportPage.getTableCellRange(firstRowFinder, headersRowCount)
@@ -292,9 +308,10 @@ default Table createNameless(ReportPage reportPage,
     }
 
 
+     & TableHeaderColumn>
     Table create(ReportPage reportPage,
                  String tableName,
                  TableCellRange tableRange,
-                 Class headerDescription,
+                 Class headerDescription,
                  int headersRowCount);
 }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
index 73f0d84..f6c790e 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
@@ -68,8 +68,9 @@ protected TableFactoryTestImpl(Class> reportPa
         }
 
         @Override
-        public Table create(ReportPage reportPage, String tableName, TableCellRange tableRange,
-                            Class headerDescription, int headersRowCount) {
+        public  & TableHeaderColumn>
+        Table create(ReportPage reportPage, String tableName, TableCellRange tableRange,
+                     Class headerDescription, int headersRowCount) {
             throw new UnsupportedOperationException();
         }
     }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
index e8d46df..766e1fd 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
@@ -310,10 +310,11 @@ void testToString() {
 
     class TableTestImpl extends AbstractTable {
 
-        protected TableTestImpl(AbstractReportPage reportPage,
+        protected  & TableHeaderColumn>
+        TableTestImpl(AbstractReportPage reportPage,
                                 String tableName,
                                 TableCellRange tableRange,
-                                Class headerDescription,
+                                Class headerDescription,
                                 int headersRowCount) {
             super(reportPage, tableName, tableRange, headerDescription, headersRowCount);
         }

From 3307ac73dc537aed6b98c5a79106815e1b919fbd Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 00:39:53 +0300
Subject: [PATCH 49/94] impl some ReportPage tests

---
 .../table_wrapper/api/AbstractTable.java      |   2 +-
 .../table_wrapper/api/ReportPage.java         |  36 +-
 .../table_wrapper/api/ReportPageHelper.java   |  29 +-
 .../table_wrapper/api/TableCellAddress.java   |   4 +-
 .../table_wrapper/api/TableCellRange.java     |  20 +-
 .../table_wrapper/api/AbstractTableTest.java  |  16 +-
 .../table_wrapper/api/ReportPageTest.java     | 372 ++++++++++++++++++
 7 files changed, 443 insertions(+), 36 deletions(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
index d63cb57..8b482c7 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
@@ -82,7 +82,7 @@ public abstract class AbstractTable implements Table {
                 getHeaderDescription(reportPage, tableRange, headerDescription, headersRowCount);
         this.tableRange = empty ?
                 tableRange :
-                new TableCellRange(
+                TableCellRange.of(
                         tableRange.getFirstRow(),
                         tableRange.getLastRow(),
                         getColumnIndices(this.headerDescription).min().orElse(tableRange.getFirstColumn()),
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
index 67dfcfa..443ede7 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
@@ -155,7 +155,7 @@ default TableCellAddress findByPrefix(String prefix, int startRow, int endRow) {
      * @return cell address or {@link TableCellAddress#NOT_FOUND}
      */
     default TableCellAddress findByPrefix(@Nullable String prefix, int startRow, int endRow, int startColumn, int endColumn) {
-        return prefix == null ?
+        return (prefix == null || prefix.isEmpty()) ?
                 TableCellAddress.NOT_FOUND :
                 find(startRow, endRow, startColumn, endColumn, getCellStringValueIgnoreCasePrefixPredicate(prefix));
     }
@@ -204,7 +204,7 @@ default TableCellAddress findByPrefix(@Nullable String prefix, int startRow, int
     default TableCellRange getTableCellRange(@Nullable String firstRowPrefix,
                                              int headersRowCount,
                                              @Nullable String lastRowPrefix) {
-        if (firstRowPrefix == null || lastRowPrefix == null) {
+        if (firstRowPrefix == null || lastRowPrefix == null || firstRowPrefix.isEmpty() || lastRowPrefix.isEmpty()) {
             return TableCellRange.EMPTY_RANGE;
         }
         return getTableCellRange(
@@ -234,7 +234,7 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin
         }
         @SuppressWarnings({"nullness", "ConstantConditions"})
         ReportPageRow lastRow = requireNonNull(getRow(endAddress.getRow()), "Row not found");
-        return new TableCellRange(
+        return TableCellRange.of(
                 startAddress.getRow(),
                 endAddress.getRow(),
                 firstRow.getFirstCellNum(),
@@ -246,7 +246,7 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin
      * range ends with empty row or last row of report page.
      */
     default TableCellRange getTableCellRange(@Nullable String firstRowPrefix, int headersRowCount) {
-        if (firstRowPrefix == null) {
+        if (firstRowPrefix == null || firstRowPrefix.isEmpty()) {
             return TableCellRange.EMPTY_RANGE;
         }
         return getTableCellRange(
@@ -267,20 +267,24 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin
         }
         @SuppressWarnings({"nullness", "ConstantConditions"})
         ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row not found");
-        int lastRowNum = findEmptyRow(startAddress.getRow() + headersRowCount + 1);
-        if (lastRowNum == -1) {
-            lastRowNum = getLastRowNum(); // empty row not found
-        } else if (lastRowNum <= getLastRowNum()) {
-            lastRowNum--; // exclude last row from table
+        int emptyRowNum = findEmptyRow(startAddress.getRow() + headersRowCount + 1);
+        if (emptyRowNum == -1) {
+            emptyRowNum = getLastRowNum(); // empty row not found, use last row
+        } else if (emptyRowNum <= getLastRowNum()) {
+            emptyRowNum--; // exclude empty row
         }
-        if (lastRowNum < startAddress.getRow()) {
-            lastRowNum = startAddress.getRow();
+        ReportPageRow lastRow;
+        if (emptyRowNum <= startAddress.getRow()) {
+            emptyRowNum = startAddress.getRow();
+            lastRow = firstRow;
+        } else {
+            @SuppressWarnings({"nullness", "ConstantConditions"})
+            ReportPageRow row = requireNonNull(getRow(emptyRowNum), "Row not found");
+            lastRow = row;
         }
-        @SuppressWarnings({"nullness", "ConstantConditions"})
-        ReportPageRow lastRow = requireNonNull(getRow(lastRowNum), "Row not found");
-        return new TableCellRange(
+        return TableCellRange.of(
                 startAddress.getRow(),
-                lastRowNum,
+                emptyRowNum,
                 firstRow.getFirstCellNum(),
                 lastRow.getLastCellNum());
     }
@@ -309,7 +313,7 @@ default int findEmptyRow(int startRow) {
                     continue LAST_ROW;
                 }
             }
-            return lastRowNum; // all row cells blank
+            return lastRowNum; // all row cells are blank
         }
         return -1;
     }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
index e1301bb..3156b0e 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
@@ -18,13 +18,34 @@
 
 package org.spacious_team.table_wrapper.api;
 
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
 import java.util.function.Predicate;
 
-class ReportPageHelper {
+import static lombok.AccessLevel.PRIVATE;
+
+@RequiredArgsConstructor(access = PRIVATE)
+final class ReportPageHelper {
 
     static Predicate getCellStringValueIgnoreCasePrefixPredicate(String prefix) {
-        String lowercasePrefix = prefix.trim().toLowerCase();
-        return cell -> (cell instanceof String) &&
-                ((String) cell).trim().toLowerCase().startsWith(lowercasePrefix);
+        return new StringIgnoreCasePrefixPredicate(prefix);
+    }
+
+    @ToString
+    @EqualsAndHashCode
+    private static class StringIgnoreCasePrefixPredicate implements Predicate {
+        private final String lowercasePrefix;
+
+        private StringIgnoreCasePrefixPredicate(String prefix) {
+            this.lowercasePrefix = prefix.trim().toLowerCase();
+        }
+
+        @Override
+        public boolean test(Object cell) {
+            return (cell instanceof String) &&
+                    ((String) cell).trim().toLowerCase().startsWith(lowercasePrefix);
+        }
     }
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java
index 58635ff..a6466ee 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java
@@ -24,9 +24,9 @@
 import lombok.ToString;
 
 @Getter
-@EqualsAndHashCode
 @ToString
-@RequiredArgsConstructor
+@EqualsAndHashCode
+@RequiredArgsConstructor(staticName = "of")
 public class TableCellAddress {
     public static final TableCellAddress NOT_FOUND = new TableCellAddress(-1, -1);
 
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
index f1759a4..2557546 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
@@ -29,7 +29,7 @@
 @Getter
 @ToString
 @EqualsAndHashCode
-@RequiredArgsConstructor
+@RequiredArgsConstructor(staticName = "of")
 public class TableCellRange {
     public static final TableCellRange EMPTY_RANGE = new EmptyTableCellRange();
 
@@ -38,6 +38,14 @@ public class TableCellRange {
     private final int firstColumn;
     private final int lastColumn;
 
+    public TableCellRange of(TableCellAddress leftUpperCellAddress, TableCellAddress rightBottomCellAddress) {
+        return of(
+                leftUpperCellAddress.getRow(),
+                rightBottomCellAddress.getRow(),
+                leftUpperCellAddress.getColumn(),
+                rightBottomCellAddress.getColumn());
+    }
+
     public boolean contains(TableCellAddress address) {
         return containsRow(address.getRow()) && containsColumn(address.getColumn());
     }
@@ -52,26 +60,28 @@ public boolean containsColumn(int column) {
 
     /**
      * Adds rows without range check. First rows index of range may become negative.
+     *
      * @param number positive or negative values
      */
     public TableCellRange addRowsToTop(int number) {
-        return new TableCellRange(firstRow - number, lastRow, firstColumn, lastColumn);
+        return TableCellRange.of(firstRow - number, lastRow, firstColumn, lastColumn);
     }
 
     /**
      * @param number positive or negative values
      */
     public TableCellRange addRowsToBottom(int number) {
-        return new TableCellRange(firstRow, lastRow + number, firstColumn, lastColumn);
+        return TableCellRange.of(firstRow, lastRow + number, firstColumn, lastColumn);
     }
 
     /**
      * Adds columns without range check. First column index of range may become negative.
+     *
      * @param number positive or negative values
      */
     @SuppressWarnings("unused")
     public TableCellRange addColumnsToLeft(int number) {
-        return new TableCellRange(firstRow, lastRow, firstColumn - number, lastColumn);
+        return TableCellRange.of(firstRow, lastRow, firstColumn - number, lastColumn);
     }
 
     /**
@@ -79,7 +89,7 @@ public TableCellRange addColumnsToLeft(int number) {
      */
     @SuppressWarnings("unused")
     public TableCellRange addColumnsToRight(int number) {
-        return new TableCellRange(firstRow, lastRow, firstColumn, lastColumn + number);
+        return TableCellRange.of(firstRow, lastRow, firstColumn, lastColumn + number);
     }
 
     private static class EmptyTableCellRange extends TableCellRange {
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
index 766e1fd..46781da 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
@@ -68,7 +68,7 @@ void testEmptyRangeConstructor() {
         AbstractTable table = getEmptyTable();
 
         assertEquals(report, table.getReportPage());
-        assertEquals(new TableCellRange(2, 3, 0, 100), table.getTableRange());
+        assertEquals(TableCellRange.of(2, 3, 0, 100), table.getTableRange());
         assertTrue(table.getHeaderDescription().isEmpty());
         assertTrue(table.isEmpty());
     }
@@ -77,14 +77,14 @@ void testEmptyRangeConstructor() {
      * Builds not empty table of 2 columns, row #2 contains table name, row #3 - header, no data rows
      */
     private AbstractTable getEmptyTable() {
-        TableCellRange tableRange = new TableCellRange(2, 3, 0, 100);
+        TableCellRange tableRange = TableCellRange.of(2, 3, 0, 100);
         return new TableTestImpl(report, "table name", tableRange, headerDescription, 1);
     }
 
     @Test
     void testNotEmptyRangeConstructor() {
         AbstractTable table = getNotEmptyTable();
-        TableCellRange range = new TableCellRange(2, 6, 0, 1);
+        TableCellRange range = TableCellRange.of(2, 6, 0, 1);
         ReportPageRow[] headerRows = new EmptyTableRow[0]; // mock
         Map headerDescriptionMap = Map.of(
                 Columns.FIRST.getColumn(),
@@ -105,7 +105,7 @@ void testNotEmptyRangeConstructor() {
     @SuppressWarnings("ConstantConditions")
     private AbstractTable getNotEmptyTable() {
         // 2-th row - table name, 3-st and 4-nd rows - table header
-        TableCellRange tableRange = new TableCellRange(2, 6, 0, 100);
+        TableCellRange tableRange = TableCellRange.of(2, 6, 0, 100);
         when(report.getRow(3)).thenReturn(new EmptyTableRow(table, 3));
         when(report.getRow(4)).thenReturn(new EmptyTableRow(table, 4));
         return new TableTestImpl(report, "table name", tableRange, headerDescription, 2);
@@ -115,7 +115,7 @@ private AbstractTable getNotEmptyTable() {
     void testEmptyRangeConstructor2() {
         AbstractTable originalTable = getNotEmptyTable();
         AbstractTable table = new TableTestImpl(originalTable, -1, -1);
-        TableCellRange range = new TableCellRange(3, 5, 0, 1);
+        TableCellRange range = TableCellRange.of(3, 5, 0, 1);
 
         assertEquals(report, table.getReportPage());
         assertEquals(range, table.getTableRange());
@@ -127,7 +127,7 @@ void testEmptyRangeConstructor2() {
     void testNotEmptyRangeConstructor2() {
         AbstractTable originalTable = getEmptyTable();
         AbstractTable table = new TableTestImpl(originalTable, 1, 2);
-        TableCellRange range = new TableCellRange(1, 5,
+        TableCellRange range = TableCellRange.of(1, 5,
                 table.getTableRange().getFirstColumn(), table.getTableRange().getLastColumn());
 
         assertEquals(report, table.getReportPage());
@@ -271,7 +271,7 @@ void findRowFoundEmptyRow() {
     @Test
     @SuppressWarnings("ConstantConditions")
     void findRow() {
-        TableCellAddress address = new TableCellAddress(1, 0);
+        TableCellAddress address = TableCellAddress.of(1, 0);
         EmptyTableRow row = new EmptyTableRow(table, address.getRow());
         MutableTableRow mutableRow = new MutableTableRow<>(table, dao);
         mutableRow.setRow(row);
@@ -287,7 +287,7 @@ void findRow() {
 
     @Test
     void findRowByPrefix() {
-        TableCellAddress address = new TableCellAddress(1, 0);
+        TableCellAddress address = TableCellAddress.of(1, 0);
         when(report.findByPrefix("row value")).thenReturn(address);
 
         table.findRowByPrefix("row value");
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
new file mode 100644
index 0000000..a4c3e30
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
@@ -0,0 +1,372 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.function.Predicate;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate;
+import static org.spacious_team.table_wrapper.api.TableCellAddress.NOT_FOUND;
+import static org.spacious_team.table_wrapper.api.TableCellRange.EMPTY_RANGE;
+
+@ExtendWith(MockitoExtension.class)
+class ReportPageTest {
+
+    Object value = new Object();
+    TableCellAddress address1 = TableCellAddress.of(1, 2);
+    TableCellAddress address2 = TableCellAddress.of(3, 4);
+    String prefix1 = "A";
+    String prefix2 = "B";
+    Predicate predicate1 = getCellStringValueIgnoreCasePrefixPredicate(prefix1);
+    Predicate predicate2 = getCellStringValueIgnoreCasePrefixPredicate(prefix2);
+    @Mock
+    ReportPageRow row1;
+    @Mock
+    ReportPageRow row2;
+    @Spy
+    ReportPage reportPage;
+
+    @BeforeEach
+    void setUp() {
+    }
+
+    @Test
+    void find1() {
+        reportPage.find(value);
+        verify(reportPage).find(value, 0);
+    }
+
+    @Test
+    void find2() {
+        reportPage.find(value, 1);
+        verify(reportPage).find(value, 1, Integer.MAX_VALUE);
+    }
+
+    @Test
+    void find3() {
+        reportPage.find(value, 1, 2);
+        verify(reportPage).find(value, 1, 2, 0, Integer.MAX_VALUE);
+    }
+
+    @Test
+    void find4() {
+        reportPage.find(predicate1);
+        verify(reportPage).find(0, predicate1);
+    }
+
+    @Test
+    void find5() {
+        reportPage.find(1, predicate1);
+        verify(reportPage).find(1, Integer.MAX_VALUE, predicate1);
+    }
+
+    @Test
+    void find6() {
+        reportPage.find(1, 2, predicate1);
+        verify(reportPage).find(1, 2, 0, Integer.MAX_VALUE, predicate1);
+    }
+
+    @Test
+    void findByPrefix1() {
+        reportPage.findByPrefix(prefix1);
+        verify(reportPage).findByPrefix(prefix1, 0);
+    }
+
+    @Test
+    void findByPrefix2() {
+        reportPage.findByPrefix(prefix1, 1);
+        verify(reportPage).findByPrefix(prefix1, 1, Integer.MAX_VALUE);
+    }
+
+    @Test
+    void testFindByPrefix() {
+        reportPage.findByPrefix(prefix1, 1, 2);
+        verify(reportPage).findByPrefix(prefix1, 1, 2, 0, Integer.MAX_VALUE);
+    }
+
+    @Test
+    void testFindByPrefixWithNull() {
+        assertSame(
+                NOT_FOUND,
+                reportPage.findByPrefix(null, 1, 2, 3, 4));
+        assertSame(
+                NOT_FOUND,
+                reportPage.findByPrefix("", 1, 2, 3, 4));
+    }
+
+    @Test
+    void testFindByPrefix1() {
+        reportPage.findByPrefix(prefix1, 1, 2, 3, 4);
+        verify(reportPage).find(1, 2, 3, 4, predicate1);
+    }
+
+    @Test
+    void getNextColumnValueNull() {
+        String prefix = "test";
+        doReturn(address1).when(reportPage).findByPrefix(prefix);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(null);
+
+        @Nullable
+        Object result = reportPage.getNextColumnValue(prefix);
+
+        assertNull(result);
+        verify(reportPage).findByPrefix(prefix);
+        verify(reportPage).getRow(address1.getRow());
+    }
+
+    @Test
+    void getNextColumnValue() {
+        String prefix = "test";
+        doReturn(address1).when(reportPage).findByPrefix(prefix);
+        ReportPageRow row = TableColumnHelperTest.getRow();
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row);
+
+        @Nullable
+        Object result = reportPage.getNextColumnValue(prefix);
+
+        assertEquals(1.23, result);
+        verify(reportPage).findByPrefix(prefix);
+        verify(reportPage).getRow(address1.getRow());
+    }
+
+    @Test
+    void getCellNullReturn() {
+        assertNull(reportPage.getCell(address1));
+        verify(reportPage).getRow(address1.getRow());
+    }
+
+    @Test
+    void getCell() {
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row1);
+
+        reportPage.getCell(address1);
+
+        verify(reportPage).getRow(address1.getRow());
+        verify(row1).getCell(address1.getColumn());
+    }
+
+    @Test
+    void getTableCellRangeWithNullArgs() {
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange(null, 2, "xyz"));
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange("", 2, "xyz"));
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange("xyz", 2, null));
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange("xyz", 2, ""));
+    }
+
+    @Test
+    void getTableCellRange() {
+        doReturn(null).when(reportPage).getTableCellRange(predicate1, 2, predicate2);
+        reportPage.getTableCellRange(prefix1, 2, prefix2);
+        verify(reportPage).getTableCellRange(predicate1, 2, predicate2);
+    }
+
+    @Test
+    void testGetTableCellRangeWithNullPredicates() {
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange(null, 2, cell -> false));
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange(cell -> false, 2, null));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnEmptyRange1() {
+        doReturn(NOT_FOUND).when(reportPage).find(predicate1);
+
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange(predicate1, 2, cell -> true));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnEmptyRange2() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row1);
+        doReturn(NOT_FOUND).when(reportPage).find(anyInt(), eq(predicate2));
+
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange(predicate1, 2, predicate2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnExceptionally1() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(null);
+
+        assertThrows(
+                NullPointerException.class,
+                () -> reportPage.getTableCellRange(predicate1, 2, predicate2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnExceptionally2() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row1);
+        doReturn(address2).when(reportPage).find(anyInt(), eq(predicate2));
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address2.getRow())).thenReturn(null);
+
+        assertThrows(
+                NullPointerException.class,
+                () -> reportPage.getTableCellRange(predicate1, 2, predicate2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnOk() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row1);
+        doReturn(address2).when(reportPage).find(anyInt(), eq(predicate2));
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address2.getRow())).thenReturn(row2);
+        when(row1.getFirstCellNum()).thenReturn(10);
+        when(row2.getLastCellNum()).thenReturn(20);
+
+        assertEquals(
+                TableCellRange.of(address1.getRow(), address2.getRow(), row1.getFirstCellNum(), row2.getLastCellNum()),
+                reportPage.getTableCellRange(predicate1, 2, predicate2));
+    }
+
+    @Test
+    void getTableCellRangeWithNullArgs2() {
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange((String) null, 2));
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange("", 2));
+    }
+
+    @Test
+    void getTableCellRange2() {
+        doReturn(null).when(reportPage).getTableCellRange(predicate1, 2);
+        reportPage.getTableCellRange(prefix1, 2);
+        verify(reportPage).getTableCellRange(predicate1, 2);
+    }
+
+    @Test
+    void testGetTableCellRangeWithNullPredicates2() {
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange((Predicate) null, 2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnEmptyRange12() {
+        doReturn(NOT_FOUND).when(reportPage).find(predicate1);
+
+        assertSame(
+                EMPTY_RANGE,
+                reportPage.getTableCellRange(predicate1, 2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnExceptionally21() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(null);
+
+        assertThrows(
+                NullPointerException.class,
+                () -> reportPage.getTableCellRange(predicate1, 2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnOkLastRowNotFound() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row1);
+        doReturn(-1).when(reportPage).findEmptyRow(anyInt());
+        int lastRowNum = 3;
+        when(reportPage.getLastRowNum()).thenReturn(lastRowNum);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(lastRowNum)).thenReturn(row2);
+        when(row1.getFirstCellNum()).thenReturn(10);
+        when(row2.getLastCellNum()).thenReturn(20);
+
+        assertEquals(
+                TableCellRange.of(address1.getRow(), lastRowNum, row1.getFirstCellNum(), row2.getLastCellNum()),
+                reportPage.getTableCellRange(predicate1, 2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnOkExcludeEmptyRow() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row1);
+        int emptyRowNum = 3;
+        doReturn(emptyRowNum).when(reportPage).findEmptyRow(anyInt());
+        int lastRowNum = 4;
+        when(reportPage.getLastRowNum()).thenReturn(lastRowNum);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(emptyRowNum - 1)).thenReturn(row2);
+        when(row1.getFirstCellNum()).thenReturn(10);
+        when(row2.getLastCellNum()).thenReturn(20);
+
+        assertTrue(emptyRowNum > address1.getRow());
+        assertEquals(
+                TableCellRange.of(address1.getRow(), emptyRowNum - 1, row1.getFirstCellNum(), row2.getLastCellNum()),
+                reportPage.getTableCellRange(predicate1, 2));
+    }
+
+    @Test
+    void testGetTableCellRangeReturnOkEmptyRowGreaterThanStartAddress() {
+        doReturn(address1).when(reportPage).find(predicate1);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row1);
+        int emptyRowNum = 1;
+        doReturn(emptyRowNum).when(reportPage).findEmptyRow(anyInt());
+        int lastRowNum = 4;
+        when(reportPage.getLastRowNum()).thenReturn(lastRowNum);
+        when(row1.getFirstCellNum()).thenReturn(10);
+        when(row1.getLastCellNum()).thenReturn(20);
+
+        assertTrue(emptyRowNum <= address1.getRow());
+        assertEquals(
+                TableCellRange.of(address1.getRow(), address1.getRow(), row1.getFirstCellNum(), row1.getLastCellNum()),
+                reportPage.getTableCellRange(predicate1, 2));
+        verify(reportPage, times(1)).getRow(address1.getRow());
+    }
+}
\ No newline at end of file

From 7b51cf8567050c4e6eb2d75f58f10247bd2f7f91 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 01:05:59 +0300
Subject: [PATCH 50/94] impl ReportPage.findEmptyRow() tests

---
 .../table_wrapper/api/ReportPage.java         |  4 +-
 .../api/ConstantPositionTableColumnTest.java  |  2 +-
 .../api/MultiLineTableColumnTest.java         |  2 +-
 .../api/OptionalTableColumnTest.java          |  2 +-
 .../api/PatternTableColumnTest.java           |  2 +-
 .../api/RelativePositionTableColumnTest.java  |  2 +-
 ...Test.java => ReportPageRowHelperTest.java} | 39 +++++-----
 .../table_wrapper/api/ReportPageTest.java     | 71 ++++++++++++++++++-
 8 files changed, 93 insertions(+), 31 deletions(-)
 rename src/test/java/org/spacious_team/table_wrapper/api/{TableColumnHelperTest.java => ReportPageRowHelperTest.java} (77%)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
index 443ede7..1abb692 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
@@ -302,7 +302,7 @@ default int findEmptyRow(int startRow) {
         for (int n = getLastRowNum(); lastRowNum <= n; lastRowNum++) {
             @Nullable ReportPageRow row = getRow(lastRowNum);
             if (row == null || row.getLastCellNum() == -1) {
-                return lastRowNum; // all row cells blank
+                return lastRowNum; // all row's cells are blank
             }
             for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : row) {
                 @Nullable Object value;
@@ -313,7 +313,7 @@ default int findEmptyRow(int startRow) {
                     continue LAST_ROW;
                 }
             }
-            return lastRowNum; // all row cells are blank
+            return lastRowNum; // all row's cells are blank
         }
         return -1;
     }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java
index c854ab6..0814eda 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java
@@ -31,7 +31,7 @@ class ConstantPositionTableColumnTest {
     @ParameterizedTest
     @ValueSource(ints = {0, 1, 10, 20, 1_000_000})
     void getColumnIndex(int columnNum) {
-        ReportPageRow row = TableColumnHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelperTest.getRow();
         ConstantPositionTableColumn column = ConstantPositionTableColumn.of(columnNum);
 
         assertEquals(columnNum, column.getColumnIndex(row));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
index 02a265a..6dca631 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
@@ -29,7 +29,7 @@ class MultiLineTableColumnTest {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow[] rows = TableColumnHelperTest.getThreeRowsHeader();
+        ReportPageRow[] rows = ReportPageRowHelperTest.getThreeRowsHeader();
         assertEquals(2, MultiLineTableColumn.of("one", "a1", "b2").getColumnIndex(rows));
         assertEquals(5, MultiLineTableColumn.of("one", "a2", "b1").getColumnIndex(rows));
         assertEquals(26, MultiLineTableColumn.of("TWO", "A2", "b2").getColumnIndex(rows));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
index 32cb14b..c3bac6e 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
@@ -29,7 +29,7 @@ class OptionalTableColumnTest {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow row = TableColumnHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelperTest.getRow();
         assertEquals(0, OptionalTableColumn.of(PatternTableColumn.of()).getColumnIndex(row));
         assertEquals(9, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(row));
         assertEquals(-1, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(10, row));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
index 4049aeb..8a72e38 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
@@ -41,7 +41,7 @@ void testZeroArg() {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow row = TableColumnHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelperTest.getRow();
         assertEquals(0, PatternTableColumn.of().getColumnIndex(row));
         assertEquals(9, PatternTableColumn.of("test").getColumnIndex(row));
         assertEquals(9, PatternTableColumn.of("WORD").getColumnIndex(row));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
index b73308e..b55cf3f 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
@@ -29,7 +29,7 @@ class RelativePositionTableColumnTest {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow row = TableColumnHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelperTest.getRow();
         assertEquals(2, RelativePositionTableColumn.of(PatternTableColumn.of(), 2).getColumnIndex(row));
         assertEquals(7, RelativePositionTableColumn.of(PatternTableColumn.of("test"), -2).getColumnIndex(row));
 
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelperTest.java
similarity index 77%
rename from src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java
rename to src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelperTest.java
index 0a09399..33e8ccf 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnHelperTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelperTest.java
@@ -24,15 +24,13 @@
 import java.util.Arrays;
 import java.util.Collection;
 
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
-class TableColumnHelperTest {
+final class ReportPageRowHelperTest {
 
     @SuppressWarnings("ConstantConditions")
     static ReportPageRow getRow() {
-        ReportPageRow row = mock(ReportPageRow.class);
-        Collection cells = Arrays.asList(
+        return getRow(0,
                 null,
                 cell(null, 1),
                 cell(123, 2),
@@ -45,8 +43,6 @@ static ReportPageRow getRow() {
                 cell("London\nis the\ncapital\nof Great Britain", 20),
                 cell("The Mac\rnew line", 21),
                 cell("The Windows\r\nnew line", 22));
-        when(row.iterator()).then($ -> cells.iterator());
-        return row;
     }
 
     /**
@@ -59,21 +55,15 @@ static ReportPageRow getRow() {
      * Row 2:  | null | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 |
      * 
      */
+    @SuppressWarnings("ConstantConditions")
     static ReportPageRow[] getThreeRowsHeader() {
         ReportPageRow[] rows = new ReportPageRow[3];
-
-        rows[0] = mock(ReportPageRow.class);
-        when(rows[0].getRowNum()).thenReturn(0);
-        Collection cells0 = Arrays.asList(
+        rows[0] = getRow(0,
                 null,
                 cell("One", 1),
                 cell("Two", 20),
                 cell("Three", 30));
-        when(rows[0].iterator()).then($ -> cells0.iterator());
-
-        rows[1] = mock(ReportPageRow.class);
-        when(rows[1].getRowNum()).thenReturn(1);
-        Collection cells1 = Arrays.asList(
+        rows[1] = getRow(1,
                 null,
                 cell("a1", 1),
                 cell("a2", 5),
@@ -81,11 +71,7 @@ static ReportPageRow[] getThreeRowsHeader() {
                 cell("a2", 15),
                 cell("a1", 21),
                 cell("a2", 25));
-        when(rows[1].iterator()).then($ -> cells1.iterator());
-
-        rows[2] = mock(ReportPageRow.class);
-        when(rows[2].getRowNum()).thenReturn(2);
-        Collection cells2 = Arrays.asList(
+        rows[2] = getRow(2,
                 null,
                 cell("b1", 1),
                 cell("b2", 2),
@@ -99,11 +85,18 @@ static ReportPageRow[] getThreeRowsHeader() {
                 cell("b2", 22),
                 cell("b1", 25),
                 cell("b2", 26));
-        when(rows[2].iterator()).then($ -> cells2.iterator());
         return rows;
     }
 
-    private static TableCellTestImpl cell(Object value, int columnIndex) {
+    static ReportPageRow getRow(int rowNum, TableCell... cells) {
+        ReportPageRow row = mock(ReportPageRow.class);
+        Collection cellsCollection = Arrays.asList(cells);
+        when(row.iterator()).then($ -> cellsCollection.iterator());
+        lenient().when(row.getRowNum()).thenReturn(rowNum);
+        return row;
+    }
+
+    static TableCell cell(Object value, int columnIndex) {
         return new TableCellTestImpl(value, columnIndex);
     }
 
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
index a4c3e30..26a1685 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
@@ -32,6 +32,8 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate;
+import static org.spacious_team.table_wrapper.api.ReportPageRowHelperTest.cell;
+import static org.spacious_team.table_wrapper.api.ReportPageRowHelperTest.getRow;
 import static org.spacious_team.table_wrapper.api.TableCellAddress.NOT_FOUND;
 import static org.spacious_team.table_wrapper.api.TableCellRange.EMPTY_RANGE;
 
@@ -145,7 +147,7 @@ void getNextColumnValueNull() {
     void getNextColumnValue() {
         String prefix = "test";
         doReturn(address1).when(reportPage).findByPrefix(prefix);
-        ReportPageRow row = TableColumnHelperTest.getRow();
+        ReportPageRow row = getRow();
         //noinspection ConstantConditions
         when(reportPage.getRow(address1.getRow())).thenReturn(row);
 
@@ -369,4 +371,71 @@ void testGetTableCellRangeReturnOkEmptyRowGreaterThanStartAddress() {
                 reportPage.getTableCellRange(predicate1, 2));
         verify(reportPage, times(1)).getRow(address1.getRow());
     }
+
+    @Test
+    void findEmptyNotFound() {
+        ReportPageRow row = getRow(1, cell("abc", 0));
+        //noinspection ConstantConditions
+        when(reportPage.getRow(1)).thenReturn(row);
+        when(reportPage.getLastRowNum()).thenReturn(1);
+
+        assertEquals(-1, reportPage.findEmptyRow(1));
+    }
+
+    @Test
+    void findEmptyRowFoundFirst() {
+        //noinspection ConstantConditions
+        when(reportPage.getRow(1)).thenReturn(null);
+        when(reportPage.getLastRowNum()).thenReturn(1000);
+
+        assertEquals(1, reportPage.findEmptyRow(1));
+    }
+
+    @Test
+    void findEmptyRowFoundSecond() {
+        ReportPageRow row = getRow(1, cell("abc", 0));
+        //noinspection ConstantConditions
+        when(reportPage.getRow(1)).thenReturn(row);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(2)).thenReturn(null);
+        when(reportPage.getLastRowNum()).thenReturn(1000);
+
+        assertEquals(2, reportPage.findEmptyRow(1));
+    }
+
+    @Test
+    @SuppressWarnings("ConstantConditions")
+    void findEmptyRowFoundSecondWithNull() {
+        ReportPageRow row1 = getRow(1, cell(null, 0), cell("abc", 1));
+        ReportPageRow row2 = getRow(2, cell(null, 0), cell(null, 1));
+        when(reportPage.getRow(1)).thenReturn(row1);
+        when(reportPage.getRow(2)).thenReturn(row2);
+        when(reportPage.getLastRowNum()).thenReturn(1000);
+
+        assertEquals(2, reportPage.findEmptyRow(1));
+    }
+
+    @Test
+    @SuppressWarnings("ConstantConditions")
+    void findEmptyRowFoundSecondWithNullCell() {
+        ReportPageRow row1 = getRow(1, cell(null, 0), cell("abc", 1));
+        ReportPageRow row2 = getRow(2, null, null);
+        when(reportPage.getRow(1)).thenReturn(row1);
+        when(reportPage.getRow(2)).thenReturn(row2);
+        when(reportPage.getLastRowNum()).thenReturn(1000);
+
+        assertEquals(2, reportPage.findEmptyRow(1));
+    }
+
+    @Test
+    @SuppressWarnings("ConstantConditions")
+    void findEmptyRowFoundSecondWithEmptyString() {
+        ReportPageRow row1 = getRow(1, cell(null, 0), cell("abc", 1));
+        ReportPageRow row2 = getRow(2, cell("", 0), cell("", 20));
+        when(reportPage.getRow(1)).thenReturn(row1);
+        when(reportPage.getRow(2)).thenReturn(row2);
+        when(reportPage.getLastRowNum()).thenReturn(1000);
+
+        assertEquals(2, reportPage.findEmptyRow(1));
+    }
 }
\ No newline at end of file

From ff4ab35c251b0871820d7e89bfd0ba4ec3fecd16 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 01:41:20 +0300
Subject: [PATCH 51/94] impl all ReportPage tests

---
 .../table_wrapper/api/ReportPage.java         |   2 +-
 .../api/TableFactoryRegistry.java             |  12 +-
 .../table_wrapper/api/ReportPageTest.java     | 124 +++++++++++++++++-
 3 files changed, 129 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
index 1abb692..be69405 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
@@ -25,7 +25,7 @@
 import static java.util.Objects.requireNonNull;
 import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate;
 
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "UnusedReturnValue"})
 public interface ReportPage {
 
     /**
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
index 63d0321..36056da 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
@@ -19,12 +19,13 @@
 package org.spacious_team.table_wrapper.api;
 
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static java.util.Collections.unmodifiableCollection;
 
 public class TableFactoryRegistry {
 
-    private static final Collection factories = new HashSet<>();
+    private static final Collection factories = new CopyOnWriteArraySet<>();
 
     public static void add(TableFactory tableFactory) {
         factories.add(tableFactory);
@@ -32,7 +33,7 @@ public static void add(TableFactory tableFactory) {
 
     @SuppressWarnings("unused")
     public static Collection getAll() {
-        return Collections.unmodifiableCollection(factories);
+        return unmodifiableCollection(factories);
     }
 
     public static TableFactory get(ReportPage reportPage) {
@@ -41,6 +42,7 @@ public static TableFactory get(ReportPage reportPage) {
                 return factory;
             }
         }
-        throw new IllegalArgumentException("Нет парсера для отчета формата " + reportPage.getClass().getSimpleName());
+        throw new IllegalArgumentException(
+                "No factory registered for report page of type " + reportPage.getClass().getSimpleName());
     }
 }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
index 26a1685..a741458 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
@@ -19,7 +19,7 @@
 package org.spacious_team.table_wrapper.api;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
@@ -40,6 +40,8 @@
 @ExtendWith(MockitoExtension.class)
 class ReportPageTest {
 
+    @SuppressWarnings("NotNullFieldNotInitialized")
+    static TableFactory tableFactory;
     Object value = new Object();
     TableCellAddress address1 = TableCellAddress.of(1, 2);
     TableCellAddress address2 = TableCellAddress.of(3, 4);
@@ -47,6 +49,12 @@ class ReportPageTest {
     String prefix2 = "B";
     Predicate predicate1 = getCellStringValueIgnoreCasePrefixPredicate(prefix1);
     Predicate predicate2 = getCellStringValueIgnoreCasePrefixPredicate(prefix2);
+    String tableName = "table name";
+    String headerRow = "header row";
+    String tableFooterString = "footer";
+    Predicate tableNameFinder = cell -> true;
+    Predicate tableFooterFinder = cell -> true;
+    Class tableHeader = TableHeader.class;
     @Mock
     ReportPageRow row1;
     @Mock
@@ -54,8 +62,11 @@ class ReportPageTest {
     @Spy
     ReportPage reportPage;
 
-    @BeforeEach
-    void setUp() {
+    @BeforeAll
+    static void beforeAll() {
+        tableFactory = mock(TableFactory.class);
+        when(tableFactory.canHandle(any())).thenReturn(true);
+        TableFactoryRegistry.add(tableFactory);
     }
 
     @Test
@@ -438,4 +449,111 @@ void findEmptyRowFoundSecondWithEmptyString() {
 
         assertEquals(2, reportPage.findEmptyRow(1));
     }
+
+
+    @Test
+    void create() {
+        reportPage.create(tableName, tableFooterString, tableHeader);
+        verify(tableFactory).create(reportPage, tableName, tableFooterString, tableHeader);
+    }
+
+    @Test
+    void testCreate() {
+        reportPage.create(tableName, tableHeader);
+        verify(tableFactory).create(reportPage, tableName, tableHeader);
+    }
+
+    @Test
+    void testCreate1() {
+        reportPage.create(tableName, tableFooterString, tableHeader, 2);
+        verify(tableFactory).create(reportPage, tableName, tableFooterString, tableHeader, 2);
+    }
+
+    @Test
+    void testCreate2() {
+        reportPage.create(tableName, tableHeader, 2);
+        verify(tableFactory).create(reportPage, tableName, tableHeader, 2);
+    }
+
+    @Test
+    void testCreate3() {
+        reportPage.create(tableNameFinder, tableFooterFinder, tableHeader);
+        verify(tableFactory).create(reportPage, tableNameFinder, tableFooterFinder, tableHeader);
+    }
+
+    @Test
+    void testCreate4() {
+        reportPage.create(tableNameFinder, tableHeader);
+        verify(tableFactory).create(reportPage, tableNameFinder, tableHeader);
+    }
+
+    @Test
+    void testCreate5() {
+        reportPage.create(tableNameFinder, tableFooterFinder, tableHeader, 2);
+        verify(tableFactory).create(reportPage, tableNameFinder, tableFooterFinder, tableHeader, 2);
+    }
+
+    @Test
+    void testCreate6() {
+        reportPage.create(tableNameFinder, tableHeader, 2);
+        verify(tableFactory).create(reportPage, tableNameFinder, tableHeader, 2);
+    }
+
+    @Test
+    void createNameless() {
+        reportPage.createNameless(headerRow, tableFooterString, tableHeader);
+        verify(tableFactory).createNameless(reportPage, headerRow, tableFooterString, tableHeader);
+    }
+
+    @Test
+    void testCreateNameless() {
+        reportPage.createNameless(headerRow, tableHeader);
+        verify(tableFactory).createNameless(reportPage, headerRow, tableHeader);
+    }
+
+    @Test
+    void testCreateNameless1() {
+        reportPage.createNameless(tableName, headerRow, tableFooterString, tableHeader, 2);
+        verify(tableFactory).createNameless(reportPage, tableName, headerRow, tableFooterString, tableHeader, 2);
+    }
+
+    @Test
+    void testCreateNameless2() {
+        reportPage.createNameless(tableName, headerRow, tableHeader, 2);
+        verify(tableFactory).createNameless(reportPage, tableName, headerRow, tableHeader, 2);
+    }
+
+    @Test
+    void testCreateNameless3() {
+        reportPage.createNameless(tableNameFinder, tableFooterFinder, tableHeader);
+        verify(tableFactory).createNameless(reportPage, tableNameFinder, tableFooterFinder, tableHeader);
+    }
+
+    @Test
+    void testCreateNameless4() {
+        reportPage.createNameless(tableNameFinder, tableHeader);
+        verify(tableFactory).createNameless(reportPage, tableNameFinder, tableHeader);
+    }
+
+    @Test
+    void testCreateNameless5() {
+        reportPage.createNameless(tableName, tableNameFinder, tableFooterFinder, tableHeader, 2);
+        verify(tableFactory).createNameless(reportPage, tableName, tableNameFinder, tableFooterFinder, tableHeader, 2);
+    }
+
+    @Test
+    void testCreateNameless6() {
+        reportPage.createNameless(tableName, tableNameFinder, tableHeader, 2);
+        verify(tableFactory).createNameless(reportPage, tableName, tableNameFinder, tableHeader, 2);
+    }
+    
+
+    private enum TableHeader implements TableHeaderColumn {
+        ;
+
+        @Override
+        public TableColumn getColumn() {
+            throw new UnsupportedOperationException();
+        }
+    }
 }
\ No newline at end of file

From 38bc643441395f3b31acc0d7986e4e0ae7a49029 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 01:50:55 +0300
Subject: [PATCH 52/94] impl ReportPageHelper tests

---
 .../table_wrapper/api/ReportPageHelper.java   |  2 +-
 .../api/ReportPageHelperTest.java             | 58 +++++++++++++++++++
 2 files changed, 59 insertions(+), 1 deletion(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/ReportPageHelperTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
index 3156b0e..2e6ac3a 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
@@ -35,7 +35,7 @@ static Predicate getCellStringValueIgnoreCasePrefixPredicate(String pref
 
     @ToString
     @EqualsAndHashCode
-    private static class StringIgnoreCasePrefixPredicate implements Predicate {
+    final static class StringIgnoreCasePrefixPredicate implements Predicate {
         private final String lowercasePrefix;
 
         private StringIgnoreCasePrefixPredicate(String prefix) {
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageHelperTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageHelperTest.java
new file mode 100644
index 0000000..8e36ac1
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageHelperTest.java
@@ -0,0 +1,58 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+import org.spacious_team.table_wrapper.api.ReportPageHelper.StringIgnoreCasePrefixPredicate;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate;
+
+class ReportPageHelperTest {
+
+    @Test
+    void test() {
+        assertTrue(getCellStringValueIgnoreCasePrefixPredicate("First").test("First second"));
+        assertTrue(getCellStringValueIgnoreCasePrefixPredicate("First").test("first second"));
+        assertTrue(getCellStringValueIgnoreCasePrefixPredicate("first").test("First second"));
+        assertTrue(getCellStringValueIgnoreCasePrefixPredicate("first").test("first second"));
+        assertTrue(getCellStringValueIgnoreCasePrefixPredicate("FIRST").test("first second"));
+        assertFalse(getCellStringValueIgnoreCasePrefixPredicate("First").test("One two"));
+        //noinspection ConstantConditions
+        assertFalse(getCellStringValueIgnoreCasePrefixPredicate("First").test(null));
+        assertFalse(getCellStringValueIgnoreCasePrefixPredicate("First").test(1));
+        assertFalse(getCellStringValueIgnoreCasePrefixPredicate("First").test(1.1));
+        assertFalse(getCellStringValueIgnoreCasePrefixPredicate("First").test(new Object()));
+    }
+
+    @Test
+    void testEqualsAndHashCode() {
+        EqualsVerifier
+                .forClass(StringIgnoreCasePrefixPredicate.class)
+                .verify();
+    }
+
+    @Test
+    void testToString() {
+        assertEquals(
+                "ReportPageHelper.StringIgnoreCasePrefixPredicate(lowercasePrefix=first)",
+                getCellStringValueIgnoreCasePrefixPredicate("First").toString());
+    }
+}
\ No newline at end of file

From 76d662533ce2c2923e2c5bf0281e1075e626b74a Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 01:56:47 +0300
Subject: [PATCH 53/94] impl Table tests

---
 .../table_wrapper/api/Table.java              |  2 +-
 .../table_wrapper/api/TableTest.java          | 57 +++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/Table.java b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
index 38ccfb5..fb1d460 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/Table.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
@@ -28,7 +28,7 @@
 import java.util.function.Function;
 import java.util.stream.Stream;
 
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "UnusedReturnValue"})
 public interface Table extends Iterable {
 
     /**
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableTest.java
new file mode 100644
index 0000000..c91e3d2
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableTest.java
@@ -0,0 +1,57 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.function.Function;
+
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class TableTest {
+
+    @Spy
+    Table table;
+
+    @Test
+    void getData() {
+        Function rowExtractor = Function.identity();
+        table.getData(rowExtractor);
+        verify(table).getData("unknown", rowExtractor);
+    }
+
+    @Test
+    void getDataCollection() {
+        Function> rowExtractor = x -> Set.of();
+        table.getDataCollection(rowExtractor);
+        verify(table).getDataCollection("unknown", rowExtractor);
+    }
+
+    @Test
+    void excludeTotalRow() {
+        table.excludeTotalRow();
+        verify(table).subTable(0, -1);
+    }
+}
\ No newline at end of file

From f11dc72d963d8c457d7d3703ef6c337fd40bdefc Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 02:28:45 +0300
Subject: [PATCH 54/94] impl TableCell tests

---
 .../table_wrapper/api/TableCell.java          |   7 +-
 .../table_wrapper/api/TableCellTest.java      | 206 ++++++++++++++++++
 2 files changed, 211 insertions(+), 2 deletions(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableCellTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java
index 7245598..db46752 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCell.java
@@ -23,7 +23,7 @@
 import java.time.Instant;
 import java.time.LocalDateTime;
 
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "UnusedReturnValue"})
 public interface TableCell {
 
     /**
@@ -31,6 +31,9 @@ public interface TableCell {
      */
     int getColumnIndex();
 
+    /**
+     * @throws RuntimeException if can't extract value
+     */
     @Nullable
     Object getValue();
 
@@ -105,7 +108,7 @@ default long getLongValueOrDefault(long defaultValue) {
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default double getDoubleValue(double defaultValue) {
+    default double getDoubleValueOrDefault(double defaultValue) {
         try {
             return getDoubleValue();
         } catch (Exception e) {
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableCellTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableCellTest.java
new file mode 100644
index 0000000..b617924
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableCellTest.java
@@ -0,0 +1,206 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class TableCellTest {
+
+    @Spy
+    TableCell cell;
+
+    @Test
+    void getValueOrDefault() {
+        cell.getValueOrDefault(new Object());
+        verify(cell).getValue();
+    }
+
+    @Test
+    void getValueOrDefaultExceptionally() {
+        Object expectedDefault = new Object();
+        doThrow(RuntimeException.class).when(cell).getValue();
+
+        @Nullable Object result = cell.getValueOrDefault(expectedDefault);
+
+        assertSame(expectedDefault, result);
+        verify(cell).getValue();
+    }
+
+    @Test
+    void getIntValueOrDefault() {
+        doReturn(1).when(cell).getIntValue();
+
+        int result = cell.getIntValueOrDefault(2);
+
+        assertEquals(1, result);
+        verify(cell).getIntValue();
+    }
+
+    @Test
+    void getIntValueOrDefaultExceptionally() {
+        int expectedDefault = 2;
+        doThrow(RuntimeException.class).when(cell).getIntValue();
+
+        int result = cell.getIntValueOrDefault(expectedDefault);
+
+        assertEquals(expectedDefault, result);
+        verify(cell).getIntValue();
+    }
+
+    @Test
+    void getLongValueOrDefault() {
+        doReturn(1L).when(cell).getLongValue();
+
+        long result = cell.getLongValueOrDefault(2);
+
+        assertEquals(1, result);
+        verify(cell).getLongValue();
+    }
+
+    @Test
+    void getLongValueOrDefaultExceptionally() {
+        long expectedDefault = 2;
+        doThrow(RuntimeException.class).when(cell).getLongValue();
+
+        long result = cell.getLongValueOrDefault(expectedDefault);
+
+        assertEquals(expectedDefault, result);
+        verify(cell).getLongValue();
+    }
+
+    @Test
+    void getDoubleValue() {
+        doReturn(1.0).when(cell).getDoubleValue();
+
+        double result = cell.getDoubleValueOrDefault(2.0);
+
+        assertEquals(1.0, result);
+        verify(cell).getDoubleValue();
+    }
+
+    @Test
+    void getDoubleValueOrDefaultExceptionally() {
+        double expectedDefault = 2.0;
+        doThrow(RuntimeException.class).when(cell).getDoubleValue();
+
+        double result = cell.getDoubleValueOrDefault(expectedDefault);
+
+        assertEquals(expectedDefault, result);
+        verify(cell).getDoubleValue();
+    }
+
+    @Test
+    void getBigDecimalValueOrDefault() {
+        doReturn(BigDecimal.valueOf(1.0)).when(cell).getBigDecimalValue();
+
+        BigDecimal result = cell.getBigDecimalValueOrDefault(BigDecimal.valueOf(2.0));
+
+        assertEquals(BigDecimal.valueOf(1.0), result);
+        verify(cell).getBigDecimalValue();
+    }
+
+    @Test
+    void getBigDecimalValueOrDefaultExceptionally() {
+        BigDecimal expectedDefault = BigDecimal.valueOf(2.0);
+        doThrow(RuntimeException.class).when(cell).getBigDecimalValue();
+
+        BigDecimal result = cell.getBigDecimalValueOrDefault(expectedDefault);
+
+        assertSame(expectedDefault, result);
+        verify(cell).getBigDecimalValue();
+    }
+
+    @Test
+    void getStringValueOrDefault() {
+        doReturn("1").when(cell).getStringValue();
+
+        String result = cell.getStringValueOrDefault("2");
+
+        assertEquals("1", result);
+        verify(cell).getStringValue();
+    }
+
+    @Test
+    void getStringValueOrDefaultExceptionally() {
+        String expectedDefault = "2";
+        doThrow(RuntimeException.class).when(cell).getStringValue();
+
+        String result = cell.getStringValueOrDefault(expectedDefault);
+
+        assertSame(expectedDefault, result);
+        verify(cell).getStringValue();
+    }
+
+    @Test
+    void getInstantValueOrDefault() {
+        Instant expected = Instant.now();
+        doReturn(expected).when(cell).getInstantValue();
+
+        Instant result = cell.getInstantValueOrDefault(Instant.EPOCH);
+
+        assertEquals(expected, result);
+        verify(cell).getInstantValue();
+    }
+
+    @Test
+    void getInstantValueOrDefaultExceptionally() {
+        Instant expectedDefault = Instant.now();
+        doThrow(RuntimeException.class).when(cell).getInstantValue();
+
+        Instant result = cell.getInstantValueOrDefault(expectedDefault);
+
+        assertSame(expectedDefault, result);
+        verify(cell).getInstantValue();
+    }
+
+    @Test
+    void getLocalDateTimeValueOrDefault() {
+        LocalDateTime expected = LocalDateTime.now();
+        doReturn(expected).when(cell).getLocalDateTimeValue();
+
+        LocalDateTime result = cell.getLocalDateTimeValueOrDefault(LocalDateTime.MIN);
+
+        assertEquals(expected, result);
+        verify(cell).getLocalDateTimeValue();
+    }
+
+    @Test
+    void getLocalDateTimeValueOrDefaultExceptionally() {
+        LocalDateTime expectedDefault = LocalDateTime.now();
+        doThrow(RuntimeException.class).when(cell).getLocalDateTimeValue();
+
+        LocalDateTime result = cell.getLocalDateTimeValueOrDefault(expectedDefault);
+
+        assertSame(expectedDefault, result);
+        verify(cell).getLocalDateTimeValue();
+    }
+}
\ No newline at end of file

From 217e196a53f15c3e7e3d25038fb12677b1cbe3e9 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 02:33:21 +0300
Subject: [PATCH 55/94] impl TableCellAddress tests

---
 .../api/TableCellAddressTest.java             | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java

diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
new file mode 100644
index 0000000..31ef814
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
@@ -0,0 +1,49 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class TableCellAddressTest {
+
+    @Test
+    void testConstructor() {
+        assertEquals(1, TableCellAddress.of(1, 2).getRow());
+        assertEquals(2, TableCellAddress.of(1, 2).getColumn());
+    }
+
+    @Test
+    void testEqualsAndHashCode() {
+        EqualsVerifier
+                .forClass(TableCellAddress.class)
+                .suppress(STRICT_INHERITANCE) // no subclass for test
+                .verify();
+    }
+
+    @Test
+    void testToString() {
+        assertEquals(
+                "TableCellAddress(row=1, column=2)",
+                TableCellAddress.of(1, 2).toString());
+    }
+}
\ No newline at end of file

From 6da66232d93b897b5024444d22d1a86d363dc9a3 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Wed, 9 Nov 2022 02:42:04 +0300
Subject: [PATCH 56/94] fix code smells

---
 .../table_wrapper/api/ReportPageHelper.java              | 2 +-
 .../api/ConstantPositionTableColumnTest.java             | 2 +-
 .../table_wrapper/api/MultiLineTableColumnTest.java      | 2 +-
 .../table_wrapper/api/OptionalTableColumnTest.java       | 2 +-
 .../table_wrapper/api/PatternTableColumnTest.java        | 2 +-
 .../api/RelativePositionTableColumnTest.java             | 2 +-
 ...rtPageRowHelperTest.java => ReportPageRowHelper.java} | 9 ++++++---
 .../spacious_team/table_wrapper/api/ReportPageTest.java  | 4 ++--
 8 files changed, 14 insertions(+), 11 deletions(-)
 rename src/test/java/org/spacious_team/table_wrapper/api/{ReportPageRowHelperTest.java => ReportPageRowHelper.java} (93%)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
index 2e6ac3a..455a69c 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageHelper.java
@@ -35,7 +35,7 @@ static Predicate getCellStringValueIgnoreCasePrefixPredicate(String pref
 
     @ToString
     @EqualsAndHashCode
-    final static class StringIgnoreCasePrefixPredicate implements Predicate {
+    static final class StringIgnoreCasePrefixPredicate implements Predicate {
         private final String lowercasePrefix;
 
         private StringIgnoreCasePrefixPredicate(String prefix) {
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java
index 0814eda..a5bdfa9 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ConstantPositionTableColumnTest.java
@@ -31,7 +31,7 @@ class ConstantPositionTableColumnTest {
     @ParameterizedTest
     @ValueSource(ints = {0, 1, 10, 20, 1_000_000})
     void getColumnIndex(int columnNum) {
-        ReportPageRow row = ReportPageRowHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelper.getRow();
         ConstantPositionTableColumn column = ConstantPositionTableColumn.of(columnNum);
 
         assertEquals(columnNum, column.getColumnIndex(row));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
index 6dca631..d27bb96 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
@@ -29,7 +29,7 @@ class MultiLineTableColumnTest {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow[] rows = ReportPageRowHelperTest.getThreeRowsHeader();
+        ReportPageRow[] rows = ReportPageRowHelper.getThreeRowsHeader();
         assertEquals(2, MultiLineTableColumn.of("one", "a1", "b2").getColumnIndex(rows));
         assertEquals(5, MultiLineTableColumn.of("one", "a2", "b1").getColumnIndex(rows));
         assertEquals(26, MultiLineTableColumn.of("TWO", "A2", "b2").getColumnIndex(rows));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
index c3bac6e..2458f6e 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
@@ -29,7 +29,7 @@ class OptionalTableColumnTest {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow row = ReportPageRowHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelper.getRow();
         assertEquals(0, OptionalTableColumn.of(PatternTableColumn.of()).getColumnIndex(row));
         assertEquals(9, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(row));
         assertEquals(-1, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(10, row));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
index 8a72e38..6addd2e 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
@@ -41,7 +41,7 @@ void testZeroArg() {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow row = ReportPageRowHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelper.getRow();
         assertEquals(0, PatternTableColumn.of().getColumnIndex(row));
         assertEquals(9, PatternTableColumn.of("test").getColumnIndex(row));
         assertEquals(9, PatternTableColumn.of("WORD").getColumnIndex(row));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
index b55cf3f..2b199db 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
@@ -29,7 +29,7 @@ class RelativePositionTableColumnTest {
 
     @Test
     void getColumnIndex() {
-        ReportPageRow row = ReportPageRowHelperTest.getRow();
+        ReportPageRow row = ReportPageRowHelper.getRow();
         assertEquals(2, RelativePositionTableColumn.of(PatternTableColumn.of(), 2).getColumnIndex(row));
         assertEquals(7, RelativePositionTableColumn.of(PatternTableColumn.of("test"), -2).getColumnIndex(row));
 
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelperTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelper.java
similarity index 93%
rename from src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelperTest.java
rename to src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelper.java
index 33e8ccf..6a27033 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelperTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelper.java
@@ -19,16 +19,19 @@
 package org.spacious_team.table_wrapper.api;
 
 import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 
 import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collection;
 
+import static lombok.AccessLevel.PRIVATE;
 import static org.mockito.Mockito.*;
 
-final class ReportPageRowHelperTest {
+@RequiredArgsConstructor(access = PRIVATE)
+final class ReportPageRowHelper {
 
-    @SuppressWarnings("ConstantConditions")
+    @SuppressWarnings({"nullness", "ConstantConditions"})
     static ReportPageRow getRow() {
         return getRow(0,
                 null,
@@ -55,7 +58,7 @@ static ReportPageRow getRow() {
      * Row 2:  | null | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 | b1 | b2 |
      * 
      */
-    @SuppressWarnings("ConstantConditions")
+    @SuppressWarnings({"nullness", "ConstantConditions"})
     static ReportPageRow[] getThreeRowsHeader() {
         ReportPageRow[] rows = new ReportPageRow[3];
         rows[0] = getRow(0,
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
index a741458..f5eace5 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
@@ -32,8 +32,8 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 import static org.spacious_team.table_wrapper.api.ReportPageHelper.getCellStringValueIgnoreCasePrefixPredicate;
-import static org.spacious_team.table_wrapper.api.ReportPageRowHelperTest.cell;
-import static org.spacious_team.table_wrapper.api.ReportPageRowHelperTest.getRow;
+import static org.spacious_team.table_wrapper.api.ReportPageRowHelper.cell;
+import static org.spacious_team.table_wrapper.api.ReportPageRowHelper.getRow;
 import static org.spacious_team.table_wrapper.api.TableCellAddress.NOT_FOUND;
 import static org.spacious_team.table_wrapper.api.TableCellRange.EMPTY_RANGE;
 

From 6d4cee4794d959f8a4556ea27f34fc6cb0f26b08 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Thu, 10 Nov 2022 01:35:39 +0300
Subject: [PATCH 57/94] fix TableCellRange.containsRow() bug, add
 TableCellRange tests

---
 .../table_wrapper/api/TableCellRange.java     |  14 +-
 .../api/TableCellAddressTest.java             |   7 +
 .../table_wrapper/api/TableCellRangeTest.java | 176 ++++++++++++++++++
 3 files changed, 190 insertions(+), 7 deletions(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
index 2557546..d0d5d2f 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
@@ -38,12 +38,12 @@ public class TableCellRange {
     private final int firstColumn;
     private final int lastColumn;
 
-    public TableCellRange of(TableCellAddress leftUpperCellAddress, TableCellAddress rightBottomCellAddress) {
+    public static TableCellRange of(TableCellAddress upperLeft, TableCellAddress bottomRight) {
         return of(
-                leftUpperCellAddress.getRow(),
-                rightBottomCellAddress.getRow(),
-                leftUpperCellAddress.getColumn(),
-                rightBottomCellAddress.getColumn());
+                upperLeft.getRow(),
+                bottomRight.getRow(),
+                upperLeft.getColumn(),
+                bottomRight.getColumn());
     }
 
     public boolean contains(TableCellAddress address) {
@@ -51,7 +51,7 @@ public boolean contains(TableCellAddress address) {
     }
 
     public boolean containsRow(int row) {
-        return firstColumn <= row && row <= lastRow;
+        return firstRow <= row && row <= lastRow;
     }
 
     public boolean containsColumn(int column) {
@@ -95,7 +95,7 @@ public TableCellRange addColumnsToRight(int number) {
     private static class EmptyTableCellRange extends TableCellRange {
 
         private EmptyTableCellRange() {
-            super(0, 0, 0, 0);
+            super(-1, -1, -1, -1);
         }
 
         @Override
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
index 31ef814..73e7cc2 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
@@ -23,6 +23,7 @@
 
 import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.spacious_team.table_wrapper.api.TableCellAddress.NOT_FOUND;
 
 class TableCellAddressTest {
 
@@ -32,6 +33,12 @@ void testConstructor() {
         assertEquals(2, TableCellAddress.of(1, 2).getColumn());
     }
 
+    @Test
+    void testNotFoundCell() {
+        assertEquals(-1, NOT_FOUND.getRow());
+        assertEquals(-1, NOT_FOUND.getColumn());
+    }
+
     @Test
     void testEqualsAndHashCode() {
         EqualsVerifier
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java
new file mode 100644
index 0000000..8139565
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java
@@ -0,0 +1,176 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.spacious_team.table_wrapper.api.TableCellRange.EMPTY_RANGE;
+
+class TableCellRangeTest {
+
+    TableCellRange range = TableCellRange.of(1, 2, 3, 4);
+
+    @Test
+    void of1() {
+        assertEquals(1, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+    }
+
+    @Test
+    void of2() {
+        TableCellAddress upperLeft = TableCellAddress.of(1, 3);
+        TableCellAddress bottomRight = TableCellAddress.of(2, 4);
+        TableCellRange range = TableCellRange.of(upperLeft, bottomRight);
+        assertEquals(1, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+    }
+
+    @Test
+    void contains() {
+        TableCellAddress upperLeft = TableCellAddress.of(range.getFirstRow(), range.getFirstColumn());
+        TableCellAddress bottomRight = TableCellAddress.of(range.getLastRow(), range.getLastColumn());
+        assertTrue(range.contains(upperLeft));
+        assertTrue(range.contains(bottomRight));
+        assertTrue(range.contains(TableCellAddress.of(range.getFirstRow(), range.getLastColumn())));
+        assertFalse(range.contains(TableCellAddress.of(range.getFirstRow() - 1, range.getLastColumn())));
+        assertFalse(range.contains(TableCellAddress.of(range.getFirstRow(), range.getFirstColumn() - 1)));
+        assertFalse(range.contains(TableCellAddress.of(range.getFirstRow(), range.getLastColumn() + 1)));
+        assertFalse(range.contains(TableCellAddress.of(range.getLastRow() + 1, range.getLastColumn())));
+    }
+
+    @Test
+    void containsRow() {
+        assertTrue(range.containsRow(range.getFirstRow()));
+        assertTrue(range.containsRow(range.getLastRow()));
+        assertFalse(range.containsRow(range.getFirstRow() - 1));
+        assertFalse(range.containsRow(range.getLastRow() + 1));
+    }
+
+    @Test
+    void containsColumn() {
+        assertTrue(range.containsColumn(range.getFirstColumn()));
+        assertTrue(range.containsColumn(range.getLastColumn()));
+        assertFalse(range.containsColumn(range.getFirstColumn() - 1));
+        assertFalse(range.containsColumn(range.getLastColumn() + 1));
+    }
+
+    @Test
+    void addRowsToTop() {
+        TableCellRange range = this.range.addRowsToTop(1);
+        assertEquals(0, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+
+        range = this.range.addRowsToTop(-1);
+        assertEquals(2, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+    }
+
+    @Test
+    void addRowsToBottom() {
+        TableCellRange range = this.range.addRowsToBottom(1);
+        assertEquals(1, range.getFirstRow());
+        assertEquals(3, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+
+        range = this.range.addRowsToBottom(-1);
+        assertEquals(1, range.getFirstRow());
+        assertEquals(1, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+    }
+
+    @Test
+    void addColumnsToLeft() {
+        TableCellRange range = this.range.addColumnsToLeft(1);
+        assertEquals(1, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(2, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+
+        range = this.range.addColumnsToLeft(-1);
+        assertEquals(1, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(4, range.getFirstColumn());
+        assertEquals(4, range.getLastColumn());
+    }
+
+    @Test
+    void addColumnsToRight() {
+        TableCellRange range = this.range.addColumnsToRight(1);
+        assertEquals(1, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(5, range.getLastColumn());
+
+        range = this.range.addColumnsToRight(-1);
+        assertEquals(1, range.getFirstRow());
+        assertEquals(2, range.getLastRow());
+        assertEquals(3, range.getFirstColumn());
+        assertEquals(3, range.getLastColumn());
+    }
+
+    @Test
+    void testEmptyRange() {
+        assertEquals(-1, EMPTY_RANGE.getFirstRow());
+        assertEquals(-1, EMPTY_RANGE.getLastRow());
+        assertEquals(-1, EMPTY_RANGE.getFirstColumn());
+        assertEquals(-1, EMPTY_RANGE.getLastColumn());
+        assertFalse(EMPTY_RANGE.contains(TableCellAddress.NOT_FOUND));
+        assertFalse(EMPTY_RANGE.contains(TableCellAddress.of(-1, -1)));
+        assertFalse(EMPTY_RANGE.contains(TableCellAddress.of(0, 0)));
+        assertFalse(EMPTY_RANGE.contains(TableCellAddress.of(10, 10)));
+        assertFalse(EMPTY_RANGE.containsRow(-1));
+        assertFalse(EMPTY_RANGE.containsRow(0));
+        assertFalse(EMPTY_RANGE.containsRow(10));
+        assertFalse(EMPTY_RANGE.containsColumn(-1));
+        assertFalse(EMPTY_RANGE.containsColumn(0));
+        assertFalse(EMPTY_RANGE.containsColumn(10));
+        assertSame(EMPTY_RANGE, EMPTY_RANGE.addRowsToTop(1));
+        assertSame(EMPTY_RANGE, EMPTY_RANGE.addRowsToBottom(1));
+        assertSame(EMPTY_RANGE, EMPTY_RANGE.addColumnsToLeft(1));
+        assertSame(EMPTY_RANGE, EMPTY_RANGE.addColumnsToRight(1));
+    }
+
+    @Test
+    void testEqualsAndHashCode() {
+        EqualsVerifier
+                .forClass(TableCellRange.class)
+                .suppress(STRICT_INHERITANCE) // no subclass for test
+                .verify();
+    }
+
+    @Test
+    void testToString() {
+        assertEquals(
+                "TableCellRange(firstRow=1, lastRow=2, firstColumn=3, lastColumn=4)",
+                range.toString());
+    }
+}
\ No newline at end of file

From 5b2dcb3600714aed0ceaf0bd5e0886ef4c741042 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Thu, 10 Nov 2022 02:31:03 +0300
Subject: [PATCH 58/94] refactor TableColumn, add TableColumn tests

---
 .../table_wrapper/api/AbstractTable.java      |  9 ++--
 .../table_wrapper/api/AnyOfTableColumn.java   |  5 +-
 .../api/MultiLineTableColumn.java             |  3 +-
 .../api/OptionalTableColumn.java              |  6 ++-
 .../api/OptionalTableColumnNotFound.java      | 26 +++++++++
 .../table_wrapper/api/PatternTableColumn.java |  2 +-
 .../table_wrapper/api/TableCellAddress.java   |  4 +-
 .../table_wrapper/api/TableCellRange.java     |  4 +-
 .../table_wrapper/api/TableColumn.java        | 11 ++--
 .../api/OptionalTableColumnTest.java          |  9 ++--
 .../api/TableCellAddressTest.java             |  5 +-
 .../table_wrapper/api/TableCellRangeTest.java |  9 ++--
 .../table_wrapper/api/TableColumnTest.java    | 53 +++++++++++++++++++
 13 files changed, 122 insertions(+), 24 deletions(-)
 create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
index 8b482c7..442b486 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
@@ -124,7 +124,11 @@ Map getHeaderDescription(AbstractReportPage reportPage,
                 .map(TableHeaderColumn::getColumn)
                 .toArray(TableColumn[]::new);
         for (TableColumn column : columns) {
-            columnIndices.put(column, column.getColumnIndex(headerRows));
+            try {
+                int columnIndex = column.getColumnIndex(headerRows);
+                columnIndices.put(column, columnIndex);
+            } catch (OptionalTableColumnNotFound ignore) {
+            }
         }
         return unmodifiableMap(columnIndices);
     }
@@ -132,8 +136,7 @@ Map getHeaderDescription(AbstractReportPage reportPage,
     private static IntStream getColumnIndices(Map headerDescription) {
         return headerDescription.values()
                 .stream()
-                .mapToInt(i -> i)
-                .filter(i -> i != TableColumn.NOCOLUMN_INDEX);
+                .mapToInt(i -> i);
     }
 
     public  List getData(Object report, Function rowExtractor) {
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
index 18ad26b..eaf6fa1 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
@@ -45,9 +45,10 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows)
             } catch (RuntimeException ignore) {
             }
         }
-        throw new RuntimeException("Не обнаружен заголовок таблицы, включающий: " + String.join(", ",
+        String expected = String.join(", ",
                 Arrays.stream(columns)
                         .map(TableColumn::toString)
-                        .toArray(String[]::new)));
+                        .toArray(String[]::new));
+        throw new RuntimeException("Header including '" + expected + "' not found");
     }
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
index 87f5347..b0fc6e0 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
@@ -63,8 +63,7 @@ public static MultiLineTableColumn of(String... rowDescriptors) {
     @Override
     public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) {
         if (headerRows.length != rowDescriptors.length) {
-            throw new RuntimeException("Внутренняя ошибка, в таблице ожидается " + rowDescriptors.length +
-                    " строк в заголовке");
+            throw new RuntimeException("Internal error, " + rowDescriptors.length + " rows expected in table header");
         }
         int columnIndex = firstColumnForSearch;
         int i = 0;
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java
index 3b0c423..6769314 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumn.java
@@ -37,6 +37,10 @@ public static OptionalTableColumn of(TableColumn column) {
 
     @Override
     public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) {
-        return anyOfTableColumn.getColumnIndex(firstColumnForSearch, headerRows);
+        try {
+            return anyOfTableColumn.getColumnIndex(firstColumnForSearch, headerRows);
+        } catch (Exception e) {
+            throw new OptionalTableColumnNotFound(e);
+        }
     }
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java
new file mode 100644
index 0000000..411d5d3
--- /dev/null
+++ b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java
@@ -0,0 +1,26 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+public class OptionalTableColumnNotFound extends RuntimeException {
+
+    public OptionalTableColumnNotFound(Throwable t) {
+        super(t);
+    }
+}
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
index d0ccc95..e419f51 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
@@ -78,7 +78,7 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows)
                 }
             }
         }
-        throw new RuntimeException("Не обнаружен заголовок таблицы, включающий слова: " + String.join(", ", words));
+        throw new RuntimeException("Header including '" + String.join(", ", words) + "' not found");
     }
 
     private boolean matches(CharSequence cellText) {
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java
index a6466ee..f1e5537 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCellAddress.java
@@ -23,12 +23,14 @@
 import lombok.RequiredArgsConstructor;
 import lombok.ToString;
 
+import static java.lang.Integer.MIN_VALUE;
+
 @Getter
 @ToString
 @EqualsAndHashCode
 @RequiredArgsConstructor(staticName = "of")
 public class TableCellAddress {
-    public static final TableCellAddress NOT_FOUND = new TableCellAddress(-1, -1);
+    public static final TableCellAddress NOT_FOUND = new TableCellAddress(MIN_VALUE, MIN_VALUE);
 
     private final int row;
     private final int column;
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
index d0d5d2f..bf7455a 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableCellRange.java
@@ -23,6 +23,8 @@
 import lombok.RequiredArgsConstructor;
 import lombok.ToString;
 
+import static java.lang.Integer.MIN_VALUE;
+
 /**
  * Zero-based table cell range
  */
@@ -95,7 +97,7 @@ public TableCellRange addColumnsToRight(int number) {
     private static class EmptyTableCellRange extends TableCellRange {
 
         private EmptyTableCellRange() {
-            super(-1, -1, -1, -1);
+            super(MIN_VALUE, MIN_VALUE, MIN_VALUE, MIN_VALUE);
         }
 
         @Override
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java
index 0b161e8..7d77c84 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java
@@ -19,22 +19,25 @@
 package org.spacious_team.table_wrapper.api;
 
 public interface TableColumn {
-    int NOCOLUMN_INDEX = -1;
-    TableColumn NOCOLUMN = (i, j) -> NOCOLUMN_INDEX;
     TableColumn LEFTMOST_COLUMN = (firstColumnForSearch, $) -> firstColumnForSearch;
+    TableColumn NOCOLUMN = (i, j) -> {
+        throw new RuntimeException("No column impl");
+    };
 
     /**
      * @param headerRows header rows
      * @return column index of table
+     * @throws RuntimeException if column not found
      */
     default int getColumnIndex(ReportPageRow... headerRows) {
         return getColumnIndex(0, headerRows);
     }
 
     /**
-     * @param firstColumnForSearch start result column search from this index
-     * @param headerRows header rows
+     * @param firstColumnForSearch start column index for search from
+     * @param headerRows           header rows
      * @return column index of table
+     * @throws RuntimeException if column not found
      */
     int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows);
 }
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
index 2458f6e..0f022d0 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/OptionalTableColumnTest.java
@@ -23,7 +23,8 @@
 
 import static nl.jqno.equalsverifier.Warning.ALL_FIELDS_SHOULD_BE_USED;
 import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class OptionalTableColumnTest {
 
@@ -32,8 +33,10 @@ void getColumnIndex() {
         ReportPageRow row = ReportPageRowHelper.getRow();
         assertEquals(0, OptionalTableColumn.of(PatternTableColumn.of()).getColumnIndex(row));
         assertEquals(9, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(row));
-        assertEquals(-1, OptionalTableColumn.of(PatternTableColumn.of("test")).getColumnIndex(10, row));
-        assertEquals(-1, OptionalTableColumn.of(PatternTableColumn.of("not found")).getColumnIndex(row));
+        OptionalTableColumn column1 = OptionalTableColumn.of(PatternTableColumn.of("test"));
+        OptionalTableColumn column2 =  OptionalTableColumn.of(PatternTableColumn.of("not found"));
+        assertThrows(OptionalTableColumnNotFound.class, () -> column1.getColumnIndex(10, row));
+        assertThrows(OptionalTableColumnNotFound.class, () -> column2.getColumnIndex(row));
     }
 
     @Test
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
index 73e7cc2..51d6049 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableCellAddressTest.java
@@ -21,6 +21,7 @@
 import nl.jqno.equalsverifier.EqualsVerifier;
 import org.junit.jupiter.api.Test;
 
+import static java.lang.Integer.MIN_VALUE;
 import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.spacious_team.table_wrapper.api.TableCellAddress.NOT_FOUND;
@@ -35,8 +36,8 @@ void testConstructor() {
 
     @Test
     void testNotFoundCell() {
-        assertEquals(-1, NOT_FOUND.getRow());
-        assertEquals(-1, NOT_FOUND.getColumn());
+        assertEquals(MIN_VALUE, NOT_FOUND.getRow());
+        assertEquals(MIN_VALUE, NOT_FOUND.getColumn());
     }
 
     @Test
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java
index 8139565..7b42c4b 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableCellRangeTest.java
@@ -21,6 +21,7 @@
 import nl.jqno.equalsverifier.EqualsVerifier;
 import org.junit.jupiter.api.Test;
 
+import static java.lang.Integer.MIN_VALUE;
 import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.spacious_team.table_wrapper.api.TableCellRange.EMPTY_RANGE;
@@ -139,10 +140,10 @@ void addColumnsToRight() {
 
     @Test
     void testEmptyRange() {
-        assertEquals(-1, EMPTY_RANGE.getFirstRow());
-        assertEquals(-1, EMPTY_RANGE.getLastRow());
-        assertEquals(-1, EMPTY_RANGE.getFirstColumn());
-        assertEquals(-1, EMPTY_RANGE.getLastColumn());
+        assertEquals(MIN_VALUE, EMPTY_RANGE.getFirstRow());
+        assertEquals(MIN_VALUE, EMPTY_RANGE.getLastRow());
+        assertEquals(MIN_VALUE, EMPTY_RANGE.getFirstColumn());
+        assertEquals(MIN_VALUE, EMPTY_RANGE.getLastColumn());
         assertFalse(EMPTY_RANGE.contains(TableCellAddress.NOT_FOUND));
         assertFalse(EMPTY_RANGE.contains(TableCellAddress.of(-1, -1)));
         assertFalse(EMPTY_RANGE.contains(TableCellAddress.of(0, 0)));
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java
new file mode 100644
index 0000000..b6e167e
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java
@@ -0,0 +1,53 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class TableColumnTest {
+
+    @Spy
+    TableColumn column;
+    @Mock
+    ReportPageRow row;
+
+    @Test
+    void getColumnIndex() {
+        column.getColumnIndex(row);
+        verify(column).getColumnIndex(0, row);
+    }
+
+    @Test
+    void testConstants() {
+        assertThrows(RuntimeException.class, () -> TableColumn.NOCOLUMN.getColumnIndex(0));
+        assertThrows(RuntimeException.class, () -> TableColumn.NOCOLUMN.getColumnIndex(1));
+        assertThrows(RuntimeException.class, () -> TableColumn.NOCOLUMN.getColumnIndex(-2));
+        assertEquals(0, TableColumn.LEFTMOST_COLUMN.getColumnIndex(0));
+        assertEquals(1, TableColumn.LEFTMOST_COLUMN.getColumnIndex(1));
+        assertEquals(-1, TableColumn.LEFTMOST_COLUMN.getColumnIndex(-1));
+    }
+}
\ No newline at end of file

From 4ecc7f5668cb59bb8d3c6c4d93ca743b0af4287c Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Thu, 10 Nov 2022 02:37:30 +0300
Subject: [PATCH 59/94] impl TableColumnNotFound

---
 .../table_wrapper/api/AnyOfTableColumn.java   |  2 +-
 .../api/MultiLineTableColumn.java             |  2 +-
 .../api/OptionalTableColumnNotFound.java      |  2 +-
 .../table_wrapper/api/PatternTableColumn.java |  2 +-
 .../table_wrapper/api/TableColumn.java        |  6 ++--
 .../api/TableColumnNotFound.java              | 30 +++++++++++++++++++
 .../api/AnyOfTableColumnTest.java             |  4 +--
 .../api/MultiLineTableColumnTest.java         |  8 ++---
 .../api/PatternTableColumnTest.java           | 10 +++----
 .../api/RelativePositionTableColumnTest.java  |  4 +--
 .../table_wrapper/api/TableColumnTest.java    |  6 ++--
 11 files changed, 53 insertions(+), 23 deletions(-)
 create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/TableColumnNotFound.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
index eaf6fa1..52b3cf1 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
@@ -49,6 +49,6 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows)
                 Arrays.stream(columns)
                         .map(TableColumn::toString)
                         .toArray(String[]::new));
-        throw new RuntimeException("Header including '" + expected + "' not found");
+        throw new TableColumnNotFound("Header including '" + expected + "' not found");
     }
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
index b0fc6e0..349cee8 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/MultiLineTableColumn.java
@@ -63,7 +63,7 @@ public static MultiLineTableColumn of(String... rowDescriptors) {
     @Override
     public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) {
         if (headerRows.length != rowDescriptors.length) {
-            throw new RuntimeException("Internal error, " + rowDescriptors.length + " rows expected in table header");
+            throw new TableColumnNotFound("Internal error, " + rowDescriptors.length + " rows expected in table header");
         }
         int columnIndex = firstColumnForSearch;
         int i = 0;
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java
index 411d5d3..f7f3f10 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/OptionalTableColumnNotFound.java
@@ -18,7 +18,7 @@
 
 package org.spacious_team.table_wrapper.api;
 
-public class OptionalTableColumnNotFound extends RuntimeException {
+public class OptionalTableColumnNotFound extends TableColumnNotFound {
 
     public OptionalTableColumnNotFound(Throwable t) {
         super(t);
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
index e419f51..edab332 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
@@ -78,7 +78,7 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows)
                 }
             }
         }
-        throw new RuntimeException("Header including '" + String.join(", ", words) + "' not found");
+        throw new TableColumnNotFound("Header including '" + String.join(", ", words) + "' not found");
     }
 
     private boolean matches(CharSequence cellText) {
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java
index 7d77c84..e98bfbe 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableColumn.java
@@ -21,13 +21,13 @@
 public interface TableColumn {
     TableColumn LEFTMOST_COLUMN = (firstColumnForSearch, $) -> firstColumnForSearch;
     TableColumn NOCOLUMN = (i, j) -> {
-        throw new RuntimeException("No column impl");
+        throw new TableColumnNotFound("No column impl");
     };
 
     /**
      * @param headerRows header rows
      * @return column index of table
-     * @throws RuntimeException if column not found
+     * @throws TableColumnNotFound if column not found
      */
     default int getColumnIndex(ReportPageRow... headerRows) {
         return getColumnIndex(0, headerRows);
@@ -37,7 +37,7 @@ default int getColumnIndex(ReportPageRow... headerRows) {
      * @param firstColumnForSearch start column index for search from
      * @param headerRows           header rows
      * @return column index of table
-     * @throws RuntimeException if column not found
+     * @throws TableColumnNotFound if column not found
      */
     int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows);
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableColumnNotFound.java b/src/main/java/org/spacious_team/table_wrapper/api/TableColumnNotFound.java
new file mode 100644
index 0000000..03e5e51
--- /dev/null
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableColumnNotFound.java
@@ -0,0 +1,30 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+public class TableColumnNotFound extends RuntimeException {
+
+    public TableColumnNotFound(String message) {
+        super(message);
+    }
+
+    public TableColumnNotFound(Throwable t) {
+        super(t);
+    }
+}
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java
index d12ffa9..8f690f6 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AnyOfTableColumnTest.java
@@ -42,8 +42,8 @@ void getColumnIndex() {
         assertEquals(10, column1.getColumnIndex(10));
         assertEquals(0, column2.getColumnIndex());
         assertEquals(10, column2.getColumnIndex(10));
-        assertThrows(RuntimeException.class, column3::getColumnIndex);
-        assertThrows(RuntimeException.class, () -> column3.getColumnIndex(10));
+        assertThrows(TableColumnNotFound.class, column3::getColumnIndex);
+        assertThrows(TableColumnNotFound.class, () -> column3.getColumnIndex(10));
     }
 
     @Test
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
index d27bb96..48df79a 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
@@ -35,13 +35,13 @@ void getColumnIndex() {
         assertEquals(26, MultiLineTableColumn.of("TWO", "A2", "b2").getColumnIndex(rows));
 
         TableColumn column1 = MultiLineTableColumn.of("not", "fo", "und");
-        assertThrows(RuntimeException.class, () -> column1.getColumnIndex(rows));
+        assertThrows(TableColumnNotFound.class, () -> column1.getColumnIndex(rows));
         TableColumn column2 = MultiLineTableColumn.of("one");
-        assertThrows(RuntimeException.class, () -> column2.getColumnIndex(rows));
+        assertThrows(TableColumnNotFound.class, () -> column2.getColumnIndex(rows));
         TableColumn column3 = MultiLineTableColumn.of("Three", "a2");
-        assertThrows(RuntimeException.class, () -> column3.getColumnIndex(rows));
+        assertThrows(TableColumnNotFound.class, () -> column3.getColumnIndex(rows));
         TableColumn column4 = MultiLineTableColumn.of("one", "a1", "b2");
-        assertThrows(RuntimeException.class, () -> column4.getColumnIndex(3, rows));
+        assertThrows(TableColumnNotFound.class, () -> column4.getColumnIndex(3, rows));
     }
 
     @Test
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
index 6addd2e..6a97d29 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/PatternTableColumnTest.java
@@ -58,15 +58,15 @@ void getColumnIndex() {
         assertEquals(22, PatternTableColumn.of("windows").getColumnIndex(21, row));
 
         TableColumn column1 = PatternTableColumn.of("windows");
-        assertThrows(RuntimeException.class, () -> column1.getColumnIndex(23, row));
+        assertThrows(TableColumnNotFound.class, () -> column1.getColumnIndex(23, row));
         TableColumn column2 = PatternTableColumn.of("not found");
-        assertThrows(RuntimeException.class, () -> column2.getColumnIndex(row));
+        assertThrows(TableColumnNotFound.class, () -> column2.getColumnIndex(row));
         TableColumn column3 = PatternTableColumn.of("london is");
-        assertThrows(RuntimeException.class, () -> column3.getColumnIndex(row));
+        assertThrows(TableColumnNotFound.class, () -> column3.getColumnIndex(row));
         TableColumn column4 = PatternTableColumn.of("mac new");
-        assertThrows(RuntimeException.class, () -> column4.getColumnIndex(row));
+        assertThrows(TableColumnNotFound.class, () -> column4.getColumnIndex(row));
         TableColumn column5 = PatternTableColumn.of("windows new");
-        assertThrows(RuntimeException.class, () -> column5.getColumnIndex(row));
+        assertThrows(TableColumnNotFound.class, () -> column5.getColumnIndex(row));
     }
 
     @Test
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
index 2b199db..a25e124 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
@@ -34,10 +34,10 @@ void getColumnIndex() {
         assertEquals(7, RelativePositionTableColumn.of(PatternTableColumn.of("test"), -2).getColumnIndex(row));
 
         TableColumn column1 = RelativePositionTableColumn.of(PatternTableColumn.of("test"), 2);
-        assertThrows(RuntimeException.class, () -> column1.getColumnIndex(10, row));
+        assertThrows(TableColumnNotFound.class, () -> column1.getColumnIndex(10, row));
 
         TableColumn column2 = RelativePositionTableColumn.of(PatternTableColumn.of("not found"), 2);
-        assertThrows(RuntimeException.class, () -> column2.getColumnIndex(row));
+        assertThrows(TableColumnNotFound.class, () -> column2.getColumnIndex(row));
     }
 
     @Test
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java
index b6e167e..33a0377 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableColumnTest.java
@@ -43,9 +43,9 @@ void getColumnIndex() {
 
     @Test
     void testConstants() {
-        assertThrows(RuntimeException.class, () -> TableColumn.NOCOLUMN.getColumnIndex(0));
-        assertThrows(RuntimeException.class, () -> TableColumn.NOCOLUMN.getColumnIndex(1));
-        assertThrows(RuntimeException.class, () -> TableColumn.NOCOLUMN.getColumnIndex(-2));
+        assertThrows(TableColumnNotFound.class, () -> TableColumn.NOCOLUMN.getColumnIndex(0));
+        assertThrows(TableColumnNotFound.class, () -> TableColumn.NOCOLUMN.getColumnIndex(1));
+        assertThrows(TableColumnNotFound.class, () -> TableColumn.NOCOLUMN.getColumnIndex(-2));
         assertEquals(0, TableColumn.LEFTMOST_COLUMN.getColumnIndex(0));
         assertEquals(1, TableColumn.LEFTMOST_COLUMN.getColumnIndex(1));
         assertEquals(-1, TableColumn.LEFTMOST_COLUMN.getColumnIndex(-1));

From e033968b64aa0a900fe1d1e2e16409e803159639 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Thu, 10 Nov 2022 22:02:52 +0300
Subject: [PATCH 60/94] fix passive voice typo

---
 .../table_wrapper/api/AbstractTable.java         |  3 ++-
 .../table_wrapper/api/AnyOfTableColumn.java      |  2 +-
 .../table_wrapper/api/CellDataAccessObject.java  | 16 ++++++++--------
 .../table_wrapper/api/MutableTableRow.java       |  2 +-
 .../table_wrapper/api/PatternTableColumn.java    |  2 +-
 .../table_wrapper/api/ReportPage.java            | 12 ++++++------
 .../spacious_team/table_wrapper/api/Table.java   |  4 ++--
 .../table_wrapper/api/TableColumn.java           |  4 ++--
 8 files changed, 23 insertions(+), 22 deletions(-)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
index 442b486..bc221ed 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
@@ -127,7 +127,8 @@ Map getHeaderDescription(AbstractReportPage reportPage,
             try {
                 int columnIndex = column.getColumnIndex(headerRows);
                 columnIndices.put(column, columnIndex);
-            } catch (OptionalTableColumnNotFound ignore) {
+            } catch (OptionalTableColumnNotFound e) {
+                log.debug("Optional header column is not found: {}", column, e);
             }
         }
         return unmodifiableMap(columnIndices);
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
index 52b3cf1..f6bf0e3 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AnyOfTableColumn.java
@@ -49,6 +49,6 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows)
                 Arrays.stream(columns)
                         .map(TableColumn::toString)
                         .toArray(String[]::new));
-        throw new TableColumnNotFound("Header including '" + expected + "' not found");
+        throw new TableColumnNotFound("Header including '" + expected + "' is not found");
     }
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
index 885fcbe..bdd8356 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
@@ -145,7 +145,7 @@ default LocalDateTime getLocalDateTimeValue(C cell, ZoneId zoneId) {
      */
     default int getIntValue(R row, Integer cellIndex) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getIntValue(cell);
     }
 
@@ -154,7 +154,7 @@ default int getIntValue(R row, Integer cellIndex) {
      */
     default long getLongValue(R row, Integer cellIndex) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getLongValue(cell);
     }
 
@@ -163,7 +163,7 @@ default long getLongValue(R row, Integer cellIndex) {
      */
     default double getDoubleValue(R row, Integer cellIndex) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getDoubleValue(cell);
     }
 
@@ -172,7 +172,7 @@ default double getDoubleValue(R row, Integer cellIndex) {
      */
     default BigDecimal getBigDecimalValue(R row, Integer cellIndex) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getBigDecimalValue(cell);
     }
 
@@ -181,7 +181,7 @@ default BigDecimal getBigDecimalValue(R row, Integer cellIndex) {
      */
     default String getStringValue(R row, Integer cellIndex) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getStringValue(cell);
     }
 
@@ -190,7 +190,7 @@ default String getStringValue(R row, Integer cellIndex) {
      */
     default Instant getInstantValue(R row, Integer cellIndex) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getInstantValue(cell);
     }
 
@@ -201,7 +201,7 @@ default Instant getInstantValue(R row, Integer cellIndex) {
      */
     default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getLocalDateTimeValue(cell);
     }
 
@@ -211,7 +211,7 @@ default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex) {
     @SuppressWarnings("UnusedReturnValue")
     default LocalDateTime getLocalDateTimeValue(R row, Integer cellIndex, ZoneId zoneId) {
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        C cell = requireNonNull(getCell(row, cellIndex), "Cell not found");
+        C cell = requireNonNull(getCell(row, cellIndex), "Cell is not found");
         return getLocalDateTimeValue(cell, zoneId);
     }
 }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
index 998a4c2..5f659f6 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
@@ -125,7 +125,7 @@ private int getCellIndex(TableHeaderColumn column) {
         @Nullable Integer cellIndex = table.getHeaderDescription()
                 .get(column.getColumn());
         @SuppressWarnings("nullness")
-        int cell = requireNonNull(cellIndex, "Cell not found");
+        int cell = requireNonNull(cellIndex, "Cell is not found");
         return cell;
     }
 
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
index edab332..298cbee 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
@@ -78,7 +78,7 @@ public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows)
                 }
             }
         }
-        throw new TableColumnNotFound("Header including '" + String.join(", ", words) + "' not found");
+        throw new TableColumnNotFound("Header including '" + String.join(", ", words) + "' is not found");
     }
 
     private boolean matches(CharSequence cellText) {
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
index be69405..9ea1b04 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
@@ -227,13 +227,13 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin
             return TableCellRange.EMPTY_RANGE;
         }
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row not found");
+        ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row is not found");
         TableCellAddress endAddress = find(startAddress.getRow() + headersRowCount + 1, lastRowFinder);
         if (endAddress.equals(TableCellAddress.NOT_FOUND)) {
             return TableCellRange.EMPTY_RANGE;
         }
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        ReportPageRow lastRow = requireNonNull(getRow(endAddress.getRow()), "Row not found");
+        ReportPageRow lastRow = requireNonNull(getRow(endAddress.getRow()), "Row is not found");
         return TableCellRange.of(
                 startAddress.getRow(),
                 endAddress.getRow(),
@@ -266,10 +266,10 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin
             return TableCellRange.EMPTY_RANGE;
         }
         @SuppressWarnings({"nullness", "ConstantConditions"})
-        ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row not found");
+        ReportPageRow firstRow = requireNonNull(getRow(startAddress.getRow()), "Row is not found");
         int emptyRowNum = findEmptyRow(startAddress.getRow() + headersRowCount + 1);
         if (emptyRowNum == -1) {
-            emptyRowNum = getLastRowNum(); // empty row not found, use last row
+            emptyRowNum = getLastRowNum(); // empty row is not found, use last row
         } else if (emptyRowNum <= getLastRowNum()) {
             emptyRowNum--; // exclude empty row
         }
@@ -279,7 +279,7 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin
             lastRow = firstRow;
         } else {
             @SuppressWarnings({"nullness", "ConstantConditions"})
-            ReportPageRow row = requireNonNull(getRow(emptyRowNum), "Row not found");
+            ReportPageRow row = requireNonNull(getRow(emptyRowNum), "Row is not found");
             lastRow = row;
         }
         return TableCellRange.of(
@@ -294,7 +294,7 @@ default TableCellRange getTableCellRange(@Nullable Predicate firstRowFin
      * This implementation generates a huge amount of garbage. May be overridden for improve performance.
      *
      * @param startRow first row for check
-     * @return index of first empty row or -1 if not found
+     * @return index of first empty row or -1 if empty row is not found
      */
     default int findEmptyRow(int startRow) {
         int lastRowNum = startRow;
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/Table.java b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
index fb1d460..c367983 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/Table.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
@@ -74,13 +74,13 @@  List getDataCollection(Object report, Function
Date: Sun, 13 Nov 2022 20:50:20 +0300
Subject: [PATCH 61/94] add TableFactory tests

---
 .../table_wrapper/api/TableFactory.java       |  23 +-
 .../table_wrapper/api/TableFactoryHelper.java |  48 ++++
 .../table_wrapper/api/ReportPageTest.java     |   2 +-
 .../api/TableFactoryHelperTest.java           |  90 +++++++
 .../table_wrapper/api/TableFactoryTest.java   | 247 ++++++++++++++++++
 5 files changed, 388 insertions(+), 22 deletions(-)
 create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/TableFactoryHelper.java
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableFactoryHelperTest.java
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableFactoryTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
index f561fc6..2262a38 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactory.java
@@ -20,8 +20,6 @@
 
 import java.util.function.Predicate;
 
-import static java.util.Objects.requireNonNull;
-
 public interface TableFactory {
 
     boolean canHandle(ReportPage reportPage);
@@ -130,7 +128,7 @@ Table create(ReportPage reportPage,
                  Class headerDescription,
                  int headersRowCount) {
         TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount, lastRowFinder);
-        String tableName = getTableName(reportPage, tableNameFinder, range);
+        String tableName = TableFactoryHelper.getTableName(reportPage, tableNameFinder, range);
         return create(reportPage, tableName, range, headerDescription, headersRowCount);
     }
 
@@ -146,27 +144,10 @@ Table create(ReportPage reportPage,
                  Class headerDescription,
                  int headersRowCount) {
         TableCellRange range = reportPage.getTableCellRange(tableNameFinder, headersRowCount);
-        String tableName = getTableName(reportPage, tableNameFinder, range);
+        String tableName = TableFactoryHelper.getTableName(reportPage, tableNameFinder, range);
         return create(reportPage, tableName, range, headerDescription, headersRowCount);
     }
 
-    private static String getTableName(ReportPage reportPage, Predicate tableNameFinder, TableCellRange range) {
-        if (!range.equals(TableCellRange.EMPTY_RANGE)) {
-            TableCellAddress tableNameCell =
-                    reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder);
-            if (!tableNameCell.equals(TableCellAddress.NOT_FOUND)) {
-                try {
-                    @SuppressWarnings({"nullness", "ConstantConditions"})
-                    TableCell cell = requireNonNull(reportPage.getCell(tableNameCell));
-                    return cell.getStringValue();
-                } catch (Exception ignore) {
-                    return "";
-                }
-            }
-        }
-        return "";
-    }
-
     /**
      * Creates table without name which starts with header and ends with row containing cell with text starting with
      * given string.
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryHelper.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryHelper.java
new file mode 100644
index 0000000..e78356d
--- /dev/null
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryHelper.java
@@ -0,0 +1,48 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import lombok.RequiredArgsConstructor;
+
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import static java.util.Objects.requireNonNull;
+import static lombok.AccessLevel.PRIVATE;
+
+@RequiredArgsConstructor(access = PRIVATE)
+final class TableFactoryHelper {
+
+    static String getTableName(ReportPage reportPage, Predicate tableNameFinder, TableCellRange range) {
+        try {
+            if (!Objects.equals(range, TableCellRange.EMPTY_RANGE)) {
+                TableCellAddress tableNameCell =
+                        reportPage.find(range.getFirstRow(), range.getFirstRow() + 1, tableNameFinder);
+                if (!Objects.equals(tableNameCell, TableCellAddress.NOT_FOUND)) {
+                    @SuppressWarnings({"nullness", "ConstantConditions"})
+                    TableCell cell = requireNonNull(reportPage.getCell(tableNameCell));
+                    return cell.getStringValue();
+                }
+            }
+        } catch (Exception ignore) {
+            return "";
+        }
+        return "";
+    }
+}
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
index f5eace5..08ab286 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
@@ -548,7 +548,7 @@ void testCreateNameless6() {
     }
     
 
-    private enum TableHeader implements TableHeaderColumn {
+    enum TableHeader implements TableHeaderColumn {
         ;
 
         @Override
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryHelperTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryHelperTest.java
new file mode 100644
index 0000000..06299e7
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryHelperTest.java
@@ -0,0 +1,90 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.function.Predicate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+import static org.spacious_team.table_wrapper.api.TableCellRange.EMPTY_RANGE;
+
+@ExtendWith(MockitoExtension.class)
+class TableFactoryHelperTest {
+
+    @Mock
+    ReportPage reportPage;
+    @Mock
+    TableCellRange range;
+    @Mock
+    TableCellAddress address;
+    @Mock
+    TableCell cell;
+    Predicate tableNameFinder = cell -> true;
+
+    @Test
+    void getTableNameFromEmptyRange() {
+        assertEquals("", TableFactoryHelper.getTableName(reportPage, tableNameFinder, EMPTY_RANGE));
+        //noinspection ConstantConditions
+        assertEquals("", TableFactoryHelper.getTableName(reportPage, tableNameFinder, null));
+    }
+
+    @Test
+    void getTableNameWithEmptyCellAddress() {
+        when(range.getFirstRow()).thenReturn(1);
+        when(reportPage.find(1, 2, tableNameFinder)).thenReturn(TableCellAddress.NOT_FOUND);
+
+        assertEquals("", TableFactoryHelper.getTableName(reportPage, tableNameFinder, range));
+    }
+
+    @Test
+    void getTableNameWithNullCellAddress() {
+        when(range.getFirstRow()).thenReturn(1);
+        //noinspection ConstantConditions
+        when(reportPage.find(1, 2, tableNameFinder)).thenReturn(null);
+
+        assertEquals("", TableFactoryHelper.getTableName(reportPage, tableNameFinder, range));
+    }
+
+    @Test
+    void getTableNameWithNullCell() {
+        when(range.getFirstRow()).thenReturn(1);
+        when(reportPage.find(1, 2, tableNameFinder)).thenReturn(address);
+        //noinspection ConstantConditions
+        when(reportPage.getCell(address)).thenReturn(null);
+
+        assertEquals("", TableFactoryHelper.getTableName(reportPage, tableNameFinder, range));
+    }
+
+    @Test
+    void getTableName() {
+        String expected = "test";
+        when(range.getFirstRow()).thenReturn(1);
+        when(reportPage.find(1, 2, tableNameFinder)).thenReturn(address);
+        //noinspection ConstantConditions
+        when(reportPage.getCell(address)).thenReturn(cell);
+        when(cell.getStringValue()).thenReturn(expected);
+
+        assertEquals(expected, TableFactoryHelper.getTableName(reportPage, tableNameFinder, range));
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryTest.java
new file mode 100644
index 0000000..16ad390
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryTest.java
@@ -0,0 +1,247 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.function.Predicate;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class TableFactoryTest {
+
+    @Spy
+    TableFactory tableFactory;
+    @Mock
+    ReportPage reportPage;
+    @Mock
+    TableCellRange tableCellRange;
+    @Mock
+    TableCellRange tableCellRangeAddTop1;
+    String tableName = "table name";
+    String lastRowString = "footer";
+    Predicate tableNameFinder = cell -> true;
+    Predicate lastRowFinder = cell -> true;
+    String firstRowString = "first row";
+    String providedTableName = "provided table name";
+    Predicate firstRowFinder = cell -> true;
+    Class headerDescription = ReportPageTest.TableHeader.class;
+
+    @BeforeEach
+    void setUp() {
+
+    }
+
+    @Test
+    void create() {
+        tableFactory.create(reportPage, tableName, lastRowString, headerDescription);
+        verify(tableFactory).create(reportPage, tableName, lastRowString, headerDescription, 1);
+    }
+
+    @Test
+    void testCreate() {
+        tableFactory.create(reportPage, tableName, headerDescription);
+        verify(tableFactory).create(reportPage, tableName, headerDescription, 1);
+    }
+
+    @Test
+    void testCreate1() {
+        when(reportPage.getTableCellRange(tableName, 2, lastRowString)).thenReturn(tableCellRange);
+
+        tableFactory.create(reportPage, tableName, lastRowString, headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(tableName, 2, lastRowString);
+        verify(tableFactory).create(
+                reportPage,
+                tableName,
+                tableCellRange,
+                headerDescription,
+                2);
+    }
+
+    @Test
+    void testCreate2() {
+        when(reportPage.getTableCellRange(tableName, 2)).thenReturn(tableCellRange);
+
+        tableFactory.create(reportPage, tableName, headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(tableName, 2);
+        verify(tableFactory).create(
+                reportPage,
+                tableName,
+                tableCellRange,
+                headerDescription,
+                2);
+    }
+
+    @Test
+    void testCreate3() {
+        when(reportPage.getTableCellRange(tableNameFinder, 1, lastRowFinder)).thenReturn(tableCellRange);
+        tableFactory.create(reportPage, tableNameFinder, lastRowFinder, headerDescription);
+        verify(tableFactory).create(reportPage, tableNameFinder, lastRowFinder, headerDescription, 1);
+    }
+
+    @Test
+    void testCreate4() {
+        when(reportPage.getTableCellRange(tableNameFinder, 1)).thenReturn(tableCellRange);
+        tableFactory.create(reportPage, tableNameFinder, headerDescription);
+        verify(tableFactory).create(reportPage, tableNameFinder, headerDescription, 1);
+    }
+
+    @Test
+    void testCreate5() {
+        when(reportPage.getTableCellRange(tableNameFinder, 2, lastRowFinder)).thenReturn(tableCellRange);
+
+        tableFactory.create(reportPage, tableNameFinder, lastRowFinder, headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(tableNameFinder, 2, lastRowFinder);
+        verify(tableFactory).create(
+                eq(reportPage),
+                any(String.class),
+                any(TableCellRange.class),
+                eq(headerDescription),
+                eq(2));
+    }
+
+    @Test
+    void testCreate6() {
+        when(reportPage.getTableCellRange(tableNameFinder, 2)).thenReturn(tableCellRange);
+
+        tableFactory.create(reportPage, tableNameFinder, headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(tableNameFinder, 2);
+        verify(tableFactory).create(
+                eq(reportPage),
+                any(String.class),
+                any(TableCellRange.class),
+                eq(headerDescription),
+                eq(2));
+    }
+
+    @Test
+    void createNameless() {
+        when(reportPage.getTableCellRange(firstRowString, 1, lastRowString)).thenReturn(tableCellRange);
+        tableFactory.createNameless(reportPage, firstRowString, lastRowString, headerDescription);
+        verify(tableFactory).createNameless(reportPage, "undefined", firstRowString, lastRowString,
+                headerDescription, 1);
+    }
+
+    @Test
+    void testCreateNameless() {
+        when(reportPage.getTableCellRange(firstRowString, 1)).thenReturn(tableCellRange);
+        tableFactory.createNameless(reportPage, firstRowString, headerDescription);
+        verify(tableFactory).createNameless(reportPage, "undefined", firstRowString,
+                headerDescription, 1);
+    }
+
+    @Test
+    void testCreateNameless1() {
+        when(reportPage.getTableCellRange(firstRowString, 2, lastRowString)).thenReturn(tableCellRange);
+        when(tableCellRange.addRowsToTop(1)).thenReturn(tableCellRangeAddTop1);
+
+        tableFactory.createNameless(reportPage, providedTableName, firstRowString, lastRowString,
+                headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(firstRowString, 2, lastRowString);
+        verify(tableCellRange).addRowsToTop(1);
+        verify(tableFactory).create(
+                reportPage,
+                providedTableName,
+                tableCellRangeAddTop1,
+                headerDescription,
+                2);
+    }
+
+    @Test
+    void testCreateNameless2() {
+        when(reportPage.getTableCellRange(firstRowString, 2)).thenReturn(tableCellRange);
+        when(tableCellRange.addRowsToTop(1)).thenReturn(tableCellRangeAddTop1);
+
+        tableFactory.createNameless(reportPage, providedTableName, firstRowString, headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(firstRowString, 2);
+        verify(tableCellRange).addRowsToTop(1);
+        verify(tableFactory).create(
+                reportPage,
+                providedTableName,
+                tableCellRangeAddTop1,
+                headerDescription,
+                2);
+    }
+
+    @Test
+    void testCreateNameless3() {
+        when(reportPage.getTableCellRange(firstRowFinder, 1, lastRowFinder)).thenReturn(tableCellRange);
+        tableFactory.createNameless(reportPage, firstRowFinder, lastRowFinder, headerDescription);
+        verify(tableFactory).createNameless(reportPage, "undefined", firstRowFinder, lastRowFinder,
+                headerDescription, 1);
+    }
+
+    @Test
+    void testCreateNameless4() {
+        when(reportPage.getTableCellRange(firstRowFinder, 1)).thenReturn(tableCellRange);
+        tableFactory.createNameless(reportPage, firstRowFinder, headerDescription);
+        verify(tableFactory).createNameless(reportPage, "undefined", firstRowFinder,
+                headerDescription, 1);
+    }
+
+    @Test
+    void testCreateNameless5() {
+        when(reportPage.getTableCellRange(firstRowFinder, 2, lastRowFinder)).thenReturn(tableCellRange);
+        when(tableCellRange.addRowsToTop(1)).thenReturn(tableCellRangeAddTop1);
+
+        tableFactory.createNameless(reportPage, providedTableName, firstRowFinder, lastRowFinder,
+                headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(firstRowFinder, 2, lastRowFinder);
+        verify(tableCellRange).addRowsToTop(1);
+        verify(tableFactory).create(
+                reportPage,
+                providedTableName,
+                tableCellRangeAddTop1,
+                headerDescription,
+                2);
+    }
+
+    @Test
+    void testCreateNameless6() {
+        when(reportPage.getTableCellRange(firstRowFinder, 2)).thenReturn(tableCellRange);
+        when(tableCellRange.addRowsToTop(1)).thenReturn(tableCellRangeAddTop1);
+
+        tableFactory.createNameless(reportPage, providedTableName, firstRowFinder, headerDescription, 2);
+
+        verify(reportPage).getTableCellRange(firstRowFinder, 2);
+        verify(tableCellRange).addRowsToTop(1);
+        verify(tableFactory).create(
+                reportPage,
+                providedTableName,
+                tableCellRangeAddTop1,
+                headerDescription,
+                2);
+    }
+}
\ No newline at end of file

From 55f6560d92796fa594816ee8a334e105b18e1a7a Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Sun, 13 Nov 2022 21:19:31 +0300
Subject: [PATCH 62/94] add TableFactoryRegistry tests

---
 .../api/TableFactoryRegistry.java             |  21 +++-
 .../api/TableFactoryRegistryTest.java         | 102 ++++++++++++++++++
 2 files changed, 120 insertions(+), 3 deletions(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
index 36056da..a08e6de 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
@@ -19,24 +19,39 @@
 package org.spacious_team.table_wrapper.api;
 
 import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
 
-import static java.util.Collections.unmodifiableCollection;
+import static java.util.Collections.unmodifiableSet;
 
 public class TableFactoryRegistry {
 
-    private static final Collection factories = new CopyOnWriteArraySet<>();
+    private static final Set factories = new CopyOnWriteArraySet<>();
 
     public static void add(TableFactory tableFactory) {
         factories.add(tableFactory);
     }
 
+    /**
+     * @return {@code true} if factory was removed as a result of this call
+     */
+    public static boolean remove(TableFactory tableFactory) {
+        return factories.remove(tableFactory);
+    }
+
     @SuppressWarnings("unused")
     public static Collection getAll() {
-        return unmodifiableCollection(factories);
+        return unmodifiableSet(factories);
     }
 
+    public static void clear() {
+        factories.clear();
+    }
+
+
     public static TableFactory get(ReportPage reportPage) {
+        Objects.requireNonNull(reportPage, "Report page is null");
         for (TableFactory factory : factories) {
             if (factory.canHandle(reportPage)) {
                 return factory;
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java
new file mode 100644
index 0000000..6eee2db
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java
@@ -0,0 +1,102 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+
+@ExtendWith(MockitoExtension.class)
+class TableFactoryRegistryTest {
+
+    ReportPage reportPage1;
+    ReportPage reportPage2;
+    ReportPage reportPageOfNotRegisteredFactory;
+
+    TableFactory factory1;
+    TableFactory factory2;
+    TableFactory factory1Copy;
+
+    @BeforeEach
+    void setUp() {
+        reportPage1 = mock(ReportPage.class);
+        reportPage2 = mock(ReportPage.class);
+        reportPageOfNotRegisteredFactory = mock(ReportPage.class);
+
+        factory1 = mock(TableFactory.class);
+        factory2 = mock(TableFactory.class);
+        factory1Copy = factory1;
+
+        lenient().when(factory1.canHandle(reportPage1)).thenReturn(true);
+        lenient().when(factory1.canHandle(reportPage2)).thenReturn(false);
+        lenient().when(factory1.canHandle(reportPageOfNotRegisteredFactory)).thenReturn(false);
+
+        lenient().when(factory2.canHandle(reportPage1)).thenReturn(false);
+        lenient().when(factory2.canHandle(reportPage2)).thenReturn(true);
+        lenient().when(factory2.canHandle(reportPageOfNotRegisteredFactory)).thenReturn(false);
+    }
+
+    @AfterEach
+    void tearDown() {
+        TableFactoryRegistry.clear();
+    }
+
+    @Test
+    void test() {
+        assertTrue(TableFactoryRegistry.getAll().isEmpty());
+
+        TableFactoryRegistry.add(factory1);
+        assertEquals(Set.of(factory1), TableFactoryRegistry.getAll());
+
+        TableFactoryRegistry.add(factory2);
+        assertEquals(Set.of(factory1, factory2), TableFactoryRegistry.getAll());
+
+        TableFactoryRegistry.add(factory1Copy);
+        assertEquals(Set.of(factory1, factory2), TableFactoryRegistry.getAll());
+
+        TableFactoryRegistry.add(factory2);
+        assertEquals(Set.of(factory1, factory2), TableFactoryRegistry.getAll());
+
+        //noinspection ConstantConditions
+        assertThrows(NullPointerException.class, () -> TableFactoryRegistry.get(null));
+        assertThrows(IllegalArgumentException.class, () -> TableFactoryRegistry.get(reportPageOfNotRegisteredFactory));
+        assertSame(factory1, TableFactoryRegistry.get(reportPage1));
+        assertSame(factory2, TableFactoryRegistry.get(reportPage2));
+
+        assertTrue(TableFactoryRegistry.remove(factory1Copy));
+        assertEquals(Set.of(factory2), TableFactoryRegistry.getAll());
+
+        assertTrue(TableFactoryRegistry.remove(factory2));
+        assertTrue(TableFactoryRegistry.getAll().isEmpty());
+
+        TableFactoryRegistry.add(factory1);
+        assertEquals(Set.of(factory1), TableFactoryRegistry.getAll());
+
+        TableFactoryRegistry.clear();
+        assertTrue(TableFactoryRegistry.getAll().isEmpty());
+    }
+}
\ No newline at end of file

From 0d41f65e7d132ba3465f488b08f603766e1db1cd Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Mon, 14 Nov 2022 02:20:44 +0300
Subject: [PATCH 63/94] add TableRow tests

---
 .../table_wrapper/api/TableRow.java           |   2 +-
 .../table_wrapper/api/ReportPageTest.java     |   6 +
 .../api/TableFactoryRegistryTest.java         |   2 +
 .../table_wrapper/api/TableRowTest.java       | 151 ++++++++++++++++++
 4 files changed, 160 insertions(+), 1 deletion(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/TableRowTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java
index 0d3d02c..f964c96 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableRow.java
@@ -111,7 +111,7 @@ default long getLongCellValueOrDefault(TableHeaderColumn column, long defaultVal
     /**
      * @return return cell value or defaultValue if the cell is missing or the type does not match the expected
      */
-    default double getDoubleCellValue(TableHeaderColumn column, double defaultValue) {
+    default double getDoubleCellValueOrDefault(TableHeaderColumn column, double defaultValue) {
         try {
             return getDoubleCellValue(column);
         } catch (Exception e) {
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
index 08ab286..4ce8f0c 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
@@ -19,6 +19,7 @@
 package org.spacious_team.table_wrapper.api;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -69,6 +70,11 @@ static void beforeAll() {
         TableFactoryRegistry.add(tableFactory);
     }
 
+    @AfterAll
+    static void afterAll() {
+        TableFactoryRegistry.remove(tableFactory);
+    }
+
     @Test
     void find1() {
         reportPage.find(value);
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java
index 6eee2db..758866f 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableFactoryRegistryTest.java
@@ -58,6 +58,8 @@ void setUp() {
         lenient().when(factory2.canHandle(reportPage1)).thenReturn(false);
         lenient().when(factory2.canHandle(reportPage2)).thenReturn(true);
         lenient().when(factory2.canHandle(reportPageOfNotRegisteredFactory)).thenReturn(false);
+
+        TableFactoryRegistry.clear();
     }
 
     @AfterEach
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/TableRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/TableRowTest.java
new file mode 100644
index 0000000..292d20a
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/TableRowTest.java
@@ -0,0 +1,151 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class TableRowTest {
+
+    @Spy
+    TableRow row;
+    @Mock
+    TableHeaderColumn column;
+
+    @Test
+    void getCellValueOrDefault() {
+        Object expected = new Object();
+        //noinspection ConstantConditions
+        when(row.getCellValue(any())).thenReturn(expected);
+        assertSame(expected, row.getCellValueOrDefault(column, new Object()));
+    }
+
+    @Test
+    void getCellValueOrDefaultThrowable() {
+        Object expected = new Object();
+        //noinspection ConstantConditions
+        when(row.getCellValue(any())).thenThrow(RuntimeException.class);
+        assertSame(expected, row.getCellValueOrDefault(column, expected));
+    }
+
+    @Test
+    void getIntCellValueOrDefault() {
+        when(row.getIntCellValue(any())).thenReturn(2);
+        assertEquals(2, row.getIntCellValueOrDefault(column, 1));
+    }
+
+    @Test
+    void getIntCellValueOrDefaultThrowable() {
+        when(row.getIntCellValue(any())).thenThrow(RuntimeException.class);
+        assertEquals(1, row.getIntCellValueOrDefault(column, 1));
+    }
+
+    @Test
+    void getLongCellValueOrDefault() {
+        when(row.getLongCellValue(any())).thenReturn(2L);
+        assertEquals(2L, row.getLongCellValueOrDefault(column, 1L));
+    }
+
+    @Test
+    void getLongCellValueOrDefaultThrowable() {
+        when(row.getLongCellValue(any())).thenThrow(RuntimeException.class);
+        assertEquals(1L, row.getLongCellValueOrDefault(column, 1L));
+    }
+
+    @Test
+    void getDoubleCellValue() {
+        when(row.getDoubleCellValue(any())).thenReturn(2.0);
+        assertEquals(2.0, row.getDoubleCellValueOrDefault(column, 1.0));
+    }
+
+    @Test
+    void getDoubleCellValueThrowable() {
+        when(row.getDoubleCellValue(any())).thenThrow(RuntimeException.class);
+        assertEquals(1.0, row.getDoubleCellValueOrDefault(column, 1.0));
+    }
+
+    @Test
+    void getBigDecimalCellValueOrDefault() {
+        BigDecimal expectedBigDecimal = BigDecimal.valueOf(1.0);
+        when(row.getBigDecimalCellValue(any())).thenReturn(expectedBigDecimal);
+        assertSame(expectedBigDecimal, row.getBigDecimalCellValueOrDefault(column, BigDecimal.ZERO));
+    }
+
+    @Test
+    void getBigDecimalCellValueOrDefaultThrowable() {
+        BigDecimal expectedBigDecimal = BigDecimal.valueOf(1.0);
+        when(row.getBigDecimalCellValue(any())).thenThrow(RuntimeException.class);
+        assertSame(expectedBigDecimal, row.getBigDecimalCellValueOrDefault(column, expectedBigDecimal));
+    }
+
+    @Test
+    void getStringCellValueOrDefault() {
+        String expectedString = "test";
+        when(row.getStringCellValue(any())).thenReturn(expectedString);
+        assertSame(expectedString, row.getStringCellValueOrDefault(column, "default"));
+    }
+
+    @Test
+    void getStringCellValueOrDefaultThrowable() {
+        String expectedString = "test";
+        when(row.getStringCellValue(any())).thenThrow(RuntimeException.class);
+        assertSame(expectedString, row.getStringCellValueOrDefault(column, expectedString));
+    }
+
+    @Test
+    void getInstantCellValueOrDefault() {
+        Instant expectedInstant = Instant.now();
+        when(row.getInstantCellValue(any())).thenThrow(RuntimeException.class);
+        assertSame(expectedInstant, row.getInstantCellValueOrDefault(column, expectedInstant));
+    }
+
+    @Test
+    void getInstantCellValueOrDefaultThrowable() {
+        Instant expectedInstant = Instant.now();
+        when(row.getInstantCellValue(any())).thenReturn(expectedInstant);
+        assertSame(expectedInstant, row.getInstantCellValueOrDefault(column, Instant.MIN));
+    }
+
+    @Test
+    void getLocalDateTimeCellValueOrDefault() {
+        LocalDateTime expectedLocalDateTime = LocalDateTime.now();
+        when(row.getLocalDateTimeCellValue(any())).thenThrow(RuntimeException.class);
+        assertSame(expectedLocalDateTime, row.getLocalDateTimeCellValueOrDefault(column, expectedLocalDateTime));
+    }
+
+    @Test
+    void getLocalDateTimeCellValueOrDefaultThrowable() {
+        LocalDateTime expectedLocalDateTime = LocalDateTime.now();
+        when(row.getLocalDateTimeCellValue(any())).thenReturn(expectedLocalDateTime);
+        assertSame(expectedLocalDateTime, row.getLocalDateTimeCellValueOrDefault(column, LocalDateTime.MIN));
+    }
+}
\ No newline at end of file

From 00d894a928601d3ec14140cb41e559afb1081927 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Mon, 14 Nov 2022 03:58:46 +0300
Subject: [PATCH 64/94] add some unit tests

---
 .../api/CellDataAccessObject.java             |  2 -
 .../api/TableFactoryRegistry.java             |  6 +-
 .../api/AbstractReportPageRowTest.java        | 56 ++++++++++++++----
 .../api/AbstractReportPageTest.java           | 57 +++++++++++++++++++
 .../api/AbstractTableFactoryTest.java         |  5 ++
 .../table_wrapper/api/AbstractTableTest.java  |  8 ++-
 .../api/CellDataAccessObjectTest.java         |  1 +
 .../api/MultiLineTableColumnTest.java         |  9 +++
 .../api/RelativePositionTableColumnTest.java  |  6 +-
 .../api/ReportPageRowHelper.java              | 10 ++--
 .../table_wrapper/api/ReportPageTest.java     | 38 +++++++++++--
 11 files changed, 169 insertions(+), 29 deletions(-)
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
index bdd8356..6c14e87 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/CellDataAccessObject.java
@@ -77,8 +77,6 @@ default double getDoubleValue(C cell) {
             } catch (NumberFormatException e) {
                 if (str.indexOf(',') != -1) {
                     return Double.parseDouble(str.replace(',', '.'));
-                } else if (str.indexOf('.') != -1) {
-                    return Double.parseDouble(str.replace('.', ','));
                 }
                 throw e;
             }
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
index a08e6de..91c7768 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/TableFactoryRegistry.java
@@ -18,14 +18,18 @@
 
 package org.spacious_team.table_wrapper.api;
 
+import lombok.RequiredArgsConstructor;
+
 import java.util.Collection;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 import static java.util.Collections.unmodifiableSet;
+import static lombok.AccessLevel.PRIVATE;
 
-public class TableFactoryRegistry {
+@RequiredArgsConstructor(access = PRIVATE)
+public final class TableFactoryRegistry {
 
     private static final Set factories = new CopyOnWriteArraySet<>();
 
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java
index d96c1ca..5ab2334 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageRowTest.java
@@ -18,7 +18,7 @@
 
 package org.spacious_team.table_wrapper.api;
 
-import org.junit.jupiter.api.BeforeEach;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
@@ -29,6 +29,7 @@
 import java.util.Iterator;
 import java.util.function.Function;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -44,21 +45,16 @@ class AbstractReportPageRowTest {
     @Mock
     AbstractReportPageRow row;
 
-
-    @BeforeEach
-    void beforeEach() {
-        when(row.iterator()).thenReturn(iterator);
-        when(innerIterator.next()).thenReturn(IteratorElement.INSTANCE);
-    }
-
-    static class IteratorElement {
-        @SuppressWarnings("InstantiationOfUtilityClass")
-        static final IteratorElement INSTANCE = new IteratorElement();
+    @Test
+    void testDefaultConstructor() {
+        assertDoesNotThrow(ReportPageRowTestImpl::new);
     }
 
     @Test
     @SuppressWarnings("ResultOfMethodCallIgnored")
     void testIterator() {
+        when(row.iterator()).thenReturn(iterator);
+        when(innerIterator.next()).thenReturn(IteratorElement.INSTANCE);
         Iterator it = row.iterator();
 
         it.hasNext();
@@ -68,4 +64,42 @@ void testIterator() {
         verify(innerIterator).next();
         verify(converter).apply(IteratorElement.INSTANCE);
     }
+
+    private static class ReportPageRowTestImpl extends AbstractReportPageRow {
+
+        @Override
+        public @Nullable TableCell getCell(int i) {
+            throw new UnsupportedOperationException();
+        }
+        @Override
+        public int getRowNum() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getFirstCellNum() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getLastCellNum() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean rowContains(Object expected) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Iterator iterator() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    private static class IteratorElement {
+        @SuppressWarnings("InstantiationOfUtilityClass")
+        static final IteratorElement INSTANCE = new IteratorElement();
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageTest.java
new file mode 100644
index 0000000..3de8c79
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractReportPageTest.java
@@ -0,0 +1,57 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Predicate;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+class AbstractReportPageTest {
+
+    @Test
+    void testDefaultConstructor() {
+        assertDoesNotThrow(ReportPageTestImpl::new);
+    }
+
+    static class ReportPageTestImpl extends AbstractReportPage {
+
+        @Override
+        public TableCellAddress find(Object value, int startRow, int endRow, int startColumn, int endColumn) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public TableCellAddress find(int startRow, int endRow, int startColumn, int endColumn, Predicate cellValuePredicate) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public @Nullable ReportPageRow getRow(int i) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getLastRowNum() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
index f6c790e..3c2d351 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableFactoryTest.java
@@ -61,6 +61,11 @@ void testEqualsAndHashCode() {
                 .verify();
     }
 
+    @Test
+    void testToString() {
+        assertTrue(factory.toString().startsWith("AbstractTableFactory(reportPageType="));
+    }
+
     static class TableFactoryTestImpl extends AbstractTableFactory> {
 
         protected TableFactoryTestImpl(Class> reportPageType) {
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
index 46781da..7c67025 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
@@ -45,6 +45,7 @@
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 import static org.spacious_team.table_wrapper.api.TableColumn.LEFTMOST_COLUMN;
+import static org.spacious_team.table_wrapper.api.TableColumn.NOCOLUMN;
 
 @ExtendWith(MockitoExtension.class)
 class AbstractTableTest {
@@ -112,7 +113,7 @@ private AbstractTable getNotEmptyTable() {
     }
 
     @Test
-    void testEmptyRangeConstructor2() {
+    void testNotEmptyRangeConstructor2() {
         AbstractTable originalTable = getNotEmptyTable();
         AbstractTable table = new TableTestImpl(originalTable, -1, -1);
         TableCellRange range = TableCellRange.of(3, 5, 0, 1);
@@ -124,7 +125,7 @@ void testEmptyRangeConstructor2() {
     }
 
     @Test
-    void testNotEmptyRangeConstructor2() {
+    void testEmptyRangeConstructor2() {
         AbstractTable originalTable = getEmptyTable();
         AbstractTable table = new TableTestImpl(originalTable, 1, 2);
         TableCellRange range = TableCellRange.of(1, 5,
@@ -338,7 +339,8 @@ public Table subTable(int topRows, int bottomRows) {
     @RequiredArgsConstructor
     enum Columns implements TableHeaderColumn {
         FIRST(LEFTMOST_COLUMN),
-        SECOND(ConstantPositionTableColumn.of(1));
+        SECOND(ConstantPositionTableColumn.of(1)),
+        NOT_FOUND(OptionalTableColumn.of(NOCOLUMN));
         private final TableColumn column;
     }
 }
\ No newline at end of file
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
index 6886532..6aef856 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/CellDataAccessObjectTest.java
@@ -100,6 +100,7 @@ void getDoubleValueException() {
         assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("abc"));
         assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("a.bc"));
         assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("0xFF"));
+        assertThrows(NumberFormatException.class, () -> dao.getDoubleValue("1.2.3"));
     }
 
     @ParameterizedTest
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
index 48df79a..4cec8ad 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/MultiLineTableColumnTest.java
@@ -27,6 +27,15 @@
 
 class MultiLineTableColumnTest {
 
+    @Test
+    void testOf() {
+        assertEquals(
+                MultiLineTableColumn.of("a1", "b2"),
+                MultiLineTableColumn.of(
+                        PatternTableColumn.of("a1"),
+                        PatternTableColumn.of("b2")));
+    }
+
     @Test
     void getColumnIndex() {
         ReportPageRow[] rows = ReportPageRowHelper.getThreeRowsHeader();
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
index a25e124..2d6f64a 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/RelativePositionTableColumnTest.java
@@ -30,13 +30,13 @@ class RelativePositionTableColumnTest {
     @Test
     void getColumnIndex() {
         ReportPageRow row = ReportPageRowHelper.getRow();
-        assertEquals(2, RelativePositionTableColumn.of(PatternTableColumn.of(), 2).getColumnIndex(row));
+        assertEquals(4, RelativePositionTableColumn.of(PatternTableColumn.of(), 4).getColumnIndex(row));
         assertEquals(7, RelativePositionTableColumn.of(PatternTableColumn.of("test"), -2).getColumnIndex(row));
 
-        TableColumn column1 = RelativePositionTableColumn.of(PatternTableColumn.of("test"), 2);
+        TableColumn column1 = RelativePositionTableColumn.of(PatternTableColumn.of("test"), 4);
         assertThrows(TableColumnNotFound.class, () -> column1.getColumnIndex(10, row));
 
-        TableColumn column2 = RelativePositionTableColumn.of(PatternTableColumn.of("not found"), 2);
+        TableColumn column2 = RelativePositionTableColumn.of(PatternTableColumn.of("not found"), 4);
         assertThrows(TableColumnNotFound.class, () -> column2.getColumnIndex(row));
     }
 
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelper.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelper.java
index 6a27033..44f9388 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelper.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageRowHelper.java
@@ -36,11 +36,11 @@ static ReportPageRow getRow() {
         return getRow(0,
                 null,
                 cell(null, 1),
-                cell(123, 2),
-                cell(1.23, 3),
-                cell(BigDecimal.valueOf(1), 4),
-                cell("", 5),
-                cell(" ", 6),
+                cell("", 2),
+                cell(" ", 3),
+                cell(123, 4),
+                cell(1.23, 5),
+                cell(BigDecimal.valueOf(1), 6),
                 cell("test word", 9),
                 cell("This Is Sparta", 10),
                 cell("London\nis the\ncapital\nof Great Britain", 20),
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
index 4ce8f0c..c850360 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/ReportPageTest.java
@@ -23,6 +23,8 @@
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -160,18 +162,46 @@ void getNextColumnValueNull() {
         verify(reportPage).getRow(address1.getRow());
     }
 
+    @ParameterizedTest
+    @MethodSource("nextColumnValueRows")
+    void getNextColumnValue(Object expected, ReportPageRow row) {
+        String prefix = "test";
+        doReturn(address1).when(reportPage).findByPrefix(prefix);
+        //noinspection ConstantConditions
+        when(reportPage.getRow(address1.getRow())).thenReturn(row);
+
+        @Nullable
+        Object result = reportPage.getNextColumnValue(prefix);
+
+        assertEquals(expected, result);
+        verify(reportPage).findByPrefix(prefix);
+        verify(reportPage).getRow(address1.getRow());
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    static Object[][] nextColumnValueRows() {
+        return new Object[][]{
+                {null, getRow(0, null, null)},
+                {"test", getRow(0, null, cell("test", 3))},
+                {123, getRow(0,
+                        cell("", 2),
+                        cell(" ", 3),
+                        cell(123, 4))}};
+    }
+
     @Test
-    void getNextColumnValue() {
+    void getNextColumnValue2() {
         String prefix = "test";
         doReturn(address1).when(reportPage).findByPrefix(prefix);
-        ReportPageRow row = getRow();
+        ReportPageRow row = getRow(0,
+                cell(123, 3));
         //noinspection ConstantConditions
         when(reportPage.getRow(address1.getRow())).thenReturn(row);
 
         @Nullable
         Object result = reportPage.getNextColumnValue(prefix);
 
-        assertEquals(1.23, result);
+        assertEquals(123, result);
         verify(reportPage).findByPrefix(prefix);
         verify(reportPage).getRow(address1.getRow());
     }
@@ -552,7 +582,7 @@ void testCreateNameless6() {
         reportPage.createNameless(tableName, tableNameFinder, tableHeader, 2);
         verify(tableFactory).createNameless(reportPage, tableName, tableNameFinder, tableHeader, 2);
     }
-    
+
 
     enum TableHeader implements TableHeaderColumn {
         ;

From d3c1e400383d9b744e310b923ae35702be46a60f Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Sat, 19 Nov 2022 01:34:26 +0300
Subject: [PATCH 65/94] return by Table.iterator() correct rows number, add
 EmptyTableCell

---
 .../table_wrapper/api/AbstractTable.java      | 23 +++---
 .../table_wrapper/api/EmptyTableCell.java     | 73 +++++++++++++++++++
 .../table_wrapper/api/MutableTableRow.java    |  2 +-
 .../table_wrapper/api/PatternTableColumn.java |  2 +-
 .../table_wrapper/api/ReportPage.java         |  4 +-
 .../table_wrapper/api/ReportPageRow.java      |  2 +-
 .../table_wrapper/api/Table.java              |  4 +-
 .../table_wrapper/api/AbstractTableTest.java  | 27 ++++++-
 .../table_wrapper/api/EmptyTableCellTest.java | 48 ++++++++++++
 9 files changed, 165 insertions(+), 20 deletions(-)
 create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
index bc221ed..84f6a42 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractTable.java
@@ -173,7 +173,7 @@ public  List getDataCollection(Object report, Function List getDataCollection(Object report, BiConsumer> rowHandler) {
         List data = new ArrayList<>();
-        for (@SuppressWarnings("NullableProblems") @Nullable TableRow row : this) {
+        for (@Nullable TableRow row : this) {
             if (row != null) {
                 try {
                     rowHandler.accept(row, data);
@@ -214,13 +214,15 @@ public static  void addWithEqualityChecker(T element,
      * Call {@link TableRow#clone()} if you want to use row object outside stream() block.
      */
     @Override
-    public Stream stream() {
+    public Stream<@Nullable TableRow> stream() {
         return StreamSupport.stream(spliterator(), false);
     }
 
     /**
-     * {@link TableRow} impl is mutable.
-     * For performance issue same object with changed state is provided in each loop cycle.
+     * Iterator which returns {@link MutableTableRow} or {@link EmptyTableRow}.
+     *
+     * @implSpec This iterator never returns null values. Null rows is wrapped by {@link EmptyTableRow}
+     * @implNote For performance issue same object with changed state is provided in each loop cycle.
      * Call {@link TableRow#clone()} if you want to use row object outside iterator() block.
      */
     @Override
@@ -239,18 +241,17 @@ public boolean hasNext() {
             return i < numberOfRows;
         }
 
+        /**
+         * Returns mutable {@link TableRow} impl. Never returns null value.
+         */
         @Override
         public TableRow next() {
             if (!hasNext()) {
                 throw new NoSuchElementException();
             }
-            int rowNum;
-            @Nullable R row;
-            do {
-                rowNum = tableRange.getFirstRow() + (i++);
-                row = getRow(rowNum);
-            } while (row == null && hasNext());
-            if (row == null) { // Last row is empty
+            int rowNum = tableRange.getFirstRow() + (i++);
+            @Nullable R row = getRow(rowNum);
+            if (row == null) {
                 return new EmptyTableRow(AbstractTable.this, rowNum);
             }
             tableRow.setRow(row);
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java
new file mode 100644
index 0000000..b915173
--- /dev/null
+++ b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java
@@ -0,0 +1,73 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+
+@RequiredArgsConstructor(staticName = "of")
+public class EmptyTableCell implements TableCell {
+    @Getter
+    private final int columnIndex;
+
+    @Override
+    public @Nullable Object getValue() {
+        return null;
+    }
+
+    @Override
+    public int getIntValue() {
+        throw new NullPointerException("Can't get int value");
+    }
+
+    @Override
+    public long getLongValue() {
+        throw new NullPointerException("Can't get long value");
+    }
+
+    @Override
+    public Double getDoubleValue() {
+        throw new NullPointerException("Can't get double value");
+    }
+
+    @Override
+    public BigDecimal getBigDecimalValue() {
+        throw new NullPointerException("Can't get BigDecimal value");
+    }
+
+    @Override
+    public String getStringValue() {
+        throw new NullPointerException("Can't get String value");
+    }
+
+    @Override
+    public Instant getInstantValue() {
+        throw new NullPointerException("Can't get Instant value");
+    }
+
+    @Override
+    public LocalDateTime getLocalDateTimeValue() {
+        throw new NullPointerException("Can't get LocalDateTime value");
+    }
+}
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
index 5f659f6..11e0384 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/MutableTableRow.java
@@ -77,7 +77,7 @@ public boolean rowContains(Object expected) {
     }
 
     @Override
-    public Iterator iterator() {
+    public Iterator<@Nullable TableCell> iterator() {
         return row.iterator();
     }
 
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
index 298cbee..1864927 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/PatternTableColumn.java
@@ -67,7 +67,7 @@ public static TableColumn of(@Nullable String... words) {
 
     public int getColumnIndex(int firstColumnForSearch, ReportPageRow... headerRows) {
         for (ReportPageRow header : headerRows) {
-            for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : header) {
+            for (@Nullable TableCell cell : header) {
                 @Nullable Object value;
                 if (cell != null &&
                         cell.getColumnIndex() >= firstColumnForSearch &&
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
index 9ea1b04..1f03ff9 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPage.java
@@ -167,7 +167,7 @@ default TableCellAddress findByPrefix(@Nullable String prefix, int startRow, int
         TableCellAddress address = findByPrefix(firstColumnValuePrefix);
         @Nullable ReportPageRow row = getRow(address.getRow());
         if (row != null) {
-            for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : row) {
+            for (@Nullable TableCell cell : row) {
                 if (cell != null && cell.getColumnIndex() > address.getColumn()) {
                     @Nullable Object value = cell.getValue();
                     if (value != null && (!(value instanceof String) || !((String) value).isBlank())) {
@@ -304,7 +304,7 @@ default int findEmptyRow(int startRow) {
             if (row == null || row.getLastCellNum() == -1) {
                 return lastRowNum; // all row's cells are blank
             }
-            for (@SuppressWarnings("NullableProblems") @Nullable TableCell cell : row) {
+            for (@Nullable TableCell cell : row) {
                 @Nullable Object value;
                 if (!(cell == null
                         || ((value = cell.getValue()) == null)
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java
index 15d2efc..c167e43 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/ReportPageRow.java
@@ -20,7 +20,7 @@
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
-public interface ReportPageRow extends Iterable {
+public interface ReportPageRow extends Iterable<@Nullable TableCell> {
 
     /**
      * @param i zero-based cell number
diff --git a/src/main/java/org/spacious_team/table_wrapper/api/Table.java b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
index c367983..6d5bd65 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/Table.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/Table.java
@@ -29,7 +29,7 @@
 import java.util.stream.Stream;
 
 @SuppressWarnings({"unused", "UnusedReturnValue"})
-public interface Table extends Iterable {
+public interface Table extends Iterable<@Nullable TableRow> {
 
     /**
      * Report page this table belongs to
@@ -63,7 +63,7 @@  List getDataCollection(Object report, Function stream();
+    Stream<@Nullable TableRow> stream();
 
     /**
      * @param i zero-based index
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
index 7c67025..ff07c2c 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/AbstractTableTest.java
@@ -100,8 +100,8 @@ void testNotEmptyRangeConstructor() {
     }
 
     /**
-     * Builds not empty table of 2 columns, row #2 contains table name, row #3 and #4 - header, row #5 - data,
-     * row #6 - data with is equals to null
+     * Builds not empty table of 2 columns, row #2 contains table name, row #3 and #4 - header, row #5 and
+     * row #6 - data with row equals to null
      */
     @SuppressWarnings("ConstantConditions")
     private AbstractTable getNotEmptyTable() {
@@ -251,6 +251,29 @@ void testIteration() {
         assertEquals(6, rowB.getRowNum());
     }
 
+    @Test
+    void testIterationCount() {
+        table = getNotEmptyTable();
+        int cnt = 0;
+        for (TableRow ignored : table) {
+            cnt++;
+        }
+        assertEquals(2, cnt);
+    }
+
+    @Test
+    void testIteratorWithNullRows() {
+        TableCellRange tableRange = TableCellRange.of(2, 6, 0, 100);
+        //noinspection ConstantConditions
+        when(report.getRow(3)).thenReturn(new EmptyTableRow(table, 3)); // not empty header required
+        table = new TableTestImpl(report, "table name", tableRange, headerDescription, 1);
+        int i = tableRange.getFirstRow() + 2; // 2 - table name and header
+        for (TableRow row : table) {
+            assertNull(report.getRow(i++));
+            assertEquals(EmptyTableRow.class, row.getClass());
+        }
+    }
+
     @Test
     void getRow() {
         table.getRow(1);
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java
new file mode 100644
index 0000000..d196d9f
--- /dev/null
+++ b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java
@@ -0,0 +1,48 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class EmptyTableCellTest {
+
+    EmptyTableCell cell = EmptyTableCell.of(1);
+
+    @ParameterizedTest
+    @ValueSource(ints = {-1, 0, 1, 10})
+    void getColumnIndex(int columnIndex) {
+        assertEquals(columnIndex, EmptyTableCell.of(columnIndex).getColumnIndex());
+    }
+
+    @Test
+    void getValue() {
+        assertNull(cell.getValue());
+        assertThrows(NullPointerException.class, () -> cell.getIntValue());
+        assertThrows(NullPointerException.class, () -> cell.getLongValue());
+        assertThrows(NullPointerException.class, () -> cell.getDoubleValue());
+        assertThrows(NullPointerException.class, () -> cell.getBigDecimalValue());
+        assertThrows(NullPointerException.class, () -> cell.getStringValue());
+        assertThrows(NullPointerException.class, () -> cell.getInstantValue());
+        assertThrows(NullPointerException.class, () -> cell.getLocalDateTimeValue());
+    }
+}
\ No newline at end of file

From 0d31e472865398ed6e88fcd86f652b9e1062b929 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Sat, 19 Nov 2022 01:37:30 +0300
Subject: [PATCH 66/94] add EmptyTableCell.equals(), hashCode() and toString()

---
 .../table_wrapper/api/EmptyTableCell.java         |  4 ++++
 .../table_wrapper/api/EmptyTableCellTest.java     | 15 +++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java
index b915173..e48161a 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/EmptyTableCell.java
@@ -18,14 +18,18 @@
 
 package org.spacious_team.table_wrapper.api;
 
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import lombok.ToString;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.math.BigDecimal;
 import java.time.Instant;
 import java.time.LocalDateTime;
 
+@ToString
+@EqualsAndHashCode
 @RequiredArgsConstructor(staticName = "of")
 public class EmptyTableCell implements TableCell {
     @Getter
diff --git a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java
index d196d9f..2817c0d 100644
--- a/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java
+++ b/src/test/java/org/spacious_team/table_wrapper/api/EmptyTableCellTest.java
@@ -18,10 +18,12 @@
 
 package org.spacious_team.table_wrapper.api;
 
+import nl.jqno.equalsverifier.EqualsVerifier;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
+import static nl.jqno.equalsverifier.Warning.STRICT_INHERITANCE;
 import static org.junit.jupiter.api.Assertions.*;
 
 class EmptyTableCellTest {
@@ -45,4 +47,17 @@ void getValue() {
         assertThrows(NullPointerException.class, () -> cell.getInstantValue());
         assertThrows(NullPointerException.class, () -> cell.getLocalDateTimeValue());
     }
+
+    @Test
+    void testEqualsAndHashCode() {
+        EqualsVerifier
+                .forClass(EmptyTableCell.class)
+                .suppress(STRICT_INHERITANCE) // no subclass for test
+                .verify();
+    }
+
+    @Test
+    void testToString() {
+        assertEquals("EmptyTableCell(columnIndex=1)", cell.toString());
+    }
 }
\ No newline at end of file

From 24d52702c53649414625e7bf283bf9df19af9e77 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Sat, 19 Nov 2022 01:43:25 +0300
Subject: [PATCH 67/94] fix AbstractReportPageRow

---
 .../table_wrapper/api/AbstractReportPageRow.java         | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPageRow.java b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPageRow.java
index 325c55f..e432a4b 100644
--- a/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPageRow.java
+++ b/src/main/java/org/spacious_team/table_wrapper/api/AbstractReportPageRow.java
@@ -19,6 +19,7 @@
 package org.spacious_team.table_wrapper.api;
 
 import lombok.RequiredArgsConstructor;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.Iterator;
 import java.util.function.Function;
@@ -26,10 +27,10 @@
 public abstract class AbstractReportPageRow implements ReportPageRow {
 
     @RequiredArgsConstructor
-    protected static class ReportPageRowIterator implements Iterator {
+    protected static class ReportPageRowIterator implements Iterator<@Nullable TableCell> {
 
-        private final Iterator innerIterator;
-        private final Function converter;
+        private final Iterator<@Nullable T> innerIterator;
+        private final Function<@Nullable T, @Nullable TableCell> converter;
 
 
         @Override
@@ -38,7 +39,7 @@ public boolean hasNext() {
         }
 
         @Override
-        public TableCell next() {
+        public @Nullable TableCell next() {
             return converter.apply(innerIterator.next());
         }
     }

From bf890e514ffb7efa29e388e6b751e4f2aa439a50 Mon Sep 17 00:00:00 2001
From: Vitalii Ananev 
Date: Sun, 20 Nov 2022 03:34:48 +0300
Subject: [PATCH 68/94] add InstantParser

---
 .../api/DateTimeFormatParser.java             | 221 ++++++++++++++++++
 .../table_wrapper/api/InstantParser.java      | 158 +++++++++++++
 .../api/DateTimeFormatParserTest.java         | 108 +++++++++
 .../table_wrapper/api/InstantParserTest.java  | 174 ++++++++++++++
 4 files changed, 661 insertions(+)
 create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/DateTimeFormatParser.java
 create mode 100644 src/main/java/org/spacious_team/table_wrapper/api/InstantParser.java
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/DateTimeFormatParserTest.java
 create mode 100644 src/test/java/org/spacious_team/table_wrapper/api/InstantParserTest.java

diff --git a/src/main/java/org/spacious_team/table_wrapper/api/DateTimeFormatParser.java b/src/main/java/org/spacious_team/table_wrapper/api/DateTimeFormatParser.java
new file mode 100644
index 0000000..21d4df6
--- /dev/null
+++ b/src/main/java/org/spacious_team/table_wrapper/api/DateTimeFormatParser.java
@@ -0,0 +1,221 @@
+/*
+ * Table Wrapper API
+ * Copyright (C) 2022  Spacious Team 
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see .
+ */
+
+package org.spacious_team.table_wrapper.api;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static lombok.AccessLevel.PRIVATE;
+
+@NoArgsConstructor(access = PRIVATE)
+final class DateTimeFormatParser {
+
+    private static final Map dateTimeFormatters = new ConcurrentHashMap<>();
+
+    static DateTimeFormatter getDateTimeFormatter(String dateTimeOffset) {
+        Pattern pattern = getPattern(dateTimeOffset);
+        return getDateTimeFormatter(pattern);
+    }
+
+    private static Pattern getPattern(String dateTimeOffset) {
+        @Nullable Pattern pattern = null;
+        int length = dateTimeOffset.length();
+        if (length == 8 || length == 12) { // without and with millis
+            pattern = getForTime(dateTimeOffset, 0);
+        } else if (length == 10) {
+            pattern = getForDate(dateTimeOffset, 0);
+        } else if (length == 19 || length == 23) { // without and with millis
+            pattern = getForDateTime(dateTimeOffset);
+        } else if (length > 19) {
+            pattern = getForDateTimeZone(dateTimeOffset);
+        }
+        if (pattern == null) {
+            throw new IllegalArgumentException("Unknown date time format for: " + dateTimeOffset);
+        }
+        return pattern;
+    }
+
+    private static TimePattern getForTime(String time, int offset) {
+        boolean hasMillis = (time.length() > (offset + 8)) && (time.charAt(offset + 8) == '.');
+        return TimePattern.of(hasMillis);
+    }
+
+    private static DatePattern getForDate(String date, int dateOffset) {
+        boolean isYearAtFirst;
+        char dateSplitter;
+        char ch = date.charAt(dateOffset + 2);
+        if (!Character.isDigit(ch)) {
+            // date format is DD MM YYYY
+            isYearAtFirst = false;
+            dateSplitter = ch;
+        } else {
+            // date format is YYYY MM DD
+            isYearAtFirst = true;
+            dateSplitter = date.charAt(dateOffset + 4);
+        }
+        return DatePattern.of(isYearAtFirst, dateSplitter);
+    }
+
+    private static DateTimePattern getForDateTime(String dateTime) {
+        boolean isDateAtFirst;
+        DatePattern datePattern;
+        TimePattern timePattern;
+        char dateTimeSeparator;
+        if (dateTime.charAt(2) == ':') {
+            // format is