001package com.streamconverter.command;
002
003import com.streamconverter.util.MeasuredInputStream;
004import com.streamconverter.util.MeasuredOutputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.io.OutputStream;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Abstract class for stream commands.
013 *
014 * <p>This class provides a template for executing commands on input streams and writing the results
015 * to output streams.
016 */
017public abstract class AbstractStreamCommand implements IStreamCommand {
018  private static final Logger log = LoggerFactory.getLogger(AbstractStreamCommand.class);
019
020  /**
021   * Default constructor.
022   *
023   * <p>Initializes the command with default settings.
024   */
025  public AbstractStreamCommand() {
026    super();
027  }
028
029  /**
030   * Executes the command on the provided input stream and writes the result to the output stream.
031   *
032   * <p>This method automatically logs execution details including: - Command name and execution
033   * time - Input/output data sizes - Exception details if execution fails - Performance metrics
034   *
035   * @param inputStream The input stream to read data from.
036   * @param outputStream The output stream to write data to.
037   * @throws IOException If an I/O error occurs during the execution of the command.
038   */
039  @Override
040  public final void execute(InputStream inputStream, OutputStream outputStream) throws IOException {
041    String commandName = this.getClass().getSimpleName();
042    long startTime = System.currentTimeMillis();
043    long startMemory = getUsedMemory();
044
045    // 実行開始ログ
046    if (log.isInfoEnabled()) {
047      log.info("Starting command execution: {}", commandName);
048    }
049    if (log.isDebugEnabled()) {
050      log.debug("Command details: {}", getCommandDetails());
051    }
052
053    // データサイズ測定用のストリームでラップ(try-with-resourcesでリソース管理)
054    long inputBytes = 0;
055    long outputBytes = 0;
056    try (MeasuredInputStream measuredInput = new MeasuredInputStream(inputStream);
057        MeasuredOutputStream measuredOutput = new MeasuredOutputStream(outputStream)) {
058
059      // 実際の処理実行
060      executeInternal(measuredInput, measuredOutput);
061
062      // データサイズ測定値を取得
063      inputBytes = measuredInput.getBytesRead();
064      outputBytes = measuredOutput.getBytesWritten();
065
066      // 成功時のログ出力
067      long duration = System.currentTimeMillis() - startTime;
068      long memoryUsed = getUsedMemory() - startMemory;
069
070      if (log.isInfoEnabled()) {
071        log.info(
072            "Command execution completed: {} ({}ms, input: {}bytes, output: {}bytes, memory: {}MB)",
073            commandName,
074            duration,
075            inputBytes,
076            outputBytes,
077            memoryUsed / 1024 / 1024);
078      }
079
080      // パフォーマンス警告
081      if (duration > 5000) { // 5秒以上
082        log.warn("Command {} took longer than expected: {}ms", commandName, duration);
083      }
084
085    } catch (Exception e) {
086      // 例外発生時のログ出力
087      long duration = System.currentTimeMillis() - startTime;
088      long memoryUsed = getUsedMemory() - startMemory;
089
090      if (log.isErrorEnabled()) {
091        log.error(
092            "Command execution failed: {} ({}ms, input: {}bytes, output: {}bytes, memory: {}MB) - {}",
093            commandName,
094            duration,
095            inputBytes,
096            outputBytes,
097            memoryUsed / 1024 / 1024,
098            e.getMessage(),
099            e);
100      }
101
102      // NullPointerExceptionをIOExceptionでラップ
103      if (e instanceof NullPointerException) {
104        throw new IOException("Invalid null stream parameter", e);
105      }
106
107      throw e;
108    }
109  }
110
111  /**
112   * Abstract method to be implemented by subclasses for executing the command.
113   *
114   * @param inputStream The input stream to read data from.
115   * @param outputStream The output stream to write data to.
116   * @throws IOException If an I/O error occurs during the execution of the command.
117   */
118  protected abstract void executeInternal(InputStream inputStream, OutputStream outputStream)
119      throws IOException;
120
121  /**
122   * コマンドの詳細情報を取得します。
123   *
124   * <p>サブクラスでオーバーライドして、コマンド固有の設定や状態を返すことができます。 この情報はデバッグログに出力され、問題の診断に役立ちます。
125   *
126   * @return コマンドの詳細情報
127   */
128  protected String getCommandDetails() {
129    return this.getClass().getSimpleName() + " (default implementation)";
130  }
131
132  /**
133   * 現在の使用メモリ量を取得します。
134   *
135   * @return 使用メモリ量(バイト)
136   */
137  private long getUsedMemory() {
138    Runtime runtime = Runtime.getRuntime();
139    return runtime.totalMemory() - runtime.freeMemory();
140  }
141}