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