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