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