java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java
branchv_0
changeset 238 4a1864c3e867
parent 237 7e08730da258
child 239 39e6c2ad3571
     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 -}