Java tutorial
/* * Copyright 2012-2014 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.shell.util; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import java.io.PrintStream; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.annotation.Nullable; /** * Utility class to print an ASCII table. e.g. * * +=======================================================================+ * | pid | end status | start | stop | * +=======================================================================+ * | 9bd22850-0017-4a10-972a-bc5ca8173584 | STOPPED | 1405986408 | 0 | * | 7f9f8054-a71f-48e3-965d-39e2aab16d5d | STOPPED | 1405978322 | 0 | * | e1a2d4a9-667c-40e0-86fa-32ea68cc25f6 | STOPPED | 1405645401 | 0 | * | 9276574a-cc2f-458c-973b-aed9669fc80e | STOPPED | 1405644974 | 0 | * | 1c5868d6-04c7-443b-b4db-aab1c3368be3 | STOPPED | 1405457462 | 0 | * | 4003fa1d-15bd-4a09-ad2b-f2c52b4dda54 | STOPPED | 1405456719 | 0 | * | 531dff0a-0441-424b-ae5b-023cc7383344 | STOPPED | 1405454043 | 0 | * | d9cae8f9-3fd3-45f4-b4e9-102ef38cf4e1 | STOPPED | 1405371545 | 0 | * +=======================================================================+ * * @param <T> type of object that the rows represent */ public class AsciiTable<T> { private final List<String> header; private final List<T> records; private final RowMaker<T> rowMaker; /** * @param header strings representing the header of the table * @param records list of objects that represent the rows * @param rowMaker makes Object arrays from a row object */ public AsciiTable(@Nullable String[] header, List<T> records, RowMaker<T> rowMaker) { this.header = (header == null) ? ImmutableList.<String>of() : ImmutableList.copyOf(header); this.records = records; this.rowMaker = rowMaker; } /** * Prints the ASCII table to the {@link PrintStream} output. * * @param output {@link PrintStream} to print to */ public void print(PrintStream output) { // Collects all output cells for all records. // If any record has multiple lines output, a row divider is printed between each row. boolean useRowDivider = false; List<Row> rows = Lists.newArrayList(); for (T row : records) { useRowDivider = generateRow(rowMaker.makeRow(row), rows) || useRowDivider; } int[] columnWidths = calculateColumnWidths(header, rows); // If has header, prints the header. if (!header.isEmpty()) { outputDivider(output, columnWidths, '=', '+'); for (int i = 0; i < columnWidths.length; i++) { output.printf("| %-" + columnWidths[i] + "s ", header.get(i)); } output.printf("|").println(); } // Prints a divider between header and first row if no divider is needed between rows. // Otherwise it's printed as part of the following row loop. char edgeChar = '+'; char lineChar = '='; if (!useRowDivider) { outputDivider(output, columnWidths, lineChar, edgeChar); } // Output each row. for (Row row : rows) { if (useRowDivider) { // The first divider uses a different set of line and edge char // As it's either the separate for the header of the table border (without header case) outputDivider(output, columnWidths, lineChar, edgeChar); edgeChar = '|'; lineChar = '-'; } // Print each cell. It has to loop until all lines from all cells are printed. boolean done = false; int line = 0; while (!done) { done = true; for (int i = 0; i < row.size(); i++) { Cell cell = row.get(i); cell.output(output, "| %-" + columnWidths[i] + "s ", line); done = done && (line + 1 >= cell.size()); } output.printf("|").println(); line++; } } outputDivider(output, columnWidths, '=', '+'); } /** * Prints a divider. * * @param output The {@link PrintStream} to output to * @param columnWidths Columns widths for each column * @param lineChar Character to use for printing the divider line * @param edgeChar Character to use for the left and right edge character */ private void outputDivider(PrintStream output, int[] columnWidths, char lineChar, char edgeChar) { output.print(edgeChar); for (int columnWidth : columnWidths) { output.print(Strings.repeat(Character.toString(lineChar), columnWidth + 2)); } // one for each divider output.print(Strings.repeat(Character.toString(lineChar), columnWidths.length - 1)); output.print(edgeChar); output.println(); } /** * Generates a record row. A record row can span across multiple lines on the screen. * * @param columns The set of columns to output. * @param collection Collection for collecting the generated {@link Row} object. * @return Returns true if the row spans multiple lines. */ private boolean generateRow(Object[] columns, Collection<? super Row> collection) { ImmutableList.Builder<Cell> builder = ImmutableList.builder(); boolean multiLines = false; Splitter splitter = Splitter.on(System.getProperty("line.separator")); for (Object field : columns) { String fieldString = field == null ? "" : field.toString(); Cell cell = new Cell(splitter.split(fieldString)); multiLines = multiLines || cell.size() > 1; builder.add(cell); } collection.add(new Row(builder.build())); return multiLines; } /** * Calculates the maximum columns' widths. * * @param header The table header. * @param rows All rows that is going to display. * @return An array of integers, with contains maximum width for each column. */ private int[] calculateColumnWidths(List<String> header, List<Row> rows) { int[] widths = new int[header.isEmpty() ? rows.get(0).size() : header.size()]; for (int i = 0; i < header.size(); i++) { widths[i] = header.get(i).length(); } // Find the maximum width for each column by consulting the width of each cell. for (Row row : rows) { for (int i = 0; i < row.size(); i++) { Cell cell = row.get(i); if (cell.getWidth() > widths[i]) { widths[i] = cell.getWidth(); } } } return widths; } /** * Represents data in one output table cell, which the content can spans multiple lines. */ private static final class Cell implements Iterable<String> { private final List<String> content; private final int width; Cell(Iterable<String> content) { this.content = ImmutableList.copyOf(content); int maxWidth = 0; for (String row : content) { if (row.length() > maxWidth) { maxWidth = row.length(); } } this.width = maxWidth; } /** * Returns the maximum width of this cell content. */ int getWidth() { return width; } /** * Writes a line to the given output with the given format. * * @param output The {@link PrintStream} to write to. * @param format The formatting string to use for printing. * @param line The line within this cell. */ void output(PrintStream output, String format, int line) { output.printf(format, line >= content.size() ? "" : content.get(line)); } /** * Returns the number of rows span for the content in this cell. */ int size() { return content.size(); } @Override public Iterator<String> iterator() { return content.iterator(); } } /** * Represents a Row content in the output Table. Each row contains multiple cells. */ private static final class Row implements Iterable<Cell> { private final List<Cell> cells; private Row(Iterable<Cell> cells) { this.cells = ImmutableList.copyOf(cells); } @Override public Iterator<Cell> iterator() { return null; } /** * Returns the {@link Cell} at the given column. */ Cell get(int i) { return cells.get(i); } /** * Returns the number of cells this row contains. */ int size() { return cells.size(); } } }