AutoLFOutputStream.java

  1. /*
  2.  * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
  3.  * Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.util.io;

  12. import java.io.IOException;
  13. import java.io.OutputStream;

  14. import org.eclipse.jgit.diff.RawText;

  15. /**
  16.  * An OutputStream that reduces CRLF to LF.
  17.  * <p>
  18.  * Existing single CR are not changed to LF, but retained as is.
  19.  * </p>
  20.  * <p>
  21.  * A binary check on the first 8000 bytes is performed and in case of binary
  22.  * files, canonicalization is turned off (for the complete file). If the binary
  23.  * check determines that the input is not binary but text with CR/LF,
  24.  * canonicalization is also turned off.
  25.  * </p>
  26.  *
  27.  * @since 4.3
  28.  */
  29. public class AutoLFOutputStream extends OutputStream {

  30.     static final int BUFFER_SIZE = 8000;

  31.     private final OutputStream out;

  32.     private int buf = -1;

  33.     private byte[] binbuf = new byte[BUFFER_SIZE];

  34.     private byte[] onebytebuf = new byte[1];

  35.     private int binbufcnt = 0;

  36.     private boolean detectBinary;

  37.     private boolean isBinary;

  38.     /**
  39.      * Constructor for AutoLFOutputStream.
  40.      *
  41.      * @param out
  42.      *            an {@link java.io.OutputStream} object.
  43.      */
  44.     public AutoLFOutputStream(OutputStream out) {
  45.         this(out, true);
  46.     }

  47.     /**
  48.      * Constructor for AutoLFOutputStream.
  49.      *
  50.      * @param out
  51.      *            an {@link java.io.OutputStream} object.
  52.      * @param detectBinary
  53.      *            whether binaries should be detected
  54.      */
  55.     public AutoLFOutputStream(OutputStream out, boolean detectBinary) {
  56.         this.out = out;
  57.         this.detectBinary = detectBinary;
  58.     }

  59.     /** {@inheritDoc} */
  60.     @Override
  61.     public void write(int b) throws IOException {
  62.         onebytebuf[0] = (byte) b;
  63.         write(onebytebuf, 0, 1);
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public void write(byte[] b) throws IOException {
  68.         int overflow = buffer(b, 0, b.length);
  69.         if (overflow > 0) {
  70.             write(b, b.length - overflow, overflow);
  71.         }
  72.     }

  73.     /** {@inheritDoc} */
  74.     @Override
  75.     public void write(byte[] b, int startOff, int startLen)
  76.             throws IOException {
  77.         final int overflow = buffer(b, startOff, startLen);
  78.         if (overflow <= 0) {
  79.             return;
  80.         }
  81.         final int off = startOff + startLen - overflow;
  82.         final int len = overflow;
  83.         int lastw = off;
  84.         if (isBinary) {
  85.             out.write(b, off, len);
  86.             return;
  87.         }
  88.         for (int i = off; i < off + len; ++i) {
  89.             final byte c = b[i];
  90.             switch (c) {
  91.             case '\r':
  92.                 // skip write r but backlog r
  93.                 if (lastw < i) {
  94.                     out.write(b, lastw, i - lastw);
  95.                 }
  96.                 lastw = i + 1;
  97.                 buf = '\r';
  98.                 break;
  99.             case '\n':
  100.                 if (buf == '\r') {
  101.                     out.write('\n');
  102.                     lastw = i + 1;
  103.                     buf = -1;
  104.                 } else {
  105.                     if (lastw < i + 1) {
  106.                         out.write(b, lastw, i + 1 - lastw);
  107.                     }
  108.                     lastw = i + 1;
  109.                 }
  110.                 break;
  111.             default:
  112.                 if (buf == '\r') {
  113.                     out.write('\r');
  114.                     lastw = i;
  115.                 }
  116.                 buf = -1;
  117.                 break;
  118.             }
  119.         }
  120.         if (lastw < off + len) {
  121.             out.write(b, lastw, off + len - lastw);
  122.         }
  123.     }

  124.     private int buffer(byte[] b, int off, int len) throws IOException {
  125.         if (binbufcnt > binbuf.length) {
  126.             return len;
  127.         }
  128.         int copy = Math.min(binbuf.length - binbufcnt, len);
  129.         System.arraycopy(b, off, binbuf, binbufcnt, copy);
  130.         binbufcnt += copy;
  131.         int remaining = len - copy;
  132.         if (remaining > 0) {
  133.             decideMode();
  134.         }
  135.         return remaining;
  136.     }

  137.     private void decideMode() throws IOException {
  138.         if (detectBinary) {
  139.             isBinary = RawText.isBinary(binbuf, binbufcnt);
  140.             if (!isBinary) {
  141.                 isBinary = RawText.isCrLfText(binbuf, binbufcnt);
  142.             }
  143.             detectBinary = false;
  144.         }
  145.         int cachedLen = binbufcnt;
  146.         binbufcnt = binbuf.length + 1; // full!
  147.         write(binbuf, 0, cachedLen);
  148.     }

  149.     /** {@inheritDoc} */
  150.     @Override
  151.     public void flush() throws IOException {
  152.         if (binbufcnt <= binbuf.length) {
  153.             decideMode();
  154.         }
  155.         out.flush();
  156.     }

  157.     /** {@inheritDoc} */
  158.     @Override
  159.     public void close() throws IOException {
  160.         flush();
  161.         if (buf == '\r') {
  162.             out.write(buf);
  163.             buf = -1;
  164.         }
  165.         out.close();
  166.     }
  167. }