1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright (c) 2000-2017 Oracle and/or its affiliates. All rights reserved. 5 * 6 * The contents of this file are subject to the terms of either the GNU 7 * General Public License Version 2 only ("GPL") or the Common Development 8 * and Distribution License("CDDL") (collectively, the "License"). You 9 * may not use this file except in compliance with the License. You can 10 * obtain a copy of the License at 11 * https://oss.oracle.com/licenses/CDDL+GPL-1.1 12 * or LICENSE.txt. See the License for the specific 13 * language governing permissions and limitations under the License. 14 * 15 * When distributing the software, include this License Header Notice in each 16 * file and include the License file at LICENSE.txt. 17 * 18 * GPL Classpath Exception: 19 * Oracle designates this particular file as subject to the "Classpath" 20 * exception as provided by Oracle in the GPL Version 2 section of the License 21 * file that accompanied this code. 22 * 23 * Modifications: 24 * If applicable, add the following below the License Header, with the fields 25 * enclosed by brackets [] replaced by your own identifying information: 26 * "Portions Copyright [year] [name of copyright owner]" 27 * 28 * Contributor(s): 29 * If you wish your version of this file to be governed by only the CDDL or 30 * only the GPL Version 2, indicate your decision by adding "[Contributor] 31 * elects to include this software in this distribution under the [CDDL or GPL 32 * Version 2] license." If you don't indicate a single choice of license, a 33 * recipient has the option to distribute your version of this file under 34 * either the CDDL, the GPL Version 2 or to extend the choice of license to 35 * its licensees as provided above. However, if you add GPL Version 2 code 36 * and therefore, elected the GPL Version 2 license, then the option applies 37 * only if the new code is made subject to such option by the copyright 38 * holder. 39 */ 40 41 import java.util.Enumeration; 42 import java.util.Vector; 43 import java.util.Map; 44 import java.util.TreeMap; 45 import java.util.Iterator; 46 47 /** 48 * Utility class for printing aligned collumns of text. How/where 49 * the text is printed is determined by the abstract methods 50 * doPrint(String) and doPrintln(String). The typical case is to 51 * create a subclass and make these methods print the string to standard 52 * out or error. 53 * <P> 54 * This class allows you to specify: 55 * <UL> 56 * <LI>The number of collumns in the output. This will determine 57 * the dimension of the string arrays passed to add(String[]) 58 * or addTitle(String[]). 59 * <LI>spacing/gap between columns 60 * <LI>character to use for title border (null means no border) 61 * <LI>column alignment. Only LEFT/CENTER is supported for now. 62 * </UL> 63 * 64 * <P> 65 * Example usage: 66 * <PRE> 67 * MyPrinter mp = new MyPrinter(3, 2, "-"); 68 * String oneRow[] = new String [ 3 ]; 69 * oneRow[0] = "User Name"; 70 * oneRow[1] = "Email Address"; 71 * oneRow[2] = "Phone Number"; 72 * mp.addTitle(oneRow); 73 * 74 * oneRow[0] = "Bob"; 75 * oneRow[1] = "bob@foo.com"; 76 * oneRow[2] = "123-4567"; 77 * mp.add(oneRow); 78 * 79 * oneRow[0] = "John"; 80 * oneRow[1] = "john@foo.com"; 81 * oneRow[2] = "456-7890"; 82 * mp.add(oneRow); 83 * mp.print(); 84 * </PRE> 85 * 86 * <P> 87 * The above would print: 88 * <P> 89 * <PRE> 90 * -------------------------------------- 91 * User Name Email Address Phone Number 92 * -------------------------------------- 93 * Bob bob@foo.com 123-4567 94 * John john@foo.com 456-7890 95 * </PRE> 96 * 97 *<P> 98 * This class also supports multi-row titles and having title 99 * strings spanning multiple collumns. Example usage: 100 * <PRE> 101 * TestPrinter tp = new TestPrinter(4, 2, "-"); 102 * String oneRow[] = new String [ 4 ]; 103 * int[] span = new int[ 4 ]; 104 * 105 * span[0] = 2; // spans 2 collumns 106 * span[1] = 0; // spans 0 collumns 107 * span[2] = 2; // spans 2 collumns 108 * span[3] = 0; // spans 0 collumns 109 * 110 * tp.setTitleAlign(CENTER); 111 * oneRow[0] = "Name"; 112 * oneRow[1] = ""; 113 * oneRow[2] = "Contact"; 114 * oneRow[3] = ""; 115 * tp.addTitle(oneRow, span); 116 * 117 * oneRow[0] = "First"; 118 * oneRow[1] = "Last"; 119 * oneRow[2] = "Email"; 120 * oneRow[3] = "Phone"; 121 * tp.addTitle(oneRow); 122 * 123 * oneRow[0] = "Bob"; 124 * oneRow[1] = "Jones"; 125 * oneRow[2] = "bob@foo.com"; 126 * oneRow[3] = "123-4567"; 127 * tp.add(oneRow); 128 * 129 * oneRow[0] = "John"; 130 * oneRow[1] = "Doe"; 131 * oneRow[2] = "john@foo.com"; 132 * oneRow[3] = "456-7890"; 133 * tp.add(oneRow); 134 * 135 * tp.println(); 136 * </PRE> 137 * 138 * <P> 139 * The above would print: 140 * <P> 141 * <PRE> 142 * ------------------------------------ 143 * Name Contact 144 * First Last Email Phone 145 * ------------------------------------ 146 * Bob Jones bob@foo.com 123-4567 147 * John Doe john@foo.com 456-7890 148 * </PRE> 149 * 150 */ 151 public abstract class MultiColumnPrinter { 152 153 final public static int LEFT = 0; 154 final public static int CENTER = 1; 155 156 /* 157 * Sets the default sorting behavior. 158 * When set to true, the table entries are sorted unless otherwise 159 * specified in a constructor. 160 */ 161 final private static boolean DEFAULT_SORT = true; 162 163 private int numCol = 2; 164 private int gap = 4; 165 private int align = CENTER; 166 private int titleAlign = CENTER; 167 private String border = null; 168 169 private Vector table = null; 170 private Vector titleTable = null; 171 private Vector titleSpanTable = null; 172 private int curLength[]; 173 174 private boolean sortNeeded = DEFAULT_SORT; 175 private int[] keyCriteria = null; 176 177 /** 178 * Creates a new MultiColumnPrinter class. 179 * 180 * @param numCol number of columns 181 * @param gap gap between each column 182 * @param border character used to frame the titles 183 * @param align type of alignment within columns 184 * @param sort true if the output is sorted; false otherwise 185 * 186 * REVISIT: Possibly adding another argument that specifies which ones can 187 * be truncated (xxx...) 188 */ MultiColumnPrinter(int numCol, int gap, String border, int align, boolean sort)189 public MultiColumnPrinter(int numCol, int gap, String border, 190 int align, boolean sort) { 191 192 table = new Vector(); 193 titleTable = new Vector(); 194 titleSpanTable = new Vector(); 195 curLength = new int[numCol]; 196 197 this.numCol = numCol; 198 this.gap = gap; 199 this.border = border; 200 this.align = align; 201 this.titleAlign = LEFT; 202 this.sortNeeded = sort; 203 } 204 205 /** 206 * Creates a new sorted MultiColumnPrinter class. 207 * 208 * @param numCol number of columns 209 * @param gap gap between each column 210 * @param border character used to frame the titles 211 * @param align type of alignment within columns 212 */ MultiColumnPrinter(int numCol, int gap, String border, int align)213 public MultiColumnPrinter(int numCol, int gap, String border, int align) { 214 this(numCol, gap, border, align, DEFAULT_SORT); 215 } 216 217 /** 218 * Creates a sorted new MultiColumnPrinter class using LEFT alignment. 219 * 220 * @param numCol number of columns 221 * @param gap gap between each column 222 * @param border character used to frame the titles 223 */ MultiColumnPrinter(int numCol, int gap, String border)224 public MultiColumnPrinter(int numCol, int gap, String border) { 225 this(numCol, gap, border, LEFT); 226 } 227 228 /** 229 * Creates a sorted new MultiColumnPrinter class using LEFT alignment 230 * and with no title border. 231 * 232 * @param numCol number of columns 233 * @param gap gap between each column 234 */ MultiColumnPrinter(int numCol, int gap)235 public MultiColumnPrinter(int numCol, int gap) { 236 this(numCol, gap, null, LEFT); 237 } 238 239 /** 240 * Adds to the row of strings to be used as the title for the table. 241 * 242 * @param row Array of strings to print in one row of title. 243 */ addTitle(String[] row)244 public void addTitle(String[] row) { 245 if (row == null) 246 return; 247 248 int[] span = new int [ row.length ]; 249 for (int i = 0; i < row.length; i++) { 250 span[i] = 1; 251 } 252 253 addTitle(row, span); 254 } 255 256 /** 257 * Adds to the row of strings to be used as the title for the table. 258 * Also allows for certain title strings to span multiple collumns 259 * The span parameter is an array of integers which indicate how 260 * many collumns the corresponding title string will occupy. 261 * For a row that is 4 collumns wide, it is possible to have some 262 * title strings in a row to 'span' multiple collumns: 263 * 264 * <P> 265 * <PRE> 266 * ------------------------------------ 267 * Name Contact 268 * First Last Email Phone 269 * ------------------------------------ 270 * Bob Jones bob@foo.com 123-4567 271 * John Doe john@foo.com 456-7890 272 * </PRE> 273 * 274 * In the example above, the title row has a string 'Name' that 275 * spans 2 collumns. The string 'Contact' also spans 2 collumns. 276 * The above is done by passing in to addTitle() an array that 277 * contains: 278 * 279 * <PRE> 280 * span[0] = 2; // spans 2 collumns 281 * span[1] = 0; // spans 0 collumns, ignore 282 * span[2] = 2; // spans 2 collumns 283 * span[3] = 0; // spans 0 collumns, ignore 284 * </PRE> 285 * <P> 286 * A span value of 1 is the default. 287 * The method addTitle(String[] row) basically does: 288 * 289 * <PRE> 290 * int[] span = new int [ row.length ]; 291 * for (int i = 0; i < row.length; i++) { 292 * span[i] = 1; 293 * } 294 * addTitle(row, span); 295 * </PRE> 296 * 297 * @param row Array of strings to print in one row of title. 298 * @param span Array of integers that reflect the number of collumns 299 * the corresponding title string will occupy. 300 */ addTitle(String[] row, int span[])301 public void addTitle(String[] row, int span[]) { 302 // Need to create a new instance of it, otherwise the new values will 303 // always overwrite the old values. 304 305 String[] rowInstance = new String[(row.length)]; 306 for (int i = 0; i < row.length; i++) { 307 rowInstance[i] = row[i]; 308 } 309 titleTable.addElement(rowInstance); 310 311 titleSpanTable.addElement(span); 312 } 313 314 /** 315 * Set alignment for title strings 316 * 317 * @param titleAlign 318 */ setTitleAlign(int titleAlign)319 public void setTitleAlign(int titleAlign) { 320 this.titleAlign = titleAlign; 321 } 322 323 /** 324 * Adds one row of text to output. 325 * 326 * @param row Array of strings to print in one row. 327 */ add(String[] row)328 public void add(String[] row) { 329 // Need to create a new instance of it, otherwise the new values will 330 // always overwrite the old values. 331 String[] rowInstance = new String[(row.length)]; 332 for (int i = 0; i < row.length; i++) { 333 rowInstance[i] = row[i]; 334 } 335 table.addElement(rowInstance); 336 } 337 338 /** 339 * Clears title strings. 340 */ clearTitle()341 public void clearTitle() { 342 titleTable.clear(); 343 titleSpanTable.clear(); 344 } 345 346 /** 347 * Clears strings. 348 */ clear()349 public void clear() { 350 table.clear(); 351 352 if (curLength != null) { 353 for (int i = 0; i < curLength.length; ++i) { 354 curLength[i] = 0; 355 } 356 } 357 } 358 359 /** 360 * Prints the multi-column table, including the title. 361 */ print()362 public void print() { 363 print(true); 364 } 365 366 /** 367 * Prints the multi-column table. 368 * 369 * @param printTitle Specifies if the title rows should be printed. 370 */ print(boolean printTitle)371 public void print(boolean printTitle) { 372 373 // REVISIT: 374 // Make sure you take care of curLength and row being null value cases. 375 376 377 // Get the longest string for each column and store in curLength[] 378 379 // Scan through title rows 380 Enumeration elm = titleTable.elements(); 381 Enumeration spanEnum = titleSpanTable.elements(); 382 int rowNum = 0; 383 while (elm.hasMoreElements()) { 384 String[] row = (String[])elm.nextElement(); 385 int[] curSpan = (int[])spanEnum.nextElement(); 386 387 for (int i = 0; i < numCol; i++) { 388 // Fix for 4627901: NullPtrException seen when 389 // execute 'jmqcmd list dur' 390 // This happens when a field to be listed is null. 391 // None of the fields should be null, but if it 392 // happens to be so, replace it with "-". 393 if (row[i] == null) 394 row[i] = "-"; 395 396 int len = row[i].length(); 397 398 /* 399 * If a title string spans multiple collumns, then 400 * the space it occupies in each collumn is at most 401 * len/span (since we have gap to take into account 402 * as well). 403 */ 404 int span = curSpan[i], rem = 0; 405 if (span > 1) { 406 rem = len % span; 407 len = len/span; 408 } 409 410 if (curLength[i] < len) { 411 curLength[i] = len; 412 413 if ((span > 1) && ((i+span) <= numCol)) { 414 for (int j=i+1; j<(i+span); ++j) { 415 curLength[j] = len; 416 } 417 418 /* 419 * Add remainder to last collumn in span 420 * to avoid round-off errors. 421 */ 422 curLength[(i+span)-1] += rem; 423 } 424 } 425 } 426 ++rowNum; 427 } 428 429 // Scan through rest of rows 430 elm = table.elements(); 431 while (elm.hasMoreElements()) { 432 String[] row = (String[])elm.nextElement(); 433 for (int i = 0; i < numCol; i++) { 434 435 // Fix for 4627901: NullPtrException seen when 436 // execute 'jmqcmd list dur' 437 // This happens when a field to be listed is null. 438 // None of the fields should be null, but if it 439 // happens to be so, replace it with "-". 440 if (row[i] == null) 441 row[i] = "-"; 442 443 if (curLength[i] < row[i].length()) 444 curLength[i] = row[i].length(); 445 } 446 } 447 448 /* 449 * Print title 450 */ 451 if (printTitle) { 452 printBorder(); 453 elm = titleTable.elements(); 454 spanEnum = titleSpanTable.elements(); 455 456 while (elm.hasMoreElements()) { 457 String[] row = (String[])elm.nextElement(); 458 int[] curSpan = (int[])spanEnum.nextElement(); 459 460 for (int i = 0; i < numCol; i++) { 461 int availableSpace = 0, span = curSpan[i]; 462 463 if (span == 0) 464 continue; 465 466 availableSpace = curLength[i]; 467 468 if ((span > 1) && ((i+span) <= numCol)) { 469 for (int j=i+1; j<(i+span); ++j) { 470 availableSpace += gap; 471 availableSpace += curLength[j]; 472 } 473 } 474 475 if (titleAlign == CENTER) { 476 int space_before, space_after; 477 space_before = (availableSpace-row[i].length())/2; 478 space_after = availableSpace-row[i].length() - space_before; 479 480 printSpaces(space_before); 481 doPrint(row[i]); 482 printSpaces(space_after); 483 if (i < numCol-1) printSpaces(gap); 484 } else { 485 doPrint(row[i]); 486 if (i < numCol-1) printSpaces(availableSpace-row[i].length()+gap); 487 } 488 489 } 490 doPrintln(""); 491 } 492 printBorder(); 493 } 494 495 if (sortNeeded) 496 printSortedTable(); 497 else 498 printUnsortedTable(); 499 } 500 501 /* 502 * Prints the table entries in the sorted order. 503 */ printSortedTable()504 private void printSortedTable() { 505 // Sort the table entries 506 TreeMap sortedTable = new TreeMap(); 507 Enumeration elm = table.elements(); 508 while (elm.hasMoreElements()) { 509 String[] row = (String[])elm.nextElement(); 510 511 // If keyCriteria contains valid info use that 512 // to create the key; otherwise, use the default row[0] 513 // for the key. 514 if (keyCriteria != null && keyCriteria.length > 0) { 515 String key = getKey(row); 516 if (key != null) 517 sortedTable.put(key, row); 518 else 519 sortedTable.put(row[0], row); 520 } else { 521 sortedTable.put(row[0], row); 522 } 523 } 524 525 // Iterate through the table entries 526 Iterator iterator = sortedTable.entrySet().iterator(); 527 while (iterator.hasNext()) { 528 Map.Entry entry = (Map.Entry)iterator.next(); 529 String[] row = ((String[])entry.getValue()); 530 531 printRow(row); 532 } 533 } 534 535 /* 536 * Creates the key for the current row based on the 537 * criteria specified by setKeyCriteria(). 538 * If key cannot be created by the criteria, it simply returns 539 * null. 540 * 541 * Examples: 542 * String[] row = {"foo", "bar", "hello"); 543 * 544 * int[] keyCriteria = {0}; 545 * key = "foo"; 546 * 547 * int[] keyCriteria = {0, 1}; 548 * key = "foobar"; 549 * 550 * int[] keyCriteria = {2, 1}; 551 * key = "hellobar"; 552 * 553 * int[] keyCriteria = {4}; 554 * key = null; 555 */ getKey(String[] row)556 private String getKey(String[] row) { 557 String key = ""; 558 559 for (int i = 0; i < keyCriteria.length; i++) { 560 int content = keyCriteria[i]; 561 try { 562 key = key + row[content]; 563 } catch (ArrayIndexOutOfBoundsException ae) { 564 // Happens when keyCriteria[] contains an index that 565 // does not exist in 'row'. 566 return null; 567 } 568 } 569 return key; 570 } 571 572 /* 573 * Prints the table entries in the order they were entered. 574 */ printUnsortedTable()575 private void printUnsortedTable() { 576 Enumeration elm = table.elements(); 577 while (elm.hasMoreElements()) { 578 String[] row = (String[])elm.nextElement(); 579 580 printRow(row); 581 } 582 } 583 printRow(String[] row)584 private void printRow(String[] row) { 585 for (int i = 0; i < numCol; i++) { 586 if (align == CENTER) { 587 int space1, space2; 588 space1 = (curLength[i]-row[i].length())/2; 589 space2 = curLength[i]-row[i].length() - space1; 590 591 printSpaces(space1); 592 doPrint(row[i]); 593 printSpaces(space2); 594 if (i < numCol-1) printSpaces(gap); 595 } else { 596 doPrint(row[i]); 597 if (i < numCol-1) printSpaces(curLength[i]-row[i].length()+gap); 598 } 599 } 600 doPrintln(""); 601 } 602 603 /** 604 * Prints the multi-column table, with a carriage return. 605 */ println()606 public void println() { 607 print(); 608 doPrintln(""); 609 } 610 printSpaces(int count)611 private void printSpaces(int count) { 612 for (int i = 0; i < count; ++i) { 613 doPrint(" "); 614 } 615 } 616 printBorder()617 private void printBorder() { 618 619 int colNum = 1; 620 if (border == null) return; 621 622 // For the value in each column 623 for (int i = 0; i < numCol; i++) { 624 for (int j = 0; j < curLength[i]; j++) { 625 doPrint(border); 626 } 627 } 628 629 // For the gap between each column 630 for (int i = 0; i < numCol-1; i++) { 631 for (int j = 0; j < gap; j++) { 632 doPrint(border); 633 } 634 } 635 doPrintln(""); 636 } 637 638 /* 639 * Sets the criteria for the key. 640 * new int[] {0, 1} means use the first and the second 641 * elements of the array. 642 */ setKeyCriteria(int[] criteria)643 public void setKeyCriteria(int[] criteria) { 644 this.keyCriteria = criteria; 645 } 646 647 /** 648 * Method that does the actual printing. Override this method to 649 * print to your destination of choice (stdout, stream, etc). 650 * 651 * @param str String to print. 652 */ doPrint(String str)653 public abstract void doPrint(String str); 654 655 /** 656 * Method that does the actual printing. Override this method to 657 * print to your destination of choice (stdout, stream, etc). 658 * 659 * This method also prints a newline at the end of the string. 660 * 661 * @param str String to print. 662 */ doPrintln(String str)663 public abstract void doPrintln(String str); 664 } 665