From 65c02b778b1eb00e0f42331ee4a93c7e4d9914be Mon Sep 17 00:00:00 2001 From: mpollack Date: Tue, 1 Apr 2014 23:50:55 -0400 Subject: [PATCH] SHL-126 - Add utility classes for rendering ascii based tables --- build.gradle | 1 + .../shell/support/table/Table.java | 130 ++++++++ .../shell/support/table/TableHeader.java | 146 +++++++++ .../shell/support/table/TableRenderer.java | 267 +++++++++++++++++ .../shell/support/table/TableRow.java | 88 ++++++ .../shell/support/util/StringUtils.java | 279 ++++++++++++++++++ .../support/table/TableRendererTest.java | 198 +++++++++++++ .../shell/support/util/StringUtilsTest.java | 74 +++++ ...erParameterInfoDataAsTableWithMaxWidth.txt | 5 + ...rTableWithRowShorthand-expected-output.txt | 7 + .../testRenderTextTable-expected-output.txt | 5 + ...extTable-single-column-expected-output.txt | 3 + ...e-single-column-width4-expected-output.txt | 7 + 13 files changed, 1210 insertions(+) create mode 100644 src/main/java/org/springframework/shell/support/table/Table.java create mode 100644 src/main/java/org/springframework/shell/support/table/TableHeader.java create mode 100644 src/main/java/org/springframework/shell/support/table/TableRenderer.java create mode 100644 src/main/java/org/springframework/shell/support/table/TableRow.java create mode 100644 src/main/java/org/springframework/shell/support/util/StringUtils.java create mode 100644 src/test/java/org/springframework/shell/support/table/TableRendererTest.java create mode 100644 src/test/java/org/springframework/shell/support/util/StringUtilsTest.java create mode 100644 src/test/resources/testRenderParameterInfoDataAsTableWithMaxWidth.txt create mode 100644 src/test/resources/testRenderTableWithRowShorthand-expected-output.txt create mode 100644 src/test/resources/testRenderTextTable-expected-output.txt create mode 100644 src/test/resources/testRenderTextTable-single-column-expected-output.txt create mode 100644 src/test/resources/testRenderTextTable-single-column-width4-expected-output.txt diff --git a/build.gradle b/build.gradle index 679fdf3b..3116f362 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { compile "org.springframework:spring-core:$springVersion" compile "org.springframework:spring-context-support:$springVersion" compile "commons-io:commons-io:$commonsioVersion" + compile "com.google.guava:guava:15.0" compile "jline:jline:$jlineVersion" compile "cglib:cglib:$cglibVersion" diff --git a/src/main/java/org/springframework/shell/support/table/Table.java b/src/main/java/org/springframework/shell/support/table/Table.java new file mode 100644 index 00000000..df364540 --- /dev/null +++ b/src/main/java/org/springframework/shell/support/table/Table.java @@ -0,0 +1,130 @@ +/* + * Copyright 2009-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell.support.table; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Provide a basic concept of a table structure containing a map of column + * headers and a collection of rows. Used to render text-based tables (console + * output). + * + * @see TableRenderer + * + * @author Gunnar Hillert + * + */ +public class Table { + + private final Map headers = new TreeMap(); + + private volatile List rows = new ArrayList(0); + + public List getRows() { + return rows; + } + + public Map getHeaders() { + return headers; + } + + public Table addHeader(Integer columnIndex, TableHeader tableHeader) { + this.headers.put(columnIndex, tableHeader); + return this; + } + + /** + * Add a new empty row to the table. + * + * @return the newly created row, which can be then be populated + */ + public TableRow newRow() { + TableRow row = new TableRow(); + rows.add(row); + return row; + } + + public Table addRow(String... values) { + + final TableRow row = new TableRow(); + + int column = 1; + + for (String value : values) { + row.addValue(column, value); + column++; + } + + rows.add(row); + + return this; + } + + public void calculateColumnWidths() { + for (java.util.Map.Entry headerEntry : headers + .entrySet()) { + final Integer headerEntryKey = headerEntry.getKey(); + for (TableRow tableRow : rows) { + headerEntry.getValue().updateWidth( + tableRow.getValue(headerEntryKey).length()); + } + } + } + + @Override + public String toString() { + return TableRenderer.renderTextTable(this); + } + + @Override + public int hashCode() { + calculateColumnWidths(); + final int prime = 31; + int result = 1; + result = prime * result + ((headers == null) ? 0 : headers.hashCode()); + result = prime * result + ((rows == null) ? 0 : rows.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + Table other = (Table) obj; + this.calculateColumnWidths(); + other.calculateColumnWidths(); + if (headers == null) { + if (other.headers != null) + return false; + } else if (!headers.equals(other.headers)) + return false; + if (rows == null) { + if (other.rows != null) + return false; + } else if (!rows.equals(other.rows)) + return false; + return true; + } +} diff --git a/src/main/java/org/springframework/shell/support/table/TableHeader.java b/src/main/java/org/springframework/shell/support/table/TableHeader.java new file mode 100644 index 00000000..6d3bcf68 --- /dev/null +++ b/src/main/java/org/springframework/shell/support/table/TableHeader.java @@ -0,0 +1,146 @@ +/* + * Copyright 2009-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell.support.table; + +/** + * Defines table column headers used by {@link Table}. + * + * @see TableRenderer + * + * @author Gunnar Hillert + * + */ +public class TableHeader { + + private int maxWidth = -1; + + private int width = 0; + + private String name; + + /** + * Constructor that initializes the table header with the provided header + * name and the with of the table header. + * + * @param name + * @param width + */ + public TableHeader(String name, int width) { + + super(); + this.width = width; + this.name = name; + + } + + /** + * Constructor that initializes the table header with the provided header + * name. The with of the table header is calculated and assigned based on + * the provided header name. + * + * @param name + */ + public TableHeader(String name) { + super(); + this.name = name; + + if (name == null) { + this.width = 0; + } else { + this.width = name.length(); + } + + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + /** + * Updated the width for this particular column, but only if the value of + * the passed-in width is higher than the value of the pre-existing width. + * + * @param width + */ + public void updateWidth(int width) { + if (this.width < width) { + if (this.maxWidth > 0 && this.maxWidth < width) { + this.width = this.maxWidth; + } else { + this.width = width; + } + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getMaxWidth() { + return maxWidth; + } + + /** + * Defaults to -1 indicating to ignore the property. + * + * @param maxWidth + * If negative or zero this property will be ignored. + */ + public void setMaxWidth(int maxWidth) { + this.maxWidth = maxWidth; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + maxWidth; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + width; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TableHeader other = (TableHeader) obj; + if (maxWidth != other.maxWidth) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (width != other.width) + return false; + return true; + } + +} diff --git a/src/main/java/org/springframework/shell/support/table/TableRenderer.java b/src/main/java/org/springframework/shell/support/table/TableRenderer.java new file mode 100644 index 00000000..4119b2bb --- /dev/null +++ b/src/main/java/org/springframework/shell/support/table/TableRenderer.java @@ -0,0 +1,267 @@ +/* + * Copyright 2009-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell.support.table; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.shell.support.util.StringUtils; + +import com.google.common.base.Splitter; + +/** + * Contains utility methods for rendering data to a formatted console output. + * E.g. it provides helper methods for rendering ASCII-based data tables. + * + * @author Gunnar Hillert + * @author Thomas Risberg + * + */ +public final class TableRenderer { + + public static final String HORIZONTAL_LINE = "-------------------------------------------------------------------------------\n"; + + public static final int COLUMN_1 = 1; + + public static final int COLUMN_2 = 2; + + public static final int COLUMN_3 = 3; + + public static final int COLUMN_4 = 4; + + public static final int COLUMN_5 = 5; + + public static final int COLUMN_6 = 6; + + /** + * Prevent instantiation. + * + */ + private TableRenderer() { + throw new AssertionError(); + } + + /** + * Renders a textual representation of the list of provided Map data + * + * @param columns + * List of Maps + * @return The rendered table representation as String + * + */ + public static String renderMapDataAsTable(List> data, + List columns) { + + Table table = new Table(); + + int col = 0; + for (String colName : columns) { + col++; + table.getHeaders().put(col, new TableHeader(colName)); + if (col >= 6) { + break; + } + } + + for (Map dataRow : data) { + + TableRow tableRow = new TableRow(); + + for (int i = 0; i < col; i++) { + String value = dataRow.get(columns.get(i)).toString(); + table.getHeaders().get(i + 1).updateWidth(value.length()); + tableRow.addValue(i + 1, value); + } + + table.getRows().add(tableRow); + } + + return renderTextTable(table); + } + + public static String renderParameterInfoDataAsTable( + Map parameters, boolean withHeader, + int lastColumnMaxWidth) { + final Table table = new Table(); + + table.getHeaders().put(COLUMN_1, new TableHeader("Parameter")); + + final TableHeader tableHeader2 = new TableHeader( + "Value (Configured or Default)"); + tableHeader2.setMaxWidth(lastColumnMaxWidth); + table.getHeaders().put(COLUMN_2, tableHeader2); + + for (Entry entry : parameters.entrySet()) { + + final TableRow tableRow = new TableRow(); + + table.getHeaders().get(COLUMN_1) + .updateWidth(entry.getKey().length()); + tableRow.addValue(COLUMN_1, entry.getKey()); + + int width = entry.getValue() != null ? entry.getValue().length() + : 0; + + table.getHeaders().get(COLUMN_2).updateWidth(width); + tableRow.addValue(COLUMN_2, entry.getValue()); + + table.getRows().add(tableRow); + } + + return renderTextTable(table, withHeader); + } + + /** + * Renders a textual representation of provided parameter map. + * + * @param parameters + * Map of parameters (key, value) + * @return The rendered table representation as String + * + */ + public static String renderParameterInfoDataAsTable( + Map parameters) { + return renderParameterInfoDataAsTable(parameters, true, -1); + } + + public static String renderTextTable(Table table) { + return renderTextTable(table, true); + } + + /** + * Renders a textual representation of the provided {@link Table} + * + * @param table + * Table data {@link Table} + * @return The rendered table representation as String + */ + public static String renderTextTable(Table table, boolean withHeader) { + + table.calculateColumnWidths(); + + final String padding = " "; + final String headerBorder = getHeaderBorder(table.getHeaders()); + final StringBuilder textTable = new StringBuilder(); + + if (withHeader) { + final StringBuilder headerline = new StringBuilder(); + for (TableHeader header : table.getHeaders().values()) { + + if (header.getName().length() > header.getWidth()) { + Iterable chunks = Splitter.fixedLength( + header.getWidth()).split(header.getName()); + int length = headerline.length(); + boolean first = true; + for (String chunk : chunks) { + final String lineToAppend; + if (first) { + lineToAppend = padding + + StringUtils.padRight(chunk, + header.getWidth()); + } else { + lineToAppend = StringUtils.padLeft("", length) + + padding + + StringUtils.padRight(chunk, + header.getWidth()); + } + first = false; + headerline.append(lineToAppend); + headerline.append("\n"); + } + headerline.deleteCharAt(headerline.lastIndexOf("\n")); + } else { + String lineToAppend = padding + + StringUtils.padRight(header.getName(), + header.getWidth()); + headerline.append(lineToAppend); + } + } + textTable.append(org.springframework.util.StringUtils + .trimTrailingWhitespace(headerline.toString())); + textTable.append("\n"); + } + + textTable.append(headerBorder); + + for (TableRow row : table.getRows()) { + StringBuilder rowLine = new StringBuilder(); + for (Entry entry : table.getHeaders() + .entrySet()) { + String value = row.getValue(entry.getKey()); + if (value.length() > entry.getValue().getWidth()) { + Iterable chunks = Splitter.fixedLength( + entry.getValue().getWidth()).split(value); + int length = rowLine.length(); + boolean first = true; + for (String chunk : chunks) { + final String lineToAppend; + if (first) { + lineToAppend = padding + + StringUtils.padRight(chunk, entry + .getValue().getWidth()); + } else { + lineToAppend = StringUtils.padLeft("", length) + + padding + + StringUtils.padRight(chunk, entry + .getValue().getWidth()); + } + first = false; + rowLine.append(lineToAppend); + rowLine.append("\n"); + } + rowLine.deleteCharAt(rowLine.lastIndexOf("\n")); + } else { + String lineToAppend = padding + + StringUtils.padRight(value, entry.getValue() + .getWidth()); + rowLine.append(lineToAppend); + } + } + textTable.append(org.springframework.util.StringUtils + .trimTrailingWhitespace(rowLine.toString())); + textTable.append("\n"); + } + + if (!withHeader) { + textTable.append(headerBorder); + } + + return textTable.toString(); + } + + /** + * Renders the Table header border, based on the map of provided headers. + * + * @param headers + * Map of headers containing meta information e.g. name+width of + * header + * @return Returns the rendered header border as String + */ + public static String getHeaderBorder(Map headers) { + + final StringBuilder headerBorder = new StringBuilder(); + + for (TableHeader header : headers.values()) { + headerBorder.append(StringUtils.padRight(" ", + header.getWidth() + 2, '-')); + } + headerBorder.append("\n"); + + return headerBorder.toString(); + } +} diff --git a/src/main/java/org/springframework/shell/support/table/TableRow.java b/src/main/java/org/springframework/shell/support/table/TableRow.java new file mode 100644 index 00000000..44cfde36 --- /dev/null +++ b/src/main/java/org/springframework/shell/support/table/TableRow.java @@ -0,0 +1,88 @@ +/* + * Copyright 2009-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell.support.table; + +import java.util.HashMap; +import java.util.Map; + +/** + * Holds the table rows used by {@link Table}. + * + * @see TableRenderer + * + * @author Gunnar Hillert + * @author Ilayaperumal Gopinathan + * + */ +public class TableRow { + + /** Holds the data for the column */ + private Map data = new HashMap(); + + public void setData(Map data) { + this.data = data; + } + + /** + * Return a value from this row. + * + * @param key + * Column for which to return the value for + * @return Value of the specified column within this row + * + */ + public String getValue(Integer key) { + return data.get(key); + } + + /** + * Add a value to the to the specified column within this row. + * + * @param column + * @param value + */ + public TableRow addValue(Integer column, String value) { + this.data.put(column, value); + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TableRow other = (TableRow) obj; + if (data == null) { + if (other.data != null) + return false; + } else if (!data.equals(other.data)) + return false; + return true; + } + +} diff --git a/src/main/java/org/springframework/shell/support/util/StringUtils.java b/src/main/java/org/springframework/shell/support/util/StringUtils.java new file mode 100644 index 00000000..74fddf94 --- /dev/null +++ b/src/main/java/org/springframework/shell/support/util/StringUtils.java @@ -0,0 +1,279 @@ +/* + * Copyright 2009-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.support.util; + + +/** + * Utility methods for Strings focused on the use in formatting of tables. padLeft methods taken from + * Commons Lang 2.6 to avoid an extra compile time dependency. + * + * @author Gunnar Hillert + * @author Mark Pollack + * + */ +public class StringUtils { + + /** + *

+ * The maximum size to which the padding constant(s) can expand. + *

+ */ + private static final int PAD_LIMIT = 8192; + + /** + *

+ * Left pad a String with spaces (' '). + *

+ * + *

+ * The String is padded to the size of size. + *

+ * + *
+	 * StringUtils.leftPad(null, *) = null
+	 * StringUtils.leftPad("", 3) = " "
+	 * StringUtils.leftPad("bat", 3) = "bat"
+	 * StringUtils.leftPad("bat", 5) = " bat"
+	 * StringUtils.leftPad("bat", 1) = "bat"
+	 * StringUtils.leftPad("bat", -1) = "bat"
+	 * 
+ * + * @param str + * the String to pad out, may be null + * @param size + * the size to pad to + * @return left padded String or original String if no padding is necessary, + * null if null String input + */ + public static String padLeft(String str, int size) { + return padLeft(str, size, ' '); + } + + /** + *

+ * Left pad a String with a specified character. + *

+ * + *

+ * Pad to a size of size. + *

+ * + *
+	 * StringUtils.leftPad(null, *, *) = null
+	 * StringUtils.leftPad("", 3, 'z') = "zzz"
+	 * StringUtils.leftPad("bat", 3, 'z') = "bat"
+	 * StringUtils.leftPad("bat", 5, 'z') = "zzbat"
+	 * StringUtils.leftPad("bat", 1, 'z') = "bat"
+	 * StringUtils.leftPad("bat", -1, 'z') = "bat"
+	 * 
+ * + * @param str + * the String to pad out, may be null + * @param size + * the size to pad to + * @param padChar + * the character to pad with + * @return left padded String or original String if no padding is necessary, + * null if null String input + * @since 2.0 + */ + public static String padLeft(String str, int size, char padChar) { + if (str == null) { + return null; + } + int pads = size - str.length(); + if (pads <= 0) { + return str; // returns original String when possible + } + if (pads > PAD_LIMIT) { + return padLeft(str, size, String.valueOf(padChar)); + } + return padding(pads, padChar).concat(str); + } + + /** + *

+ * Left pad a String with a specified String. + *

+ * + *

+ * Pad to a size of size. + *

+ * + *
+	 * StringUtils.leftPad(null, *, *) = null
+	 * StringUtils.leftPad("", 3, "z") = "zzz"
+	 * StringUtils.leftPad("bat", 3, "yz") = "bat"
+	 * StringUtils.leftPad("bat", 5, "yz") = "yzbat"
+	 * StringUtils.leftPad("bat", 8, "yz") = "yzyzybat"
+	 * StringUtils.leftPad("bat", 1, "yz") = "bat"
+	 * StringUtils.leftPad("bat", -1, "yz") = "bat"
+	 * StringUtils.leftPad("bat", 5, null) = " bat"
+	 * StringUtils.leftPad("bat", 5, "") = " bat"
+	 * 
+ * + * @param str + * the String to pad out, may be null + * @param size + * the size to pad to + * @param padStr + * the String to pad with, null or empty treated as single space + * @return left padded String or original String if no padding is necessary, + * null if null String input + */ + public static String padLeft(String str, int size, String padStr) { + if (str == null) { + return null; + } + if (isEmpty(padStr)) { + padStr = " "; + } + int padLen = padStr.length(); + int strLen = str.length(); + int pads = size - strLen; + if (pads <= 0) { + return str; // returns original String when possible + } + if (padLen == 1 && pads <= PAD_LIMIT) { + return padLeft(str, size, padStr.charAt(0)); + } + + if (pads == padLen) { + return padStr.concat(str); + } else if (pads < padLen) { + return padStr.substring(0, pads).concat(str); + } else { + char[] padding = new char[pads]; + char[] padChars = padStr.toCharArray(); + for (int i = 0; i < pads; i++) { + padding[i] = padChars[i % padLen]; + } + return new String(padding).concat(str); + } + } + + /** + *

+ * Checks if a String is empty ("") or null. + *

+ * + *
+	 * StringUtils.isEmpty(null) = true
+	 * StringUtils.isEmpty("") = true
+	 * StringUtils.isEmpty(" ") = false
+	 * StringUtils.isEmpty("bob") = false
+	 * StringUtils.isEmpty(" bob ") = false
+	 * 
+ * + *

+ * NOTE: This method changed in Lang version 2.0. It no longer trims the + * String. That functionality is available in isBlank(). + *

+ * + * @param str + * the String to check, may be null + * @return true if the String is empty or null + */ + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + /** + *

+ * Returns padding using the specified delimiter repeated to a given length. + *

+ * + *
+	 * StringUtils.padding(0, 'e') = ""
+	 * StringUtils.padding(3, 'e') = "eee"
+	 * StringUtils.padding(-2, 'e') = IndexOutOfBoundsException
+	 * 
+ * + *

+ * Note: this method doesn't not support padding with Unicode + * Supplementary Characters as they require a pair of chars + * to be represented. If you are needing to support full I18N of your + * applications consider using {@link #repeat(String, int)} instead. + *

+ * + * @param repeat + * number of times to repeat delim + * @param padChar + * character to repeat + * @return String with repeated character + * @throws IndexOutOfBoundsException + * if repeat < 0 + * @see #repeat(String, int) + */ + private static String padding(int repeat, char padChar) + throws IndexOutOfBoundsException { + if (repeat < 0) { + throw new IndexOutOfBoundsException( + "Cannot pad a negative amount: " + repeat); + } + final char[] buf = new char[repeat]; + for (int i = 0; i < buf.length; i++) { + buf[i] = padChar; + } + return new String(buf); + } + + /** + * Right-pad a String with a configurable padding character. + * + * @param inputString + * The String to pad. A {@code null} String will be treated like + * an empty String. + * @param size + * Pad String by the number of characters. + * @param paddingChar + * The character to pad the String with. + * @return The padded String. If the provided String is null, an empty + * String is returned. + */ + public static String padRight(String inputString, int size, char paddingChar) { + + final String stringToPad; + + if (inputString == null) { + stringToPad = ""; + } else { + stringToPad = inputString; + } + + StringBuilder padded = new StringBuilder(stringToPad); + while (padded.length() < size) { + padded.append(paddingChar); + } + return padded.toString(); + } + + /** + * Right-pad the provided String with empty spaces. + * + * @param string + * The String to pad + * @param size + * Pad String by the number of characters. + * @return The padded String. If the provided String is null, an empty + * String is returned. + */ + public static String padRight(String string, int size) { + return padRight(string, size, ' '); + } + +} diff --git a/src/test/java/org/springframework/shell/support/table/TableRendererTest.java b/src/test/java/org/springframework/shell/support/table/TableRendererTest.java new file mode 100644 index 00000000..87754914 --- /dev/null +++ b/src/test/java/org/springframework/shell/support/table/TableRendererTest.java @@ -0,0 +1,198 @@ +/* + * Copyright 2009-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.shell.support.table; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Map; +import java.util.TreeMap; + +import org.junit.Test; + +import org.springframework.util.FileCopyUtils; + +/** + * @author Gunnar Hillert + * + */ +public class TableRendererTest { + + @Test + public void testRenderTextTable() { + + final Table table = new Table(); + table.addHeader(1, new TableHeader("Tap Name")) + .addHeader(2, new TableHeader("Stream Name")) + .addHeader(3, new TableHeader("Tap Definition")); + + for (int i = 1; i <= 3; i++) { + final TableRow row = new TableRow(); + row.addValue(1, "tap" + i) + .addValue(2, "ticktock") + .addValue(3, "tap@ticktock|log"); + table.getRows().add(row); + } + + String expectedTableAsString = null; + + final InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("testRenderTextTable-expected-output.txt"); + + assertNotNull("The inputstream is null.", inputStream); + + try { + expectedTableAsString = FileCopyUtils.copyToString(new InputStreamReader(inputStream)); + } + catch (IOException e) { + e.printStackTrace(); + fail(); + } + + final String tableRenderedAsString = TableRenderer.renderTextTable(table); + + assertEquals(expectedTableAsString.replaceAll("\r", ""), tableRenderedAsString); + } + + @Test + public void testRenderTextTableWithSingleColumn() { + + final Table table = new Table(); + table.addHeader(1, new TableHeader("Gauge name")); + + final TableRow row = new TableRow(); + row.addValue(1, "simplegauge"); + table.getRows().add(row); + + String expectedTableAsString = null; + + final InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("testRenderTextTable-single-column-expected-output.txt"); + + assertNotNull("The inputstream is null.", inputStream); + + try { + expectedTableAsString = FileCopyUtils.copyToString(new InputStreamReader(inputStream)); + } + catch (IOException e) { + e.printStackTrace(); + fail(); + } + + final String tableRenderedAsString = TableRenderer.renderTextTable(table); + assertEquals(expectedTableAsString.replaceAll("\r", ""), tableRenderedAsString); + } + + @Test + public void testRenderTextTableWithSingleColumnAndWidthOf4() { + + final Table table = new Table(); + final TableHeader tableHeader = new TableHeader("Gauge name"); + tableHeader.setMaxWidth(4); + table.addHeader(1, tableHeader); + + final TableRow row = new TableRow(); + row.addValue(1, "simplegauge"); + table.getRows().add(row); + + String expectedTableAsString = null; + + final InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("testRenderTextTable-single-column-width4-expected-output.txt"); + + assertNotNull("The inputstream is null.", inputStream); + + try { + expectedTableAsString = FileCopyUtils.copyToString(new InputStreamReader(inputStream)); + } + catch (IOException e) { + e.printStackTrace(); + fail(); + } + + final String tableRenderedAsString = TableRenderer.renderTextTable(table); + assertEquals(expectedTableAsString.replaceAll("\r", ""), tableRenderedAsString); + } + + @Test + public void testRenderParameterInfoDataAsTableWithMaxWidth() { + + final Map values = new TreeMap(); + + values.put("Key1", "Lorem ipsum dolor sit posuere."); + values.put("My super key 2", "Lorem ipsum"); + + String expectedTableAsString = null; + + final InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("testRenderParameterInfoDataAsTableWithMaxWidth.txt"); + + assertNotNull("The inputstream is null.", inputStream); + + try { + expectedTableAsString = FileCopyUtils.copyToString(new InputStreamReader(inputStream)); + } + catch (IOException e) { + e.printStackTrace(); + fail(); + } + + final String tableRenderedAsString = TableRenderer.renderParameterInfoDataAsTable(values, false, 20); + assertEquals(expectedTableAsString.replaceAll("\r", ""), tableRenderedAsString); + } + + @Test + public void testRenderTableWithRowShorthand() { + + final Table table = new Table(); + table.addHeader(1, new TableHeader("Property")) + .addHeader(2, new TableHeader("Value")); + + table.addRow("Job Execution ID", String.valueOf(1)) + .addRow("Job Name", "My Job Name") + .addRow("Start Time", "12:30") + .addRow("Step Execution Count", String.valueOf(12)) + .addRow("Status", "COMPLETED"); + + assertNotNull(table.toString()); + + final InputStream inputStream = getClass() + .getClassLoader() + .getResourceAsStream("testRenderTableWithRowShorthand-expected-output.txt"); + + assertNotNull("The inputstream is null.", inputStream); + + String expectedTableAsString = null; + + try { + expectedTableAsString = FileCopyUtils.copyToString(new InputStreamReader(inputStream)); + } + catch (IOException e) { + e.printStackTrace(); + fail(); + } + assertEquals(expectedTableAsString.replaceAll("\r", ""), table.toString()); + } +} diff --git a/src/test/java/org/springframework/shell/support/util/StringUtilsTest.java b/src/test/java/org/springframework/shell/support/util/StringUtilsTest.java new file mode 100644 index 00000000..2c858dd3 --- /dev/null +++ b/src/test/java/org/springframework/shell/support/util/StringUtilsTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2011-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.support.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class StringUtilsTest { + + @Test + public void testPadRightWithNullString() { + assertEquals(" ", StringUtils.padRight(null, 5)); + } + + @Test + public void testPadRightWithEmptyString() { + assertEquals(" ", StringUtils.padRight("", 5)); + } + + @Test + public void testPadRight() { + assertEquals("foo ", StringUtils.padRight("foo", 5)); + } + + @Test + public void testLeftPad_StringInt() { + assertEquals(null, StringUtils.padLeft(null, 5)); + assertEquals(" ", StringUtils.padLeft("", 5)); + assertEquals(" abc", StringUtils.padLeft("abc", 5)); + assertEquals("abc", StringUtils.padLeft("abc", 2)); + } + + @Test + public void testLeftPad_StringIntChar() { + assertEquals(null, StringUtils.padLeft(null, 5, ' ')); + assertEquals(" ", StringUtils.padLeft("", 5, ' ')); + assertEquals(" abc", StringUtils.padLeft("abc", 5, ' ')); + assertEquals("xxabc", StringUtils.padLeft("abc", 5, 'x')); + assertEquals("\uffff\uffffabc", StringUtils.padLeft("abc", 5, '\uffff')); + assertEquals("abc", StringUtils.padLeft("abc", 2, ' ')); + String str = StringUtils.padLeft("aaa", 10000, 'a'); // bigger than pad length + assertEquals(10000, str.length()); + //Note, did not include the next assert to avoid pulling in a long chain of methods from commons lang + //assertEquals(true, StringUtils.containsOnly(str, new char[] {'a'})); + } + + @Test + public void testLeftPad_StringIntString() { + assertEquals(null, StringUtils.padLeft(null, 5, "-+")); + assertEquals(null, StringUtils.padLeft(null, 5, null)); + assertEquals(" ", StringUtils.padLeft("", 5, " ")); + assertEquals("-+-+abc", StringUtils.padLeft("abc", 7, "-+")); + assertEquals("-+~abc", StringUtils.padLeft("abc", 6, "-+~")); + assertEquals("-+abc", StringUtils.padLeft("abc", 5, "-+~")); + assertEquals("abc", StringUtils.padLeft("abc", 2, " ")); + assertEquals("abc", StringUtils.padLeft("abc", -1, " ")); + assertEquals(" abc", StringUtils.padLeft("abc", 5, null)); + assertEquals(" abc", StringUtils.padLeft("abc", 5, "")); + } +} diff --git a/src/test/resources/testRenderParameterInfoDataAsTableWithMaxWidth.txt b/src/test/resources/testRenderParameterInfoDataAsTableWithMaxWidth.txt new file mode 100644 index 00000000..8e40391d --- /dev/null +++ b/src/test/resources/testRenderParameterInfoDataAsTableWithMaxWidth.txt @@ -0,0 +1,5 @@ + -------------- -------------------- + Key1 Lorem ipsum dolor si + t posuere. + My super key 2 Lorem ipsum + -------------- -------------------- diff --git a/src/test/resources/testRenderTableWithRowShorthand-expected-output.txt b/src/test/resources/testRenderTableWithRowShorthand-expected-output.txt new file mode 100644 index 00000000..428e7da4 --- /dev/null +++ b/src/test/resources/testRenderTableWithRowShorthand-expected-output.txt @@ -0,0 +1,7 @@ + Property Value + -------------------- ----------- + Job Execution ID 1 + Job Name My Job Name + Start Time 12:30 + Step Execution Count 12 + Status COMPLETED diff --git a/src/test/resources/testRenderTextTable-expected-output.txt b/src/test/resources/testRenderTextTable-expected-output.txt new file mode 100644 index 00000000..598af963 --- /dev/null +++ b/src/test/resources/testRenderTextTable-expected-output.txt @@ -0,0 +1,5 @@ + Tap Name Stream Name Tap Definition + -------- ----------- ---------------- + tap1 ticktock tap@ticktock|log + tap2 ticktock tap@ticktock|log + tap3 ticktock tap@ticktock|log diff --git a/src/test/resources/testRenderTextTable-single-column-expected-output.txt b/src/test/resources/testRenderTextTable-single-column-expected-output.txt new file mode 100644 index 00000000..5b36a888 --- /dev/null +++ b/src/test/resources/testRenderTextTable-single-column-expected-output.txt @@ -0,0 +1,3 @@ + Gauge name + ----------- + simplegauge diff --git a/src/test/resources/testRenderTextTable-single-column-width4-expected-output.txt b/src/test/resources/testRenderTextTable-single-column-width4-expected-output.txt new file mode 100644 index 00000000..deda57c9 --- /dev/null +++ b/src/test/resources/testRenderTextTable-single-column-width4-expected-output.txt @@ -0,0 +1,7 @@ + Gaug + e na + me + ---- + simp + lega + uge