001package com.streamconverter.command.impl;
002
003import com.streamconverter.command.AbstractStreamCommand;
004import java.io.IOException;
005import java.io.InputStream;
006import java.io.InputStreamReader;
007import java.io.OutputStream;
008import java.io.OutputStreamWriter;
009import java.io.Reader;
010import java.io.Writer;
011import java.nio.charset.StandardCharsets;
012import java.util.Objects;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Line ending normalization command.
018 *
019 * <p>This command normalizes line endings in text streams to a specified format. It can convert
020 * between different line ending styles (Unix LF, Windows CRLF, classic Mac CR) or preserve the
021 * input format.
022 *
023 * <p>The command processes data efficiently while preserving the exact structure of the input,
024 * including whether the input ends with a line terminator.
025 */
026public class LineEndingNormalizeCommand extends AbstractStreamCommand {
027  private static final Logger logger = LoggerFactory.getLogger(LineEndingNormalizeCommand.class);
028
029  /** Supported line ending types for normalization. */
030  public enum LineEndingType {
031    /** Unix/Linux style line endings (LF only) */
032    UNIX("\n"),
033
034    /** Windows style line endings (CRLF) */
035    WINDOWS("\r\n"),
036
037    /** Classic Mac style line endings (CR only) - rarely used in modern systems */
038    MAC_CLASSIC("\r"),
039
040    /** Preserve input line endings as-is */
041    PRESERVE_INPUT(null),
042
043    /** Use system default line separator */
044    SYSTEM_DEFAULT(System.lineSeparator());
045
046    private final String separator;
047
048    LineEndingType(String separator) {
049      this.separator = separator;
050    }
051
052    /**
053     * Gets the line separator string for this type.
054     *
055     * @return the line separator string, or null for PRESERVE_INPUT
056     */
057    public String getSeparator() {
058      return separator;
059    }
060  }
061
062  private final LineEndingType targetType;
063
064  /**
065   * Creates a new line ending normalization command.
066   *
067   * @param targetType the target line ending type to convert to
068   * @throws NullPointerException if targetType is null
069   */
070  public LineEndingNormalizeCommand(LineEndingType targetType) {
071    this.targetType = Objects.requireNonNull(targetType, "Target type cannot be null");
072  }
073
074  @Override
075  public void execute(InputStream inputStream, OutputStream outputStream) throws IOException {
076    logger.debug("Starting line ending normalization to: {}", targetType);
077
078    // Stream processing for memory efficiency
079    try (Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
080        Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
081
082      if (targetType == LineEndingType.PRESERVE_INPUT) {
083        // For PRESERVE_INPUT, copy directly without modification
084        char[] buffer = new char[8192];
085        int bytesRead;
086        while ((bytesRead = reader.read(buffer)) != -1) {
087          writer.write(buffer, 0, bytesRead);
088        }
089      } else {
090        // Process character by character for line ending normalization
091        String targetSeparator = targetType.getSeparator();
092        int current;
093
094        while ((current = reader.read()) != -1) {
095          if (current == '\r') {
096            // Handle CR - could be CR, CRLF, or standalone CR
097            int next = reader.read();
098            if (next == '\n') {
099              // CRLF -> convert to target
100              writer.write(targetSeparator);
101            } else {
102              // Standalone CR -> convert to target
103              writer.write(targetSeparator);
104              // Write the next character that wasn't part of line ending
105              if (next != -1) {
106                writer.write(next);
107              }
108            }
109          } else if (current == '\n') {
110            // LF -> convert to target (handles Unix style)
111            writer.write(targetSeparator);
112          } else {
113            // Regular character
114            writer.write(current);
115          }
116        }
117      }
118
119      writer.flush();
120    }
121
122    logger.debug("Line ending normalization completed successfully");
123  }
124}