All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.ColumnFormatter Maven / Gradle / Ivy

/*
 * Copyright 2009-2019 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2009-2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.util;



import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Date;

import static com.unboundid.util.UtilityMessages.*;



/**
 * This class provides a utility for formatting output in multiple columns.
 * Each column will have a defined width and alignment.  It can alternately
 * generate output as tab-delimited text or comma-separated values (CSV).
 */
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class ColumnFormatter
       implements Serializable
{
  /**
   * The symbols to use for special characters that might be encountered when
   * using a decimal formatter.
   */
  private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS =
       new DecimalFormatSymbols();
  static
  {
    DECIMAL_FORMAT_SYMBOLS.setInfinity("inf");
    DECIMAL_FORMAT_SYMBOLS.setNaN("NaN");
  }



  /**
   * The default output format to use.
   */
  private static final OutputFormat DEFAULT_OUTPUT_FORMAT =
       OutputFormat.COLUMNS;



  /**
   * The default spacer to use between columns.
   */
  private static final String DEFAULT_SPACER = " ";



  /**
   * The default date format string that will be used for timestamps.
   */
  private static final String DEFAULT_TIMESTAMP_FORMAT = "HH:mm:ss";



  /**
   * The serial version UID for this serializable class.
   */
  private static final long serialVersionUID = -2524398424293401200L;



  // Indicates whether to insert a timestamp before the first column.
  private final boolean includeTimestamp;

  // The column to use for the timestamp.
  private final FormattableColumn timestampColumn;

  // The columns to be formatted.
  private final FormattableColumn[] columns;

  // The output format to use.
  private final OutputFormat outputFormat;

  // The string to insert between columns.
  private final String spacer;

  // The format string to use for the timestamp.
  private final String timestampFormat;

  // The thread-local formatter to use for floating-point values.
  private final transient ThreadLocal decimalFormatter;

  // The thread-local formatter to use when formatting timestamps.
  private final transient ThreadLocal timestampFormatter;



  /**
   * Creates a column formatter that will format the provided columns with the
   * default settings.
   *
   * @param  columns  The columns to be formatted.  At least one column must be
   *                  provided.
   */
  public ColumnFormatter(final FormattableColumn... columns)
  {
    this(false, null, null, null, columns);
  }



  /**
   * Creates a column formatter that will format the provided columns.
   *
   * @param  includeTimestamp  Indicates whether to insert a timestamp before
   *                           the first column when generating data lines
   * @param  timestampFormat   The format string to use for the timestamp.  It
   *                           may be {@code null} if no timestamp should be
   *                           included or the default format should be used.
   *                           If a format is provided, then it should be one
   *                           that will always generate timestamps with a
   *                           constant width.
   * @param  outputFormat      The output format to use.
   * @param  spacer            The spacer to use between columns.  It may be
   *                           {@code null} if the default spacer should be
   *                           used.  This will only apply for an output format
   *                           of {@code COLUMNS}.
   * @param  columns           The columns to be formatted.  At least one
   *                           column must be provided.
   */
  public ColumnFormatter(final boolean includeTimestamp,
                         final String timestampFormat,
                         final OutputFormat outputFormat, final String spacer,
                         final FormattableColumn... columns)
  {
    Validator.ensureNotNull(columns);
    Validator.ensureTrue(columns.length > 0);

    this.includeTimestamp = includeTimestamp;
    this.columns          = columns;

    decimalFormatter   = new ThreadLocal<>();
    timestampFormatter = new ThreadLocal<>();

    if (timestampFormat == null)
    {
      this.timestampFormat = DEFAULT_TIMESTAMP_FORMAT;
    }
    else
    {
      this.timestampFormat = timestampFormat;
    }

    if (outputFormat == null)
    {
      this.outputFormat = DEFAULT_OUTPUT_FORMAT;
    }
    else
    {
      this.outputFormat = outputFormat;
    }

    if (spacer == null)
    {
      this.spacer = DEFAULT_SPACER;
    }
    else
    {
      this.spacer = spacer;
    }

    if (includeTimestamp)
    {
      final SimpleDateFormat dateFormat =
           new SimpleDateFormat(this.timestampFormat);
      final String timestamp = dateFormat.format(new Date());
      final String label = INFO_COLUMN_LABEL_TIMESTAMP.get();
      final int width = Math.max(label.length(), timestamp.length());

      timestampFormatter.set(dateFormat);
      timestampColumn =
           new FormattableColumn(width, HorizontalAlignment.LEFT, label);
    }
    else
    {
      timestampColumn = null;
    }
  }



  /**
   * Indicates whether timestamps will be included in the output.
   *
   * @return  {@code true} if timestamps should be included, or {@code false}
   *          if not.
   */
  public boolean includeTimestamps()
  {
    return includeTimestamp;
  }



  /**
   * Retrieves the format string that will be used for generating timestamps.
   *
   * @return  The format string that will be used for generating timestamps.
   */
  public String getTimestampFormatString()
  {
    return timestampFormat;
  }



  /**
   * Retrieves the output format that will be used.
   *
   * @return  The output format for this formatter.
   */
  public OutputFormat getOutputFormat()
  {
    return outputFormat;
  }



  /**
   * Retrieves the spacer that will be used between columns.
   *
   * @return  The spacer that will be used between columns.
   */
  public String getSpacer()
  {
    return spacer;
  }



  /**
   * Retrieves the set of columns for this formatter.
   *
   * @return  The set of columns for this formatter.
   */
  public FormattableColumn[] getColumns()
  {
    final FormattableColumn[] copy = new FormattableColumn[columns.length];
    System.arraycopy(columns, 0, copy, 0, columns.length);
    return copy;
  }



  /**
   * Obtains the lines that should comprise the column headers.
   *
   * @param  includeDashes  Indicates whether to include a row of dashes below
   *                        the headers if appropriate for the output format.
   *
   * @return  The lines that should comprise the column headers.
   */
  public String[] getHeaderLines(final boolean includeDashes)
  {
    if (outputFormat == OutputFormat.COLUMNS)
    {
      int maxColumns = 1;
      final String[][] headerLines = new String[columns.length][];
      for (int i=0; i < columns.length; i++)
      {
        headerLines[i] = columns[i].getLabelLines();
        maxColumns = Math.max(maxColumns, headerLines[i].length);
      }

      final StringBuilder[] buffers = new StringBuilder[maxColumns];
      for (int i=0; i < maxColumns; i++)
      {
        final StringBuilder buffer = new StringBuilder();
        buffers[i] = buffer;
        if (includeTimestamp)
        {
          if (i == (maxColumns - 1))
          {
            timestampColumn.format(buffer, timestampColumn.getSingleLabelLine(),
                 outputFormat);
          }
          else
          {
            timestampColumn.format(buffer, "", outputFormat);
          }
        }

        for (int j=0; j < columns.length; j++)
        {
          if (includeTimestamp || (j > 0))
          {
            buffer.append(spacer);
          }

          final int rowNumber = i + headerLines[j].length - maxColumns;
          if (rowNumber < 0)
          {
            columns[j].format(buffer, "", outputFormat);
          }
          else
          {
            columns[j].format(buffer, headerLines[j][rowNumber], outputFormat);
          }
        }
      }

      final String[] returnArray;
      if (includeDashes)
      {
        returnArray = new String[maxColumns+1];
      }
      else
      {
        returnArray = new String[maxColumns];
      }

      for (int i=0; i < maxColumns; i++)
      {
        returnArray[i] = buffers[i].toString();
      }

      if (includeDashes)
      {
        final StringBuilder buffer = new StringBuilder();
        if (timestampColumn != null)
        {
          for (int i=0; i < timestampColumn.getWidth(); i++)
          {
            buffer.append('-');
          }
        }

        for (int i=0; i < columns.length; i++)
        {
          if (includeTimestamp || (i > 0))
          {
            buffer.append(spacer);
          }

          for (int j=0; j < columns[i].getWidth(); j++)
          {
            buffer.append('-');
          }
        }

        returnArray[returnArray.length - 1] = buffer.toString();
      }

      return returnArray;
    }
    else
    {
      final StringBuilder buffer = new StringBuilder();
      if (timestampColumn != null)
      {
        timestampColumn.format(buffer, timestampColumn.getSingleLabelLine(),
             outputFormat);
      }

      for (int i=0; i < columns.length; i++)
      {
        if (includeTimestamp || (i > 0))
        {
          if (outputFormat == OutputFormat.TAB_DELIMITED_TEXT)
          {
            buffer.append('\t');
          }
          else if (outputFormat == OutputFormat.CSV)
          {
            buffer.append(',');
          }
        }

        final FormattableColumn c = columns[i];
        c.format(buffer, c.getSingleLabelLine(), outputFormat);
      }

      return new String[] { buffer.toString() };
    }
  }



  /**
   * Formats a row of data.  The provided data must correspond to the columns
   * used when creating this formatter.
   *
   * @param  columnData  The elements to include in each row of the data.
   *
   * @return  A string containing the formatted row.
   */
  public String formatRow(final Object... columnData)
  {
    final StringBuilder buffer = new StringBuilder();

    if (includeTimestamp)
    {
      SimpleDateFormat dateFormat = timestampFormatter.get();
      if (dateFormat == null)
      {
        dateFormat = new SimpleDateFormat(timestampFormat);
        timestampFormatter.set(dateFormat);
      }

      timestampColumn.format(buffer, dateFormat.format(new Date()),
           outputFormat);
    }

    for (int i=0; i < columns.length; i++)
    {
      if (includeTimestamp || (i > 0))
      {
        switch (outputFormat)
        {
          case TAB_DELIMITED_TEXT:
            buffer.append('\t');
            break;
          case CSV:
            buffer.append(',');
            break;
          case COLUMNS:
            buffer.append(spacer);
            break;
        }
      }

      if (i >= columnData.length)
      {
        columns[i].format(buffer, "", outputFormat);
      }
      else
      {
        columns[i].format(buffer, toString(columnData[i]), outputFormat);
      }
    }

    return buffer.toString();
  }



  /**
   * Retrieves a string representation of the provided object.  If the object
   * is {@code null}, then the empty string will be returned.  If the object is
   * a {@code Float} or {@code Double}, then it will be formatted using a
   * DecimalFormat with a format string of "0.000".  Otherwise, the
   * {@code String.valueOf} method will be used to obtain the string
   * representation.
   *
   * @param  o  The object for which to retrieve the string representation.
   *
   * @return  A string representation of the provided object.
   */
  private String toString(final Object o)
  {
    if (o == null)
    {
      return "";
    }

    if ((o instanceof Float) || (o instanceof Double))
    {
      DecimalFormat f = decimalFormatter.get();
      if (f == null)
      {
        f = new DecimalFormat("0.000", DECIMAL_FORMAT_SYMBOLS);
        decimalFormatter.set(f);
      }

      final double d;
      if (o instanceof Float)
      {
        d = ((Float) o).doubleValue();
      }
      else
      {
        d = ((Double) o);
      }

      return f.format(d);
    }

    return String.valueOf(o);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy