View Javadoc
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  
11  package org.eclipse.jgit.util.io;
12  
13  import java.io.BufferedOutputStream;
14  import java.io.IOException;
15  import java.io.OutputStream;
16  
17  import org.eclipse.jgit.diff.RawText;
18  
19  /**
20   * An OutputStream that expands LF to CRLF.
21   *
22   * Existing CRLF are not expanded to CRCRLF, but retained as is.
23   *
24   * A binary check on the first {@link RawText#getBufferSize()} bytes is
25   * performed and in case of binary files, canonicalization is turned off (for
26   * the complete file).
27   */
28  public class AutoCRLFOutputStream extends OutputStream {
29  
30  	private final OutputStream out;
31  
32  	private int buf = -1;
33  
34  	private byte[] binbuf = new byte[RawText.getBufferSize()];
35  
36  	private byte[] onebytebuf = new byte[1];
37  
38  	private int binbufcnt = 0;
39  
40  	private boolean detectBinary;
41  
42  	private boolean isBinary;
43  
44  	/**
45  	 * <p>Constructor for AutoCRLFOutputStream.</p>
46  	 *
47  	 * @param out a {@link java.io.OutputStream} object.
48  	 */
49  	public AutoCRLFOutputStream(OutputStream out) {
50  		this(out, true);
51  	}
52  
53  	/**
54  	 * <p>Constructor for AutoCRLFOutputStream.</p>
55  	 *
56  	 * @param out a {@link java.io.OutputStream} object.
57  	 * @param detectBinary
58  	 *            whether binaries should be detected
59  	 * @since 4.3
60  	 */
61  	public AutoCRLFOutputStream(OutputStream out, boolean detectBinary) {
62  		// avoid to write single lines directly to FileOutputStream:
63  		this.out = out instanceof BufferedOutputStream ? out
64  				: new BufferedOutputStream(out);
65  		this.detectBinary = detectBinary;
66  	}
67  
68  	/** {@inheritDoc} */
69  	@Override
70  	public void write(int b) throws IOException {
71  		onebytebuf[0] = (byte) b;
72  		write(onebytebuf, 0, 1);
73  	}
74  
75  	/** {@inheritDoc} */
76  	@Override
77  	public void write(byte[] b) throws IOException {
78  		int overflow = buffer(b, 0, b.length);
79  		if (overflow > 0)
80  			write(b, b.length - overflow, overflow);
81  	}
82  
83  	/** {@inheritDoc} */
84  	@Override
85  	public void write(byte[] b, int startOff, int startLen)
86  			throws IOException {
87  		final int overflow = buffer(b, startOff, startLen);
88  		if (overflow < 0)
89  			return;
90  		final int off = startOff + startLen - overflow;
91  		final int len = overflow;
92  		if (len == 0)
93  			return;
94  		int lastw = off;
95  		if (isBinary) {
96  			out.write(b, off, len);
97  			return;
98  		}
99  		for (int i = off; i < off + len; ++i) {
100 			final byte c = b[i];
101 			switch (c) {
102 			case '\r':
103 				buf = '\r';
104 				break;
105 			case '\n':
106 				if (buf != '\r') {
107 					if (lastw < i) {
108 						out.write(b, lastw, i - lastw);
109 					}
110 					out.write('\r');
111 					lastw = i;
112 				}
113 				buf = -1;
114 				break;
115 			default:
116 				buf = -1;
117 				break;
118 			}
119 		}
120 		if (lastw < off + len) {
121 			out.write(b, lastw, off + len - lastw);
122 		}
123 		if (b[off + len - 1] == '\r')
124 			buf = '\r';
125 	}
126 
127 	private int buffer(byte[] b, int off, int len) throws IOException {
128 		if (binbufcnt > binbuf.length) {
129 			return len;
130 		}
131 		int copy = Math.min(binbuf.length - binbufcnt, len);
132 		System.arraycopy(b, off, binbuf, binbufcnt, copy);
133 		binbufcnt += copy;
134 		int remaining = len - copy;
135 		if (remaining > 0) {
136 			decideMode(false);
137 		}
138 		return remaining;
139 	}
140 
141 	private void decideMode(boolean complete) throws IOException {
142 		if (detectBinary) {
143 			isBinary = RawText.isBinary(binbuf, binbufcnt, complete);
144 			if (!isBinary) {
145 				isBinary = RawText.isCrLfText(binbuf, binbufcnt, complete);
146 			}
147 			detectBinary = false;
148 		}
149 		int cachedLen = binbufcnt;
150 		binbufcnt = binbuf.length + 1; // full!
151 		write(binbuf, 0, cachedLen);
152 	}
153 
154 	/** {@inheritDoc} */
155 	@Override
156 	public void flush() throws IOException {
157 		if (binbufcnt <= binbuf.length) {
158 			decideMode(true);
159 		}
160 		buf = -1;
161 		out.flush();
162 	}
163 
164 	/** {@inheritDoc} */
165 	@Override
166 	public void close() throws IOException {
167 		flush();
168 		out.close();
169 	}
170 }