001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.io; 007 008import java.io.*; 009import java.util.*; 010import java.util.regex.*; 011 012import fc.util.*; 013 014/** 015Prints a table formatted using plain text/ascii characters. 016Useful for console based output and/or formatted output to a 017file. 018 019@author hursh jain 020**/ 021public class TablePrinter 022 { 023 private static final boolean dbg = false; 024 private static final Object Extra_Content_Marker = new Object(); 025 026 String linesep = IOUtil.LINE_SEP; 027 PrintConfig config; 028 PrintWriter out; 029 int columncount; 030 String rowborder; 031 int cellwidth; 032 String spacing_str; 033 String padding_str; 034 boolean usingSpacing; 035 boolean printingHeader; //printing a header line ? 036 int current_linenum; //not including headers 037 int current_cellnum; 038 String[] wrappedcells; 039 int wrapcells_left; 040 041 //use for max cell widths when using autofit 042 int[] largest_width; 043 //used to cache table data when using autofit 044 //list of String[] 045 List autoFitTableData = new ArrayList(); 046 047 int currentAutoFitRow; 048 int currentAutoFitCell; 049 050 051 /** 052 Constructs a new table printer. 053 054 @param columnCount the number of columns in the table. 055 Attemps to print more than these 056 number of columns will result in 057 a runtime exception. 058 @param pw the destination print stream 059 @param config the printing configuration 060 **/ 061 public TablePrinter (int columnCount, PrintStream ps, PrintConfig config) 062 { 063 this(columnCount, new PrintWriter(ps), config); 064 } 065 066 /** 067 Constructs a new table printer. 068 069 @param columnCount the number of columns in the table. 070 Attemps to print more than these 071 number of columns will result in 072 a runtime exception. 073 @param pw the destination print writer 074 @param config the printing configuration 075 **/ 076 public TablePrinter (int columnCount, PrintWriter pw, PrintConfig config) 077 { 078 Argcheck.notnull(config, "config was null"); 079 Argcheck.notnull(pw, "printwriter/stream was null"); 080 this.columncount= columnCount; 081 this.out = pw; 082 this.config = config; 083 cellwidth = config.cellwidth; 084 padding_str = StringUtil.repeatToWidth( 085 config.cellpadding_glyph, config.cellpadding); 086 087 spacing_str = StringUtil.repeatToWidth( 088 config.cellspacing_glyph, config.cellspacing); 089 090 usingSpacing = config.cellspacing > 0; 091 printingHeader = false; 092 wrappedcells = new String[columncount]; 093 initRowBorder(); 094 } 095 096 097 /******************************************************* 098 099 Layout rules: 100 upper and lower cell borders look like: 101 102 +-----+------+-----+------+ 103 | | | | | 104 | | | | | 105 +-----+------+-----+------+ 106 107 or: 108 109 +-----+ +-----+ +-----+ 110 | | | | | | 111 | | | | | | 112 +-----+ +-----+ +-----+ 113 114 115 We don't support cell specific borders and know the 116 number of columns in the table beforehand. 117 118 0) startTable 119 - prints the initial table header is applicable 120 121 1) startRow - 122 - initializes some variables 123 - if this is a new row, 124 - writes spacing 125 - writes entire top border 126 127 2) printcell 128 - if spacing 129 - write spacing, left border, content, right border 130 - no spacing 131 - write left border, content 132 - tracks whether row contents need to be wrapped 133 if wrapping needed, sets a flag ( we have to wait 134 until entire row is written before writing a new 135 row containing the wrapped contents ) 136 137 3) endRow 138 - if no spacing 139 writes last most right border (not needed if we 140 are using spacing, since in that case each cell 141 writes it's right border) 142 - write spacing (if using spacing) 143 - if wrapping is needed, calls startRow again 144 specifying that this is *not* a new row (and 145 hence the new row border will not be written) 146 - if wrapping not needed, and using spacing, writes 147 the bottom border (needed to delimit rows with spacing) 148 - write header row again if applicable (if header rows are 149 repeated every x number of lines) 150 151 4) endTable 152 - write border and spacing after the last row (we don't know 153 beforehand the # of rows, so we wait until the last 154 row is written before writing the ending spacing) 155 156 ********************************************************/ 157 158 /** This method should be called to start the table **/ 159 public void startTable() 160 { 161 if (config.autoFit) { 162 startTableAutoFit(); 163 return; 164 } 165 166 if (config.header != null) 167 printHeader(); 168 } 169 170 /** This method should be called to finish the table **/ 171 public void endTable() 172 { 173 if (config.autoFit) { 174 endTableAutoFit(); 175 return; 176 } 177 178 if (! usingSpacing) { 179 printRowBorder(); 180 } 181 182 if (usingSpacing) { 183 printVerticalSpacing(); 184 } 185 186 //if prinstream->printwriter, then unless we flush, 187 //writes to underlying printstream may not appear 188 //as expected (and the sequence can be screwy if 189 //we are also writing to the underlying printstream 190 //independently 191 out.flush(); 192 } 193 194 /** This method should be called to start a new row **/ 195 public void startRow() 196 { 197 if (config.autoFit) { 198 startRowAutoFit(); 199 return; 200 } 201 202 current_cellnum = 0; 203 204 //logical non header lines 205 if (! printingHeader && rowFinished()) 206 current_linenum++; //first line = 1 207 208 if (rowFinished()) 209 { 210 if (usingSpacing) { 211 printVerticalSpacing(); 212 } 213 printRowBorder(); 214 } 215 } 216 217 void endRowCommon() 218 { 219 if (! usingSpacing) { 220 if (config.printborder) { 221 out.print(config.cellborder_v); //right most border 222 } 223 } 224 else 225 out.print(spacing_str); //right most spacing 226 227 out.print(linesep); 228 } 229 230 /** This method should be called to finish the existing row **/ 231 public void endRow() 232 { 233 if (config.autoFit) { 234 endRowAutoFit(); 235 return; 236 } 237 238 endRowCommon(); 239 240 if (config.cellwrap) 241 { 242 while (! rowFinished() ) 243 { 244 startRow(); 245 for (int n = 0; n < columncount; n++) { 246 printCell(wrappedcells[n]); 247 } 248 endRowCommon(); 249 } 250 } 251 252 if (usingSpacing) { 253 printRowBorder(); 254 } 255 256 if (! printingHeader && repeatHeader()) { 257 printHeader(); 258 } 259 } 260 261 boolean repeatHeader() 262 { 263 int pagesize = config.pageSize; 264 265 if (pagesize <= 0 || ! config.headerEveryPage) 266 return false; 267 268 if (current_linenum == 0) 269 return false; 270 271 //System.out.println("current_linenum[" + current_linenum + "] % pagesize[" + pagesize + "] = " + (current_linenum % pagesize)); 272 273 if ((current_linenum % pagesize) == 0) { 274 return true; 275 } 276 277 return false; 278 } 279 280 /** 281 This method should be called to print a new cell in the 282 current row. This method should not be invoked for more 283 columns than the table was instantiated with, otherwise a 284 runtime exception will be thrown. 285 **/ 286 public void printCell(String str) 287 { 288 if (current_cellnum == columncount) 289 throw new IllegalArgumentException("Cannot add more cells than number of columns in this table. (max columns=" + columncount + ")"); 290 291 str = removeEmbeddedSpaces(str); 292 293 if (config.autoFit) { 294 printCellAutoFit(str); 295 return; 296 } 297 298 //left spacing 299 if (usingSpacing) { 300 out.print(spacing_str); 301 } 302 303 //left border 304 if (config.printborder) { 305 out.print(config.cellborder_v); 306 } 307 308 printCellContent(str); 309 310 //right border if spaced 311 if (usingSpacing) { 312 if (config.printborder) { 313 out.print(config.cellborder_v); 314 } 315 } 316 317 current_cellnum++; 318 } 319 320 public String toString() { 321 return "TablePrinter. Config = " + config; 322 } 323 324 //== impl. methods =================================== 325 326 void printCellContent(String str) 327 { 328 int width = config.getCellWidthForColumn(current_cellnum); 329 330 int strlen = (str == null) ? 0 : str.length(); 331 332 //align cell contents appropriately 333 String content = StringUtil.fixedWidth(str, width, config.align); 334 335 out.print(padding_str); 336 out.print(content); 337 out.print(padding_str); 338 339 if (dbg) System.out.println("current cell=" + current_cellnum); 340 341 if (config.cellwrap) 342 { 343 if (strlen > width) 344 { 345 //don't double count wrapped cells 346 if (wrappedcells[current_cellnum] == null) 347 wrapcells_left++; 348 349 //this cell needs to be wrapped,save the extra portion 350 wrappedcells[current_cellnum] = str.substring(width, strlen); 351 } 352 else { 353 if (wrappedcells[current_cellnum] != null) 354 wrapcells_left--; 355 wrappedcells[current_cellnum] = null; 356 } 357 } 358 if (dbg) System.out.println("Wrapped cells left=" + wrapcells_left + "; Wrapped:" + Arrays.asList(wrappedcells)); 359 } 360 361 boolean rowFinished() { 362 return wrapcells_left == 0; 363 } 364 365 void printHeader() 366 { 367 if (config.header == null) 368 return; 369 370 printingHeader = true; 371 startRow(); 372 for (int n = 0; n < config.header.length; n++) 373 printCell(config.header[n]); 374 endRow(); 375 printingHeader = false; 376 } 377 378 void printRowBorder() 379 { 380 if (! config.printborder) 381 return; 382 383 out.print(rowborder); 384 } 385 386 private void initRowBorder() 387 { 388 //new Exception().printStackTrace(); 389 int default_padded_cellwidth = 390 cellwidth + 2 * config.cellpadding; 391 392 if (! config.printborder) 393 return; 394 395 StringBuffer buf = new StringBuffer( 396 columncount * (1+default_padded_cellwidth+config.cellspacing)); 397 398 for (int n = 0; n < columncount; n++) 399 { 400 if (usingSpacing) 401 buf.append(spacing_str); 402 buf.append(config.cellcorner); 403 int padded_cellwidth = config.getCellWidthForColumn(n) 404 + 2 * config.cellpadding; 405 for (int k = 0; k < padded_cellwidth; k++) 406 { 407 buf.append(config.cellborder_h); 408 } 409 if (usingSpacing) 410 buf.append(config.cellcorner); 411 } 412 if (! usingSpacing) 413 buf.append(config.cellcorner); 414 buf.append(linesep); 415 rowborder = buf.toString(); 416 } 417 418void printVerticalSpacing() 419 { 420 //1 lesser for aesthetics 421 for (int k = 1; k < config.cellspacing; k++) { 422 out.print(linesep); 423 } 424 } 425 426//== autofit methods ================ 427 428void startTableAutoFit() { 429 largest_width = new int[columncount]; 430 } 431 432void endTableAutoFit() 433 { 434 if (dbg) { 435 for (Iterator it = autoFitTableData.iterator(); it.hasNext(); /*empty*/) { 436 String[] item = (String[]) it.next(); 437 System.out.println(Arrays.asList(item)); 438 } 439 } 440 441 // == 2nd pass ================================== 442 int rowcount = autoFitTableData.size(); 443 if (rowcount == 0) 444 return; 445 446 //we are already handling autofit rendering, so terminate further recursion 447 config.setAutoFit(false); 448 449 for (int n = 0; n < largest_width.length; n++) { 450 if (dbg) System.out.println("largest_width["+n+"]="+largest_width[n]); 451 config.setCellWidthForColumn(n, largest_width[n]); 452 } 453 454 //TablePrinter printer = new TablePrinter(columncount, out, config); 455 TablePrinter printer = this; 456 printer.initRowBorder(); 457 458 printer.startTable(); 459 int n = 0; 460 while (n < rowcount) 461 { 462 printer.startRow(); 463 for (int k = 0; k < columncount; k++) { 464 printer.printCell( 465 ((String[])autoFitTableData.get(n))[k] ); 466 } 467 printer.endRow(); 468 n++; 469 } 470 471 printer.endTable(); 472 } 473 474void startRowAutoFit() { 475 currentAutoFitCell = 0; 476 autoFitTableData.add(currentAutoFitRow, new String[columncount]); 477 } 478 479void endRowAutoFit() { 480 currentAutoFitRow++; 481 } 482 483void printCellAutoFit(String str) 484 { 485 int len = (str != null) ? str.length() : "null".length(); 486 487 if (largest_width[currentAutoFitCell] < len) 488 largest_width[currentAutoFitCell] = len; 489 490 String[] row = (String[]) autoFitTableData.get(currentAutoFitRow); 491 row[currentAutoFitCell++] = str; 492 } 493 494final private Pattern newlines = Pattern.compile("\\r|\\n|\\r\\n"); 495String removeEmbeddedSpaces(String str) 496 { 497 /* 498 we convert embedded newlines in the string to spaces 499 otherwise the embedded newline will cause printing 500 to start at the left most margin. i.e., 501 502 baz\n\baz2 will print like 503 504 A B C 505 x y baz 506 baz2 507 508 but we want 509 <---> 510 A B C 511 x y baz 512 baz2 513 514 */ 515 516 if (str != null) 517 { 518 str = newlines.matcher(str).replaceAll(" "); 519 } 520 return str; 521 } 522 523/** 524Configuration object containing for table printing object. 525 526Some methods in this class return <tt>this</tt> object for method 527chaining convenience. 528<p> 529Note: Alas ! TablePrinter does not support cellspans across 530columns or rows. That would make things too complicated for 531this implementation. 532**/ 533public static class PrintConfig 534 { 535 static final ToString.Style style = new ToString.Style(); 536 static { 537 style.reflectVisibleLevel = ToString.Style.VisibleLevel.PRIVATE; 538 } 539 540 private int cellwidth = 20; 541 542 //individual widths if specified 543 private Map cellwidthMap = new HashMap(); 544 545 private String cellcorner = "+"; 546 private String cellborder_h = "-"; 547 private String cellborder_v = "|"; 548 private String cellpadding_glyph = " "; 549 private String cellspacing_glyph = " "; 550 private boolean cellwrap = true; 551 private boolean autoFit = false; 552 private boolean printborder = true; 553 private int cellspacing = 0; 554 private int cellpadding = 0; 555 private HAlign align = HAlign.LEFT; 556 private int pageSize = -1; 557 private boolean headerEveryPage = true; 558 private String[] header; 559 560 /** 561 Constructs a new PrintConfig with the following default options. 562 <ul> 563 <li>cell corner: the <tt>+</tt> character 564 <li>horizontal cell border: the <tt>-</tt> character 565 <li>vertical cell border, the <tt>|</tt> character 566 <li>width of each column: 20 chars 567 <li>horizontal alignment of each cell: {@link HAlign#LEFT} 568 <li>The character used for cellpadding and cellspacing is a 569 blank space. 570 </ul> 571 **/ 572 public PrintConfig() { 573 } 574 575 /** 576 Optionally sets the header row for the table **/ 577 public void setHeader(String[] header) { 578 this.header = header; 579 } 580 581 /** 582 Gets the header row for the table if set. If not 583 set, return null 584 **/ 585 public String[] getHeader() { 586 return this.header; 587 } 588 589 590 /** 591 Sets the number of lines on each page. A page is a 592 logical unit that typically shows some number of lines 593 on the screen without the need to scroll the page. This 594 value is useful in conjunction with other page specific 595 settings like {@link showPageHeading()} 596 597 @param lines number of lines on the page, specify a zero or 598 negative quantity for a single page. (number of 599 lines <b>not</b> including lines occupied by the table 600 header itself, if the header is printed). 601 **/ 602 public PrintConfig setPageSize(int lines) { 603 this.pageSize = lines; 604 return this; 605 } 606 607 /** 608 Specifies that page heading (if set) on each separate 609 page. This may be ignored if no heading has been set. 610 <tt>true</tt> by default, although headers will still 611 not be printed until the {@link setPageSize} method is 612 invoked. 613 **/ 614 public PrintConfig headerEveryPage(boolean show) { 615 this.headerEveryPage = show; 616 return this; 617 } 618 619 /** Sets the alignment of each cell. By default: 620 <tt>{@link HAlign#LEFT left}</tt> 621 **/ 622 public PrintConfig setAlign(HAlign align) { 623 this.align = align; 624 return this; 625 } 626 627 /** 628 Sets the string (typically a single character) that makes up a 629 cell corner. Defaults to <tt>+</tt> 630 **/ 631 public PrintConfig setCellCorner(String str) { 632 this.cellcorner = str; 633 return this; 634 } 635 636 /** 637 Sets the string (typically a single character) that makes up a 638 horizontal cell border. Defaults to <tt>-</tt> 639 **/ 640 public PrintConfig setCellBorderHorizontal(String str) { 641 this.cellborder_h = str; 642 return this; 643 } 644 645 /** 646 Sets the string (typically a single character) that makes up a 647 vertical cell border. Defaults to <tt>|</tt> 648 **/ 649 public PrintConfig setCellBorderVertical(String str) { 650 this.cellborder_v = str; 651 return this; 652 } 653 654 /** 655 Sets the string (typically a single character) that makes up 656 the cellpadding. Defaults to <tt>" "</tt> 657 **/ 658 public PrintConfig setCellPaddingGlyph(String str) { 659 this.cellpadding_glyph = str; 660 return this; 661 } 662 663 /** 664 Sets the string (typically a single character) that makes up 665 the cellspacing. Defaults to <tt>" "</tt> 666 **/ 667 public PrintConfig setCellSpacingGlyph(String str) { 668 this.cellspacing_glyph = str; 669 return this; 670 } 671 672 /** 673 Sets the width of each cell. Defaults to <tt>20</tt>. 674 This width is common to all cells. 675 676 @param width the width of each cell 677 **/ 678 public PrintConfig setCellWidth(int width) { 679 if (width <= 0) { 680 throw new IllegalArgumentException("cell width must be greater than zero"); 681 } 682 this.cellwidth = width; 683 return this; 684 } 685 686 /** 687 Sets the cell width of the the specified column. cells 688 are numbered starting from 0 689 690 @param column the cell number 691 @param width the desired character width 692 **/ 693 public PrintConfig setCellWidthForColumn(int column, int width) { 694 cellwidthMap.put(Integer.valueOf(column), Integer.valueOf(width)); 695 return this; 696 } 697 698 /** 699 convenience method for internal use: 700 returns the cellwidth if set otherwise the default cellwidth 701 **/ 702 int getCellWidthForColumn(int column) 703 { 704 Object obj = cellwidthMap.get(Integer.valueOf(column)); 705 if (obj != null) { 706 return ((Integer) obj).intValue(); 707 } 708 return cellwidth; 709 } 710 711 712 /** 713 Cell wrapping is on by default and the contents of any column that 714 exceed the width are wrapped within each cell (using the current platforms line 715 seperator for newlines within the cell). 716 717 @param wrap <tt>true</tt> To turn cell wrapping on, <tt>false</tt> 718 to turn cell wrapping off and show 719 fixed width contents only. 720 **/ 721 public PrintConfig setCellWrap(boolean wrap) { 722 cellwrap = wrap; 723 return this; 724 } 725 726 /** 727 Sets each cell to expand to the size needed for the maximum 728 sized cell in that column. By default, this is <tt>false</tt>. 729 Setting this to <tt>true</tt> automatically turns <b>off</b> 730 cell wrapping. 731 <p> 732 Note: using autofit may result in slower performance 733 because the table almost always has to be rendered in 734 two passes. 735 736 @param autofit <tt>true</tt> to turn autofit on, 737 <tt>false</tt> otherwise. 738 **/ 739 public PrintConfig setAutoFit(boolean autofit) 740 { 741 autoFit = autofit; 742 if (autofit) 743 cellwrap = false; 744 return this; 745 } 746 747 /** 748 Specifies whether table and cell borders are printed. By 749 default this is true. 750 751 @param print <tt>true</tt> to print borders, <tt>false</tt> otherwise 752 **/ 753 public PrintConfig setPrintBorders(boolean print) { 754 printborder = print; 755 return this; 756 } 757 758 /** 759 Specifies the cell spacing between cells. This is useful 760 for tables with no borders. By default, this value is 761 zero. 762 **/ 763 public PrintConfig setCellSpacing(int width) { 764 cellspacing = width; 765 return this; 766 } 767 768 /** 769 Specifies the cell padding for each cell. This is useful for 770 tables with no borders. By default, this value is zero. 771 **/ 772 public PrintConfig setCellPadding(int width) { 773 cellpadding = width; 774 return this; 775 } 776 777 /** 778 Prints a short description of this object. 779 **/ 780 public String toString() 781 { 782 return new ToString(this, style).reflect().render(); 783 } 784 785 } //~PrintConfig 786 787public static void main(String[] args) 788 { 789 System.out.println("Table with borders and *NO* wrapping, (truncated to fixed cell width)"); 790 //borders 791 PrintConfig config = new PrintConfig(); 792 config.setCellWrap(false); 793 printTest(config, null); 794 795 796 System.out.println("Table with borders and autofit"); 797 //borders 798 config = new PrintConfig(); 799 config.setAutoFit(true); 800 printTest(config, null); 801 802 803 System.out.println("Table with borders and wrapping"); 804 //borders 805 config = new PrintConfig(); 806 printTest(config, null); 807 808 System.out.println(""); 809 System.out.println("Table with borders and padding & spacing"); 810 //borders + padding 811 config = new PrintConfig(); 812 config.setAutoFit(true); 813 config.setCellSpacing(2); 814 config.setCellPadding(1); 815 config.setCellWidth(5); 816 printTest(config, null); 817 818 System.out.println(""); 819 System.out.println("Table with borders and padding & spacing and AUTOFIT"); 820 //borders + padding 821 config = new PrintConfig(); 822 config.setCellSpacing(2); 823 config.setCellPadding(1); 824 config.setCellWidth(5); 825 printTest(config, null); 826 827 System.out.println(""); 828 System.out.println("Table with borders, spacing=2 page size = 2 lines and headers = true"); 829 config = new PrintConfig(); 830 config.setCellSpacing(2); 831 config.setCellWidth(5); 832 config.setPageSize(2); 833 config.headerEveryPage(true); 834 printTest(config, new String[] {"Header", "A", "helloworld", "d", "End Header"}); 835 836 System.out.println(""); 837 System.out.println("Table with borders, no padding and default alignment of CENTER"); 838 //borders + padding 839 config = new PrintConfig(); 840 config.setCellSpacing(0); 841 config.setCellPadding(0); 842 config.setCellWidth(15); 843 config.setAlign(HAlign.CENTER); 844 printTest(config, null); 845 846 System.out.println(""); 847 System.out.println("Table with no borders and padding & spacing"); 848 //no borders + padding 849 config = new PrintConfig(); 850 config.setPrintBorders(false); 851 config.setCellWidth(5); 852 config.setCellSpacing(2); 853 config.setCellPadding(3); 854 printTest(config, null); 855 856 } 857 858 859static void printTest(PrintConfig config, String[] header) 860 { 861 TablePrinter printer = new TablePrinter(5, System.out, config); 862 863 if (header != null) 864 config.setHeader(header); 865 866 printer.startTable(); 867 868 //row 1 869 printer.startRow(); 870 printer.printCell("abcdef"); 871 printer.printCell("4324"); 872 printer.printCell("Q"); 873 printer.printCell(""); 874 printer.printCell("abc"); 875 printer.endRow(); 876 877 //2 878 printer.startRow(); 879 printer.printCell("row2: abc"); 880 printer.printCell("the quick brown \n-fox jumps over the lazy dog"); 881 printer.printCell("hello"); 882 printer.printCell("world"); 883 printer.printCell(null); 884 printer.endRow(); 885 886 887 //3 888 printer.startRow(); 889 printer.printCell("row3"); 890 printer.printCell("a"); 891 printer.printCell(null); 892 printer.printCell(null); 893 printer.printCell(null); 894 printer.endRow(); 895 896 printer.endTable(); 897 898 System.out.println("TablePrinter.toString()=" + printer); 899 System.out.println(""); 900 } 901 902} //~TablePrinter