001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2007-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.util.table;
028
029
030
031 import static org.opends.server.util.ServerConstants.*;
032
033 import java.io.BufferedWriter;
034 import java.io.OutputStream;
035 import java.io.OutputStreamWriter;
036 import java.io.PrintWriter;
037 import java.io.Writer;
038 import java.util.ArrayList;
039 import java.util.HashMap;
040 import java.util.List;
041 import java.util.Map;
042
043
044
045 /**
046 * An interface for creating a text based table. Tables have
047 * configurable column widths, padding, and column separators.
048 */
049 public final class TextTablePrinter extends TablePrinter {
050
051 /**
052 * Table serializer implementation.
053 */
054 private final class Serializer extends TableSerializer {
055
056 // The current column being output.
057 private int column = 0;
058
059 // The real column widths taking into account size constraints but
060 // not including padding or separators.
061 private final List<Integer> columnWidths = new ArrayList<Integer>();
062
063 // The cells in the current row.
064 private final List<String> currentRow = new ArrayList<String>();
065
066 // Width of the table in columns.
067 private int totalColumns = 0;
068
069 // The padding to use for indenting the table.
070 private final String indentPadding;
071
072
073
074 // Private constructor.
075 private Serializer() {
076 // Compute the indentation padding.
077 StringBuilder builder = new StringBuilder();
078 for (int i = 0; i < indentWidth; i++) {
079 builder.append(' ');
080 }
081 this.indentPadding = builder.toString();
082 }
083
084
085
086 /**
087 * {@inheritDoc}
088 */
089 @Override
090 public void addCell(String s) {
091 currentRow.add(s);
092 column++;
093 }
094
095
096
097 /**
098 * {@inheritDoc}
099 */
100 @Override
101 public void addColumn(int width) {
102 columnWidths.add(width);
103 totalColumns++;
104 }
105
106
107
108 /**
109 * {@inheritDoc}
110 */
111 @Override
112 public void addHeading(String s) {
113 if (displayHeadings) {
114 addCell(s);
115 }
116 }
117
118
119
120 /**
121 * {@inheritDoc}
122 */
123 @Override
124 public void endHeader() {
125 if (displayHeadings) {
126 endRow();
127
128 // Print the header separator.
129 StringBuilder builder = new StringBuilder(indentPadding);
130 for (int i = 0; i < totalColumns; i++) {
131 int width = columnWidths.get(i);
132 if (totalColumns > 1) {
133 if (i == 0 || i == (totalColumns - 1)) {
134 // Only one lot of padding for first and last columns.
135 width += padding;
136 } else {
137 width += padding * 2;
138 }
139 }
140
141 for (int j = 0; j < width; j++) {
142 if (headingSeparatorStartColumn > 0) {
143 if (i < headingSeparatorStartColumn) {
144 builder.append(' ');
145 } else if (i == headingSeparatorStartColumn && j < padding) {
146 builder.append(' ');
147 } else {
148 builder.append(headingSeparator);
149 }
150 } else {
151 builder.append(headingSeparator);
152 }
153 }
154
155 if ((i >= headingSeparatorStartColumn) && i < (totalColumns - 1)) {
156 builder.append(columnSeparator);
157 }
158 }
159 writer.println(builder.toString());
160 }
161 }
162
163
164
165 /**
166 * {@inheritDoc}
167 */
168 @Override
169 public void endRow() {
170 boolean isRemainingText;
171 do {
172 StringBuilder builder = new StringBuilder(indentPadding);
173 isRemainingText = false;
174 for (int i = 0; i < currentRow.size(); i++) {
175 int width = columnWidths.get(i);
176 String contents = currentRow.get(i);
177
178 // Determine what parts of contents can be displayed on this
179 // line.
180 String head;
181 String tail = null;
182
183 if (contents == null) {
184 // This cell has been displayed fully.
185 head = "";
186 } else if (contents.length() > width) {
187 // We're going to have to split the cell on next word
188 // boundary.
189 int endIndex = contents.lastIndexOf(' ', width);
190 if (endIndex == -1) {
191 endIndex = width;
192 head = contents.substring(0, endIndex);
193 tail = contents.substring(endIndex);
194
195 } else {
196 head = contents.substring(0, endIndex);
197 tail = contents.substring(endIndex + 1);
198 }
199 } else {
200 // The contents fits ok.
201 head = contents;
202 }
203
204 // Add this cell's contents to the current line.
205 if (i > 0) {
206 // Add right padding for previous cell.
207 for (int j = 0; j < padding; j++) {
208 builder.append(' ');
209 }
210
211 // Add separator.
212 builder.append(columnSeparator);
213
214 // Add left padding for this cell.
215 for (int j = 0; j < padding; j++) {
216 builder.append(' ');
217 }
218 }
219
220 // Add cell contents.
221 builder.append(head);
222
223 // Now pad with extra space to make up the width.
224 // Only if it's not the last cell (see issue #3210)
225 if (i != currentRow.size() - 1)
226 {
227 for (int j = head.length(); j < width; j++)
228 {
229 builder.append(' ');
230 }
231 }
232
233 // Update the row contents.
234 currentRow.set(i, tail);
235 if (tail != null) {
236 isRemainingText = true;
237 }
238 }
239
240 // Output the line.
241 writer.println(builder.toString());
242
243 } while (isRemainingText);
244 }
245
246
247
248 /**
249 * {@inheritDoc}
250 */
251 @Override
252 public void endTable() {
253 writer.flush();
254 }
255
256
257
258 /**
259 * {@inheritDoc}
260 */
261 @Override
262 public void startHeader() {
263 determineColumnWidths();
264
265 column = 0;
266 currentRow.clear();
267 }
268
269
270
271 /**
272 * {@inheritDoc}
273 */
274 @Override
275 public void startRow() {
276 column = 0;
277 currentRow.clear();
278 }
279
280
281
282 // We need to calculate the effective width of each column.
283 private void determineColumnWidths() {
284 // First calculate the minimum width so that we know how much
285 // expandable columns can expand.
286 int minWidth = indentWidth;
287 int expandableColumnSize = 0;
288
289 for (int i = 0; i < totalColumns; i++) {
290 int actualSize = columnWidths.get(i);
291
292 if (fixedColumns.containsKey(i)) {
293 int requestedSize = fixedColumns.get(i);
294
295 if (requestedSize == 0) {
296 expandableColumnSize += actualSize;
297 } else {
298 columnWidths.set(i, requestedSize);
299 minWidth += requestedSize;
300 }
301 } else {
302 minWidth += actualSize;
303 }
304
305 // Must also include padding and separators.
306 if (i > 0) {
307 minWidth += padding * 2 + columnSeparator.length();
308 }
309 }
310
311 if (minWidth > totalWidth) {
312 // The table is too big: leave expandable columns at their
313 // requested width, as there's not much else that can be done.
314 } else {
315 int available = totalWidth - minWidth;
316
317 if (expandableColumnSize > available) {
318 // Only modify column sizes if necessary.
319 for (int i = 0; i < totalColumns; i++) {
320 int actualSize = columnWidths.get(i);
321
322 if (fixedColumns.containsKey(i)) {
323 int requestedSize = fixedColumns.get(i);
324 if (requestedSize == 0) {
325 // Calculate size based on requested actual size as a
326 // proportion of the total.
327 requestedSize =
328 ((actualSize * available) / expandableColumnSize);
329 columnWidths.set(i, requestedSize);
330 }
331 }
332 }
333 }
334 }
335 }
336 }
337
338 /**
339 * The default string which should be used to separate one column
340 * from the next (not including padding).
341 */
342 private static final String DEFAULT_COLUMN_SEPARATOR = "";
343
344 /**
345 * The default character which should be used to separate the table
346 * heading row from the rows beneath.
347 */
348 private static final char DEFAULT_HEADING_SEPARATOR = '-';
349
350 /**
351 * The default padding which will be used to separate a cell's
352 * contents from its adjacent column separators.
353 */
354 private static final int DEFAULT_PADDING = 1;
355
356 // The string which should be used to separate one column
357 // from the next (not including padding).
358 private String columnSeparator = DEFAULT_COLUMN_SEPARATOR;
359
360 // Indicates whether or not the headings should be output.
361 private boolean displayHeadings = true;
362
363 // Table indicating whether or not a column is fixed width.
364 private final Map<Integer, Integer> fixedColumns =
365 new HashMap<Integer, Integer>();
366
367 // The number of characters the table should be indented.
368 private int indentWidth = 0;
369
370 // The character which should be used to separate the table
371 // heading row from the rows beneath.
372 private char headingSeparator = DEFAULT_HEADING_SEPARATOR;
373
374 // The column where the heading separator should begin.
375 private int headingSeparatorStartColumn = 0;
376
377 // The padding which will be used to separate a cell's
378 // contents from its adjacent column separators.
379 private int padding = DEFAULT_PADDING;
380
381 // Total permitted width for the table which expandable columns
382 // can use up.
383 private int totalWidth = MAX_LINE_WIDTH;
384
385 // The output destination.
386 private PrintWriter writer = null;
387
388
389
390 /**
391 * Creates a new text table printer for the specified output stream.
392 * The text table printer will have the following initial settings:
393 * <ul>
394 * <li>headings will be displayed
395 * <li>no separators between columns
396 * <li>columns are padded by one character
397 * </ul>
398 *
399 * @param stream
400 * The stream to output tables to.
401 */
402 public TextTablePrinter(OutputStream stream) {
403 this(new BufferedWriter(new OutputStreamWriter(stream)));
404 }
405
406
407
408 /**
409 * Creates a new text table printer for the specified writer. The
410 * text table printer will have the following initial settings:
411 * <ul>
412 * <li>headings will be displayed
413 * <li>no separators between columns
414 * <li>columns are padded by one character
415 * </ul>
416 *
417 * @param writer
418 * The writer to output tables to.
419 */
420 public TextTablePrinter(Writer writer) {
421 this.writer = new PrintWriter(writer);
422 }
423
424
425
426 /**
427 * Sets the column separator which should be used to separate one
428 * column from the next (not including padding).
429 *
430 * @param columnSeparator
431 * The column separator.
432 */
433 public void setColumnSeparator(String columnSeparator) {
434 this.columnSeparator = columnSeparator;
435 }
436
437
438
439 /**
440 * Set the maximum width for a column. If a cell is too big to fit
441 * in its column then it will be wrapped.
442 *
443 * @param column
444 * The column to make fixed width (0 is the first column).
445 * @param width
446 * The width of the column (this should not include column
447 * separators or padding), or <code>0</code> to indicate
448 * that this column should be expandable.
449 * @throws IllegalArgumentException
450 * If column is less than 0.
451 */
452 public void setColumnWidth(int column, int width)
453 throws IllegalArgumentException {
454 if (column < 0) {
455 throw new IllegalArgumentException("Negative column " + column);
456 }
457
458 if (width < 0) {
459 throw new IllegalArgumentException("Negative width " + width);
460 }
461
462 fixedColumns.put(column, width);
463 }
464
465
466
467 /**
468 * Specify whether the column headings should be displayed or not.
469 *
470 * @param displayHeadings
471 * <code>true</code> if column headings should be
472 * displayed.
473 */
474 public void setDisplayHeadings(boolean displayHeadings) {
475 this.displayHeadings = displayHeadings;
476 }
477
478
479
480 /**
481 * Sets the heading separator which should be used to separate the
482 * table heading row from the rows beneath.
483 *
484 * @param headingSeparator
485 * The heading separator.
486 */
487 public void setHeadingSeparator(char headingSeparator) {
488 this.headingSeparator = headingSeparator;
489 }
490
491
492
493 /**
494 * Sets the heading separator start column. The heading separator
495 * will only be display in the specified column and all subsequent
496 * columns. Usually this should be left at zero (the default) but
497 * sometimes it useful to indent the heading separate in order to
498 * provide additional emphasis (for example in menus).
499 *
500 * @param startColumn
501 * The heading separator start column.
502 */
503 public void setHeadingSeparatorStartColumn(int startColumn) {
504 if (startColumn < 0) {
505 throw new IllegalArgumentException("Negative start column "
506 + startColumn);
507 }
508 this.headingSeparatorStartColumn = startColumn;
509 }
510
511
512
513 /**
514 * Sets the amount of characters that the table should be indented.
515 * By default the table is not indented.
516 *
517 * @param indentWidth
518 * The number of characters the table should be indented.
519 * @throws IllegalArgumentException
520 * If indentWidth is less than 0.
521 */
522 public void setIndentWidth(int indentWidth) throws IllegalArgumentException {
523 if (indentWidth < 0) {
524 throw new IllegalArgumentException("Negative indentation width "
525 + indentWidth);
526 }
527
528 this.indentWidth = indentWidth;
529 }
530
531
532
533 /**
534 * Sets the padding which will be used to separate a cell's contents
535 * from its adjacent column separators.
536 *
537 * @param padding
538 * The padding.
539 */
540 public void setPadding(int padding) {
541 this.padding = padding;
542 }
543
544
545
546 /**
547 * Sets the total permitted width for the table which expandable
548 * columns can use up.
549 *
550 * @param totalWidth
551 * The total width.
552 */
553 public void setTotalWidth(int totalWidth) {
554 this.totalWidth = totalWidth;
555 }
556
557
558
559 /**
560 * {@inheritDoc}
561 */
562 @Override
563 protected TableSerializer getSerializer() {
564 return new Serializer();
565 }
566 }