code: package morfologik.tools;
  
  import java.io.BufferedInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  import com.beust.jcommander.Parameter;
  
  final class BinaryInput {
    private static final String ARG_ACCEPT_BOM = "--accept-bom";
    private static final String ARG_ACCEPT_CR = "--accept-cr";
    private static final String ARG_IGNORE_EMPTY  = "--ignore-empty";
  
    private static interface LineConsumer {
      byte[] process(byte[] buffer, int length);
    }
  
    @Parameter(
        names = BinaryInput.ARG_ACCEPT_BOM,
        arity = 0,
        description = "Accept leading BOM bytes (UTF-8).")  
    private boolean acceptBom;
  
    @Parameter(
        names = BinaryInput.ARG_ACCEPT_CR,
        arity = 0,
        description = "Accept CR bytes in input sequences (\\r).")  
    private boolean acceptCr;
  
    @Parameter(
        names = BinaryInput.ARG_IGNORE_EMPTY,
        arity = 0,
        description = "Ignore empty lines in the input.")  
    private boolean ignoreEmpty;
    
    BinaryInput() {}
  
    public BinaryInput(boolean acceptBom,
                       boolean acceptCr,
                       boolean ignoreEmpty) {
      this.acceptBom = acceptBom;
      this.acceptCr = acceptCr;
      this.ignoreEmpty = ignoreEmpty;
    }
    
    List<byte[]> readBinarySequences(Path input, byte separator) throws IOException {
      final List<byte[]> sequences = new ArrayList<>();
      try (InputStream is = new BufferedInputStream(Files.newInputStream(input))) {
        if (!acceptBom) {
          is.mark(4);
          if (is.read() == 0xef &&
              is.read() == 0xbb &&
              is.read() == 0xbf) {
            throw new ExitStatusException(ExitStatus.ERROR_OTHER,
                "The input starts with UTF-8 BOM bytes which is" +
                " most likely not what you want. Use header-less UTF-8 file or override with %s.", ARG_ACCEPT_BOM);
          }
          is.reset();
        }
  
        forAllLines(is, separator, new LineConsumer() {
          @Override
          public byte[] process(byte[] buffer, int length) {
            if (!acceptCr && hasCr(buffer, length)) {
              throw new ExitStatusException(ExitStatus.ERROR_OTHER,
                  "The input contains \\r byte (CR) which would be encoded as part of the"
                  + " automaton. If this is desired, use %s.", ARG_ACCEPT_CR);
            }
  
            if (length == 0) {
              if (!ignoreEmpty) {
                throw new ExitStatusException(ExitStatus.ERROR_OTHER,
                    "The input contains empty sequences."
                    + " If these can be ignored, use --ignore-empty.");
              }
            } else {
              sequences.add(Arrays.copyOf(buffer, length));
            }
  
            return buffer;
          }
        });
      }
      
      return sequences;
    }
  
    private static boolean hasCr(byte[] seq, int length) {
      for (int o = length; --o >= 0;) {
        if (seq[o] == '\r') {
          return true;
        }
      }
      return false;
    }
  
    /**
     * Read all byte-separated sequences.
     */
    private static int forAllLines(InputStream is, byte separator, LineConsumer lineConsumer) throws IOException {
      int lines = 0;
      byte[] buffer = new byte[0];
      int b, pos = 0;
      while ((b = is.read()) != -1) {
        if (b == separator) {
          buffer = lineConsumer.process(buffer, pos);
          pos = 0;
          lines++;
        } else {
          if (pos >= buffer.length) {
            buffer = java.util.Arrays.copyOf(buffer, buffer.length + Math.max(10, buffer.length / 10));
          }
          buffer[pos++] = (byte) b;
        }
      }
  
      if (pos > 0) {
        lineConsumer.process(buffer, pos);
        lines++;
      }
      return lines;
    }
  }
  