From 005991236e17bc703b9df1b126c927765fe035f8 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 26 Jan 2024 17:58:23 +0300 Subject: [PATCH 1/3] Add new table format methods --- CHANGELOG.md | 3 + .../utils/markdown/MarkdownUtils.java | 86 +++++++++++++++--- .../utils/markdown/MarkdownUtilsTest.java | 87 +++++++++++++++---- 3 files changed, 147 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 551a82aa..d6c19189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## [Unreleased] +### Added +- Table size limit in `MarkdownUtils.formatDataTable(List, int)` method, by @HardNorth +- `MarkdownUtils.formatDataTable(Map)` method, by @HardNorth ## [5.2.1] ### Added diff --git a/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java b/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java index a4db048e..c80f0237 100644 --- a/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java +++ b/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java @@ -15,9 +15,10 @@ */ package com.epam.reportportal.utils.markdown; +import org.apache.commons.lang3.tuple.Pair; + import javax.annotation.Nonnull; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -36,6 +37,9 @@ public class MarkdownUtils { public static final String TABLE_INDENT = "\u00A0\u00A0\u00A0\u00A0"; public static final String TABLE_COLUMN_SEPARATOR = "|"; public static final String TABLE_ROW_SEPARATOR = "-"; + public static final String TRUNCATION_REPLACEMENT = "..."; + public static final int PADDING_SPACES_NUM = 2; + public static final int MAX_TABLE_SIZE = 83; /** * Adds special prefix to make log message being processed as markdown @@ -55,35 +59,67 @@ public static String asMarkdown(String message) { * @return Message to be sent to ReportPortal */ public static String asCode(String language, String script) { - return asMarkdown("```" + ofNullable(language).orElse("") + NEW_LINE + - script + NEW_LINE + - "```"); + return asMarkdown("```" + ofNullable(language).orElse("") + NEW_LINE + script + NEW_LINE + "```"); + } + + @Nonnull + private static List calculateColSizes(@Nonnull List colSizes, int maxTableSize) { + int colTableSize = colSizes.stream().reduce(Integer::sum).orElse(-1); + colTableSize += (PADDING_SPACES_NUM + TABLE_COLUMN_SEPARATOR.length()) * colSizes.size() - 1; // Inner columns grid + colTableSize += 2; // Outer table grid + if (maxTableSize >= colTableSize) { + return colSizes; + } + List> colsBySize = IntStream.range(0, colSizes.size()) + .mapToObj(i -> Pair.of(colSizes.get(i), i)) + .sorted() + .collect(Collectors.toList()); + Collections.reverse(colsBySize); + int sizeToShrink = colTableSize - maxTableSize; + for (int i = 0; i < sizeToShrink; i++) { + for (int j = 0; j < colsBySize.size(); j++) { + Pair currentCol = colsBySize.get(j); + Pair nextCol = colsBySize.size() > j + 1 ? colsBySize.get(j + 1) : Pair.of(0, 0); + if(currentCol.getKey() >= nextCol.getKey()) { + colsBySize.set(j, Pair.of(currentCol.getKey() - 1, currentCol.getValue())); + break; + } + } + } + List result = new ArrayList<>(colSizes); + colsBySize.forEach(col -> result.set(col.getValue(), col.getKey())); + return result; } /** - * Converts a table represented as List of Lists to a formatted table string + * Converts a table represented as List of Lists to a formatted table string. * * @param table a table object * @return string representation of the table */ @Nonnull - public static String formatDataTable(@Nonnull final List> table) { + public static String formatDataTable(@Nonnull final List> table, int maxTableSize) { StringBuilder result = new StringBuilder(); - int tableLength = table.stream().mapToInt(List::size).max().orElse(-1); + int tableColNum = table.stream().mapToInt(List::size).max().orElse(-1); List> iterList = table.stream().map(List::iterator).collect(Collectors.toList()); - List colSizes = IntStream.range(0, tableLength) + List colSizes = IntStream.range(0, tableColNum) .mapToObj(n -> iterList.stream().filter(Iterator::hasNext).map(Iterator::next).collect(Collectors.toList())) .map(col -> col.stream().mapToInt(String::length).max().orElse(0)) .collect(Collectors.toList()); + colSizes = calculateColSizes(colSizes, maxTableSize); boolean header = true; for (List row : table) { result.append(TABLE_INDENT).append(TABLE_COLUMN_SEPARATOR); for (int i = 0; i < row.size(); i++) { String cell = row.get(i); - int maxSize = colSizes.get(i) - cell.length() + 2; - int lSpace = maxSize / 2; - int rSpace = maxSize - lSpace; + int colSize = colSizes.get(i); + if (colSize < cell.length()) { + cell = cell.substring(0, colSize - TRUNCATION_REPLACEMENT.length()) + TRUNCATION_REPLACEMENT; + } + int padSize = colSize - cell.length() + PADDING_SPACES_NUM; + int lSpace = padSize / 2; + int rSpace = padSize - lSpace; IntStream.range(0, lSpace).forEach(j -> result.append(ONE_SPACE)); result.append(cell); IntStream.range(0, rSpace).forEach(j -> result.append(ONE_SPACE)); @@ -103,4 +139,30 @@ public static String formatDataTable(@Nonnull final List> table) { } return result.toString().trim(); } + + /** + * Converts a table represented as List of Lists to a formatted table string. + * + * @param table a table object + * @return string representation of the table + */ + @Nonnull + public static String formatDataTable(@Nonnull final List> table) { + return formatDataTable(table, MAX_TABLE_SIZE); + } + + /** + * Converts a table represented as Map to a formatted table string. + * + * @param table a table object + * @return string representation of the table + */ + @Nonnull + public static String formatDataTable(@Nonnull final Map table) { + List> toFormat = new ArrayList<>(); + List keys = new ArrayList<>(table.keySet()); + toFormat.add(keys); + toFormat.add(keys.stream().map(table::get).collect(Collectors.toList())); + return formatDataTable(toFormat); + } } diff --git a/src/test/java/com/epam/reportportal/utils/markdown/MarkdownUtilsTest.java b/src/test/java/com/epam/reportportal/utils/markdown/MarkdownUtilsTest.java index d9cc2e17..e1f0f4db 100644 --- a/src/test/java/com/epam/reportportal/utils/markdown/MarkdownUtilsTest.java +++ b/src/test/java/com/epam/reportportal/utils/markdown/MarkdownUtilsTest.java @@ -17,11 +17,9 @@ import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.List; +import java.util.*; -import static com.epam.reportportal.utils.markdown.MarkdownUtils.asCode; -import static com.epam.reportportal.utils.markdown.MarkdownUtils.formatDataTable; +import static com.epam.reportportal.utils.markdown.MarkdownUtils.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -29,13 +27,16 @@ * @author Andrei Varabyeu */ public class MarkdownUtilsTest { + //@formatter:off + public static final String ONE_ROW_EXPECTED_TABLE = + TABLE_INDENT + "|\u00A0var_a\u00A0|\u00A0var_b\u00A0|\u00A0result\u00A0|\n" + + TABLE_INDENT + "|-------|-------|--------|\n" + + TABLE_INDENT + "|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A04\u00A0\u00A0\u00A0\u00A0|"; - public static final String ONE_ROW_EXPECTED_TABLE = "\u00A0\u00A0\u00A0\u00A0|\u00A0var_a\u00A0|\u00A0var_b\u00A0|\u00A0result\u00A0|\n" - + "\u00A0\u00A0\u00A0\u00A0|-------|-------|--------|\n" - + "\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A04\u00A0\u00A0\u00A0\u00A0|"; - - public static final String TWO_ROWS_EXPECTED_TABLE = ONE_ROW_EXPECTED_TABLE + "\n" - + "\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0|"; + public static final String TWO_ROWS_EXPECTED_TABLE = + ONE_ROW_EXPECTED_TABLE + + "\n" + TABLE_INDENT + "|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0|"; + //@formatter:on @Test public void asMarkdown() { @@ -47,16 +48,68 @@ public void toMarkdownScript() { assertThat("Incorrect markdown prefix", asCode("groovy", "hello"), equalTo("!!!MARKDOWN_MODE!!!```groovy\nhello\n```")); } - @Test public void test_format_data_table() { - List> table = - Arrays.asList( - Arrays.asList("var_a", "var_b", "result"), - Arrays.asList("2", "2", "4"), - Arrays.asList("1", "2", "3") - ); + List> table = Arrays.asList(Arrays.asList("var_a", "var_b", "result"), + Arrays.asList("2", "2", "4"), + Arrays.asList("1", "2", "3") + ); assertThat(formatDataTable(table), equalTo(TWO_ROWS_EXPECTED_TABLE)); } + + public static final String BIG_COLUMN_VALUE = "4CZtyV3qsjVX08vBKu5YpvY2ckoFxLUombHEj1yf4uaBmrSGTzcXvlfba52HtUGLm56a8Vx4fBa0onEjlXY"; + public static final String TRUNCATED_COLUMN_VALUE = "4CZtyV3qsjVX08vBKu5YpvY2ckoFxLUombHEj1yf4uaBmrSGTzcXvlfba52..."; + + //@formatter:off + public static final String ONE_ROW_LONG_EXPECTED_TABLE = + TABLE_INDENT + "|\u00A0var_a\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0var_b\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0result\u00A0|\n" + + TABLE_INDENT + "|-------|----------------------------------------------------------------|--------|\n" + + TABLE_INDENT + "|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A04\u00A0\u00A0\u00A0\u00A0|"; + + public static final String TWO_ROWS_LONG_EXPECTED_TABLE = + ONE_ROW_LONG_EXPECTED_TABLE + "\n" + + TABLE_INDENT + "|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0" + TRUNCATED_COLUMN_VALUE + "\u00A0|\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0|"; + //@formatter:on + + @Test + public void test_format_data_table_one_big_col() { + List> table = Arrays.asList(Arrays.asList("var_a", "var_b", "result"), + Arrays.asList("2", "2", "4"), + Arrays.asList("1", BIG_COLUMN_VALUE, "3") + ); + assertThat(formatDataTable(table), equalTo(TWO_ROWS_LONG_EXPECTED_TABLE)); + } + + public static final String SECOND_TRUNCATED_COLUMN_VALUE = "4CZtyV3qsjVX08vBKu5YpvY2ckoFxLU..."; + + //@formatter:off + public static final String ONE_ROW_LONG_EXPECTED_TABLE_TWO = + TABLE_INDENT + "|\u00A0var_a\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0var_b\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0result\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\n" + + TABLE_INDENT + "|-------|------------------------------------|------------------------------------|\n" + + TABLE_INDENT + "|\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A02\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|\u00A0" + SECOND_TRUNCATED_COLUMN_VALUE + "\u00A0|"; + + public static final String TWO_ROWS_LONG_EXPECTED_TABLE_TWO = + ONE_ROW_LONG_EXPECTED_TABLE_TWO + "\n" + + TABLE_INDENT + "|\u00A0\u00A0\u00A01\u00A0\u00A0\u00A0|\u00A0" + SECOND_TRUNCATED_COLUMN_VALUE + "\u00A0|\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A03\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0|"; + //@formatter:on + + @Test + public void test_format_data_table_two_big_col() { + List> table = Arrays.asList(Arrays.asList("var_a", "var_b", "result"), + Arrays.asList("2", "2", BIG_COLUMN_VALUE), + Arrays.asList("1", BIG_COLUMN_VALUE, "3") + ); + assertThat(formatDataTable(table), equalTo(TWO_ROWS_LONG_EXPECTED_TABLE_TWO)); + } + + @Test + public void test_format_data_table_map() { + Map table = new LinkedHashMap(){{ + put("var_a", "2"); + put("var_b", "2"); + put("result", "4"); + }}; + assertThat(formatDataTable(table), equalTo(ONE_ROW_EXPECTED_TABLE)); + } } From 407787770095d75aeb53d341899c98f85b62c59f Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 26 Jan 2024 18:21:23 +0300 Subject: [PATCH 2/3] Add javadocs --- .../com/epam/reportportal/utils/markdown/MarkdownUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java b/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java index c80f0237..a3825968 100644 --- a/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java +++ b/src/main/java/com/epam/reportportal/utils/markdown/MarkdownUtils.java @@ -80,7 +80,7 @@ private static List calculateColSizes(@Nonnull List colSizes, for (int j = 0; j < colsBySize.size(); j++) { Pair currentCol = colsBySize.get(j); Pair nextCol = colsBySize.size() > j + 1 ? colsBySize.get(j + 1) : Pair.of(0, 0); - if(currentCol.getKey() >= nextCol.getKey()) { + if (currentCol.getKey() >= nextCol.getKey()) { colsBySize.set(j, Pair.of(currentCol.getKey() - 1, currentCol.getValue())); break; } @@ -94,7 +94,8 @@ private static List calculateColSizes(@Nonnull List colSizes, /** * Converts a table represented as List of Lists to a formatted table string. * - * @param table a table object + * @param table a table object + * @param maxTableSize maximum size in characters of result table, cells will be truncated * @return string representation of the table */ @Nonnull From 725ecafe77edff68e2c9ae080c89c566259332fe Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 26 Jan 2024 18:40:56 +0300 Subject: [PATCH 3/3] Add javadocs --- .../java/com/epam/reportportal/utils/reflect/Accessible.java | 4 +++- .../com/epam/reportportal/utils/reflect/AccessibleField.java | 2 ++ .../com/epam/reportportal/utils/reflect/AccessibleMethod.java | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/epam/reportportal/utils/reflect/Accessible.java b/src/main/java/com/epam/reportportal/utils/reflect/Accessible.java index 749a0eda..fd8ada9c 100644 --- a/src/main/java/com/epam/reportportal/utils/reflect/Accessible.java +++ b/src/main/java/com/epam/reportportal/utils/reflect/Accessible.java @@ -55,7 +55,7 @@ public AccessibleMethod method(@Nonnull Method m) { * @param m method to access * @param parameterTypes an array of specific parameters to distinguish the method * @return decorator instance - * @throws NoSuchMethodException no such method found + * @throws NoSuchMethodException no method with such name found */ @Nonnull public AccessibleMethod method(@Nonnull String m, @Nullable Class... parameterTypes) throws NoSuchMethodException { @@ -78,6 +78,7 @@ public AccessibleField field(@Nonnull Field f) { * * @param name field to access * @return decorator instance + * @throws NoSuchFieldException no field with such name found */ @Nonnull public AccessibleField field(@Nonnull String name) throws NoSuchFieldException { @@ -88,6 +89,7 @@ public AccessibleField field(@Nonnull String name) throws NoSuchFieldException { * Create the decorator for given object. * * @param object an instance of object on which you need reflective access + * @return decorator instance */ @Nonnull public static Accessible on(@Nonnull Object object) { diff --git a/src/main/java/com/epam/reportportal/utils/reflect/AccessibleField.java b/src/main/java/com/epam/reportportal/utils/reflect/AccessibleField.java index f4143ece..d0f95b76 100644 --- a/src/main/java/com/epam/reportportal/utils/reflect/AccessibleField.java +++ b/src/main/java/com/epam/reportportal/utils/reflect/AccessibleField.java @@ -58,6 +58,8 @@ public void setValue(@Nullable Object value) { /** * Return given field value. + * + * @return field value */ @Nullable public Object getValue() { diff --git a/src/main/java/com/epam/reportportal/utils/reflect/AccessibleMethod.java b/src/main/java/com/epam/reportportal/utils/reflect/AccessibleMethod.java index 87374492..ace73f36 100644 --- a/src/main/java/com/epam/reportportal/utils/reflect/AccessibleMethod.java +++ b/src/main/java/com/epam/reportportal/utils/reflect/AccessibleMethod.java @@ -38,6 +38,8 @@ public class AccessibleMethod { * Set given method. * * @param args arguments to pass to the method + * @return method call result + * @throws Throwable a throwable which was thrown during the method execution */ @Nullable public Object invoke(@Nullable Object... args) throws Throwable {