/* * Copyright 2014 Jake Wharton * Copyright 2014 gitblit.com. * * 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 com.gitblit.utils; /** * This is a forked version of FlipTables which supports controlling the * displayed borders and gracefully handles null cell values. * * FULL = all borders * BODY_COLS = header + perimeter + column separators * COLS = header + column separators * BODY = header + perimeter * HEADER = header only * * <pre> * ╔═════════════╤════════════════════════════╤══════════════╗ * ║ Name │ Function │ Author ║ * ╠═════════════╪════════════════════════════╪══════════════╣ * ║ Flip Tables │ Pretty-print a text table. │ Jake Wharton ║ * ╚═════════════╧════════════════════════════╧══════════════╝ * </pre> */ public final class FlipTable { public static final String EMPTY = "(empty)"; public static enum Borders { 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() { return isset(0x1); } boolean rows() { return isset(0x2); } boolean columns() { return isset(0x4); } boolean isset(int v) { return (bitmask & v) == v; } } /** Create a new table with the specified headers and row 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, Object[][] data, Borders borders) { if (headers == null) throw new NullPointerException("headers == null"); if (headers.length == 0) throw new IllegalArgumentException("Headers must not be empty."); if (data == null) throw new NullPointerException("data == null"); return new FlipTable(headers, data, borders).toString(); } private final String[] headers; private final Object[][] data; private final Borders borders; private final int columns; private final int[] columnWidths; private final int emptyWidth; 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++) { 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++) { Object cell = rowData[column]; if (cell == null) { continue; } for (String rowDataLine : cell.toString().split("\\n")) { columnWidths[column] = Math.max(columnWidths[column], rowDataLine.length()); } } } // Account for column dividers and their spacing. int emptyWidth = 3 * (columns - 1); for (int columnWidth : columnWidths) { emptyWidth += columnWidth; } this.emptyWidth = emptyWidth; if (emptyWidth < EMPTY.length()) { // Make sure we're wide enough for the empty text. columnWidths[columns - 1] += EMPTY.length() - emptyWidth; } } @Override public String toString() { StringBuilder builder = new StringBuilder(); 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 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 && borders.header()) { if (borders.body()) { if (borders.columns()) { printDivider(builder, "╠═╪═╣"); } else { printDivider(builder, "╠═╧═╣"); } } else { 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()) { printDivider(builder, "╟─┼─╢"); } else { printDivider(builder, "╟─┼─╢"); } } printData(builder, data[row], false); } if (borders.body()) { if (borders.columns()) { printDivider(builder, "╚═╧═╝"); } else { printDivider(builder, "╚═══╝"); } } } return builder.toString(); } private void printDivider(StringBuilder out, String format) { for (int column = 0; column < columns; column++) { out.append(column == 0 ? format.charAt(0) : format.charAt(2)); out.append(pad(columnWidths[column], "").replace(' ', format.charAt(1))); } out.append(format.charAt(4)).append('\n'); } 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.header()) || borders.body()) { out.append('║'); } else { out.append(' '); } } else if (isHeader || borders.columns()) { out.append('│'); } else { out.append(' '); } 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.header()) || borders.body()) { out.append("║\n"); } else { out.append('\n'); } } } private static String pad(int width, String data) { return String.format(" %1$-" + width + "s ", data); } }