001package com.streamconverter.examples;
002
003import com.streamconverter.StreamConverter;
004import com.streamconverter.command.AbstractStreamCommand;
005import com.streamconverter.command.IStreamCommand;
006import com.streamconverter.command.impl.LineEndingNormalizeCommand;
007import java.io.ByteArrayInputStream;
008import java.io.ByteArrayOutputStream;
009import java.io.IOException;
010import java.io.InputStream;
011import java.io.OutputStream;
012import java.nio.charset.StandardCharsets;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * 例1: IStreamCommand と StreamConverter の仕組み
018 *
019 * <p>StreamConverter のパイプライン処理の基本を示す。
020 *
021 * <p><b>この例で学べること:</b>
022 *
023 * <ul>
024 *   <li>{@link IStreamCommand} はラムダ式でも、{@link AbstractStreamCommand} を継承したクラスでも実装できる
025 *   <li>{@link StreamConverter#create(IStreamCommand...)} に複数のコマンドを渡すと順番に接続されパイプラインになる
026 *   <li>各コマンドは別の仮想スレッドで並列実行される(ログのスレッド名で確認できる)
027 *   <li>前段コマンドの出力が後段コマンドの入力に自動的にバイト列として接続される
028 * </ul>
029 *
030 * <p><b>パイプライン構成(3段):</b>
031 *
032 * <pre>
033 * [コマンド1: ラムダ実装]     各行の前後空白をトリム
034 *          ↓
035 * [コマンド2: クラス実装]     各行を大文字に変換(AbstractStreamCommand 継承)
036 *          ↓
037 * [コマンド3: 組み込みコマンド] 行末コードを LF に統一(LineEndingNormalizeCommand)
038 * </pre>
039 */
040public class PipelineBasicsExample {
041
042  private static final Logger log = LoggerFactory.getLogger(PipelineBasicsExample.class);
043
044  /**
045   * @param args コマンドライン引数(未使用)
046   * @throws IOException I/O エラー
047   */
048  public static void main(String[] args) throws IOException {
049    log.info("=== 例1: IStreamCommand と StreamConverter の仕組み ===");
050
051    String input = "  hello world  \r\n" + "  stream converter  \r\n" + "  pipeline demo  \r\n";
052
053    log.info("入力データ(各行に前後スペース、行末 CRLF):\n{}", input);
054
055    // --- コマンド1: ラムダ実装 ---
056    // IStreamCommand は @FunctionalInterface なのでラムダで実装できる。
057    // ただしラムダはクラス名を持たないため、ログでは "IStreamCommand" と表示される。
058    IStreamCommand trimCommand =
059        (in, out) -> {
060          String text = new String(in.readAllBytes(), StandardCharsets.UTF_8);
061          StringBuilder sb = new StringBuilder();
062          for (String line : text.split("\n")) {
063            sb.append(line.stripTrailing().stripLeading()).append("\n");
064          }
065          out.write(sb.toString().getBytes(StandardCharsets.UTF_8));
066        };
067
068    // --- コマンド2: AbstractStreamCommand 継承クラス ---
069    // AbstractStreamCommand を継承することで:
070    //   - クラス名がログに表示される(UpperCaseCommand)
071    //   - protected final Logger log が自動的に用意される
072    IStreamCommand upperCaseCommand = new UpperCaseCommand();
073
074    // --- コマンド3: 組み込みコマンド ---
075    // LineEndingNormalizeCommand は AbstractStreamCommand を継承した既製コマンド。
076    // 行末コードを UNIX (LF) / WINDOWS (CRLF) / CLASSIC_MAC (CR) に統一する。
077    IStreamCommand normalizeCommand =
078        new LineEndingNormalizeCommand(LineEndingNormalizeCommand.LineEndingType.UNIX);
079
080    // --- パイプライン実行 ---
081    // StreamConverter.create() に3つのコマンドを渡すと、
082    // 内部で各コマンドを仮想スレッドで並列起動し、AbortablePipedStream で接続する。
083    StreamConverter converter =
084        StreamConverter.create(trimCommand, upperCaseCommand, normalizeCommand);
085
086    ByteArrayOutputStream output = new ByteArrayOutputStream();
087    try (InputStream inputStream =
088        new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) {
089      converter.run(inputStream, output);
090    }
091
092    String expected = "HELLO WORLD\n" + "STREAM CONVERTER\n" + "PIPELINE DEMO\n";
093
094    String result = output.toString(StandardCharsets.UTF_8);
095    log.info("期待値(空白トリム→大文字変換→行末 LF 統一):\n{}", expected);
096    log.info("出力データ:\n{}", result);
097  }
098
099  /**
100   * AbstractStreamCommand を継承したクラス実装の例。
101   *
102   * <p>{@link AbstractStreamCommand} を継承することで:
103   *
104   * <ul>
105   *   <li>クラス名(UpperCaseCommand)がログのコマンド名として自動的に使われる
106   *   <li>{@code protected final Logger log} が利用可能になる
107   *   <li>{@code execute()} メソッドを実装するだけでよい
108   * </ul>
109   */
110  static class UpperCaseCommand extends AbstractStreamCommand {
111
112    @Override
113    public void execute(InputStream inputStream, OutputStream outputStream) throws IOException {
114      String text = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
115      log.debug("UpperCaseCommand: {} 文字を大文字に変換", text.length());
116      outputStream.write(text.toUpperCase().getBytes(StandardCharsets.UTF_8));
117    }
118  }
119}