Skip to content

Commit 3a47f39

Browse files
authored
BOMInputStream fail-fast and tracks its ByteOrderMark as a final (#835)
* [IO-856] Try test on all OSs for GitHub CI * BOMInputStream now fails-fast and tracks its ByteOrderMark as a final
1 parent cd5765a commit 3a47f39

2 files changed

Lines changed: 84 additions & 104 deletions

File tree

src/main/java/org/apache/commons/io/input/BOMInputStream.java

Lines changed: 69 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
1718
package org.apache.commons.io.input;
1819

1920
import static org.apache.commons.io.IOUtils.EOF;
@@ -31,8 +32,7 @@
3132
/**
3233
* This class is used to wrap a stream that includes an encoded {@link ByteOrderMark} as its first bytes.
3334
* <p>
34-
* This class detects these bytes and, if required, can automatically skip them and return the subsequent byte as the
35-
* first byte in the stream.
35+
* This class detects these bytes and, if required, can automatically skip them and return the subsequent byte as the first byte in the stream.
3636
* </p>
3737
* <p>
3838
* The {@link ByteOrderMark} implementation has the following predefined BOMs:
@@ -60,10 +60,7 @@
6060
*
6161
* <pre>
6262
* boolean include = true;
63-
* BOMInputStream bomIn = BOMInputStream.builder()
64-
* .setInputStream(in)
65-
* .setInclude(include)
66-
* .get();
63+
* BOMInputStream bomIn = BOMInputStream.builder().setInputStream(in).setInclude(include).get();
6764
* if (bomIn.hasBOM()) {
6865
* // has a UTF-8 BOM
6966
* }
@@ -72,10 +69,8 @@
7269
* <h2>Example 3 - Detecting Multiple BOMs</h2>
7370
*
7471
* <pre>
75-
* BOMInputStream bomIn = BOMInputStream.builder()
76-
* .setInputStream(in)
77-
* .setByteOrderMarks(ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE)
78-
* .get();
72+
* BOMInputStream bomIn = BOMInputStream.builder().setInputStream(in)
73+
* .setByteOrderMarks(ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE).get();
7974
* if (bomIn.hasBOM() == false) {
8075
* // No BOM found
8176
* } else if (bomIn.hasBOM(ByteOrderMark.UTF_16LE)) {
@@ -141,7 +136,6 @@ static ByteOrderMark getDefaultByteOrderMark() {
141136
}
142137

143138
private ByteOrderMark[] byteOrderMarks = DEFAULT;
144-
145139
private boolean include;
146140

147141
/**
@@ -207,7 +201,6 @@ public Builder setInclude(final boolean include) {
207201
this.include = include;
208202
return this;
209203
}
210-
211204
}
212205

213206
/**
@@ -229,65 +222,68 @@ public static Builder builder() {
229222
* BOMs are sorted from longest to shortest.
230223
*/
231224
private final List<ByteOrderMark> bomList;
232-
233-
private ByteOrderMark byteOrderMark;
225+
private final ByteOrderMark byteOrderMark;
234226
private int fbIndex;
235227
private int[] firstBytes;
236228
private final boolean include;
237229
private boolean markedAtStart;
238230
private int markFbIndex;
239231

232+
/**
233+
* Constructs a new instance.
234+
*
235+
* @param builder The builder.
236+
* @throws IOException if an error reading the first bytes of the stream occurs.
237+
*/
240238
private BOMInputStream(final Builder builder) throws IOException {
241239
super(builder);
242240
if (IOUtils.length(builder.byteOrderMarks) == 0) {
243241
throw new IllegalArgumentException("No ByteOrderMark specified.");
244242
}
245243
this.include = builder.include;
246-
final List<ByteOrderMark> list = Arrays.asList(builder.byteOrderMarks);
244+
final List<ByteOrderMark> bomList = Arrays.asList(builder.byteOrderMarks);
247245
// Sort the BOMs to match the longest BOM first because some BOMs have the same starting two bytes.
248-
list.sort(ByteOrderMarkLengthComparator);
249-
this.bomList = list;
246+
bomList.sort(ByteOrderMarkLengthComparator);
247+
this.bomList = bomList;
248+
this.byteOrderMark = readBom();
250249
}
251250

252251
/**
253252
* Constructs a new BOM InputStream that excludes a {@link ByteOrderMark#UTF_8} BOM.
254253
*
255-
* @param delegate
256-
* the InputStream to delegate to.
257-
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
254+
* @param delegate the InputStream to delegate to.
255+
* @throws IOException if an error reading the first bytes of the stream occurs.
256+
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
258257
*/
259258
@Deprecated
260-
public BOMInputStream(final InputStream delegate) {
259+
public BOMInputStream(final InputStream delegate) throws IOException {
261260
this(delegate, false, Builder.DEFAULT);
262261
}
263262

264263
/**
265264
* Constructs a new BOM InputStream that detects a {@link ByteOrderMark#UTF_8} and optionally includes it.
266265
*
267-
* @param delegate
268-
* the InputStream to delegate to.
269-
* @param include
270-
* true to include the UTF-8 BOM or false to exclude it.
271-
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
266+
* @param delegate the InputStream to delegate to.
267+
* @param include true to include the UTF-8 BOM or false to exclude it.
268+
* @throws IOException if an error reading the first bytes of the stream occurs.
269+
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
272270
*/
273271
@Deprecated
274-
public BOMInputStream(final InputStream delegate, final boolean include) {
272+
public BOMInputStream(final InputStream delegate, final boolean include) throws IOException {
275273
this(delegate, include, Builder.DEFAULT);
276274
}
277275

278276
/**
279277
* Constructs a new BOM InputStream that detects the specified BOMs and optionally includes them.
280278
*
281-
* @param delegate
282-
* the InputStream to delegate to.
283-
* @param include
284-
* true to include the specified BOMs or false to exclude them.
285-
* @param boms
286-
* The BOMs to detect and optionally exclude.
287-
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
279+
* @param delegate the InputStream to delegate to.
280+
* @param include true to include the specified BOMs or false to exclude them.
281+
* @param boms The BOMs to detect and optionally exclude.
282+
* @throws IOException if an error reading the first bytes of the stream occurs.
283+
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
288284
*/
289285
@Deprecated
290-
public BOMInputStream(final InputStream delegate, final boolean include, final ByteOrderMark... boms) {
286+
public BOMInputStream(final InputStream delegate, final boolean include, final ByteOrderMark... boms) throws IOException {
291287
super(delegate);
292288
if (IOUtils.length(boms) == 0) {
293289
throw new IllegalArgumentException("No BOMs specified");
@@ -297,19 +293,19 @@ public BOMInputStream(final InputStream delegate, final boolean include, final B
297293
// Sort the BOMs to match the longest BOM first because some BOMs have the same starting two bytes.
298294
list.sort(ByteOrderMarkLengthComparator);
299295
this.bomList = list;
296+
this.byteOrderMark = readBom();
300297
}
301298

302299
/**
303300
* Constructs a new BOM InputStream that excludes the specified BOMs.
304301
*
305-
* @param delegate
306-
* the InputStream to delegate to.
307-
* @param boms
308-
* The BOMs to detect and exclude.
302+
* @param delegate the InputStream to delegate to.
303+
* @param boms The BOMs to detect and exclude.
304+
* @throws IOException if an error reading the first bytes of the stream occurs.
309305
* @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
310306
*/
311307
@Deprecated
312-
public BOMInputStream(final InputStream delegate, final ByteOrderMark... boms) {
308+
public BOMInputStream(final InputStream delegate, final ByteOrderMark... boms) throws IOException {
313309
this(delegate, false, boms);
314310
}
315311

@@ -326,34 +322,26 @@ private ByteOrderMark find() {
326322
* Gets the ByteOrderMark (Byte Order Mark).
327323
*
328324
* @return The BOM or null if none matched.
329-
* @throws IOException
330-
* if an error reading the first bytes of the stream occurs.
331325
*/
332-
public ByteOrderMark getBOM() throws IOException {
333-
if (firstBytes == null) {
334-
byteOrderMark = readBom();
335-
}
326+
public ByteOrderMark getBOM() {
336327
return byteOrderMark;
337328
}
338329

339330
/**
340331
* Gets the BOM charset Name - {@link ByteOrderMark#getCharsetName()}.
341332
*
342333
* @return The BOM charset Name or null if no BOM found.
343-
* @throws IOException
344-
* if an error reading the first bytes of the stream occurs.
334+
* @throws IOException if an error reading the first bytes of the stream occurs.
345335
*/
346336
public String getBOMCharsetName() throws IOException {
347-
getBOM();
348337
return byteOrderMark == null ? null : byteOrderMark.getCharsetName();
349338
}
350339

351340
/**
352341
* Tests whether the stream contains one of the specified BOMs.
353342
*
354343
* @return true if the stream has one of the specified BOMs, otherwise false if it does not.
355-
* @throws IOException
356-
* if an error reading the first bytes of the stream occurs.
344+
* @throws IOException if an error reading the first bytes of the stream occurs.
357345
*/
358346
public boolean hasBOM() throws IOException {
359347
return getBOM() != null;
@@ -362,13 +350,10 @@ public boolean hasBOM() throws IOException {
362350
/**
363351
* Tests whether the stream contains the specified BOM.
364352
*
365-
* @param bom
366-
* The BOM to check for.
353+
* @param bom The BOM to check for.
367354
* @return true if the stream has the specified BOM, otherwise false if it does not.
368-
* @throws IllegalArgumentException
369-
* if the BOM is not one the stream is configured to detect.
370-
* @throws IOException
371-
* if an error reading the first bytes of the stream occurs.
355+
* @throws IllegalArgumentException if the BOM is not one the stream is configured to detect.
356+
* @throws IOException if an error reading the first bytes of the stream occurs.
372357
*/
373358
public boolean hasBOM(final ByteOrderMark bom) throws IOException {
374359
if (!bomList.contains(bom)) {
@@ -378,10 +363,9 @@ public boolean hasBOM(final ByteOrderMark bom) throws IOException {
378363
}
379364

380365
/**
381-
* Invokes the delegate's {@code mark(int)} method.
366+
* Invokes the delegate's {@link InputStream#mark(int)} method.
382367
*
383-
* @param readLimit
384-
* read ahead limit.
368+
* @param readLimit read ahead limit.
385369
*/
386370
@Override
387371
public synchronized void mark(final int readLimit) {
@@ -393,9 +377,8 @@ public synchronized void mark(final int readLimit) {
393377
/**
394378
* Checks if the bytes match a BOM.
395379
*
396-
* @param bom
397-
* The BOM.
398-
* @return true if the bytes match the bom, otherwise false.
380+
* @param bom The BOM.
381+
* @return true if the bytes match the BOM, otherwise false.
399382
*/
400383
private boolean matches(final ByteOrderMark bom) {
401384
return bom.matches(firstBytes);
@@ -405,8 +388,7 @@ private boolean matches(final ByteOrderMark bom) {
405388
* Invokes the delegate's {@code read()} method, detecting and optionally skipping BOM.
406389
*
407390
* @return the byte read (excluding BOM) or -1 if the end of stream.
408-
* @throws IOException
409-
* if an I/O error occurs.
391+
* @throws IOException if an I/O error occurs.
410392
*/
411393
@Override
412394
public int read() throws IOException {
@@ -416,37 +398,28 @@ public int read() throws IOException {
416398
}
417399

418400
/**
419-
* Invokes the delegate's {@code read(byte[])} method, detecting and optionally skipping BOM.
401+
* Invokes the delegate's {@link InputStream#read(byte[])} method, detecting and optionally skipping BOM.
420402
*
421-
* @param buf
422-
* the buffer to read the bytes into, never {@code null}
403+
* @param buf the buffer to read the bytes into, never {@code null}
423404
* @return the number of bytes read (excluding BOM) or -1 if the end of stream.
424-
* @throws NullPointerException
425-
* if the buffer is {@code null}
426-
* @throws IOException
427-
* if an I/O error occurs.
405+
* @throws NullPointerException if the buffer is {@code null}
406+
* @throws IOException if an I/O error occurs.
428407
*/
429408
@Override
430409
public int read(final byte[] buf) throws IOException {
431410
return read(buf, 0, buf.length);
432411
}
433412

434413
/**
435-
* Invokes the delegate's {@code read(byte[], int, int)} method, detecting and optionally skipping BOM.
414+
* Invokes the delegate's {@link InputStream#read(byte[], int, int)} method, detecting and optionally skipping BOM.
436415
*
437-
* @param buf
438-
* the buffer to read the bytes into.
439-
* @param off
440-
* The start offset.
441-
* @param len
442-
* The number of bytes to read (excluding BOM).
416+
* @param buf the buffer to read the bytes into.
417+
* @param off The start offset.
418+
* @param len The number of bytes to read (excluding BOM).
443419
* @return the number of bytes read or -1 if the end of stream.
444-
* @throws NullPointerException
445-
* if the buffer is {@code null}
446-
* @throws IndexOutOfBoundsException
447-
* if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code buf.length}
448-
* @throws IOException
449-
* if an I/O error occurs.
420+
* @throws NullPointerException if the buffer is {@code null}.
421+
* @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code buf.length}.
422+
* @throws IOException if an I/O error occurs.
450423
*/
451424
@Override
452425
public int read(final byte[] buf, int off, int len) throws IOException {
@@ -469,6 +442,12 @@ public int read(final byte[] buf, int off, int len) throws IOException {
469442
return secondCount < 0 ? firstCount > 0 ? firstCount : EOF : firstCount + secondCount;
470443
}
471444

445+
/**
446+
* Reads the byte order mark.
447+
*
448+
* @return the byte order mark.
449+
* @throws IOException if an error reading the first bytes of the stream occurs.
450+
*/
472451
private ByteOrderMark readBom() throws IOException {
473452
int fbLength = 0;
474453
// BOMs are sorted from longest to shortest
@@ -504,15 +483,13 @@ private ByteOrderMark readBom() throws IOException {
504483
* @throws IOException if an I/O error occurs.
505484
*/
506485
private int readFirstBytes() throws IOException {
507-
getBOM();
508486
return fbIndex < firstBytes.length ? firstBytes[fbIndex++] : EOF;
509487
}
510488

511489
/**
512-
* Invokes the delegate's {@code reset()} method.
490+
* Invokes the delegate's {@link InputStream#reset()} method.
513491
*
514-
* @throws IOException
515-
* if an I/O error occurs.
492+
* @throws IOException if an I/O error occurs.
516493
*/
517494
@Override
518495
public synchronized void reset() throws IOException {
@@ -524,13 +501,11 @@ public synchronized void reset() throws IOException {
524501
}
525502

526503
/**
527-
* Invokes the delegate's {@code skip(long)} method, detecting and optionally skipping BOM.
504+
* Invokes the delegate's {@link InputStream#skip(long)} method, detecting and optionally skipping BOM.
528505
*
529-
* @param n
530-
* the number of bytes to skip.
506+
* @param n the number of bytes to skip.
531507
* @return the number of bytes to skipped or -1 if the end of stream.
532-
* @throws IOException
533-
* if an I/O error occurs.
508+
* @throws IOException if an I/O error occurs.
534509
*/
535510
@Override
536511
public long skip(final long n) throws IOException {

0 commit comments

Comments
 (0)