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  	 * <p>
80  	 * Constructor for AutoLFOutputStream.
81  	 * </p>
82  	 *
83  	 * @param out
84  	 *            an {@link java.io.OutputStream} object.
85  	 */
86  	public AutoLFOutputStream(OutputStream out) {
87  		this(out, true);
88  	}
89  
90  	/**
91  	 * <p>
92  	 * Constructor for AutoLFOutputStream.
93  	 * </p>
94  	 *
95  	 * @param out
96  	 *            an {@link java.io.OutputStream} object.
97  	 * @param detectBinary
98  	 *            whether binaries should be detected
99  	 */
100 	public AutoLFOutputStream(OutputStream out, boolean detectBinary) {
101 		this.out = out;
102 		this.detectBinary = detectBinary;
103 	}
104 
105 	/** {@inheritDoc} */
106 	@Override
107 	public void write(int b) throws IOException {
108 		onebytebuf[0] = (byte) b;
109 		write(onebytebuf, 0, 1);
110 	}
111 
112 	/** {@inheritDoc} */
113 	@Override
114 	public void write(byte[] b) throws IOException {
115 		int overflow = buffer(b, 0, b.length);
116 		if (overflow > 0) {
117 			write(b, b.length - overflow, overflow);
118 		}
119 	}
120 
121 	/** {@inheritDoc} */
122 	@Override
123 	public void write(byte[] b, int startOff, int startLen)
124 			throws IOException {
125 		final int overflow = buffer(b, startOff, startLen);
126 		if (overflow < 0) {
127 			return;
128 		}
129 		final int off = startOff + startLen - overflow;
130 		final int len = overflow;
131 		if (len == 0) {
132 			return;
133 		}
134 		int lastw = off;
135 		if (isBinary) {
136 			out.write(b, off, len);
137 			return;
138 		}
139 		for (int i = off; i < off + len; ++i) {
140 			final byte c = b[i];
141 			switch (c) {
142 			case '\r':
143 				// skip write r but backlog r
144 				if (lastw < i) {
145 					out.write(b, lastw, i - lastw);
146 				}
147 				lastw = i + 1;
148 				buf = '\r';
149 				break;
150 			case '\n':
151 				if (buf == '\r') {
152 					out.write('\n');
153 					lastw = i + 1;
154 					buf = -1;
155 				} else {
156 					if (lastw < i + 1) {
157 						out.write(b, lastw, i + 1 - lastw);
158 					}
159 					lastw = i + 1;
160 				}
161 				break;
162 			default:
163 				if (buf == '\r') {
164 					out.write('\r');
165 					lastw = i;
166 				}
167 				buf = -1;
168 				break;
169 			}
170 		}
171 		if (lastw < off + len) {
172 			out.write(b, lastw, off + len - lastw);
173 		}
174 	}
175 
176 	private int buffer(byte[] b, int off, int len) throws IOException {
177 		if (binbufcnt > binbuf.length) {
178 			return len;
179 		}
180 		int copy = Math.min(binbuf.length - binbufcnt, len);
181 		System.arraycopy(b, off, binbuf, binbufcnt, copy);
182 		binbufcnt += copy;
183 		int remaining = len - copy;
184 		if (remaining > 0) {
185 			decideMode();
186 		}
187 		return remaining;
188 	}
189 
190 	private void decideMode() throws IOException {
191 		if (detectBinary) {
192 			isBinary = RawText.isBinary(binbuf, binbufcnt);
193 			detectBinary = false;
194 		}
195 		int cachedLen = binbufcnt;
196 		binbufcnt = binbuf.length + 1; // full!
197 		write(binbuf, 0, cachedLen);
198 	}
199 
200 	/** {@inheritDoc} */
201 	@Override
202 	public void flush() throws IOException {
203 		if (binbufcnt <= binbuf.length) {
204 			decideMode();
205 		}
206 		out.flush();
207 	}
208 
209 	/** {@inheritDoc} */
210 	@Override
211 	public void close() throws IOException {
212 		flush();
213 		if (buf == '\r') {
214 			out.write(buf);
215 			buf = -1;
216 		}
217 		out.close();
218 	}
219 }