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 8000 bytes is performed and in case of binary
24   * files, canonicalization is turned off (for the complete file).
25   */
26  public class AutoCRLFOutputStream extends OutputStream {
27  
28  	static final int BUFFER_SIZE = 8000;
29  
30  	private final OutputStream out;
31  
32  	private int buf = -1;
33  
34  	private byte[] binbuf = new byte[BUFFER_SIZE];
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  		this.out = out;
63  		this.detectBinary = detectBinary;
64  	}
65  
66  	/** {@inheritDoc} */
67  	@Override
68  	public void write(int b) throws IOException {
69  		onebytebuf[0] = (byte) b;
70  		write(onebytebuf, 0, 1);
71  	}
72  
73  	/** {@inheritDoc} */
74  	@Override
75  	public void write(byte[] b) throws IOException {
76  		int overflow = buffer(b, 0, b.length);
77  		if (overflow > 0)
78  			write(b, b.length - overflow, overflow);
79  	}
80  
81  	/** {@inheritDoc} */
82  	@Override
83  	public void write(byte[] b, int startOff, int startLen)
84  			throws IOException {
85  		final int overflow = buffer(b, startOff, startLen);
86  		if (overflow < 0)
87  			return;
88  		final int off = startOff + startLen - overflow;
89  		final int len = overflow;
90  		if (len == 0)
91  			return;
92  		int lastw = off;
93  		if (isBinary) {
94  			out.write(b, off, len);
95  			return;
96  		}
97  		for (int i = off; i < off + len; ++i) {
98  			final byte c = b[i];
99  			switch (c) {
100 			case '\r':
101 				buf = '\r';
102 				break;
103 			case '\n':
104 				if (buf != '\r') {
105 					if (lastw < i) {
106 						out.write(b, lastw, i - lastw);
107 					}
108 					out.write('\r');
109 					lastw = i;
110 				}
111 				buf = -1;
112 				break;
113 			default:
114 				buf = -1;
115 				break;
116 			}
117 		}
118 		if (lastw < off + len) {
119 			out.write(b, lastw, off + len - lastw);
120 		}
121 		if (b[off + len - 1] == '\r')
122 			buf = '\r';
123 	}
124 
125 	private int buffer(byte[] b, int off, int len) throws IOException {
126 		if (binbufcnt > binbuf.length)
127 			return len;
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();
134 		return remaining;
135 	}
136 
137 	private void decideMode() throws IOException {
138 		if (detectBinary) {
139 			isBinary = RawText.isBinary(binbuf, binbufcnt);
140 			detectBinary = false;
141 		}
142 		int cachedLen = binbufcnt;
143 		binbufcnt = binbuf.length + 1; // full!
144 		write(binbuf, 0, cachedLen);
145 	}
146 
147 	/** {@inheritDoc} */
148 	@Override
149 	public void flush() throws IOException {
150 		if (binbufcnt <= binbuf.length)
151 			decideMode();
152 		buf = -1;
153 		out.flush();
154 	}
155 
156 	/** {@inheritDoc} */
157 	@Override
158 	public void close() throws IOException {
159 		flush();
160 		out.close();
161 	}
162 }