View Javadoc
1   /*
2    * Copyright (C) 2010, 2013 Marc Strapetz <marc.strapetz@syntevo.com>
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.util.io;
45  
46  import java.io.IOException;
47  import java.io.InputStream;
48  
49  import org.eclipse.jgit.diff.RawText;
50  
51  /**
52   * An input stream which canonicalizes EOLs bytes on the fly to '\n'.
53   *
54   * Optionally, a binary check on the first 8000 bytes is performed
55   * and in case of binary files, canonicalization is turned off
56   * (for the complete file).
57   */
58  public class EolCanonicalizingInputStream extends InputStream {
59  	private final byte[] single = new byte[1];
60  
61  	private final byte[] buf = new byte[8096];
62  
63  	private final InputStream in;
64  
65  	private int cnt;
66  
67  	private int ptr;
68  
69  	private boolean isBinary;
70  
71  	private boolean detectBinary;
72  
73  	private boolean abortIfBinary;
74  
75  	/**
76  	 * A special exception thrown when {@link EolCanonicalizingInputStream} is
77  	 * told to throw an exception when attempting to read a binary file. The
78  	 * exception may be thrown at any stage during reading.
79  	 *
80  	 * @since 3.3
81  	 */
82  	public static class IsBinaryException extends IOException {
83  		private static final long serialVersionUID = 1L;
84  
85  		IsBinaryException() {
86  			super();
87  		}
88  	}
89  
90  	/**
91  	 * Creates a new InputStream, wrapping the specified stream
92  	 *
93  	 * @param in
94  	 *            raw input stream
95  	 * @param detectBinary
96  	 *            whether binaries should be detected
97  	 * @since 2.0
98  	 */
99  	public EolCanonicalizingInputStream(InputStream in, boolean detectBinary) {
100 		this(in, detectBinary, false);
101 	}
102 
103 	/**
104 	 * Creates a new InputStream, wrapping the specified stream
105 	 *
106 	 * @param in
107 	 *            raw input stream
108 	 * @param detectBinary
109 	 *            whether binaries should be detected
110 	 * @param abortIfBinary
111 	 *            throw an IOException if the file is binary
112 	 * @since 3.3
113 	 */
114 	public EolCanonicalizingInputStream(InputStream in, boolean detectBinary,
115 			boolean abortIfBinary) {
116 		this.in = in;
117 		this.detectBinary = detectBinary;
118 		this.abortIfBinary = abortIfBinary;
119 	}
120 
121 	@Override
122 	public int read() throws IOException {
123 		final int read = read(single, 0, 1);
124 		return read == 1 ? single[0] & 0xff : -1;
125 	}
126 
127 	@Override
128 	public int read(byte[] bs, final int off, final int len) throws IOException {
129 		if (len == 0)
130 			return 0;
131 
132 		if (cnt == -1)
133 			return -1;
134 
135 		int i = off;
136 		final int end = off + len;
137 
138 		while (i < end) {
139 			if (ptr == cnt && !fillBuffer()) {
140 				break;
141 			}
142 
143 			byte b = buf[ptr++];
144 			if (isBinary || b != '\r') {
145 				// Logic for binary files ends here
146 				bs[i++] = b;
147 				continue;
148 			}
149 
150 			if (ptr == cnt && !fillBuffer()) {
151 				bs[i++] = '\r';
152 				break;
153 			}
154 
155 			if (buf[ptr] == '\n') {
156 				bs[i++] = '\n';
157 				ptr++;
158 			} else
159 				bs[i++] = '\r';
160 		}
161 
162 		return i == off ? -1 : i - off;
163 	}
164 
165 	/**
166 	 * @return true if the stream has detected as a binary so far
167 	 * @since 3.3
168 	 */
169 	public boolean isBinary() {
170 		return isBinary;
171 	}
172 
173 	@Override
174 	public void close() throws IOException {
175 		in.close();
176 	}
177 
178 	private boolean fillBuffer() throws IOException {
179 		cnt = in.read(buf, 0, buf.length);
180 		if (cnt < 1)
181 			return false;
182 		if (detectBinary) {
183 			isBinary = RawText.isBinary(buf, cnt);
184 			detectBinary = false;
185 			if (isBinary && abortIfBinary)
186 				throw new IsBinaryException();
187 		}
188 		ptr = 0;
189 		return true;
190 	}
191 }