AutoCRLFOutputStream.java

  1. /*
  2.  * Copyright (C) 2011, 2013 Robin Rosenberg and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

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

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

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

  15. /**
  16.  * An OutputStream that expands LF to CRLF.
  17.  *
  18.  * Existing CRLF are not expanded to CRCRLF, but retained as is.
  19.  *
  20.  * A binary check on the first {@link RawText#getBufferSize()} bytes is
  21.  * performed and in case of binary files, canonicalization is turned off (for
  22.  * the complete file).
  23.  */
  24. public class AutoCRLFOutputStream extends OutputStream {

  25.     private final OutputStream out;

  26.     private int buf = -1;

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

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

  29.     private int binbufcnt = 0;

  30.     private boolean detectBinary;

  31.     private boolean isBinary;

  32.     /**
  33.      * <p>Constructor for AutoCRLFOutputStream.</p>
  34.      *
  35.      * @param out a {@link java.io.OutputStream} object.
  36.      */
  37.     public AutoCRLFOutputStream(OutputStream out) {
  38.         this(out, true);
  39.     }

  40.     /**
  41.      * <p>Constructor for AutoCRLFOutputStream.</p>
  42.      *
  43.      * @param out a {@link java.io.OutputStream} object.
  44.      * @param detectBinary
  45.      *            whether binaries should be detected
  46.      * @since 4.3
  47.      */
  48.     public AutoCRLFOutputStream(OutputStream out, boolean detectBinary) {
  49.         // avoid to write single lines directly to FileOutputStream:
  50.         this.out = out instanceof BufferedOutputStream ? out
  51.                 : new BufferedOutputStream(out);
  52.         this.detectBinary = detectBinary;
  53.     }

  54.     /** {@inheritDoc} */
  55.     @Override
  56.     public void write(int b) throws IOException {
  57.         onebytebuf[0] = (byte) b;
  58.         write(onebytebuf, 0, 1);
  59.     }

  60.     /** {@inheritDoc} */
  61.     @Override
  62.     public void write(byte[] b) throws IOException {
  63.         int overflow = buffer(b, 0, b.length);
  64.         if (overflow > 0)
  65.             write(b, b.length - overflow, overflow);
  66.     }

  67.     /** {@inheritDoc} */
  68.     @Override
  69.     public void write(byte[] b, int startOff, int startLen)
  70.             throws IOException {
  71.         final int overflow = buffer(b, startOff, startLen);
  72.         if (overflow < 0)
  73.             return;
  74.         final int off = startOff + startLen - overflow;
  75.         final int len = overflow;
  76.         if (len == 0)
  77.             return;
  78.         int lastw = off;
  79.         if (isBinary) {
  80.             out.write(b, off, len);
  81.             return;
  82.         }
  83.         for (int i = off; i < off + len; ++i) {
  84.             final byte c = b[i];
  85.             switch (c) {
  86.             case '\r':
  87.                 buf = '\r';
  88.                 break;
  89.             case '\n':
  90.                 if (buf != '\r') {
  91.                     if (lastw < i) {
  92.                         out.write(b, lastw, i - lastw);
  93.                     }
  94.                     out.write('\r');
  95.                     lastw = i;
  96.                 }
  97.                 buf = -1;
  98.                 break;
  99.             default:
  100.                 buf = -1;
  101.                 break;
  102.             }
  103.         }
  104.         if (lastw < off + len) {
  105.             out.write(b, lastw, off + len - lastw);
  106.         }
  107.         if (b[off + len - 1] == '\r')
  108.             buf = '\r';
  109.     }

  110.     private int buffer(byte[] b, int off, int len) throws IOException {
  111.         if (binbufcnt > binbuf.length) {
  112.             return len;
  113.         }
  114.         int copy = Math.min(binbuf.length - binbufcnt, len);
  115.         System.arraycopy(b, off, binbuf, binbufcnt, copy);
  116.         binbufcnt += copy;
  117.         int remaining = len - copy;
  118.         if (remaining > 0) {
  119.             decideMode(false);
  120.         }
  121.         return remaining;
  122.     }

  123.     private void decideMode(boolean complete) throws IOException {
  124.         if (detectBinary) {
  125.             isBinary = RawText.isBinary(binbuf, binbufcnt, complete);
  126.             if (!isBinary) {
  127.                 isBinary = RawText.isCrLfText(binbuf, binbufcnt, complete);
  128.             }
  129.             detectBinary = false;
  130.         }
  131.         int cachedLen = binbufcnt;
  132.         binbufcnt = binbuf.length + 1; // full!
  133.         write(binbuf, 0, cachedLen);
  134.     }

  135.     /** {@inheritDoc} */
  136.     @Override
  137.     public void flush() throws IOException {
  138.         if (binbufcnt <= binbuf.length) {
  139.             decideMode(true);
  140.         }
  141.         buf = -1;
  142.         out.flush();
  143.     }

  144.     /** {@inheritDoc} */
  145.     @Override
  146.     public void close() throws IOException {
  147.         flush();
  148.         out.close();
  149.     }
  150. }