1.1 --- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java Mon Mar 04 17:06:42 2019 +0100
1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1.3 @@ -1,308 +0,0 @@
1.4 -/**
1.5 - * SQL-DK
1.6 - * Copyright © 2013 František Kučera (frantovo.cz)
1.7 - *
1.8 - * This program is free software: you can redistribute it and/or modify
1.9 - * it under the terms of the GNU General Public License as published by
1.10 - * the Free Software Foundation, either version 3 of the License, or
1.11 - * (at your option) any later version.
1.12 - *
1.13 - * This program is distributed in the hope that it will be useful,
1.14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.16 - * GNU General Public License for more details.
1.17 - *
1.18 - * You should have received a copy of the GNU General Public License
1.19 - * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.20 - */
1.21 -package info.globalcode.sql.dk.formatting;
1.22 -
1.23 -import info.globalcode.sql.dk.ColorfulPrintWriter;
1.24 -import static info.globalcode.sql.dk.ColorfulPrintWriter.*;
1.25 -import info.globalcode.sql.dk.Functions;
1.26 -import static info.globalcode.sql.dk.Functions.lpad;
1.27 -import static info.globalcode.sql.dk.Functions.rpad;
1.28 -import static info.globalcode.sql.dk.Functions.repeat;
1.29 -import info.globalcode.sql.dk.configuration.PropertyDeclaration;
1.30 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
1.31 -import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
1.32 -import java.sql.SQLException;
1.33 -import java.sql.SQLXML;
1.34 -import java.util.List;
1.35 -import java.util.logging.Level;
1.36 -import java.util.logging.Logger;
1.37 -
1.38 -/**
1.39 - * <p>
1.40 - * Prints human-readable output – tables of result sets and text messages with update counts.
1.41 - * </p>
1.42 - *
1.43 - * <p>
1.44 - * Longer values might break the table – overflow the cells – see alternative tabular formatters and
1.45 - * the {@linkplain #PROPERTY_TRIM} property.
1.46 - * </p>
1.47 - *
1.48 - * @author Ing. František Kučera (frantovo.cz)
1.49 - * @see TabularPrefetchingFormatter
1.50 - * @see TabularWrappingFormatter
1.51 - */
1.52 -@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
1.53 -@PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones")
1.54 -@PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width")
1.55 -@PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers")
1.56 -public class TabularFormatter extends AbstractFormatter {
1.57 -
1.58 - private static final Logger log = Logger.getLogger(TabularFormatter.class.getName());
1.59 - public static final String NAME = "tabular"; // bash-completion:formatter
1.60 - private static final String HEADER_TYPE_PREFIX = " (";
1.61 - private static final String HEADER_TYPE_SUFFIX = ")";
1.62 - public static final String PROPERTY_ASCII = "ascii";
1.63 - public static final String PROPERTY_TRIM = "trim";
1.64 - public static final String PROPERTY_HEADER_TYPE = "headerTypes";
1.65 - protected ColorfulPrintWriter out;
1.66 - private boolean firstResult = true;
1.67 - private int[] columnWidth;
1.68 - /**
1.69 - * use ASCII borders instead of unicode ones
1.70 - */
1.71 - private final boolean asciiNostalgia;
1.72 - /**
1.73 - * Trim values if they are longer than cell size
1.74 - */
1.75 - private final boolean trimValues;
1.76 - /**
1.77 - * Print data type of each column in the header
1.78 - */
1.79 - private final boolean printHeaderTypes;
1.80 -
1.81 - public TabularFormatter(FormatterContext formatterContext) {
1.82 - super(formatterContext);
1.83 - out = new ColorfulPrintWriter(formatterContext.getOutputStream());
1.84 - asciiNostalgia = formatterContext.getProperties().getBoolean(PROPERTY_ASCII, false);
1.85 - trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false);
1.86 - printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true);
1.87 - out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
1.88 - }
1.89 -
1.90 - @Override
1.91 - public void writeStartResultSet(ColumnsHeader header) {
1.92 - super.writeStartResultSet(header);
1.93 - printResultSeparator();
1.94 -
1.95 - initColumnWidths(header.getColumnCount());
1.96 -
1.97 - printTableIndent();
1.98 - printTableBorder("╭");
1.99 -
1.100 - List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
1.101 -
1.102 - for (ColumnDescriptor cd : columnDescriptors) {
1.103 - // padding: make header cell at least same width as data cells in this column
1.104 - int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0;
1.105 - cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth));
1.106 - updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth);
1.107 -
1.108 - if (!cd.isFirstColumn()) {
1.109 - printTableBorder("┬");
1.110 - }
1.111 - printTableBorder(repeat('─', getColumnWidth(cd.getColumnNumber()) + 2));
1.112 - }
1.113 - printTableBorder("╮");
1.114 - out.println();
1.115 -
1.116 - for (ColumnDescriptor cd : columnDescriptors) {
1.117 - if (cd.isFirstColumn()) {
1.118 - printTableIndent();
1.119 - printTableBorder("│ ");
1.120 - } else {
1.121 - printTableBorder(" │ ");
1.122 - }
1.123 - out.print(TerminalStyle.Bright, cd.getLabel());
1.124 - if (printHeaderTypes) {
1.125 - out.print(HEADER_TYPE_PREFIX);
1.126 - out.print(cd.getTypeName());
1.127 - out.print(HEADER_TYPE_SUFFIX);
1.128 - }
1.129 - if (cd.isLastColumn()) {
1.130 - printTableBorder(" │");
1.131 - }
1.132 - }
1.133 - out.println();
1.134 -
1.135 - printTableIndent();
1.136 - printTableBorder("├");
1.137 - for (int i = 1; i <= header.getColumnCount(); i++) {
1.138 - if (i > 1) {
1.139 - printTableBorder("┼");
1.140 - }
1.141 - printTableBorder(repeat('─', getColumnWidth(i) + 2));
1.142 - }
1.143 - printTableBorder("┤");
1.144 - out.println();
1.145 -
1.146 - out.flush();
1.147 - }
1.148 -
1.149 - /**
1.150 - * Must be called before {@linkplain #updateColumnWidth(int, int)} and
1.151 - * {@linkplain #getColumnWidth(int)} for each result set.
1.152 - *
1.153 - * @param columnCount number of columns in current result set
1.154 - */
1.155 - protected void initColumnWidths(int columnCount) {
1.156 - if (columnWidth == null) {
1.157 - columnWidth = new int[columnCount];
1.158 - }
1.159 - }
1.160 -
1.161 - protected void cleanColumnWidths() {
1.162 - columnWidth = null;
1.163 - }
1.164 -
1.165 - @Override
1.166 - public void writeColumnValue(Object value) {
1.167 - super.writeColumnValue(value);
1.168 - writeColumnValueInternal(value);
1.169 - }
1.170 -
1.171 - protected void writeColumnValueInternal(Object value) {
1.172 -
1.173 - if (isCurrentColumnFirst()) {
1.174 - printTableIndent();
1.175 - printTableBorder("│ ");
1.176 - } else {
1.177 - printTableBorder(" │ ");
1.178 - }
1.179 -
1.180 - printValueWithWhitespaceReplaced(toString(value));
1.181 -
1.182 - if (isCurrentColumnLast()) {
1.183 - printTableBorder(" │");
1.184 - }
1.185 -
1.186 - }
1.187 -
1.188 - protected void printValueWithWhitespaceReplaced(String text) {
1.189 - Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red);
1.190 - }
1.191 -
1.192 - protected int getColumnWidth(int columnNumber) {
1.193 - return columnWidth[columnNumber - 1];
1.194 - }
1.195 -
1.196 - private void setColumnWidth(int columnNumber, int width) {
1.197 - columnWidth[columnNumber - 1] = width;
1.198 - }
1.199 -
1.200 - protected void updateColumnWidth(int columnNumber, int width) {
1.201 - int oldWidth = getColumnWidth(columnNumber);
1.202 - setColumnWidth(columnNumber, Math.max(width, oldWidth));
1.203 -
1.204 - }
1.205 -
1.206 - protected String toString(Object value) {
1.207 - final int width = getColumnWidth(getCurrentColumnsCount());
1.208 - String result;
1.209 - if (value instanceof Number || value instanceof Boolean) {
1.210 - result = lpad(String.valueOf(value), width);
1.211 - } else {
1.212 - if (value instanceof SQLXML) {
1.213 - // TODO: move to a common method, share with other formatters
1.214 - try {
1.215 - value = ((SQLXML) value).getString();
1.216 - } catch (SQLException e) {
1.217 - log.log(Level.SEVERE, "Unable to format XML", e);
1.218 - }
1.219 - }
1.220 -
1.221 - result = rpad(String.valueOf(value), width);
1.222 - }
1.223 - // ? value = (boolean) value ? "✔" : "✗";
1.224 -
1.225 - if (trimValues && result.length() > width) {
1.226 - result = result.substring(0, width - 1) + "…";
1.227 - }
1.228 -
1.229 - return result;
1.230 - }
1.231 -
1.232 - @Override
1.233 - public void writeEndRow() {
1.234 - super.writeEndRow();
1.235 - writeEndRowInternal();
1.236 - }
1.237 -
1.238 - public void writeEndRowInternal() {
1.239 - out.println();
1.240 - out.flush();
1.241 - }
1.242 -
1.243 - @Override
1.244 - public void writeEndResultSet() {
1.245 - int columnCount = getCurrentColumnsHeader().getColumnCount();
1.246 - super.writeEndResultSet();
1.247 -
1.248 - printTableIndent();
1.249 - printTableBorder("╰");
1.250 - for (int i = 1; i <= columnCount; i++) {
1.251 - if (i > 1) {
1.252 - printTableBorder("┴");
1.253 - }
1.254 - printTableBorder(repeat('─', getColumnWidth(i) + 2));
1.255 - }
1.256 - printTableBorder("╯");
1.257 - out.println();
1.258 -
1.259 - cleanColumnWidths();
1.260 -
1.261 - out.print(TerminalColor.Yellow, "Record count: ");
1.262 - out.println(getCurrentRowCount());
1.263 - out.bell();
1.264 - out.flush();
1.265 - }
1.266 -
1.267 - @Override
1.268 - public void writeUpdatesResult(int updatedRowsCount) {
1.269 - super.writeUpdatesResult(updatedRowsCount);
1.270 - printResultSeparator();
1.271 - out.print(TerminalColor.Red, "Updated records: ");
1.272 - out.println(updatedRowsCount);
1.273 - out.bell();
1.274 - out.flush();
1.275 - }
1.276 -
1.277 - @Override
1.278 - public void writeEndDatabase() {
1.279 - super.writeEndDatabase();
1.280 - out.flush();
1.281 - }
1.282 -
1.283 - private void printResultSeparator() {
1.284 - if (firstResult) {
1.285 - firstResult = false;
1.286 - } else {
1.287 - out.println();
1.288 - }
1.289 - }
1.290 -
1.291 - protected void printTableBorder(String border) {
1.292 - if (asciiNostalgia) {
1.293 - border = border.replaceAll("─", "-");
1.294 - border = border.replaceAll("│", "|");
1.295 - border = border.replaceAll("[╭┬╮├┼┤╰┴╯]", "+");
1.296 - }
1.297 -
1.298 - out.print(TerminalColor.Green, border);
1.299 - }
1.300 -
1.301 - protected void printTableIndent() {
1.302 - out.print(" ");
1.303 - }
1.304 -
1.305 - /**
1.306 - * @return whether should print only ASCII characters instead of unlimited Unicode.
1.307 - */
1.308 - protected boolean isAsciiNostalgia() {
1.309 - return asciiNostalgia;
1.310 - }
1.311 -}