blob: 0197517d5777b2a87bd381e657da75b48eb1ff70 [file] [log] [blame]
/*
* 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);
}
}