Package co.cask.cdap.shell.util

Source Code of co.cask.cdap.shell.util.AsciiTable$Row

/*
* 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();
    }
  }
}
TOP

Related Classes of co.cask.cdap.shell.util.AsciiTable$Row

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.