MultiColumnPrinter.java Source code

Java tutorial

Introduction

Here is the source code for MultiColumnPrinter.java

Source

/*
 * @(#)VMMetrics.java   1.3 04/01/05
 *
 * Copyright (c) 2000-2003 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

/*
 * @(#)MultiColumnPrinter.java   1.3 04/09/15
 *
 * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved
 * SUN PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 *
 */

import java.util.Enumeration;
import java.util.Vector;
import java.util.Map;
import java.util.TreeMap;
import java.util.Iterator;

/**
 * Utility class for printing aligned collumns of text. How/where
 * the text is printed is determined by the abstract methods
 * doPrint(String) and doPrintln(String). The typical case is to 
 * create a subclass and make these methods print the string to standard 
 * out or error.
 * <P>
 * This class allows you to specify:
 * <UL>
 * <LI>The number of collumns in the output. This will determine
 * the dimension of the string arrays passed to add(String[])
 * or addTitle(String[]).
 * <LI>spacing/gap between columns
 * <LI>character to use for title border (null means no border)
 * <LI>column alignment. Only LEFT/CENTER is supported for now.
 * </UL>
 *
 * <P>
 * Example usage:
 * <PRE>
 *   MyPrinter   mp = new MyPrinter(3, 2, "-");
 *   String      oneRow[] = new String [ 3 ];
 *   oneRow[0] = "User Name";
 *   oneRow[1] = "Email Address";
 *   oneRow[2] = "Phone Number";
 *   mp.addTitle(oneRow);
 *
 *   oneRow[0] = "Bob";
 *   oneRow[1] = "bob@foo.com";
 *   oneRow[2] = "123-4567";
 *   mp.add(oneRow);
 *
 *   oneRow[0] = "John";
 *   oneRow[1] = "john@foo.com";
 *   oneRow[2] = "456-7890";
 *   mp.add(oneRow);
 *   mp.print();
 * </PRE>
 *
 * <P>
 * The above would print:
 * <P>
 * <PRE>
 *   --------------------------------------
 *   User Name  Email Address  Phone Number
 *   --------------------------------------
 *   Bob        bob@foo.com    123-4567
 *   John       john@foo.com   456-7890
 * </PRE>
 *
 *<P>
 * This class also supports multi-row titles and having title
 * strings spanning multiple collumns. Example usage:
 * <PRE>
 *     TestPrinter   tp = new TestPrinter(4, 2, "-");
 *     String      oneRow[] = new String [ 4 ];
 *     int[]      span = new int[ 4 ];
 *
 *     span[0] = 2; // spans 2 collumns
 *     span[1] = 0; // spans 0 collumns
 *     span[2] = 2; // spans 2 collumns
 *     span[3] = 0; // spans 0 collumns
 *
 *     tp.setTitleAlign(CENTER);
 *     oneRow[0] = "Name";
 *     oneRow[1] = "";
 *     oneRow[2] = "Contact";
 *     oneRow[3] = "";
 *     tp.addTitle(oneRow, span);
 * 
 *     oneRow[0] = "First";
 *     oneRow[1] = "Last";
 *     oneRow[2] = "Email";
 *     oneRow[3] = "Phone";
 *     tp.addTitle(oneRow);
 * 
 *     oneRow[0] = "Bob";
 *     oneRow[1] = "Jones";
 *     oneRow[2] = "bob@foo.com";
 *     oneRow[3] = "123-4567";
 *     tp.add(oneRow);
 * 
 *     oneRow[0] = "John";
 *     oneRow[1] = "Doe";
 *     oneRow[2] = "john@foo.com";
 *     oneRow[3] = "456-7890";
 *     tp.add(oneRow);
 * 
 *     tp.println();
 * </PRE>
 *
 * <P>
 * The above would print:
 * <P>
 * <PRE>
 *      ------------------------------------
 *          Name             Contact          
 *      First  Last      Email       Phone  
 *      ------------------------------------
 *      Bob    Jones  bob@foo.com   123-4567
 *      John   Doe    john@foo.com  456-7890
 * </PRE>
 *
 */
public abstract class MultiColumnPrinter {

    final public static int LEFT = 0;
    final public static int CENTER = 1;

    /*
     * Sets the default sorting behavior.
     * When set to true, the table entries are sorted unless otherwise
     * specified in a constructor.
     */
    final private static boolean DEFAULT_SORT = true;

