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 |
} |