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 {@link RawText#getBufferSize()} bytes is
  22.  * performed and in case of binary files, canonicalization is turned off (for
  23.  * the complete file). If the binary check determines that the input is not
  24.  * binary but text with CR/LF, canonicalization is also turned off.
  25.  * </p>
  26.  *
  27.  * @since 4.3
  28.  */
  29. public class AutoLFOutputStream extends OutputStream {

  30.     private final OutputStream out;

  31.     private int buf = -1;

  32.     private byte[] binbuf = new byte[RawText.getBufferSize()];

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

  34.     private int binbufcnt = 0;

  35.     private boolean detectBinary;

  36.     private boolean isBinary;

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

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

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

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

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

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

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

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

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