    private int numCol = 2;
    private int gap = 4;
    private int align = CENTER;
    private int titleAlign = CENTER;
    private String border = null;

    private Vector table = null;
    private Vector titleTable = null;
    private Vector titleSpanTable = null;
    private int curLength[];

    private boolean sortNeeded = DEFAULT_SORT;
    private int[] keyCriteria = null;

    /**
     * Creates a new MultiColumnPrinter class.
     *
     * @param numCol number of columns
     * @param gap gap between each column
     * @param border character used to frame the titles
     * @param align type of alignment within columns
     * @param sort true if the output is sorted; false otherwise
     *
     * REVISIT: Possibly adding another argument that specifies which ones can
     * be truncated (xxx...)
     */
    public MultiColumnPrinter(int numCol, int gap, String border, int align, boolean sort) {

        table = new Vector();
        titleTable = new Vector();
        titleSpanTable = new Vector();
        curLength = new int[numCol];

        this.numCol = numCol;
        this.gap = gap;
        this.border = border;
        this.align = align;
        this.titleAlign = LEFT;
        this.sortNeeded = sort;
    }

    /**
     * Creates a new sorted MultiColumnPrinter class.
     *
     * @param numCol number of columns
     * @param gap gap between each column
     * @param border character used to frame the titles
     * @param align type of alignment within columns
     */
    public MultiColumnPrinter(int numCol, int gap, String border, int align) {
        this(numCol, gap, border, align, DEFAULT_SORT);
    }

    /**
     * Creates a sorted new MultiColumnPrinter class using LEFT alignment.
     *
     * @param numCol number of columns
     * @param gap gap between each column
     * @param border character used to frame the titles
     */
    public MultiColumnPrinter(int numCol, int gap, String border) {
        this(numCol, gap, border, LEFT);
    }

    /**
     * Creates a sorted new MultiColumnPrinter class using LEFT alignment
     * and with no title border.
     *
     * @param numCol number of columns
     * @param gap gap between each column
     */
    public MultiColumnPrinter(int numCol, int gap) {
        this(numCol, gap, null, LEFT);
    }

    /**
     * Adds to the row of strings to be used as the title for the table.
     *
     * @param row Array of strings to print in one row of title.
     */
    public void addTitle(String[] row) {
        if (row == null)
            return;

        int[] span = new int[row.length];
        for (int i = 0; i < row.length; i++) {
            span[i] = 1;
        }

        addTitle(row, span);
    }

    /**
     * Adds to the row of strings to be used as the title for the table.
     * Also allows for certain title strings to span multiple collumns
     * The span parameter is an array of integers which indicate how
     * many collumns the corresponding title string will occupy.
     * For a row that is 4 collumns wide, it is possible to have some 
     * title strings in a row to 'span' multiple collumns:
     *
     * <P>
     * <PRE>
     * ------------------------------------
     *     Name             Contact          
     * First  Last      Email       Phone  
     * ------------------------------------
     * Bob    Jones  bob@foo.com   123-4567
     * John   Doe    john@foo.com  456-7890
     * </PRE>
     *
     * In the example above, the title row has a string 'Name' that
     * spans 2 collumns. The string 'Contact' also spans 2 collumns.
     * The above is done by passing in to addTitle() an array that
     * contains:
     *
     * <PRE>
     *    span[0] = 2; // spans 2 collumns
     *    span[1] = 0; // spans 0 collumns, ignore
     *    span[2] = 2; // spans 2 collumns
     *    span[3] = 0; // spans 0 collumns, ignore
     * </PRE>
     * <P>
     * A span value of 1 is the default.
     * The method addTitle(String[] row) basically does:
     *
     * <PRE>
     *   int[] span = new int [ row.length ];
     *   for (int i = 0; i < row.length; i++) {
     *       span[i] = 1;
     *   }
     *   addTitle(row, span);
     * </PRE>
     *
     * @param row Array of strings to print in one row of title.
     * @param span Array of integers that reflect the number of collumns
     * the corresponding title string will occupy.
     */
    public void addTitle(String[] row, int span[]) {
        // Need to create a new instance of it, otherwise the new values will
        // always overwrite the old values.

        String[] rowInstance = new String[(row.length)];
        for (int i = 0; i < row.length; i++) {
            rowInstance[i] = row[i];
        }
        titleTable.addElement(rowInstance);

        titleSpanTable.addElement(span);
    }

