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.IOException;
  12. import java.io.OutputStream;

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

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

  23.     static final int BUFFER_SIZE = 8000;

  24.     private final OutputStream out;

  25.     private int buf = -1;

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

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

  28.     private int binbufcnt = 0;

  29.     private boolean detectBinary;

  30.     private boolean isBinary;

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

  39.     /**
  40.      * <p>Constructor for AutoCRLFOutputStream.</p>
  41.      *
  42.      * @param out a {@link java.io.OutputStream} object.
  43.      * @param detectBinary
  44.      *            whether binaries should be detected
  45.      * @since 4.3
  46.      */
  47.     public AutoCRLFOutputStream(OutputStream out, boolean detectBinary) {
  48.         this.out = out;
  49.         this.detectBinary = detectBinary;
  50.     }

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

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

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

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

  118.     private void decideMode() throws IOException {
  119.         if (detectBinary) {
  120.             isBinary = RawText.isBinary(binbuf, binbufcnt);
  121.             detectBinary = false;
  122.         }
  123.         int cachedLen = binbufcnt;
  124.         binbufcnt = binbuf.length + 1; // full!
  125.         write(binbuf, 0, cachedLen);
  126.     }

  127.     /** {@inheritDoc} */
  128.     @Override
  129.     public void flush() throws IOException {
  130.         if (binbufcnt <= binbuf.length)
  131.             decideMode();
  132.         buf = -1;
  133.         out.flush();
  134.     }

  135.     /** {@inheritDoc} */
  136.     @Override
  137.     public void close() throws IOException {
  138.         flush();
  139.         out.close();
  140.     }
  141. }