View Javadoc
1   /*
2    * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
3    *
4    * This program and the accompanying materials are made available
5    * under the terms of the Eclipse Distribution License v1.0 which
6    * accompanies this distribution, is reproduced below, and is
7    * available at http://www.eclipse.org/org/documents/edl-v10.php
8    *
9    * All rights reserved.
10   *
11   * Redistribution and use in source and binary forms, with or
12   * without modification, are permitted provided that the following
13   * conditions are met:
14   *
15   * - Redistributions of source code must retain the above copyright
16   *   notice, this list of conditions and the following disclaimer.
17   *
18   * - Redistributions in binary form must reproduce the above
19   *   copyright notice, this list of conditions and the following
20   *   disclaimer in the documentation and/or other materials provided
21   *   with the distribution.
22   *
23   * - Neither the name of the Eclipse Foundation, Inc. nor the
24   *   names of its contributors may be used to endorse or promote
25   *   products derived from this software without specific prior
26   *   written permission.
27   *
28   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
30   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
31   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
33   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
35   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
36   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
37   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
38   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
40   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41   */
42  
43  package org.eclipse.jgit.util.io;
44  
45  import java.io.IOException;
46  import java.io.OutputStream;
47  
48  import org.eclipse.jgit.diff.RawText;
49  
50  /**
51   * An OutputStream that reduces CRLF to LF.
52   *
53   * Existing single CR are not changed to LF, but retained as is.
54   *
55   * A binary check on the first 8000 bytes is performed and in case of binary
56   * files, canonicalization is turned off (for the complete file).
57   *
58   * @since 4.3
59   */
60  public class AutoLFOutputStream extends OutputStream {
61  
62  	static final int BUFFER_SIZE = 8000;
63  
64  	private final OutputStream out;
65  
66  	private int buf = -1;
67  
68  	private byte[] binbuf = new byte[BUFFER_SIZE];
69  
70  	private byte[] onebytebuf = new byte[1];
71  
72  	private int binbufcnt = 0;
73  
74  	private boolean detectBinary;
75  
76  	private boolean isBinary;
77  
78  	/**
79  	 * @param out
80  	 */
81  	public AutoLFOutputStream(OutputStream out) {
82  		this(out, true);
83  	}
84  
85  	/**
86  	 * @param out
87  	 * @param detectBinary
88  	 *            whether binaries should be detected
89  	 */
90  	public AutoLFOutputStream(OutputStream out, boolean detectBinary) {
91  		this.out = out;
92  		this.detectBinary = detectBinary;
93  	}
94  
95  	@Override
96  	public void write(int b) throws IOException {
97  		onebytebuf[0] = (byte) b;
98  		write(onebytebuf, 0, 1);
99  	}
100 
101 	@Override
102 	public void write(byte[] b) throws IOException {
103 		int overflow = buffer(b, 0, b.length);
104 		if (overflow > 0) {
105 			write(b, b.length - overflow, overflow);
106 		}
107 	}
108 
109 	@Override
110 	public void write(byte[] b, final int startOff, final int startLen)
111 			throws IOException {
112 		final int overflow = buffer(b, startOff, startLen);
113 		if (overflow < 0) {
114 			return;
115 		}
116 		final int off = startOff + startLen - overflow;
117 		final int len = overflow;
118 		if (len == 0) {
119 			return;
120 		}
121 		int lastw = off;
122 		if (isBinary) {
123 			out.write(b, off, len);
124 			return;
125 		}
126 		for (int i = off; i < off + len; ++i) {
127 			final byte c = b[i];
128 			if (c == '\r') {
129 				// skip write r but backlog r
130 				if (lastw < i) {
131 					out.write(b, lastw, i - lastw);
132 				}
133 				lastw = i + 1;
134 				buf = '\r';
135 			} else if (c == '\n') {
136 				if (buf == '\r') {
137 					out.write('\n');
138 					lastw = i + 1;
139 					buf = -1;
140 				} else {
141 					if (lastw < i + 1) {
142 						out.write(b, lastw, i + 1 - lastw);
143 					}
144 					lastw = i + 1;
145 				}
146 			} else {
147 				if (buf == '\r') {
148 					out.write('\r');
149 					lastw = i;
150 				}
151 				buf = -1;
152 			}
153 		}
154 		if (lastw < off + len) {
155 			out.write(b, lastw, off + len - lastw);
156 		}
157 	}
158 
159 	private int buffer(byte[] b, int off, int len) throws IOException {
160 		if (binbufcnt > binbuf.length) {
161 			return len;
162 		}
163 		int copy = Math.min(binbuf.length - binbufcnt, len);
164 		System.arraycopy(b, off, binbuf, binbufcnt, copy);
165 		binbufcnt += copy;
166 		int remaining = len - copy;
167 		if (remaining > 0) {
168 			decideMode();
169 		}
170 		return remaining;
171 	}
172 
173 	private void decideMode() throws IOException {
174 		if (detectBinary) {
175 			isBinary = RawText.isBinary(binbuf, binbufcnt);
176 			detectBinary = false;
177 		}
178 		int cachedLen = binbufcnt;
179 		binbufcnt = binbuf.length + 1; // full!
180 		write(binbuf, 0, cachedLen);
181 	}
182 
183 	@Override
184 	public void flush() throws IOException {
185 		if (binbufcnt <= binbuf.length) {
186 			decideMode();
187 		}
188 		out.flush();
189 	}
190 
191 	@Override
192 	public void close() throws IOException {
193 		flush();
194 		if (buf == '\r') {
195 			out.write(buf);
196 			buf = -1;
197 		}
198 		out.close();
199 	}
200 }