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 447 //further recursion 448 config.setAutoFit(false); 449 450 for (int n = 0; n < largest_width.length; n++) { 451 if (dbg) System.out.println("largest_width["+n+"]="+largest_width[n]); 452 config.setCellWidthForColumn(n, largest_width[n]); 453 } 454 455 //TablePrinter printer = new TablePrinter(columncount, out, config); 456 TablePrinter printer = this; 457 printer.initRowBorder(); 458 459 printer.startTable(); 460 int n = 0; 461 while (n < rowcount) 462 { 463 printer.startRow(); 464 for (int k = 0; k < columncount; k++) { 465 printer.printCell( 466 ((String[])autoFitTableData.get(n))[k] ); 467 } 468 printer.endRow(); 469 n++; 470 } 471 472 printer.endTable(); 473 } 474 475void startRowAutoFit() { 476 currentAutoFitCell = 0; 477 autoFitTableData.add(currentAutoFitRow, new String[columncount]); 478 } 479 480void endRowAutoFit() { 481 currentAutoFitRow++; 482 } 483 484void printCellAutoFit(String str) 485 { 486 int len = (str != null) ? str.length() : "null".length(); 487 488 if (largest_width[currentAutoFitCell] < len) 489 largest_width[currentAutoFitCell] = len; 490 491 String[] row = (String[]) autoFitTableData.get(currentAutoFitRow); 492 row[currentAutoFitCell++] = str; 493 } 494 495final private Pattern newlines = Pattern.compile("\\r|\\n|\\r\\n"); 496String removeEmbeddedSpaces(String str) 497 { 498 /* 499 we convert embedded newlines in the string to spaces 500 otherwise the embedded newline will cause printing 501 to start at the left most margin. i.e., 502 503 baz\n\baz2 will print like 504 505 A B C 506 x y baz 507 baz2 508 509 but we want 510 <---> 511 A B C 512 x y baz 513 baz2 514 515 */ 516 517 if (str != null) 518 { 519 str = newlines.matcher(str).replaceAll(" "); 520 } 521 return str; 522 } 523 524/** 525Configuration object containing for table printing object. 526 527Some methods in this class return <tt>this</tt> object for method 528chaining convenience. 529<p> 530Note: Alas ! TablePrinter does not support cellspans across 531columns or rows. That would make things too complicated for 532this implementation. 533**/ 534public static class PrintConfig 535 { 536 static final ToString.Style style = new ToString.Style(); 537 static { 538 style.reflectVisibleLevel = ToString.Style.VisibleLevel.PRIVATE; 539 } 540 541 private int cellwidth = 20; 542 543 //individual widths if specified 544 private Map cellwidthMap = new HashMap(); 545 546 private String cellcorner = "+"; 547 private String cellborder_h = "-"; 548 private String cellborder_v = "|"; 549 private String cellpadding_glyph = " "; 550 private String cellspacing_glyph = " "; 551 private boolean cellwrap = true; 552 private boolean autoFit = false; 553 private boolean printborder = true; 554 private int cellspacing = 0; 555 private int cellpadding = 0; 556 private HAlign align = HAlign.LEFT; 557 private int pageSize = -1; 558 private boolean headerEveryPage = true; 559 private String[] header; 560 561 /** 562 Constructs a new PrintConfig with the following default options. 563 <ul> 564 <li>cell corner: the <tt>+</tt> character 565 <li>horizontal cell border: the <tt>-</tt> character 566 <li>vertical cell border, the <tt>|</tt> character 567 <li>width of each column: 20 chars 568 <li>horizontal alignment of each cell: {@link HAlign#LEFT} 569 <li>The character used for cellpadding and cellspacing is a 570 blank space. 571 </ul> 572 **/ 573 public PrintConfig() { 574 } 575 576 /** 577 Optionally sets the header row for the table **/ 578 public void setHeader(String[] header) { 579 this.header = header; 580 } 581 582 /** 583 Gets the header row for the table if set. If not 584 set, return null 585 **/ 586 public String[] getHeader() { 587 return this.header; 588 } 589 590 591 /** 592 Sets the number of lines on each page. A page is a 593 logical unit that typically shows some number of lines 594 on the screen without the need to scroll the page. This 595 value is useful in conjunction with other page specific 596 settings like {@link showPageHeading()} 597 598 @param lines number of lines on the page, specify a zero or 599 negative quantity for a single page. (number of 600 lines <b>not</b> including lines occupied by the table 601 header itself, if the header is printed). 602 **/ 603 public PrintConfig setPageSize(int lines) { 604 this.pageSize = lines; 605 return this; 606 } 607 608 /** 609 Specifies that page heading (if set) on each separate 610 page. This may be ignored if no heading has been set. 611 <tt>true</tt> by default, although headers will still 612 not be printed until the {@link setPageSize} method is 613 invoked. 614 **/ 615 public PrintConfig headerEveryPage(boolean show) { 616 this.headerEveryPage = show; 617 return this; 618 } 619 620 /** Sets the alignment of each cell. By default: 621 <tt>{@link HAlign#LEFT left}</tt> 622 **/ 623 public PrintConfig setAlign(HAlign align) { 624 this.align = align; 625 return this; 626 } 627 628 /** 629 Sets the string (typically a single character) that makes up a 630 cell corner. Defaults to <tt>+</tt> 631 **/ 632 public PrintConfig setCellCorner(String str) { 633 this.cellcorner = str; 634 return this; 635 } 636 637 /** 638 Sets the string (typically a single character) that makes up a 639 horizontal cell border. Defaults to <tt>-</tt> 640 **/ 641 public PrintConfig setCellBorderHorizontal(String str) { 642 this.cellborder_h = str; 643 return this; 644 } 645 646 /** 647 Sets the string (typically a single character) that makes up a 648 vertical cell border. Defaults to <tt>|</tt> 649 **/ 650 public PrintConfig setCellBorderVertical(String str) { 651 this.cellborder_v = str; 652 return this; 653 } 654 655 /** 656 Sets the string (typically a single character) that makes up 657 the cellpadding. Defaults to <tt>" "</tt> 658 **/ 659 public PrintConfig setCellPaddingGlyph(String str) { 660 this.cellpadding_glyph = str; 661 return this; 662 } 663 664 /** 665 Sets the string (typically a single character) that makes up 666 the cellspacing. Defaults to <tt>" "</tt> 667 **/ 668 public PrintConfig setCellSpacingGlyph(String str) { 669 this.cellspacing_glyph = str; 670 return this; 671 } 672 673 /** 674 Sets the width of each cell. Defaults to <tt>20</tt>. 675 This width is common to all cells. 676 677 @param width the width of each cell 678 **/ 679 public PrintConfig setCellWidth(int width) { 680 if (width <= 0) { 681 throw new IllegalArgumentException("cell width must be greater than zero"); 682 } 683 this.cellwidth = width; 684 return this; 685 } 686 687 /** 688 Sets the cell width of the the specified column. cells 689 are numbered starting from 0 690 691 @param column the cell number 692 @param width the desired character width 693 **/ 694 public PrintConfig setCellWidthForColumn(int column, int width) { 695 cellwidthMap.put(new Integer(column), new Integer(width)); 696 return this; 697 } 698 699 /** 700 convenience method for internal use: 701 returns the cellwidth if set otherwise the default cellwidth 702 **/ 703 int getCellWidthForColumn(int column) 704 { 705 Object obj = cellwidthMap.get(new Integer(column)); 706 if (obj != null) { 707 return ((Integer) obj).intValue(); 708 } 709 return cellwidth; 710 } 711 712 713 /** 714 Cell wrapping is on by default and the contents of any column that 715 exceed the width are wrapped within each cell (using the current platforms line 716 seperator for newlines within the cell). 717 718 @param wrap <tt>true</tt> To turn cell wrapping on, <tt>false</tt> 719 to turn cell wrapping off and show 720 fixed width contents only. 721 **/ 722 public PrintConfig setCellWrap(boolean wrap) { 723 cellwrap = wrap; 724 return this; 725 } 726 727 /** 728 Sets each cell to expand to the size needed for the maximum 729 sized cell in that column. By default, this is <tt>false</tt>. 730 Setting this to <tt>true</tt> automatically turns <b>off</b> 731 cell wrapping. 732 <p> 733 Note: using autofit may result in slower performance 734 because the table almost always has to be rendered in 735 two passes. 736 737 @param autofit <tt>true</tt> to turn autofit on, 738 <tt>false</tt> otherwise. 739 **/ 740 public PrintConfig setAutoFit(boolean autofit) 741 { 742 autoFit = autofit; 743 if (autofit) 744 cellwrap = false; 745 return this; 746 } 747 748 /** 749 Specifies whether table and cell borders are printed. By 750 default this is true. 751 752 @param print <tt>true</tt> to print borders, <tt>false</tt> otherwise 753 **/ 754 public PrintConfig setPrintBorders(boolean print) { 755 printborder = print; 756 return this; 757 } 758 759 /** 760 Specifies the cell spacing between cells. This is useful 761 for tables with no borders. By default, this value is 762 zero. 763 **/ 764 public PrintConfig setCellSpacing(int width) { 765 cellspacing = width; 766 return this; 767 } 768 769 /** 770 Specifies the cell padding for each cell. This is useful for 771 tables with no borders. By default, this value is zero. 772 **/ 773 public PrintConfig setCellPadding(int width) { 774 cellpadding = width; 775 return this; 776 } 777 778 /** 779 Prints a short description of this object. 780 **/ 781 public String toString() 782 { 783 return new ToString(this, style).reflect().render(); 784 } 785 786 } //~PrintConfig 787 788public static void main(String[] args) 789 { 790 System.out.println("Table with borders and *NO* wrapping, (truncated to fixed cell width)"); 791 //borders 792 PrintConfig config = new PrintConfig(); 793 config.setCellWrap(false); 794 printTest(config, null); 795 796 797 System.out.println("Table with borders and autofit"); 798 //borders 799 config = new PrintConfig(); 800 config.setAutoFit(true); 801 printTest(config, null); 802 803 804 System.out.println("Table with borders and wrapping"); 805 //borders 806 config = new PrintConfig(); 807 printTest(config, null); 808 809 System.out.println(""); 810 System.out.println("Table with borders and padding & spacing"); 811 //borders + padding 812 config = new PrintConfig(); 813 config.setAutoFit(true); 814 config.setCellSpacing(2); 815 config.setCellPadding(1); 816 config.setCellWidth(5); 817 printTest(config, null); 818 819 System.out.println(""); 820 System.out.println("Table with borders and padding & spacing and AUTOFIT"); 821 //borders + padding 822 config = new PrintConfig(); 823 config.setCellSpacing(2); 824 config.setCellPadding(1); 825 config.setCellWidth(5); 826 printTest(config, null); 827 828 System.out.println(""); 829 System.out.println("Table with borders, spacing=2 page size = 2 lines and headers = true"); 830 config = new PrintConfig(); 831 config.setCellSpacing(2); 832 config.setCellWidth(5); 833 config.setPageSize(2); 834 config.headerEveryPage(true); 835 printTest(config, new String[] {"Header", "A", "helloworld", "d", "End Header"}); 836 837 System.out.println(""); 838 System.out.println("Table with borders, no padding and default alignment of CENTER"); 839 //borders + padding 840 config = new PrintConfig(); 841 config.setCellSpacing(0); 842 config.setCellPadding(0); 843 config.setCellWidth(15); 844 config.setAlign(HAlign.CENTER); 845 printTest(config, null); 846 847 System.out.println(""); 848 System.out.println("Table with no borders and padding & spacing"); 849 //no borders + padding 850 config = new PrintConfig(); 851 config.setPrintBorders(false); 852 config.setCellWidth(5); 853 config.setCellSpacing(2); 854 config.setCellPadding(3); 855 printTest(config, null); 856 857 } 858 859 860static void printTest(PrintConfig config, String[] header) 861 { 862 TablePrinter printer = new TablePrinter(5, System.out, config); 863 864 if (header != null) 865 config.setHeader(header); 866 867 printer.startTable(); 868 869 //row 1 870 printer.startRow(); 871 printer.printCell("abcdef"); 872 printer.printCell("4324"); 873 printer.printCell("Q"); 874 printer.printCell(""); 875 printer.printCell("abc"); 876 printer.endRow(); 877 878 //2 879 printer.startRow(); 880 printer.printCell("row2: abc"); 881 printer.printCell("the quick brown \n-fox jumps over the lazy dog"); 882 printer.printCell("hello"); 883 printer.printCell("world"); 884 printer.printCell(null); 885 printer.endRow(); 886 887 888 //3 889 printer.startRow(); 890 printer.printCell("row3"); 891 printer.printCell("a"); 892 printer.printCell(null); 893 printer.printCell(null); 894 printer.printCell(null); 895 printer.endRow(); 896 897 printer.endTable(); 898 899 System.out.println("TablePrinter.toString()=" + printer); 900 System.out.println(""); 901 } 902 903} //~TablePrinter