java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java
1.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java Fri Sep 23 18:05:50 2022 +0200
1.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java Sun Feb 04 16:10:37 2024 +0100
1.3 @@ -27,9 +27,12 @@
1.4 import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
1.5 import java.sql.SQLException;
1.6 import java.sql.SQLXML;
1.7 +import java.util.ArrayList;
1.8 import java.util.List;
1.9 +import java.util.Objects;
1.10 import java.util.logging.Level;
1.11 import java.util.logging.Logger;
1.12 +import java.util.regex.Pattern;
1.13
1.14 /**
1.15 * <p>
1.16 @@ -49,6 +52,7 @@
1.17 @PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones")
1.18 @PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width")
1.19 @PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers")
1.20 +@PropertyDeclaration(name = TabularFormatter.PROPERTY_SEPARATE_BY, defaultValue = "", type = String.class, description = "colum(s) whose change triggers separator printing")
1.21 public class TabularFormatter extends AbstractFormatter {
1.22
1.23 private static final Logger log = Logger.getLogger(TabularFormatter.class.getName());
1.24 @@ -58,6 +62,7 @@
1.25 public static final String PROPERTY_ASCII = "ascii";
1.26 public static final String PROPERTY_TRIM = "trim";
1.27 public static final String PROPERTY_HEADER_TYPE = "headerTypes";
1.28 + public static final String PROPERTY_SEPARATE_BY = "separateBy";
1.29 protected ColorfulPrintWriter out;
1.30 private boolean firstResult = true;
1.31 private int[] columnWidth;
1.32 @@ -73,6 +78,27 @@
1.33 * Print data type of each column in the header
1.34 */
1.35 private final boolean printHeaderTypes;
1.36 + /**
1.37 + * When values of columns (names specified by this pattern) changes
1.38 + * between two consecutive rows, horizontal separator is printed.
1.39 + */
1.40 + private final Pattern separateBy;
1.41 + /**
1.42 + * Indexes of columns that matches {@linkplain #separateBy}
1.43 + */
1.44 + private final List<Boolean> separators = new ArrayList<>();
1.45 + /**
1.46 + * Internal counter for buffered data.
1.47 + */
1.48 + private int currentColumnsCount = 0;
1.49 + /**
1.50 + * Buffered data to be compared and printed later.
1.51 + */
1.52 + private final List<Object> currentRow = new ArrayList<>();
1.53 + /**
1.54 + * Buffered data to be compared later.
1.55 + */
1.56 + private final List<Object> previousRow = new ArrayList<>();
1.57
1.58 public TabularFormatter(FormatterContext formatterContext) {
1.59 super(formatterContext);
1.60 @@ -81,6 +107,7 @@
1.61 trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false);
1.62 printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true);
1.63 out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
1.64 + separateBy = formatterContext.getProperties().getPattern(PROPERTY_SEPARATE_BY, null);
1.65 }
1.66
1.67 @Override
1.68 @@ -89,12 +116,21 @@
1.69 printResultSeparator();
1.70
1.71 initColumnWidths(header.getColumnCount());
1.72 + currentRow.clear();
1.73 + previousRow.clear();
1.74 + separators.clear();
1.75
1.76 printTableIndent();
1.77 printTableBorder("╭");
1.78
1.79 List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
1.80
1.81 + if (separateBy != null) {
1.82 + for (ColumnDescriptor cd : columnDescriptors) {
1.83 + separators.add(separateBy.matcher(cd.getLabel()).matches());
1.84 + }
1.85 + }
1.86 +
1.87 for (ColumnDescriptor cd : columnDescriptors) {
1.88 // padding: make header cell at least same width as data cells in this column
1.89 int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0;
1.90 @@ -160,11 +196,49 @@
1.91
1.92 @Override
1.93 public void writeColumnValue(Object value) {
1.94 - super.writeColumnValue(value);
1.95 + if (separateBy == null) super.writeColumnValue(value);
1.96 writeColumnValueInternal(value);
1.97 }
1.98
1.99 protected void writeColumnValueInternal(Object value) {
1.100 + if (separateBy == null) {
1.101 + printColumnValue(value);
1.102 + } else {
1.103 + currentRow.add(value);
1.104 + currentColumnsCount++;
1.105 + int columnsCount = getCurrentColumnsHeader().getColumnCount();
1.106 + if (currentColumnsCount % columnsCount == 0) {
1.107 + if (!previousRow.isEmpty()) {
1.108 + boolean hasChanges = false;
1.109 + for (int i = 0; i < previousRow.size(); i++) {
1.110 + Object previous = previousRow.get(i);
1.111 + Object current = currentRow.get(i);
1.112 + boolean isSepating = separators.get(i);
1.113 + if (isSepating && !Objects.equals(previous, current)) {
1.114 + hasChanges = true;
1.115 + break;
1.116 + }
1.117 + }
1.118 +
1.119 + if (hasChanges) {
1.120 + printRecordSeparator();
1.121 + }
1.122 + }
1.123 +
1.124 + for (Object o : currentRow) {
1.125 + super.writeColumnValue(value);
1.126 + printColumnValue(o);
1.127 + }
1.128 +
1.129 + previousRow.clear();
1.130 + previousRow.addAll(currentRow);
1.131 + currentRow.clear();
1.132 + currentColumnsCount = 0;
1.133 + }
1.134 + }
1.135 + }
1.136 +
1.137 + protected void printColumnValue(Object value) {
1.138
1.139 if (isCurrentColumnFirst()) {
1.140 printTableIndent();
1.141 @@ -181,6 +255,20 @@
1.142
1.143 }
1.144
1.145 + protected void printRecordSeparator() {
1.146 + int columnCount = getCurrentColumnsHeader().getColumnCount();
1.147 + printTableIndent();
1.148 + printTableBorder("├");
1.149 + for (int i = 1; i <= columnCount; i++) {
1.150 + if (i > 1) {
1.151 + printTableBorder("┼");
1.152 + }
1.153 + printTableBorder(repeat('─', getColumnWidth(i) + 2));
1.154 + }
1.155 + printTableBorder("┤");
1.156 + out.println();
1.157 + }
1.158 +
1.159 protected void printValueWithWhitespaceReplaced(String text) {
1.160 Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red);
1.161 }
1.162 @@ -226,6 +314,12 @@
1.163 }
1.164
1.165 @Override
1.166 + public void writeStartRow() {
1.167 + super.writeStartRow();
1.168 + currentColumnsCount = 0;
1.169 + }
1.170 +
1.171 + @Override
1.172 public void writeEndRow() {
1.173 super.writeEndRow();
1.174 writeEndRowInternal();