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}