Skip to content

Commit 521fcc0

Browse files
committed
Add UnsynchronizedBufferedReader.getPosition()
1 parent 9c5c0e5 commit 521fcc0

2 files changed

Lines changed: 119 additions & 56 deletions

File tree

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

Lines changed: 85 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,17 @@ public class UnsynchronizedBufferedReader extends UnsynchronizedReader {
7777
*/
7878
private char[] buf;
7979

80-
private int pos;
80+
private int bufPos;
8181

8282
private int end;
8383

8484
private int mark = -1;
8585

8686
private int markLimit = -1;
8787

88+
private long pos;
89+
private long posMark;
90+
8891
/**
8992
* Constructs a new BufferedReader on the Reader {@code in}. The buffer gets the default size (8 KB).
9093
*
@@ -113,8 +116,8 @@ public UnsynchronizedBufferedReader(final Reader in, final int size) {
113116
* Peeks at the next input character, refilling the buffer if necessary. If this character is a newline character ("\n"), it is discarded.
114117
*/
115118
final void chompNewline() throws IOException {
116-
if ((pos != end || fillBuf() != EOF) && buf[pos] == LF) {
117-
pos++;
119+
if ((bufPos != end || fillBuf() != EOF) && buf[bufPos] == LF) {
120+
bufPos++;
118121
}
119122
}
120123

@@ -141,12 +144,12 @@ public void close() throws IOException {
141144
private int fillBuf() throws IOException {
142145
// assert(pos == end);
143146

144-
if (mark == EOF || pos - mark >= markLimit) {
147+
if (mark == EOF || bufPos - mark >= markLimit) {
145148
/* mark isn't set or has exceeded its limit. use the whole buffer */
146149
final int result = in.read(buf, 0, buf.length);
147150
if (result > 0) {
148151
mark = -1;
149-
pos = 0;
152+
bufPos = 0;
150153
end = result;
151154
}
152155
return result;
@@ -164,19 +167,49 @@ private int fillBuf() throws IOException {
164167
} else if (mark > 0) {
165168
/* make room by shifting the buffered data to left mark positions */
166169
System.arraycopy(buf, mark, buf, 0, buf.length - mark);
167-
pos -= mark;
170+
bufPos -= mark;
168171
end -= mark;
169172
mark = 0;
170173
}
171174

172175
/* Set the new position and mark position */
173-
final int count = in.read(buf, pos, buf.length - pos);
176+
final int count = in.read(buf, bufPos, buf.length - bufPos);
174177
if (count != EOF) {
175178
end += count;
176179
}
177180
return count;
178181
}
179182

183+
/**
184+
* Gets the character position in the reader.
185+
* <p>
186+
* Returns {@link Long#MAX_VALUE} instead of overflowing.
187+
* </p>
188+
* <p>
189+
* Since this is a Reader, this is the character position, not the byte position (use an InputStream to count bytes).
190+
* </p>
191+
*
192+
* @return the character position in the reader.
193+
* @since 2.23.0
194+
*/
195+
public long getPosition() {
196+
return pos;
197+
}
198+
199+
/**
200+
* Increments the position by a given value, overflow sets the value to {@link Long#MAX_VALUE};
201+
*
202+
* @param v a value.
203+
* @return the new position.
204+
* @since 2.23.0
205+
*/
206+
protected long incPos(final long v) {
207+
// Like Math.addExact(long, long) but clamp to Long.MAX_VALUE.
208+
final long r = pos + v;
209+
// Overflow iff both arguments have the opposite sign of the result.
210+
return pos = ((pos ^ r) & (v ^ r)) < 0 ? Long.MAX_VALUE : r;
211+
}
212+
180213
/**
181214
* Sets a mark position in this reader. The parameter {@code markLimit} indicates how many characters can be read before the mark is invalidated. Calling
182215
* {@link #reset()} will reposition the reader back to the marked position if {@code markLimit} has not been surpassed.
@@ -194,7 +227,8 @@ public void mark(final int markLimit) throws IOException {
194227
}
195228
checkOpen();
196229
this.markLimit = markLimit;
197-
mark = pos;
230+
mark = bufPos;
231+
posMark = pos;
198232
}
199233

200234
/**
@@ -250,8 +284,9 @@ public int peek(final char[] buf) throws IOException {
250284
public int read() throws IOException {
251285
checkOpen();
252286
/* Are there buffered characters available? */
253-
if (pos < end || fillBuf() != EOF) {
254-
return buf[pos++];
287+
if (bufPos < end || fillBuf() != EOF) {
288+
incPos(1);
289+
return buf[bufPos++];
255290
}
256291
return EOF;
257292
}
@@ -281,52 +316,45 @@ public int read(final char[] buffer, int offset, final int length) throws IOExce
281316
if (length == 0) {
282317
return 0;
283318
}
284-
285319
int outstanding = length;
286320
while (outstanding > 0) {
287-
288321
/*
289322
* If there are bytes in the buffer, grab those first.
290323
*/
291-
final int available = end - pos;
324+
final int available = end - bufPos;
292325
if (available > 0) {
293326
final int count = available >= outstanding ? outstanding : available;
294-
System.arraycopy(buf, pos, buffer, offset, count);
295-
pos += count;
327+
System.arraycopy(buf, bufPos, buffer, offset, count);
328+
bufPos += count;
296329
offset += count;
297330
outstanding -= count;
298331
}
299-
300332
/*
301333
* Before attempting to read from the underlying stream, make sure we really, really want to. We won't bother if we're done, or if we've already got
302334
* some bytes and reading from the underlying stream would block.
303335
*/
304336
if (outstanding == 0 || outstanding < length && !in.ready()) {
305337
break;
306338
}
307-
308339
// assert(pos == end);
309-
310340
/*
311341
* If we're unmarked and the requested size is greater than our buffer, read the bytes directly into the caller's buffer. We don't read into smaller
312342
* buffers because that could result in a many reads.
313343
*/
314-
if ((mark == -1 || pos - mark >= markLimit) && outstanding >= buf.length) {
344+
if ((mark == -1 || bufPos - mark >= markLimit) && outstanding >= buf.length) {
315345
final int count = in.read(buffer, offset, outstanding);
316346
if (count > 0) {
317347
outstanding -= count;
318348
mark = -1;
319349
}
320-
321350
break; // assume the source stream gave us all that it could
322351
}
323-
324352
if (fillBuf() == EOF) {
325353
break; // source is exhausted
326354
}
327355
}
328-
329356
final int count = length - outstanding;
357+
incPos(count);
330358
return count > 0 || count == length ? count : EOF;
331359
}
332360

@@ -340,24 +368,24 @@ public int read(final char[] buffer, int offset, final int length) throws IOExce
340368
public String readLine() throws IOException {
341369
checkOpen();
342370
/* has the underlying stream been exhausted? */
343-
if (pos == end && fillBuf() == EOF) {
371+
if (bufPos == end && fillBuf() == EOF) {
344372
return null;
345373
}
346-
for (int charPos = pos; charPos < end; charPos++) {
374+
for (int charPos = bufPos; charPos < end; charPos++) {
347375
final char ch = buf[charPos];
348376
if (ch > CR) {
349377
continue;
350378
}
351379
if (ch == LF) {
352-
final String res = new String(buf, pos, charPos - pos);
353-
pos = charPos + 1;
380+
final String res = new String(buf, bufPos, charPos - bufPos);
381+
bufPos = charPos + 1;
354382
return res;
355383
}
356384
if (ch == CR) {
357-
final String res = new String(buf, pos, charPos - pos);
358-
pos = charPos + 1;
359-
if ((pos < end || fillBuf() != EOF) && buf[pos] == LF) {
360-
pos++;
385+
final String res = new String(buf, bufPos, charPos - bufPos);
386+
bufPos = charPos + 1;
387+
if ((bufPos < end || fillBuf() != EOF) && buf[bufPos] == LF) {
388+
bufPos++;
361389
}
362390
return res;
363391
}
@@ -367,9 +395,9 @@ public String readLine() throws IOException {
367395
final StringBuilder result = new StringBuilder(80);
368396
/* Typical Line Length */
369397

370-
result.append(buf, pos, end - pos);
398+
result.append(buf, bufPos, end - bufPos);
371399
while (true) {
372-
pos = end;
400+
bufPos = end;
373401

374402
/* Are there buffered characters available? */
375403
if (eol == LF) {
@@ -380,19 +408,19 @@ public String readLine() throws IOException {
380408
// characters or null.
381409
return result.length() > 0 || eol != NUL ? result.toString() : null;
382410
}
383-
for (int charPos = pos; charPos < end; charPos++) {
411+
for (int charPos = bufPos; charPos < end; charPos++) {
384412
final char c = buf[charPos];
385413
if (eol != NUL) {
386414
if (eol == CR && c == LF) {
387-
if (charPos > pos) {
388-
result.append(buf, pos, charPos - pos - 1);
415+
if (charPos > bufPos) {
416+
result.append(buf, bufPos, charPos - bufPos - 1);
389417
}
390-
pos = charPos + 1;
418+
bufPos = charPos + 1;
391419
} else {
392-
if (charPos > pos) {
393-
result.append(buf, pos, charPos - pos - 1);
420+
if (charPos > bufPos) {
421+
result.append(buf, bufPos, charPos - bufPos - 1);
394422
}
395-
pos = charPos;
423+
bufPos = charPos;
396424
}
397425
return result.toString();
398426
}
@@ -401,9 +429,9 @@ public String readLine() throws IOException {
401429
}
402430
}
403431
if (eol == NUL) {
404-
result.append(buf, pos, end - pos);
432+
result.append(buf, bufPos, end - bufPos);
405433
} else {
406-
result.append(buf, pos, end - pos - 1);
434+
result.append(buf, bufPos, end - bufPos - 1);
407435
}
408436
}
409437
}
@@ -420,7 +448,7 @@ public String readLine() throws IOException {
420448
@Override
421449
public boolean ready() throws IOException {
422450
checkOpen();
423-
return end - pos > 0 || in.ready();
451+
return end - bufPos > 0 || in.ready();
424452
}
425453

426454
/**
@@ -436,7 +464,8 @@ public void reset() throws IOException {
436464
if (mark == -1) {
437465
throw new IOException("mark == -1");
438466
}
439-
pos = mark;
467+
bufPos = mark;
468+
pos = posMark;
440469
}
441470

442471
/**
@@ -460,28 +489,28 @@ public long skip(final long amount) throws IOException {
460489
if (amount < 1) {
461490
return 0;
462491
}
463-
if (end - pos >= amount) {
464-
pos += Math.toIntExact(amount);
465-
return amount;
492+
if (end - bufPos >= amount) {
493+
bufPos += Math.toIntExact(amount);
494+
return incPos(amount);
466495
}
467-
468-
long read = end - pos;
469-
pos = end;
496+
long read = end - bufPos;
497+
bufPos = end;
470498
while (read < amount) {
471499
if (fillBuf() == EOF) {
472-
return read;
500+
return incPos(read);
473501
}
474-
if (end - pos >= amount - read) {
475-
pos += Math.toIntExact(amount - read);
476-
return amount;
502+
if (end - bufPos >= amount - read) {
503+
bufPos += Math.toIntExact(amount - read);
504+
return incPos(amount);
477505
}
478506
// Couldn't get all the characters, skip what we read
479-
read += end - pos;
480-
pos = end;
507+
read += end - bufPos;
508+
bufPos = end;
481509
}
482-
return amount;
510+
return incPos(amount);
483511
}
484512

513+
485514
/**
486515
* Unwraps this instance by returning the underlying {@link Reader}.
487516
* <p>

src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,40 @@ void testEmptyInput() throws Exception {
152152
}
153153
}
154154

155+
@Test
156+
void testGetPosition() throws IOException {
157+
// new reader
158+
br = new UnsynchronizedBufferedReader(new StringReader(testString));
159+
assertEquals(0, br.getPosition());
160+
br.read();
161+
assertEquals(1, br.getPosition());
162+
br.read(new char[1]);
163+
assertEquals(2, br.getPosition());
164+
br.read(new char[1], 0, 1);
165+
assertEquals(3, br.getPosition());
166+
br.read(new char[2], 1, 1);
167+
assertEquals(4, br.getPosition());
168+
br.read(new char[10], 0, 10);
169+
assertEquals(14, br.getPosition());
170+
IOUtils.toString(br);
171+
assertEquals(testString.length(), br.getPosition());
172+
br.close();
173+
// new reader
174+
br = new UnsynchronizedBufferedReader(new StringReader(testString));
175+
br.mark(testString.length());
176+
assertEquals(testString, IOUtils.toString(br));
177+
assertEquals(testString.length(), br.getPosition());
178+
br.reset();
179+
assertEquals(testString, IOUtils.toString(br));
180+
assertEquals(testString.length(), br.getPosition());
181+
// new reader
182+
br = new UnsynchronizedBufferedReader(new StringReader(testString));
183+
br.skip(1);
184+
assertEquals(1, br.getPosition());
185+
br.skip(Integer.MAX_VALUE);
186+
assertEquals(testString.length(), br.getPosition());
187+
}
188+
155189
@Test
156190
void testIllegalSize() throws Exception {
157191
assertThrows(IllegalArgumentException.class, () -> new UnsynchronizedBufferedReader(new StringReader(""), 0));

0 commit comments

Comments
 (0)