package stexfires.io.spec;

import stexfires.io.RecordFile;
import stexfires.util.LineSeparator;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.file.Path;
import java.util.Objects;

/**
 * @author Mathias Kalb
 * @since 0.1
 */
public abstract class AbstractRecordFileSpec implements RecordFileSpec {

    private final Charset charset;
    private final CodingErrorAction codingErrorAction;
    private final LineSeparator lineSeparator;

    protected AbstractRecordFileSpec(Charset charset, CodingErrorAction codingErrorAction,
                                     LineSeparator lineSeparator) {
        Objects.requireNonNull(charset);
        Objects.requireNonNull(codingErrorAction);
        Objects.requireNonNull(lineSeparator);
        this.charset = charset;
        this.codingErrorAction = codingErrorAction;
        this.lineSeparator = lineSeparator;
    }

    public abstract RecordFile<?> file(Path path);

    @Override
    public final Charset getCharset() {
        return charset;
    }

    @Override
    public final CodingErrorAction getCodingErrorAction() {
        return codingErrorAction;
    }

    @Override
    public final LineSeparator getLineSeparator() {
        return lineSeparator;
    }

    protected final BufferedReader newBufferedReader(InputStream inputStream) {
        return new BufferedReader(new InputStreamReader(inputStream, newCharsetDecoder()));
    }

    protected final BufferedReader newBufferedReader(InputStream inputStream, String decoderReplacementValue) {
        return new BufferedReader(new InputStreamReader(inputStream, newCharsetDecoder(decoderReplacementValue)));
    }

    protected final BufferedWriter newBufferedWriter(OutputStream outputStream, byte... encoderReplacementValue) {
        return new BufferedWriter(new OutputStreamWriter(outputStream, newCharsetEncoder(encoderReplacementValue)));
    }

    protected final CharsetDecoder newCharsetDecoder() {
        return newCharsetDecoder(null);
    }

    protected final CharsetDecoder newCharsetDecoder(String decoderReplacementValue) {
        CharsetDecoder charsetDecoder = charset.newDecoder().onMalformedInput(codingErrorAction);
        if ((codingErrorAction == CodingErrorAction.REPLACE) && (decoderReplacementValue != null)) {
            charsetDecoder.replaceWith(decoderReplacementValue);
        }
        return charsetDecoder;
    }

    protected final CharsetEncoder newCharsetEncoder(byte... encoderReplacementValue) {
        CharsetEncoder charsetEncoder = charset.newEncoder().onUnmappableCharacter(codingErrorAction);
        if ((codingErrorAction == CodingErrorAction.REPLACE) && (encoderReplacementValue != null)) {
            charsetEncoder.replaceWith(encoderReplacementValue);
        }
        return charsetEncoder;
    }

}

@@ -21,16 +21,59 @@
 .spec;%0A%0A
+import org.jetbrains.annotations.Nullable;%0A
 import s
@@ -738,24 +738,114 @@
 rrorAction;%0A
+    private final String decoderReplacement;%0A    private final byte%5B%5D encoderReplacement;%0A
     private 
@@ -1051,221 +1051,967 @@
     
-Objects.requireNonNull(charset);%0A        Objects.requireNonNull(codingErrorAction);%0A        Objects.requireNonNull(lineSeparator);%0A        this.charset = charset;%0A        this.codingErrorAction = codingErrorAction
+this(charset, codingErrorAction, null, null, lineSeparator);%0A    %7D%0A%0A    protected AbstractRecordFileSpec(Charset charset, CodingErrorAction codingErrorAction,%0A                                     @Nullable String decoderReplacement, @Nullable String encoderReplacement,%0A                                     LineSeparator lineSeparator) %7B%0A        Objects.requireNonNull(charset);%0A        Objects.requireNonNull(codingErrorAction);%0A        Objects.requireNonNull(lineSeparator);%0A        this.charset = charset;%0A        this.codingErrorAction = codingErrorAction;%0A        this.decoderReplacement = (codingErrorAction == CodingErrorAction.REPLACE%0A                && decoderReplacement != null && !decoderReplacement.isEmpty()) ? decoderReplacement : null;%0A        this.encoderReplacement = (codingErrorAction == CodingErrorAction.REPLACE%0A                && encoderReplacement != null && !encoderReplacement.isEmpty()) ? encoderReplacement.getBytes(charset) : null
 ;%0A  
@@ -2597,242 +2597,8 @@
  %7D%0A%0A
-    protected final BufferedReader newBufferedReader(InputStream inputStream, String decoderReplacementValue) %7B%0A        return new BufferedReader(new InputStreamReader(inputStream, newCharsetDecoder(decoderReplacementValue)));%0A    %7D%0A%0A
     
@@ -2675,41 +2675,8 @@
 ream
-, byte... encoderReplacementValue
 ) %7B%0A
@@ -2764,39 +2764,16 @@
 Encoder(
-encoderReplacementValue
 )));%0A   
@@ -2837,142 +2837,8 @@
 ) %7B%0A
-        return newCharsetDecoder(null);%0A    %7D%0A%0A    protected final CharsetDecoder newCharsetDecoder(String decoderReplacementValue) %7B%0A
     
@@ -2945,61 +2945,8 @@
   if
- ((codingErrorAction == CodingErrorAction.REPLACE) &&
  (de
@@ -2953,37 +2953,32 @@
 coderReplacement
-Value
  != null)) %7B%0A   
@@ -2962,33 +2962,32 @@
 acement != null)
-)
  %7B%0A            c
@@ -3022,37 +3022,32 @@
 coderReplacement
-Value
 );%0A        %7D%0A   
@@ -3138,39 +3138,8 @@
 der(
-byte... encoderReplacementValue
 ) %7B%0A
@@ -3255,61 +3255,8 @@
   if
- ((codingErrorAction == CodingErrorAction.REPLACE) &&
  (en
@@ -3271,21 +3271,16 @@
 lacement
-Value
  != null
@@ -3280,17 +3280,16 @@
 != null)
-)
  %7B%0A     
@@ -3344,13 +3344,8 @@
 ment
-Value
 );%0A 

Add optional replacement parameter