001package com.streamconverter.command;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.io.OutputStream;
006import org.slf4j.Logger;
007
008/**
009 * Unified interface for stream commands.
010 *
011 * <p>This interface defines methods for executing commands on input streams and writing results to
012 * output streams.
013 *
014 * <p>This is a functional interface and can be implemented using lambda expressions or method
015 * references for simple stream processing operations.
016 *
017 * <p>Usage examples:
018 *
019 * <pre>{@code
020 * // Simple copy operation (IOException is propagated from execute method signature)
021 * IStreamCommand copyCommand = (in, out) -> in.transferTo(out);
022 *
023 * // Buffered stream processing with custom logic
024 * IStreamCommand bufferCommand = (in, out) -> {
025 *     byte[] buffer = new byte[8192];
026 *     int len;
027 *     while ((len = in.read(buffer)) != -1) {
028 *         out.write(buffer, 0, len);
029 *     }
030 * };
031 *
032 * // Using in StreamConverter pipeline
033 * StreamConverter converter = StreamConverter.create(copyCommand);
034 * converter.run(inputStream, outputStream);
035 * }</pre>
036 *
037 * <p><b>MDC への値の伝搬について:</b> {@code execute()} 内で {@code MDC.put()} を直接呼び出しても、
038 * 他コマンドのスレッドには伝搬されません。ストリームから抽出した値を全コマンドのログに反映させるには {@link
039 * com.streamconverter.command.rule.MdcPropagatingRule} を使用してください。子スレッドへの MDC 自動継承が必要な場合は {@link
040 * com.streamconverter.logging.MDCInitializer} を参照してください。
041 */
042@FunctionalInterface
043public interface IStreamCommand {
044
045  /**
046   * Executes the command on the provided input stream and writes the result to the output stream.
047   *
048   * @param inputStream The input stream to read data from.
049   * @param outputStream The output stream to write data to.
050   * @throws IOException If an I/O error occurs during the execution of the command.
051   */
052  void execute(InputStream inputStream, OutputStream outputStream) throws IOException;
053
054  /**
055   * Returns a best-effort human-readable name for this command.
056   *
057   * <p>Synthetic and anonymous implementations fall back to {@code IStreamCommand} so diagnostics
058   * remain stable even when the concrete class name is not meaningful.
059   *
060   * @return command label suitable for logging and error messages
061   */
062  default String commandName() {
063    Class<?> implClass = this.getClass();
064    if (implClass.isSynthetic()) {
065      return "IStreamCommand";
066    }
067    String simpleName = implClass.getSimpleName();
068    if (!simpleName.isEmpty()) {
069      return simpleName;
070    }
071    Class<?> superClass = implClass.getSuperclass();
072    if (superClass != null && !Object.class.equals(superClass)) {
073      String superName = superClass.getSimpleName();
074      if (!superName.isEmpty()) {
075        return superName;
076      }
077    }
078    return "IStreamCommand";
079  }
080
081  /**
082   * Wraps this command with logging. The returned command logs start, completion, and failure using
083   * the provided logger.
084   *
085   * <p>The command name is derived by {@link #commandName()}. When a caller wants a different label
086   * in logs, prefer {@link #withLogging(Logger, String)}.
087   *
088   * @param logger the logger to write messages to
089   * @return a new {@link IStreamCommand} that delegates to this command and emits log records
090   */
091  default IStreamCommand withLogging(Logger logger) {
092    return withLogging(logger, commandName());
093  }
094
095  /**
096   * Wraps this command with logging using an explicit command name.
097   *
098   * <p>This overload is recommended when the command is implemented as a lambda expression or
099   * method reference, where the underlying class name might be synthetic and not meaningful in
100   * logs.
101   *
102   * @param logger the logger to write messages to
103   * @param commandName the human-readable name of this command to appear in log messages
104   * @return a new {@link IStreamCommand} that delegates to this command and emits log records
105   */
106  default IStreamCommand withLogging(Logger logger, String commandName) {
107    return (in, out) -> {
108      logger.info("Starting command: {}", commandName);
109      long start = System.currentTimeMillis();
110      try {
111        this.execute(in, out);
112        logger.info(
113            "Completed command: {} ({}ms)", commandName, System.currentTimeMillis() - start);
114      } catch (IOException e) {
115        logger.error("Failed command: {} - {}", commandName, e.getMessage(), e);
116        throw e;
117      } catch (RuntimeException e) {
118        logger.error("Failed command: {} - {}", commandName, e.getMessage(), e);
119        throw e;
120      } catch (Error e) {
121        logger.error("Fatal error in command: {} - {}", commandName, e.getMessage(), e);
122        throw e;
123      }
124    };
125  }
126}