James Moger
2014-03-26 617909819cd1b955647dd8584036fc7b2a014265
commit | author | age
4a6999 1 /*
JM 2  * Copyright 2014 Jake Wharton
3  * Copyright 2014 gitblit.com.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package com.gitblit.utils;
18
19
20 /**
21  * This is a forked version of FlipTables which supports controlling the
07fd01 22  * displayed borders and gracefully handles null cell values.
4a6999 23  *
JM 24  * FULL = all borders
25  * BODY_COLS = header + perimeter + column separators
26  * COLS = header + column separators
27  * BODY = header + perimeter
28  * HEADER = header only
29  *
30  * <pre>
31  * ╔═════════════╤════════════════════════════╤══════════════╗
32  * ║ Name        │ Function                   │ Author       ║
33  * ╠═════════════╪════════════════════════════╪══════════════╣
34  * ║ Flip Tables │ Pretty-print a text table. │ Jake Wharton ║
35  * ╚═════════════╧════════════════════════════╧══════════════╝
36  * </pre>
37  */
38 public final class FlipTable {
617909 39     public static final String EMPTY = "(empty)";
4a6999 40
JM 41     public static enum Borders {
07fd01 42         FULL(15), BODY_HCOLS(13), HCOLS(12), BODY(9), HEADER(8), COLS(4);
4a6999 43
JM 44         final int bitmask;
45
46         private Borders(int bitmask) {
47             this.bitmask = bitmask;
07fd01 48         }
f81ed7 49
07fd01 50         boolean header() {
JM 51             return isset(0x8);
4a6999 52         }
JM 53
54         boolean body() {
55             return isset(0x1);
56         }
57
58         boolean rows() {
59             return isset(0x2);
60         }
61
62         boolean columns() {
63             return isset(0x4);
64         }
65
66         boolean isset(int v) {
67             return (bitmask & v) == v;
68         }
69     }
70
71     /** Create a new table with the specified headers and row data. */
f81ed7 72     public static String of(String[] headers, Object[][] data) {
4a6999 73         return of(headers, data, Borders.FULL);
JM 74     }
75
76     /** Create a new table with the specified headers and row data. */
f81ed7 77     public static String of(String[] headers, Object[][] data, Borders borders) {
4a6999 78         if (headers == null)
JM 79             throw new NullPointerException("headers == null");
80         if (headers.length == 0)
81             throw new IllegalArgumentException("Headers must not be empty.");
82         if (data == null)
83             throw new NullPointerException("data == null");
84         return new FlipTable(headers, data, borders).toString();
85     }
86
87     private final String[] headers;
f81ed7 88     private final Object[][] data;
4a6999 89     private final Borders borders;
JM 90     private final int columns;
91     private final int[] columnWidths;
92     private final int emptyWidth;
93
f81ed7 94     private FlipTable(String[] headers, Object[][] data, Borders borders) {
4a6999 95         this.headers = headers;
JM 96         this.data = data;
97         this.borders = borders;
98
99         columns = headers.length;
100         columnWidths = new int[columns];
101         for (int row = -1; row < data.length; row++) {
f81ed7 102             Object[] rowData = (row == -1) ? headers : data[row];
4a6999 103             if (rowData.length != columns) {
JM 104                 throw new IllegalArgumentException(String.format("Row %s's %s columns != %s columns", row + 1,
105                         rowData.length, columns));
106             }
107             for (int column = 0; column < columns; column++) {
f81ed7 108                 Object cell = rowData[column];
07fd01 109                 if (cell == null) {
JM 110                     continue;
111                 }
f81ed7 112                 for (String rowDataLine : cell.toString().split("\\n")) {
4a6999 113                     columnWidths[column] = Math.max(columnWidths[column], rowDataLine.length());
JM 114                 }
115             }
116         }
117
118          // Account for column dividers and their spacing.
119         int emptyWidth = 3 * (columns - 1);
120         for (int columnWidth : columnWidths) {
121             emptyWidth += columnWidth;
122         }
123         this.emptyWidth = emptyWidth;
124
125         if (emptyWidth < EMPTY.length()) {
126             // Make sure we're wide enough for the empty text.
127             columnWidths[columns - 1] += EMPTY.length() - emptyWidth;
128         }
129     }
130
131     @Override
132     public String toString() {
133         StringBuilder builder = new StringBuilder();
07fd01 134         if (borders.header()) {
JM 135             printDivider(builder, "╔═╤═╗");
136         }
4a6999 137         printData(builder, headers, true);
JM 138         if (data.length == 0) {
139             if (borders.body()) {
140                 printDivider(builder, "╠═╧═╣");
141                 builder.append('║').append(pad(emptyWidth, EMPTY)).append("║\n");
142                 printDivider(builder, "╚═══╝");
07fd01 143             } else if (borders.header()) {
4a6999 144                 printDivider(builder, "╚═╧═╝");
JM 145                 builder.append(' ').append(pad(emptyWidth, EMPTY)).append(" \n");
146             }
147         } else {
148             for (int row = 0; row < data.length; row++) {
07fd01 149                 if (row == 0 && borders.header()) {
4a6999 150                     if (borders.body()) {
JM 151                         if (borders.columns()) {
152                             printDivider(builder, "╠═╪═╣");
153                         } else {
154                             printDivider(builder, "╠═╧═╣");
155                         }
156                     } else {
157                         if (borders.columns()) {
158                             printDivider(builder, "╚═╪═╝");
159                         } else {
160                             printDivider(builder, "╚═╧═╝");
161                         }
07fd01 162                     }
JM 163                 } else if (row == 0 && !borders.header()) {
164                     if (borders.columns()) {
165                         printDivider(builder, " ─┼─ ");
166                     } else {
167                         printDivider(builder, " ─┼─ ");
4a6999 168                     }
JM 169                 } else if (borders.rows()) {
170                     if (borders.columns()) {
171                         printDivider(builder, "╟─┼─╢");
172                     } else {
173                         printDivider(builder, "╟─┼─╢");
174                     }
175                 }
176                 printData(builder, data[row], false);
177             }
178             if (borders.body()) {
179                 if (borders.columns()) {
180                     printDivider(builder, "╚═╧═╝");
181                 } else {
182                     printDivider(builder, "╚═══╝");
183                 }
184             }
185         }
186         return builder.toString();
187     }
188
189     private void printDivider(StringBuilder out, String format) {
190         for (int column = 0; column < columns; column++) {
191             out.append(column == 0 ? format.charAt(0) : format.charAt(2));
192             out.append(pad(columnWidths[column], "").replace(' ', format.charAt(1)));
193         }
194         out.append(format.charAt(4)).append('\n');
195     }
196
f81ed7 197     private void printData(StringBuilder out, Object[] data, boolean isHeader) {
4a6999 198         for (int line = 0, lines = 1; line < lines; line++) {
JM 199             for (int column = 0; column < columns; column++) {
200                 if (column == 0) {
07fd01 201                     if ((isHeader && borders.header()) || borders.body()) {
4a6999 202                         out.append('║');
JM 203                     } else {
204                         out.append(' ');
205                     }
206                 } else if (isHeader || borders.columns()) {
207                     out.append('│');
208                 } else {
209                     out.append(' ');
210                 }
f81ed7 211                 Object cell = data[column];
07fd01 212                 if (cell == null) {
JM 213                     cell = "";
214                 }
f81ed7 215                 String[] cellLines = cell.toString().split("\\n");
4a6999 216                 lines = Math.max(lines, cellLines.length);
JM 217                 String cellLine = line < cellLines.length ? cellLines[line] : "";
218                 out.append(pad(columnWidths[column], cellLine));
219             }
07fd01 220             if ((isHeader && borders.header()) || borders.body()) {
4a6999 221                 out.append("║\n");
JM 222             } else {
223                 out.append('\n');
224             }
225         }
226     }
227
228     private static String pad(int width, String data) {
229         return String.format(" %1$-" + width + "s ", data);
230     }
231 }