View Javadoc
1   /*
2    * Copyright (C) 2010, 2013 Marc Strapetz <marc.strapetz@syntevo.com>
3    * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> 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.InputStream;
16  
17  import org.eclipse.jgit.diff.RawText;
18  
19  /**
20   * An InputStream that normalizes CRLF to LF.
21   *
22   * Existing single CR are not changed to LF, but retained as is.
23   *
24   * Optionally, a binary check on the first 8000 bytes is performed and in case
25   * of binary files, canonicalization is turned off (for the complete file).
26   * <p>
27   * This is the former EolCanonicalizingInputStream with a new name in order to
28   * have same naming for all LF / CRLF streams
29   *
30   * @since 4.3
31   */
32  public class AutoLFInputStream extends InputStream {
33  	private final byte[] single = new byte[1];
34  
35  	private final byte[] buf = new byte[8096];
36  
37  	private final InputStream in;
38  
39  	private int cnt;
40  
41  	private int ptr;
42  
43  	private boolean isBinary;
44  
45  	private boolean detectBinary;
46  
47  	private boolean abortIfBinary;
48  
49  	/**
50  	 * A special exception thrown when {@link AutoLFInputStream} is told to
51  	 * throw an exception when attempting to read a binary file. The exception
52  	 * may be thrown at any stage during reading.
53  	 *
54  	 * @since 3.3
55  	 */
56  	public static class IsBinaryException extends IOException {
57  		private static final long serialVersionUID = 1L;
58  
59  		IsBinaryException() {
60  			super();
61  		}
62  	}
63  
64  	/**
65  	 * Creates a new InputStream, wrapping the specified stream
66  	 *
67  	 * @param in
68  	 *            raw input stream
69  	 * @param detectBinary
70  	 *            whether binaries should be detected
71  	 * @since 2.0
72  	 */
73  	public AutoLFInputStream(InputStream in, boolean detectBinary) {
74  		this(in, detectBinary, false);
75  	}
76  
77  	/**
78  	 * Creates a new InputStream, wrapping the specified stream
79  	 *
80  	 * @param in
81  	 *            raw input stream
82  	 * @param detectBinary
83  	 *            whether binaries should be detected
84  	 * @param abortIfBinary
85  	 *            throw an IOException if the file is binary
86  	 * @since 3.3
87  	 */
88  	public AutoLFInputStream(InputStream in, boolean detectBinary,
89  			boolean abortIfBinary) {
90  		this.in = in;
91  		this.detectBinary = detectBinary;
92  		this.abortIfBinary = abortIfBinary;
93  	}
94  
95  	/** {@inheritDoc} */
96  	@Override
97  	public int read() throws IOException {
98  		final int read = read(single, 0, 1);
99  		return read == 1 ? single[0] & 0xff : -1;
100 	}
101 
102 	/** {@inheritDoc} */
103 	@Override
104 	public int read(byte[] bs, int off, int len)
105 			throws IOException {
106 		if (len == 0)
107 			return 0;
108 
109 		if (cnt == -1)
110 			return -1;
111 
112 		int i = off;
113 		final int end = off + len;
114 
115 		while (i < end) {
116 			if (ptr == cnt && !fillBuffer()) {
117 				break;
118 			}
119 
120 			byte b = buf[ptr++];
121 			if (isBinary || b != '\r') {
122 				// Logic for binary files ends here
123 				bs[i++] = b;
124 				continue;
125 			}
126 
127 			if (ptr == cnt && !fillBuffer()) {
128 				bs[i++] = '\r';
129 				break;
130 			}
131 
132 			if (buf[ptr] == '\n') {
133 				bs[i++] = '\n';
134 				ptr++;
135 			} else
136 				bs[i++] = '\r';
137 		}
138 
139 		return i == off ? -1 : i - off;
140 	}
141 
142 	/**
143 	 * Whether the stream has detected as a binary so far.
144 	 *
145 	 * @return true if the stream has detected as a binary so far.
146 	 * @since 3.3
147 	 */
148 	public boolean isBinary() {
149 		return isBinary;
150 	}
151 
152 	/** {@inheritDoc} */
153 	@Override
154 	public void close() throws IOException {
155 		in.close();
156 	}
157 
158 	private boolean fillBuffer() throws IOException {
159 		cnt = 0;
160 		while (cnt < buf.length) {
161 			int n = in.read(buf, cnt, buf.length - cnt);
162 			if (n < 0) {
163 				break;
164 			}
165 			cnt += n;
166 		}
167 		if (cnt < 1) {
168 			cnt = -1;
169 			return false;
170 		}
171 		if (detectBinary) {
172 			isBinary = RawText.isBinary(buf, cnt);
173 			detectBinary = false;
174 			if (isBinary && abortIfBinary)
175 				throw new IsBinaryException();
176 		}
177 		ptr = 0;
178 		return true;
179 	}
180 }