    /**
     * Set alignment for title strings
     *
     * @param titleAlign
     */
    public void setTitleAlign(int titleAlign) {
        this.titleAlign = titleAlign;
    }

    /**
     * Adds one row of text to output.
     *
     * @param row Array of strings to print in one row.
     */
    public void add(String[] row) {
        // Need to create a new instance of it, otherwise the new values will
        // always overwrite the old values.
        String[] rowInstance = new String[(row.length)];
        for (int i = 0; i < row.length; i++) {
            rowInstance[i] = row[i];
        }
        table.addElement(rowInstance);
    }

    /**
     * Clears title strings.
     */
    public void clearTitle() {
        titleTable.clear();
        titleSpanTable.clear();
    }

    /**
     * Clears strings.
     */
    public void clear() {
        table.clear();

        if (curLength != null) {
            for (int i = 0; i < curLength.length; ++i) {
                curLength[i] = 0;
            }
        }
    }

    /**
     * Prints the multi-column table, including the title.
     */
    public void print() {
        print(true);
    }

    /**
     * Prints the multi-column table.
     * 
     * @param printTitle Specifies if the title rows should be printed.
     */
    public void print(boolean printTitle) {

        // REVISIT:
        // Make sure you take care of curLength and row being null value cases.

        // Get the longest string for each column and store in curLength[]

        // Scan through title rows
        Enumeration elm = titleTable.elements();
        Enumeration spanEnum = titleSpanTable.elements();
        int rowNum = 0;
        while (elm.hasMoreElements()) {
            String[] row = (String[]) elm.nextElement();
            int[] curSpan = (int[]) spanEnum.nextElement();

            for (int i = 0; i < numCol; i++) {
                // Fix for 4627901: NullPtrException seen when 
                // execute 'jmqcmd list dur'
                // This happens when a field to be listed is null.
                // None of the fields should be null, but if it
                // happens to be so, replace it with "-".
                if (row[i] == null)
                    row[i] = "-";

                int len = row[i].length();

                /*
                 * If a title string spans multiple collumns, then
                 * the space it occupies in each collumn is at most
                 * len/span (since we have gap to take into account
                 * as well).
                 */
                int span = curSpan[i], rem = 0;
                if (span > 1) {
                    rem = len % span;
                    len = len / span;
                }

                if (curLength[i] < len) {
                    curLength[i] = len;

                    if ((span > 1) && ((i + span) <= numCol)) {
                        for (int j = i + 1; j < (i + span); ++j) {
                            curLength[j] = len;
                        }

                        /*
                         * Add remainder to last collumn in span
                         * to avoid round-off errors.
                         */
                        curLength[(i + span) - 1] += rem;
                    }
                }
            }
            ++rowNum;
        }

        // Scan through rest of rows
        elm = table.elements();
        while (elm.hasMoreElements()) {
            String[] row = (String[]) elm.nextElement();
            for (int i = 0; i < numCol; i++) {

                // Fix for 4627901: NullPtrException seen when 
                // execute 'jmqcmd list dur'
                // This happens when a field to be listed is null.
                // None of the fields should be null, but if it
                // happens to be so, replace it with "-".
                if (row[i] == null)
                    row[i] = "-";

                if (curLength[i] < row[i].length())
                    curLength[i] = row[i].length();
            }
        }

        /*
         * Print title
         */
        if (printTitle) {
            printBorder();
            elm = titleTable.elements();
            spanEnum = titleSpanTable.elements();

            while (elm.hasMoreElements()) {
                String[] row = (String[]) elm.nextElement();
                int[] curSpan = (int[]) spanEnum.nextElement();

                for (int i = 0; i < numCol; i++) {
                    int availableSpace = 0, span = curSpan[i];

                    if (span == 0)
                        continue;

                    availableSpace = curLength[i];

                    if ((span > 1) && ((i + span) <= numCol)) {
                        for (int j = i + 1; j < (i + span); ++j) {
                            availableSpace += gap;
                            availableSpace += curLength[j];
                        }
                    }

                    if (titleAlign == CENTER) {
                        int space_before, space_after;
                        space_before = (availableSpace - row[i].length()) / 2;
                        space_after = availableSpace - row[i].length() - space_before;

                        printSpaces(space_before);
                        doPrint(row[i]);
                        printSpaces(space_after);
                        if (i < numCol - 1)
                            printSpaces(gap);
                    } else {
                        doPrint(row[i]);
                        if (i < numCol - 1)
                            printSpaces(availableSpace - row[i].length() + gap);
                    }

                }
                doPrintln("");
            }
            printBorder();
        }

        if (sortNeeded)
            printSortedTable();
        else
            printUnsortedTable();
    }

