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 }