|
| 1 | +package com.thealgorithms.io; |
| 2 | + |
| 3 | +import java.io.ByteArrayInputStream; |
| 4 | +import java.io.IOException; |
| 5 | +import java.io.InputStream; |
| 6 | + |
| 7 | +/** |
| 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> |
| 12 | + * Author: Kumaraswamy B.G (Xoma Dev) |
| 13 | + */ |
| 14 | +public class BufferedReader { |
| 15 | + |
| 16 | + private static final int DEFAULT_BUFFER_SIZE = 5; |
| 17 | + |
| 18 | + /** |
| 19 | + * 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 | + |
| 24 | + private int bufferSize; |
| 25 | + private final byte[] buffer; |
| 26 | + |
| 27 | + /** |
| 28 | + * posRead -> indicates the next byte to read |
| 29 | + */ |
| 30 | + private int posRead = 0, bufferPos = 0; |
| 31 | + |
| 32 | + private boolean foundEof = false; |
| 33 | + |
| 34 | + private InputStream input; |
| 35 | + |
| 36 | + public BufferedReader(byte[] input) throws IOException { |
| 37 | + this(new ByteArrayInputStream(input)); |
| 38 | + } |
| 39 | + |
| 40 | + public BufferedReader(InputStream input) throws IOException { |
| 41 | + this(input, DEFAULT_BUFFER_SIZE); |
| 42 | + } |
| 43 | + |
| 44 | + public BufferedReader(InputStream input, int bufferSize) throws IOException { |
| 45 | + this.input = input; |
| 46 | + if (input.available() == -1) |
| 47 | + throw new IOException("Empty or already closed stream provided"); |
| 48 | + |
| 49 | + this.bufferSize = bufferSize; |
| 50 | + buffer = new byte[bufferSize]; |
| 51 | + } |
| 52 | + |
| 53 | + /** |
| 54 | + * Reads a single byte from the stream |
| 55 | + */ |
| 56 | + public int read() throws IOException { |
| 57 | + if (needsRefill()) { |
| 58 | + if (foundEof) |
| 59 | + return -1; |
| 60 | + // the buffer is empty, or the buffer has |
| 61 | + // been completely read and needs to be refilled |
| 62 | + refill(); |
| 63 | + } |
| 64 | + return buffer[posRead++] & 0xff; // read and un-sign it |
| 65 | + } |
| 66 | + |
| 67 | + /** |
| 68 | + * Number of bytes not yet been read |
| 69 | + */ |
| 70 | + |
| 71 | + public int available() throws IOException { |
| 72 | + int available = input.available(); |
| 73 | + if (needsRefill()) |
| 74 | + // since the block is already empty, |
| 75 | + // we have no responsibility yet |
| 76 | + return available; |
| 77 | + return bufferPos - posRead + available; |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * Returns the next character |
| 82 | + */ |
| 83 | + |
| 84 | + public int peek() throws IOException { |
| 85 | + return peek(1); |
| 86 | + } |
| 87 | + |
| 88 | + /** |
| 89 | + * Peeks and returns a value located at next {n} |
| 90 | + */ |
| 91 | + |
| 92 | + public int peek(int n) throws IOException { |
| 93 | + int available = available(); |
| 94 | + if (n >= available) |
| 95 | + throw new IOException("Out of range, available %d, but trying with %d" |
| 96 | + .formatted(available, n)); |
| 97 | + pushRefreshData(); |
| 98 | + |
| 99 | + if (n >= bufferSize) |
| 100 | + throw new IllegalAccessError("Cannot peek %s, maximum upto %s (Buffer Limit)" |
| 101 | + .formatted(n, bufferSize)); |
| 102 | + return buffer[n]; |
| 103 | + } |
| 104 | + |
| 105 | + /** |
| 106 | + * Removes the already read bytes from the buffer |
| 107 | + * in-order to make space for new bytes to be filled up. |
| 108 | + * <p> |
| 109 | + * This may also do the job to read first time data (whole buffer is empty) |
| 110 | + */ |
| 111 | + |
| 112 | + private void pushRefreshData() throws IOException { |
| 113 | + for (int i = posRead, j = 0; i < bufferSize; i++, j++) |
| 114 | + buffer[j] = buffer[i]; |
| 115 | + |
| 116 | + bufferPos -= posRead; |
| 117 | + posRead = 0; |
| 118 | + |
| 119 | + // fill out the spaces that we've |
| 120 | + // emptied |
| 121 | + justRefill(); |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * Reads one complete block of size {bufferSize} |
| 126 | + * if found eof, the total length of array will |
| 127 | + * be that of what's available |
| 128 | + * |
| 129 | + * @return a completed block |
| 130 | + */ |
| 131 | + public byte[] readBlock() throws IOException { |
| 132 | + pushRefreshData(); |
| 133 | + |
| 134 | + byte[] cloned = new byte[bufferSize]; |
| 135 | + // arraycopy() function is better than clone() |
| 136 | + if (bufferPos >= 0) |
| 137 | + System.arraycopy(buffer, |
| 138 | + 0, |
| 139 | + cloned, |
| 140 | + 0, |
| 141 | + // important to note that, bufferSize does not stay constant |
| 142 | + // once the class is defined. See justRefill() function |
| 143 | + bufferSize); |
| 144 | + // we assume that already a chunk |
| 145 | + // has been read |
| 146 | + refill(); |
| 147 | + return cloned; |
| 148 | + } |
| 149 | + |
| 150 | + private boolean needsRefill() { |
| 151 | + return bufferPos == 0 || posRead == bufferSize; |
| 152 | + } |
| 153 | + |
| 154 | + private void refill() throws IOException { |
| 155 | + posRead = 0; |
| 156 | + bufferPos = 0; |
| 157 | + justRefill(); |
| 158 | + } |
| 159 | + |
| 160 | + private void justRefill() throws IOException { |
| 161 | + assertStreamOpen(); |
| 162 | + |
| 163 | + // try to fill in the maximum we can until |
| 164 | + // we reach EOF |
| 165 | + while (bufferPos < bufferSize) { |
| 166 | + int read = input.read(); |
| 167 | + if (read == -1) { |
| 168 | + // reached end-of-file, no more data left |
| 169 | + // to be read |
| 170 | + foundEof = true; |
| 171 | + // rewrite the BUFFER_SIZE, to know that we've reached |
| 172 | + // EOF when requested refill |
| 173 | + bufferSize = bufferPos; |
| 174 | + } |
| 175 | + buffer[bufferPos++] = (byte) read; |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + private void assertStreamOpen() { |
| 180 | + if (input == null) |
| 181 | + throw new IllegalStateException("Input Stream already closed!"); |
| 182 | + } |
| 183 | + |
| 184 | + public void close() throws IOException { |
| 185 | + if (input != null) { |
| 186 | + try { |
| 187 | + input.close(); |
| 188 | + } finally { |
| 189 | + input = null; |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | +} |
0 commit comments