content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.cassandra.tools.nodetool.formatter;
  
  import java.io.ByteArrayOutputStream;
  import java.io.PrintStream;
  import java.io.UncheckedIOException;
  import java.io.UnsupportedEncodingException;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  import java.util.Objects;
  import java.util.stream.Collectors;
  import javax.annotation.Nonnull;
  
  import com.google.common.collect.Iterables;
  
  /**
   * Build and print table.
   *
   * usage:
   * <pre>
   * {@code
   * TableBuilder table = new TableBuilder();
   * for (String[] row : data)
   * {
   *     table.add(row);
   * }
   * table.print(probe.outStream());
   * }
   * </pre>
   */
  public class TableBuilder
  {
      // column delimiter
      private final String columnDelimiter;
  
      private int[] maximumColumnWidth;
      private final List<String[]> rows = new ArrayList<>();
  
      public TableBuilder()
      {
          this(' ');
      }
  
      public TableBuilder(char columnDelimiter)
      {
          this(String.valueOf(columnDelimiter));
      }
  
      public TableBuilder(String columnDelimiter)
      {
          this.columnDelimiter = columnDelimiter;
      }
  
      private TableBuilder(TableBuilder base, int[] maximumColumnWidth)
      {
          this(base.columnDelimiter);
          this.maximumColumnWidth = maximumColumnWidth;
          this.rows.addAll(base.rows);
      }
  
      public static String toString(String columnDelimiter, Iterable<List<String>> rows)
      {
          TableBuilder builder = new TableBuilder(columnDelimiter);
          for (List<String> row : rows)
              builder.add(row);
          return builder.toString();
      }
  
      public static String toString(String columnDelimiter, List<String> columns, Iterable<List<String>> rows)
      {
          return toString(columnDelimiter, Iterables.concat(Collections.singleton(columns), rows));
      }
  
      public static String toStringPiped(List<String> columns, Iterable<List<String>> rows)
      {
          return toString(" | ", columns, rows);
      }
  
      public void add(@Nonnull List<String> row)
      {
          add(row.toArray(new String[0]));
      }
  
      public void add(@Nonnull String... row)
      {
          Objects.requireNonNull(row);
  
          if (rows.isEmpty())
          {
              maximumColumnWidth = new int[row.length];
          }
  
          // expand max column widths if given row has more columns
          if (row.length > maximumColumnWidth.length)
          {
              int[] tmp = new int[row.length];
              System.arraycopy(maximumColumnWidth, 0, tmp, 0, maximumColumnWidth.length);
              maximumColumnWidth = tmp;
          }
          // calculate maximum column width
          int i = 0;
          for (String col : row)
          {
              maximumColumnWidth[i] = Math.max(maximumColumnWidth[i], col != null ? col.length() : 1);
              i++;
          }
          rows.add(row);
      }
  
      public void printTo(PrintStream out)
      {
          if (rows.isEmpty())
              return;
  
          for (String[] row : rows)
          {
              for (int i = 0; i < maximumColumnWidth.length; i++)
              {
                  String col = i < row.length ? row[i] : "";
                  out.print(String.format("%-" + maximumColumnWidth[i] + 's', col != null ? col : ""));
                  if (i < maximumColumnWidth.length - 1)
                      out.print(columnDelimiter);
              }
              out.println();
          }
      }
  
      @Override
      public String toString()
      {
          ByteArrayOutputStream os = new ByteArrayOutputStream();
          try (PrintStream stream = new PrintStream(os, true, StandardCharsets.UTF_8.displayName()))
          {
              printTo(stream);
              stream.flush();
              return os.toString(StandardCharsets.UTF_8.displayName());
          }
          catch (UnsupportedEncodingException e)
          {
              throw new UncheckedIOException(e);
          }
      }
  
      /**
       * Share max offsets across multiple TableBuilders
       */
      public static class SharedTable {
          private List<TableBuilder> tables = new ArrayList<>();
          private final String columnDelimiter;
  
          public SharedTable()
          {
              this(' ');
          }
  
          public SharedTable(char columnDelimiter)
          {
              this(String.valueOf(columnDelimiter));
          }
  
          public SharedTable(String columnDelimiter)
          {
              this.columnDelimiter = columnDelimiter;
          }
  
          public TableBuilder next()
          {
              TableBuilder next = new TableBuilder(columnDelimiter);
              tables.add(next);
              return next;
          }
  
          public List<TableBuilder> complete()
          {
              if (tables.size() == 0)
                  return Collections.emptyList();
  
              final int columns = tables.stream()
                                        .max(Comparator.comparing(tb -> tb.maximumColumnWidth.length))
                                        .get().maximumColumnWidth.length;
  
              final int[] maximumColumnWidth = new int[columns];
              for (TableBuilder tb : tables)
              {
                  for (int i = 0; i < tb.maximumColumnWidth.length; i++)
                  {
                      maximumColumnWidth[i] = Math.max(tb.maximumColumnWidth[i], maximumColumnWidth[i]);
                  }
              }
              return tables.stream()
                           .map(tb -> new TableBuilder(tb, maximumColumnWidth))
                           .collect(Collectors.toList());
          }
      }
  }
  