001package com.streamconverter.sloc.command;
002
003import com.streamconverter.command.AbstractStreamCommand;
004import com.streamconverter.sloc.ModuleSloc;
005import java.io.EOFException;
006import java.io.IOException;
007import java.io.InputStream;
008import java.io.ObjectInputStream;
009import java.io.OutputStream;
010import java.io.OutputStreamWriter;
011import java.io.PrintWriter;
012import java.nio.charset.StandardCharsets;
013import java.text.NumberFormat;
014import java.util.Locale;
015
016/**
017 * {@link ModuleSloc} オブジェクトのストリームを人が読みやすいレポート形式に整形するコマンド。
018 *
019 * <p>入力: {@link SlocAggregateCommand} が出力する {@link java.io.ObjectOutputStream} ストリーム (各モジュールの
020 * {@link ModuleSloc} + name="Total" の合計行)
021 *
022 * <p><b>セキュリティ前提:</b> 入力ストリームは同一 JVM 内の前段コマンド({@link SlocAggregateCommand})が
023 * 書き出したものであり、外部入力を直接受け取らない。予期しない型が含まれる場合は {@link ClassNotFoundException} / {@link
024 * ClassCastException} を {@link java.io.IOException} に変換して伝播する。
025 *
026 * <p>出力例:
027 *
028 * <pre>
029 * === SLOC Report ===
030 *
031 *   streamconverter-core       1,869
032 *   streamconverter-db           349
033 *   streamconverter-http          92
034 *   streamconverter-tools      1,038
035 *   streamconverter-web           79
036 *   ───────────────────────────────
037 *   Total                      3,427
038 * </pre>
039 */
040public class SlocReportFormatCommand extends AbstractStreamCommand {
041
042  private static final int MODULE_COL_WIDTH = 25;
043  private static final int LINES_COL_WIDTH = 6;
044
045  @Override
046  public void execute(InputStream input, OutputStream output) throws IOException {
047    String separator = "  " + "─".repeat(MODULE_COL_WIDTH + LINES_COL_WIDTH + 1);
048    NumberFormat numberFormat = NumberFormat.getInstance(Locale.US);
049
050    try (ObjectInputStream ois = new ObjectInputStream(input);
051        PrintWriter writer =
052            new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8))) {
053      writer.println("=== SLOC Report ===");
054      writer.println();
055      while (true) {
056        try {
057          ModuleSloc sloc = (ModuleSloc) ois.readObject();
058          if (ModuleSloc.TOTAL_NAME.equals(sloc.name())) {
059            writer.println(separator);
060          }
061          writer.printf(
062              "  %-" + MODULE_COL_WIDTH + "s %" + LINES_COL_WIDTH + "s%n",
063              sloc.name(),
064              numberFormat.format(sloc.lines()));
065        } catch (EOFException e) {
066          break;
067        } catch (ClassNotFoundException | ClassCastException e) {
068          throw new IOException("Unexpected object type in stream: expected ModuleSloc", e);
069        }
070      }
071    }
072  }
073}