    /*
     * Prints the table entries in the sorted order.
     */
    private void printSortedTable() {
        // Sort the table entries
        TreeMap sortedTable = new TreeMap();
        Enumeration elm = table.elements();
        while (elm.hasMoreElements()) {
            String[] row = (String[]) elm.nextElement();

            // If keyCriteria contains valid info use that
            // to create the key; otherwise, use the default row[0]
            // for the key.
            if (keyCriteria != null && keyCriteria.length > 0) {
                String key = getKey(row);
                if (key != null)
                    sortedTable.put(key, row);
                else
                    sortedTable.put(row[0], row);
            } else {
                sortedTable.put(row[0], row);
            }
        }

        // Iterate through the table entries
        Iterator iterator = sortedTable.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            String[] row = ((String[]) entry.getValue());

            printRow(row);
        }
    }

    /*
     * Creates the key for the current row based on the
     * criteria specified by setKeyCriteria().
     * If key cannot be created by the criteria, it simply returns
     * null.
     *
     * Examples:
     * String[] row = {"foo", "bar", "hello");
     *
     * int[] keyCriteria = {0};
     * key = "foo";
     *
     * int[] keyCriteria = {0, 1};
     * key = "foobar";
     *
     * int[] keyCriteria = {2, 1};
     * key = "hellobar";
     *
     * int[] keyCriteria = {4};
     * key = null;
     */
    private String getKey(String[] row) {
        String key = "";

        for (int i = 0; i < keyCriteria.length; i++) {
            int content = keyCriteria[i];
            try {
                key = key + row[content];
            } catch (ArrayIndexOutOfBoundsException ae) {
                // Happens when keyCriteria[] contains an index that
                // does not exist in 'row'.
                return null;
            }
        }
        return key;
    }

    /*
     * Prints the table entries in the order they were entered.
     */
    private void printUnsortedTable() {
        Enumeration elm = table.elements();
        while (elm.hasMoreElements()) {
            String[] row = (String[]) elm.nextElement();

            printRow(row);
        }
    }

    private void printRow(String[] row) {
        for (int i = 0; i < numCol; i++) {
            if (align == CENTER) {
                int space1, space2;
                space1 = (curLength[i] - row[i].length()) / 2;
                space2 = curLength[i] - row[i].length() - space1;

                printSpaces(space1);
                doPrint(row[i]);
                printSpaces(space2);
                if (i < numCol - 1)
                    printSpaces(gap);
            } else {
                doPrint(row[i]);
                if (i < numCol - 1)
                    printSpaces(curLength[i] - row[i].length() + gap);
            }
        }
        doPrintln("");
    }

    /**
     * Prints the multi-column table, with a carriage return.
     */
    public void println() {
        print();
        doPrintln("");
    }

    private void printSpaces(int count) {
        for (int i = 0; i < count; ++i) {
            doPrint(" ");
        }
    }

    private void printBorder() {

        int colNum = 1;
        if (border == null)
            return;

        // For the value in each column
        for (int i = 0; i < numCol; i++) {
            for (int j = 0; j < curLength[i]; j++) {
                doPrint(border);
            }
        }

        // For the gap between each column
        for (int i = 0; i < numCol - 1; i++) {
            for (int j = 0; j < gap; j++) {
                doPrint(border);
            }
        }
        doPrintln("");
    }

    /*
     * Sets the criteria for the key.
     * new int[] {0, 1} means use the first and the second
     * elements of the array.
     */
    public void setKeyCriteria(int[] criteria) {
        this.keyCriteria = criteria;
    }

    /**
     * Method that does the actual printing. Override this method to
     * print to your destination of choice (stdout, stream, etc).
     *
     * @param str String to print.
     */
    public abstract void doPrint(String str);

    /**
     * Method that does the actual printing. Override this method to
     * print to your destination of choice (stdout, stream, etc).
     *
     * This method also prints a newline at the end of the string.
     *
     * @param str String to print.
     */
    public abstract void doPrintln(String str);
}