| | |
| | | |
| | | /** |
| | | * This is a forked version of FlipTables which supports controlling the |
| | | * displayed borders. |
| | | * displayed borders and gracefully handles null cell values. |
| | | * |
| | | * FULL = all borders |
| | | * BODY_COLS = header + perimeter + column separators |
| | |
| | | * </pre> |
| | | */ |
| | | public final class FlipTable { |
| | | private static final String EMPTY = "(empty)"; |
| | | public static final String EMPTY = "(empty)"; |
| | | |
| | | public static enum Borders { |
| | | FULL(7), BODY_COLS(5), COLS(4), BODY(1), HEADER(0); |
| | | FULL(15), BODY_HCOLS(13), HCOLS(12), BODY(9), HEADER(8), COLS(4); |
| | | |
| | | final int bitmask; |
| | | |
| | | private Borders(int bitmask) { |
| | | this.bitmask = bitmask; |
| | | } |
| | | |
| | | boolean header() { |
| | | return isset(0x8); |
| | | } |
| | | |
| | | boolean body() { |
| | |
| | | } |
| | | |
| | | /** Create a new table with the specified headers and row data. */ |
| | | public static String of(String[] headers, String[][] data) { |
| | | public static String of(String[] headers, Object[][] data) { |
| | | return of(headers, data, Borders.FULL); |
| | | } |
| | | |
| | | /** Create a new table with the specified headers and row data. */ |
| | | public static String of(String[] headers, String[][] data, Borders borders) { |
| | | public static String of(String[] headers, Object[][] data, Borders borders) { |
| | | if (headers == null) |
| | | throw new NullPointerException("headers == null"); |
| | | if (headers.length == 0) |
| | |
| | | } |
| | | |
| | | private final String[] headers; |
| | | private final String[][] data; |
| | | private final Object[][] data; |
| | | private final Borders borders; |
| | | private final int columns; |
| | | private final int[] columnWidths; |
| | | private final int emptyWidth; |
| | | |
| | | private FlipTable(String[] headers, String[][] data, Borders borders) { |
| | | private FlipTable(String[] headers, Object[][] data, Borders borders) { |
| | | this.headers = headers; |
| | | this.data = data; |
| | | this.borders = borders; |
| | |
| | | columns = headers.length; |
| | | columnWidths = new int[columns]; |
| | | for (int row = -1; row < data.length; row++) { |
| | | String[] rowData = (row == -1) ? headers : data[row]; |
| | | Object[] rowData = (row == -1) ? headers : data[row]; |
| | | if (rowData.length != columns) { |
| | | throw new IllegalArgumentException(String.format("Row %s's %s columns != %s columns", row + 1, |
| | | rowData.length, columns)); |
| | | } |
| | | for (int column = 0; column < columns; column++) { |
| | | for (String rowDataLine : rowData[column].split("\\n")) { |
| | | Object cell = rowData[column]; |
| | | if (cell == null) { |
| | | continue; |
| | | } |
| | | for (String rowDataLine : cell.toString().split("\\n")) { |
| | | columnWidths[column] = Math.max(columnWidths[column], rowDataLine.length()); |
| | | } |
| | | } |
| | |
| | | @Override |
| | | public String toString() { |
| | | StringBuilder builder = new StringBuilder(); |
| | | printDivider(builder, "╔═╤═╗"); |
| | | if (borders.header()) { |
| | | printDivider(builder, "╔═╤═╗"); |
| | | } |
| | | printData(builder, headers, true); |
| | | if (data.length == 0) { |
| | | if (borders.body()) { |
| | | printDivider(builder, "╠═╧═╣"); |
| | | builder.append('║').append(pad(emptyWidth, EMPTY)).append("║\n"); |
| | | printDivider(builder, "╚═══╝"); |
| | | } else { |
| | | } else if (borders.header()) { |
| | | printDivider(builder, "╚═╧═╝"); |
| | | builder.append(' ').append(pad(emptyWidth, EMPTY)).append(" \n"); |
| | | } |
| | | } else { |
| | | for (int row = 0; row < data.length; row++) { |
| | | if (row == 0) { |
| | | if (row == 0 && borders.header()) { |
| | | if (borders.body()) { |
| | | if (borders.columns()) { |
| | | printDivider(builder, "╠═╪═╣"); |
| | |
| | | } else { |
| | | printDivider(builder, "╚═╧═╝"); |
| | | } |
| | | } |
| | | } else if (row == 0 && !borders.header()) { |
| | | if (borders.columns()) { |
| | | printDivider(builder, " ─┼─ "); |
| | | } else { |
| | | printDivider(builder, " ─┼─ "); |
| | | } |
| | | } else if (borders.rows()) { |
| | | if (borders.columns()) { |
| | |
| | | out.append(format.charAt(4)).append('\n'); |
| | | } |
| | | |
| | | private void printData(StringBuilder out, String[] data, boolean isHeader) { |
| | | private void printData(StringBuilder out, Object[] data, boolean isHeader) { |
| | | for (int line = 0, lines = 1; line < lines; line++) { |
| | | for (int column = 0; column < columns; column++) { |
| | | if (column == 0) { |
| | | if (isHeader || borders.body()) { |
| | | if ((isHeader && borders.header()) || borders.body()) { |
| | | out.append('║'); |
| | | } else { |
| | | out.append(' '); |
| | |
| | | } else { |
| | | out.append(' '); |
| | | } |
| | | String[] cellLines = data[column].split("\\n"); |
| | | Object cell = data[column]; |
| | | if (cell == null) { |
| | | cell = ""; |
| | | } |
| | | String[] cellLines = cell.toString().split("\\n"); |
| | | lines = Math.max(lines, cellLines.length); |
| | | String cellLine = line < cellLines.length ? cellLines[line] : ""; |
| | | out.append(pad(columnWidths[column], cellLine)); |
| | | } |
| | | if (isHeader || borders.body()) { |
| | | if ((isHeader && borders.header()) || borders.body()) { |
| | | out.append("║\n"); |
| | | } else { |
| | | out.append('\n'); |