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}