Skip to content

Commit 3e8e057

Browse files
author
Niall Pemberton
committed
IO-178 BOMExclusionInputStream - an InputStream for UTF-8 data that ignores an initial Byte Order mark - thanks for patch from Keith D Gregory
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@721749 13f79535-47bb-0310-9956-ffa450edef68
1 parent faa58c0 commit 3e8e057

2 files changed

Lines changed: 415 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
22+
/**
23+
* This class is used to wrap a UTF8-encoded stream that includes an encoded
24+
* Byte Order Mark (BOM, 0xFEFF encoded as 0xEF 0xBB 0xBF) as its first bytes.
25+
* Such streams are produced by various Microsoft applications. This class
26+
* will automatically skip these bytes and return the subsequent byte as the
27+
* first byte in the stream.
28+
* <p>
29+
* If the first byte in the stream is 0xEF, this class will attempt to read
30+
* the next two bytes. Results are undefined if the stream does not contain
31+
* UTF-8 encoded data, as these next two bytes may not exist.
32+
*
33+
* @version $Revision$ $Date$
34+
* @since Commons IO 2.0
35+
*/
36+
public class BOMExclusionInputStream extends ProxyInputStream {
37+
private int[] firstBytes;
38+
private int fbLength;
39+
private int fbIndex;
40+
private boolean markedAtStart;
41+
42+
/**
43+
* Constructs a new BOM Exclusion InputStream.
44+
* @param delegate the InputStream to delegate to
45+
*/
46+
public BOMExclusionInputStream(InputStream delegate) {
47+
super(delegate);
48+
}
49+
50+
/**
51+
* This method reads and either preserves or skips the first bytes in the
52+
* stream. It behaves like the single-byte <code>read()</code> method,
53+
* either returning a valid byte or -1 to indicate that the initial bytes
54+
* have been processed already.
55+
* @return the byte read (excluding BOM) or -1 if the end of stream
56+
* @throws IOException if an I/O error occurs
57+
*/
58+
private int readFirstBytes() throws IOException {
59+
if (firstBytes == null) {
60+
firstBytes = new int[3];
61+
int b0 = in.read();
62+
if ((b0 < 0) || (b0 != 0xEF)) {
63+
return b0;
64+
}
65+
66+
int b1 = in.read();
67+
int b2 = in.read();
68+
if ((b1 == 0xBB) && (b2 == 0xBF)) {
69+
return in.read();
70+
}
71+
72+
// if the stream isn't valid UTF-8, this is where things get weird
73+
firstBytes[fbLength++] = b0;
74+
firstBytes[fbLength++] = b1;
75+
firstBytes[fbLength++] = b2;
76+
}
77+
78+
return (fbIndex < fbLength) ? firstBytes[fbIndex++] : -1;
79+
}
80+
81+
//----------------------------------------------------------------------------
82+
// Implementation of InputStream
83+
//----------------------------------------------------------------------------
84+
85+
/**
86+
* Invokes the delegate's <code>read()</code> method, skipping BOM.
87+
* @return the byte read (excluding BOM) or -1 if the end of stream
88+
* @throws IOException if an I/O error occurs
89+
*/
90+
@Override
91+
public int read() throws IOException {
92+
int b = readFirstBytes();
93+
return (b >= 0) ? b : in.read();
94+
}
95+
96+
/**
97+
* Invokes the delegate's <code>read(byte[], int, int)</code> method, skipping BOM.
98+
* @param buf the buffer to read the bytes into
99+
* @param off The start offset
100+
* @param len The number of bytes to read (excluding BOM)
101+
* @return the number of bytes read or -1 if the end of stream
102+
* @throws IOException if an I/O error occurs
103+
*/
104+
@Override
105+
public int read(byte[] buf, int off, int len) throws IOException {
106+
int firstCount = 0;
107+
int b = 0;
108+
while ((len > 0) && (b >= 0)) {
109+
b = readFirstBytes();
110+
if (b >= 0) {
111+
buf[off++] = (byte) (b & 0xFF);
112+
len--;
113+
firstCount++;
114+
}
115+
}
116+
int secondCount = in.read(buf, off, len);
117+
return (secondCount < 0) ? firstCount : firstCount + secondCount;
118+
}
119+
120+
/**
121+
* Invokes the delegate's <code>read(byte[])</code> method, skipping BOM.
122+
* @param buf the buffer to read the bytes into
123+
* @return the number of bytes read (excluding BOM)
124+
* or -1 if the end of stream
125+
* @throws IOException if an I/O error occurs
126+
*/
127+
@Override
128+
public int read(byte[] buf) throws IOException {
129+
return read(buf, 0, buf.length);
130+
}
131+
132+
/**
133+
* Invokes the delegate's <code>mark(int)</code> method.
134+
* @param readlimit read ahead limit
135+
*/
136+
@Override
137+
public synchronized void mark(int readlimit) {
138+
markedAtStart = (firstBytes == null);
139+
in.mark(readlimit);
140+
}
141+
142+
/**
143+
* Invokes the delegate's <code>reset()</code> method.
144+
* @throws IOException if an I/O error occurs
145+
*/
146+
@Override
147+
public synchronized void reset() throws IOException {
148+
if (markedAtStart) {
149+
firstBytes = null;
150+
}
151+
152+
in.reset();
153+
}
154+
155+
/**
156+
* Invokes the delegate's <code>skip(long)</code> method, skipping BOM.
157+
* @param n the number of bytes to skip
158+
* @return the number of bytes to skipped or -1 if the end of stream
159+
* @throws IOException if an I/O error occurs
160+
*/
161+
@Override
162+
public long skip(long n) throws IOException {
163+
while ((n > 0) && (readFirstBytes() >= 0)) {
164+
n--;
165+
}
166+
return in.skip(n);
167+
}
168+
}

0 commit comments

Comments
 (0)