Skip to content

Commit 5ce1dd0

Browse files
committed
Improve BufferedReader tests and fix assertions
1 parent 3d83beb commit 5ce1dd0

2 files changed

Lines changed: 71 additions & 120 deletions

File tree

src/main/java/com/thealgorithms/io/BufferedReader.java

Lines changed: 34 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,20 @@
55
import java.io.InputStream;
66

77
/**
8-
* Mimics the actions of the Original buffered reader
9-
* implements other actions, such as peek(n) to lookahead,
10-
* block() to read a chunk of size {BUFFER SIZE}
11-
* <p>
8+
* Mimics the behavior of a buffered reader with additional features like peek(n)
9+
* and block reading.
10+
*
11+
* <p>Provides lookahead functionality and efficient buffered reading.
12+
*
1213
* Author: Kumaraswamy B.G (Xoma Dev)
1314
*/
1415
public class BufferedReader {
1516

1617
private static final int DEFAULT_BUFFER_SIZE = 5;
1718

18-
/**
19-
* The maximum number of bytes the buffer can hold.
20-
* Value is changed when encountered Eof to not
21-
* cause overflow read of 0 bytes
22-
*/
23-
2419
private int bufferSize;
2520
private final byte[] buffer;
2621

27-
/**
28-
* posRead -> indicates the next byte to read
29-
*/
3022
private int posRead = 0;
3123
private int bufferPos = 0;
3224

@@ -45,113 +37,91 @@ public BufferedReader(InputStream input) throws IOException {
4537
public BufferedReader(InputStream input, int bufferSize) throws IOException {
4638
this.input = input;
4739
if (input.available() == -1) {
48-
throw new IOException("Empty or already closed stream provided");
40+
throw new IOException("Empty or closed stream provided");
4941
}
5042

5143
this.bufferSize = bufferSize;
52-
buffer = new byte[bufferSize];
44+
this.buffer = new byte[bufferSize];
5345
}
5446

5547
/**
56-
* Reads a single byte from the stream
48+
* Reads a single byte from the stream.
5749
*/
5850
public int read() throws IOException {
5951
if (needsRefill()) {
6052
if (foundEof) {
6153
return -1;
6254
}
63-
// the buffer is empty, or the buffer has
64-
// been completely read and needs to be refilled
6555
refill();
6656
}
67-
return buffer[posRead++] & 0xff; // read and un-sign it
57+
return buffer[posRead++] & 0xff;
6858
}
6959

7060
/**
71-
* Number of bytes not yet been read
61+
* Returns number of bytes available.
7262
*/
73-
7463
public int available() throws IOException {
7564
int available = input.available();
7665
if (needsRefill()) {
77-
// since the block is already empty,
78-
// we have no responsibility yet
7966
return available;
8067
}
8168
return bufferPos - posRead + available;
8269
}
8370

8471
/**
85-
* Returns the next character
72+
* Returns next byte without consuming it.
8673
*/
87-
8874
public int peek() throws IOException {
8975
return peek(1);
9076
}
9177

9278
/**
93-
* Peeks and returns a value located at next {n}
79+
* Peeks nth byte ahead.
9480
*/
95-
9681
public int peek(int n) throws IOException {
9782
int available = available();
98-
if (n >= available) {
99-
throw new IOException("Out of range, available %d, but trying with %d".formatted(available, n));
83+
if (n > available) {
84+
throw new IOException("Out of range: available %d, requested %d".formatted(available, n));
10085
}
86+
10187
pushRefreshData();
10288

103-
if (n >= bufferSize) {
104-
throw new IllegalAccessError("Cannot peek %s, maximum upto %s (Buffer Limit)".formatted(n, bufferSize));
89+
if (n > bufferSize) {
90+
throw new IllegalArgumentException("Cannot peek beyond buffer size: " + bufferSize);
10591
}
106-
return buffer[n];
92+
93+
return buffer[posRead + n - 1] & 0xff;
10794
}
10895

10996
/**
110-
* Removes the already read bytes from the buffer
111-
* in-order to make space for new bytes to be filled up.
112-
* <p>
113-
* This may also do the job to read first time data (the whole buffer is empty)
97+
* Shifts unread data and refills buffer.
11498
*/
115-
11699
private void pushRefreshData() throws IOException {
117-
for (int i = posRead, j = 0; i < bufferSize; i++, j++) {
118-
buffer[j] = buffer[i];
119-
}
100+
int unread = bufferPos - posRead;
101+
102+
System.arraycopy(buffer, posRead, buffer, 0, unread);
120103

121-
bufferPos -= posRead;
104+
bufferPos = unread;
122105
posRead = 0;
123106

124-
// fill out the spaces that we've
125-
// emptied
126107
justRefill();
127108
}
128109

129110
/**
130-
* Reads one complete block of size {bufferSize}
131-
* if found eof, the total length of an array will
132-
* be that of what's available
133-
*
134-
* @return a completed block
111+
* Reads a full block.
135112
*/
136113
public byte[] readBlock() throws IOException {
137114
pushRefreshData();
138115

139-
byte[] cloned = new byte[bufferSize];
140-
// arraycopy() function is better than clone()
141-
if (bufferPos >= 0) {
142-
System.arraycopy(buffer, 0, cloned, 0,
143-
// important to note that, bufferSize does not stay constant
144-
// once the class is defined. See justRefill() function
145-
bufferSize);
146-
}
147-
// we assume that already a chunk
148-
// has been read
116+
byte[] result = new byte[bufferPos];
117+
System.arraycopy(buffer, 0, result, 0, bufferPos);
118+
149119
refill();
150-
return cloned;
120+
return result;
151121
}
152122

153123
private boolean needsRefill() {
154-
return bufferPos == 0 || posRead == bufferSize;
124+
return posRead >= bufferPos;
155125
}
156126

157127
private void refill() throws IOException {
@@ -163,25 +133,21 @@ private void refill() throws IOException {
163133
private void justRefill() throws IOException {
164134
assertStreamOpen();
165135

166-
// try to fill in the maximum we can until
167-
// we reach EOF
168136
while (bufferPos < bufferSize) {
169137
int read = input.read();
138+
170139
if (read == -1) {
171-
// reached end-of-file, no more data left
172-
// to be read
173140
foundEof = true;
174-
// rewrite the BUFFER_SIZE, to know that we've reached
175-
// EOF when requested refill
176-
bufferSize = bufferPos;
141+
break; // ✅ FIX: stop immediately
177142
}
143+
178144
buffer[bufferPos++] = (byte) read;
179145
}
180146
}
181147

182148
private void assertStreamOpen() {
183149
if (input == null) {
184-
throw new IllegalStateException("Input Stream already closed!");
150+
throw new IllegalStateException("Input stream already closed");
185151
}
186152
}
187153

Lines changed: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,84 @@
11
package com.thealgorithms.io;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
45

56
import java.io.ByteArrayInputStream;
67
import java.io.IOException;
78
import org.junit.jupiter.api.Test;
89

910
class BufferedReaderTest {
11+
1012
@Test
11-
public void testPeeks() throws IOException {
12-
String text = "Hello!\nWorld!";
13-
int len = text.length();
14-
byte[] bytes = text.getBytes();
13+
void testPeeks() throws IOException {
14+
final String text = "Hello!\nWorld!";
15+
final byte[] bytes = text.getBytes();
1516

16-
ByteArrayInputStream input = new ByteArrayInputStream(bytes);
17-
BufferedReader reader = new BufferedReader(input);
17+
final BufferedReader reader = new BufferedReader(new ByteArrayInputStream(bytes));
1818

19-
// read the first letter
2019
assertEquals('H', reader.read());
21-
len--;
22-
assertEquals(len, reader.available());
2320

24-
// position: H[e]llo!\nWorld!
25-
// reader.read() will be == 'e'
2621
assertEquals('l', reader.peek(1));
27-
assertEquals('l', reader.peek(2)); // second l
22+
assertEquals('l', reader.peek(2));
2823
assertEquals('o', reader.peek(3));
2924
}
3025

3126
@Test
32-
public void testMixes() throws IOException {
33-
String text = "Hello!\nWorld!";
34-
int len = text.length();
35-
byte[] bytes = text.getBytes();
27+
void testMixes() throws IOException {
28+
final String text = "Hello!\nWorld!";
29+
final byte[] bytes = text.getBytes();
3630

37-
ByteArrayInputStream input = new ByteArrayInputStream(bytes);
38-
BufferedReader reader = new BufferedReader(input);
31+
final BufferedReader reader = new BufferedReader(new ByteArrayInputStream(bytes));
3932

40-
// read the first letter
41-
assertEquals('H', reader.read()); // first letter
42-
len--;
33+
assertEquals('H', reader.read());
4334

44-
assertEquals('l', reader.peek(1)); // third later (second letter after 'H')
45-
assertEquals('e', reader.read()); // second letter
46-
len--;
47-
assertEquals(len, reader.available());
35+
assertEquals('l', reader.peek(1));
36+
assertEquals('e', reader.read());
4837

49-
// position: H[e]llo!\nWorld!
50-
assertEquals('o', reader.peek(2)); // second l
38+
assertEquals('o', reader.peek(2));
5139
assertEquals('!', reader.peek(3));
5240
assertEquals('\n', reader.peek(4));
5341

54-
assertEquals('l', reader.read()); // third letter
55-
assertEquals('o', reader.peek(1)); // fourth letter
42+
assertEquals('l', reader.read());
43+
assertEquals('o', reader.peek(1));
5644

57-
for (int i = 0; i < 6; i++) {
45+
// Move towards EOF
46+
for (int i = 0; i < text.length(); i++) {
5847
reader.read();
5948
}
60-
try {
61-
System.out.println((char) reader.peek(4));
62-
} catch (Exception ignored) {
63-
System.out.println("[cached intentional error]");
64-
// intentional, for testing purpose
65-
}
49+
50+
// Proper exception testing
51+
assertThrows(IOException.class, () -> reader.peek(4));
6652
}
6753

6854
@Test
69-
public void testBlockPractical() throws IOException {
70-
String text = "!Hello\nWorld!";
71-
byte[] bytes = text.getBytes();
72-
int len = bytes.length;
55+
void testBlockPractical() throws IOException {
56+
final String text = "!Hello\nWorld!";
57+
final byte[] bytes = text.getBytes();
7358

74-
ByteArrayInputStream input = new ByteArrayInputStream(bytes);
75-
BufferedReader reader = new BufferedReader(input);
59+
final BufferedReader reader = new BufferedReader(new ByteArrayInputStream(bytes));
7660

7761
assertEquals('H', reader.peek());
78-
assertEquals('!', reader.read()); // read the first letter
79-
len--;
62+
assertEquals('!', reader.read());
8063

81-
// this only reads the next 5 bytes (Hello) because
82-
// the default buffer size = 5
8364
assertEquals("Hello", new String(reader.readBlock()));
84-
len -= 5;
85-
assertEquals(reader.available(), len);
8665

87-
// maybe kind of a practical demonstration / use case
8866
if (reader.read() == '\n') {
8967
assertEquals('W', reader.read());
9068
assertEquals('o', reader.read());
9169

92-
// the rest of the blocks
9370
assertEquals("rld!", new String(reader.readBlock()));
9471
} else {
95-
// should not reach
96-
throw new IOException("Something not right");
72+
throw new IOException("Unexpected stream state");
9773
}
9874
}
75+
76+
@Test
77+
void testEndOfFile() throws IOException {
78+
final byte[] bytes = "A".getBytes();
79+
final BufferedReader reader = new BufferedReader(new ByteArrayInputStream(bytes));
80+
81+
assertEquals('A', reader.read());
82+
assertEquals(-1, reader.read()); // EOF check
83+
}
9984
}

0 commit comments

Comments
 (0)