• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 4502

13 Mar 2025 12:14PM UTC coverage: 83.089% (+3.4%) from 79.659%
4502

push

github

adangel
[java] Fix crash when parsing class for anonymous class (#5588)

Merge pull request #5588 from oowekyala:fix-anon-class-loading

1912 of 2411 branches covered (79.3%)

Branch coverage included in aggregate %.

29 of 33 new or added lines in 2 files covered. (87.88%)

30 existing lines in 6 files now uncovered.

4559 of 5377 relevant lines covered (84.79%)

14.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.74
/pmd-core/src/main/java/net/sourceforge/pmd/lang/document/Chars.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.document;
6

7

8
import static java.lang.Math.max;
9
import static java.lang.Math.min;
10

11
import java.io.BufferedReader;
12
import java.io.IOException;
13
import java.io.Reader;
14
import java.io.Writer;
15
import java.nio.ByteBuffer;
16
import java.nio.CharBuffer;
17
import java.nio.charset.Charset;
18
import java.util.Iterator;
19
import java.util.regex.Matcher;
20
import java.util.regex.Pattern;
21
import java.util.stream.Stream;
22
import java.util.stream.StreamSupport;
23

24
import org.apache.commons.lang3.StringUtils;
25
import org.checkerframework.checker.nullness.qual.NonNull;
26

27
import net.sourceforge.pmd.util.IteratorUtil.AbstractIterator;
28

29
/**
30
 * View on a string which doesn't copy the array for subsequence operations.
31
 * This view is immutable. Since it uses a string internally it benefits from
32
 * Java 9's compacting feature, it can also be efficiently created from
33
 * a StringBuilder. When confronted with an instance of this interface, please
34
 * don't create substrings unnecessarily. Both {@link #subSequence(int, int)}
35
 * and {@link #slice(int, int)} can cut out a subsequence without copying
36
 * the underlying byte array. The {@link Pattern} API also works perfectly
37
 * on arbitrary {@link CharSequence}s, not just on strings. Lastly some
38
 * methods here provided provide mediated access to the underlying string,
39
 * which for many use cases is much more optimal than using this CharSequence
40
 * directly, eg {@link #appendChars(StringBuilder)}, {@link #writeFully(Writer)}.
41
 *
42
 * @see Chars#wrap(CharSequence) Chars::wrap, the factory method
43
 */
44
public final class Chars implements CharSequence {
45

46
    /**
47
     * An empty Chars instance.
48
     */
49
    public static final Chars EMPTY = new Chars("", 0, 0);
2✔
50

51
    /**
52
     * Special sentinel used by {@link #lines()}.
53
     */
54
    private static final int NOT_TRIED = -2;
55

56
    /**
57
     * See {@link StringUtils#INDEX_NOT_FOUND}.
58
     */
59
    private static final int NOT_FOUND = -1;
60

61
    private final String str;
62
    private final int start;
63
    private final int len;
64

65
    private Chars(String str, int start, int len) {
2✔
66
        validateRangeWithAssert(start, len, str.length());
2✔
67
        this.str = str;
2✔
68
        this.start = start;
2✔
69
        this.len = len;
2✔
70
    }
2✔
71

72
    private int idx(int off) {
73
        return this.start + off;
2✔
74
    }
75

76

77
    /** Whether this slice is the empty string. */
78
    @SuppressWarnings("PMD.MissingOverride") // with Java 15, isEmpty() has been added to java.lang.CharSequence (#4291)
79
    public boolean isEmpty() {
UNCOV
80
        return len == 0;
×
81
    }
82

83

84
    /**
85
     * Wraps the given char sequence into a {@link Chars}. This may
86
     * call {@link CharSequence#toString()}. If the sequence is already
87
     * a {@link Chars}, returns it. This is the main factory method for
88
     * this class. You can eg pass a StringBuilder if you want.
89
     */
90
    public static Chars wrap(CharSequence chars) {
91
        if (chars instanceof Chars) {
2✔
92
            return (Chars) chars;
2✔
93
        } else if (chars.length() == 0) {
2✔
94
            return EMPTY;
2✔
95
        }
96
        return new Chars(chars.toString(), 0, chars.length());
2✔
97
    }
98

99
    /**
100
     * Write all characters of this buffer into the given writer.
101
     *
102
     * @param writer A writer
103
     *
104
     * @throws NullPointerException If the writer is null
105
     */
106
    public void writeFully(@NonNull Writer writer) throws IOException {
107
        write(writer, 0, length());
2✔
108
    }
2✔
109

110
    /**
111
     * Write a range of characters to the given writer.
112
     *
113
     * @param writer A writer
114
     * @param start  Start offset in this CharSequence
115
     * @param count  Number of characters
116
     *
117
     * @throws IOException               If the writer throws
118
     * @throws IndexOutOfBoundsException See {@link Writer#write(int)}
119
     */
120
    public void write(@NonNull Writer writer, int start, int count) throws IOException {
121
        writer.write(str, idx(start), count);
2✔
122
    }
2✔
123

124
    /**
125
     * Copies 'count' characters from index 'srcBegin' into the given array,
126
     * starting at 'dstBegin'.
127
     *
128
     * @param srcBegin Start offset in this CharSequence
129
     * @param cbuf     Character array
130
     * @param count    Number of characters to copy
131
     * @param dstBegin Start index in the array
132
     *
133
     * @throws NullPointerException      If the array is null (may)
134
     * @throws IndexOutOfBoundsException See {@link String#getChars(int, int, char[], int)}
135
     */
136
    public void getChars(int srcBegin, char @NonNull [] cbuf, int dstBegin, int count) {
137
        validateRange(srcBegin, count, length());
2✔
138
        int start = idx(srcBegin);
2✔
139
        str.getChars(start, start + count, cbuf, dstBegin);
2✔
140
    }
2✔
141

142
    /**
143
     * Appends the character range identified by start and end offset into
144
     * the string builder. This is much more efficient than calling
145
     * {@link StringBuilder#append(CharSequence)} with this as the
146
     * parameter, especially on Java 9+.
147
     *
148
     * @param start Start index (inclusive)
149
     * @param end   End index (exclusive)
150
     *
151
     * @throws IndexOutOfBoundsException See {@link StringBuilder#append(CharSequence, int, int)}
152
     */
153
    public void appendChars(StringBuilder sb, int start, int end) {
154
        if (end == 0) {
2!
UNCOV
155
            return;
×
156
        }
157
        sb.append(str, idx(start), idx(end));
2✔
158
    }
2✔
159

160
    /**
161
     * Append this character sequence on the given stringbuilder.
162
     * This is much more efficient than calling {@link StringBuilder#append(CharSequence)}
163
     * with this as the parameter, especially on Java 9+.
164
     *
165
     * @param sb String builder
166
     */
167
    public void appendChars(StringBuilder sb) {
168
        sb.append(str, start, start + len);
2✔
169
    }
2✔
170

171

172
    /**
173
     * Returns the characters of this charsequence encoded with the
174
     * given charset.
175
     */
176
    public ByteBuffer getBytes(Charset charset) {
UNCOV
177
        return charset.encode(CharBuffer.wrap(str, start, start + len));
×
178
    }
179

180
    /**
181
     * See {@link String#indexOf(String, int)}.
182
     */
183
    public int indexOf(String searched, int fromIndex) {
184
        // max index in the string at which the search string may start
185
        final int max = start + len - searched.length();
2✔
186

187
        if (fromIndex < 0 || max < start + fromIndex) {
2✔
188
            return NOT_FOUND;
2✔
189
        } else if (searched.isEmpty()) {
2!
UNCOV
190
            return 0;
×
191
        }
192

193
        final char fst = searched.charAt(0);
2✔
194
        int strpos = str.indexOf(fst, idx(fromIndex));
2✔
195
        while (strpos != NOT_FOUND && strpos <= max) {
2✔
196
            if (str.startsWith(searched, strpos)) {
2✔
197
                return strpos - start;
2✔
198
            }
199
            strpos = str.indexOf(fst, strpos + 1);
2✔
200
        }
201
        return NOT_FOUND;
2✔
202
    }
203

204
    /**
205
     * See {@link String#indexOf(int)}
206
     */
207
    public int indexOf(int ch) {
UNCOV
208
        return indexOf(ch, 0);
×
209
    }
210

211
    /**
212
     * See {@link String#indexOf(int, int)}.
213
     */
214
    public int indexOf(int ch, int fromIndex) {
215
        return indexOf(ch, fromIndex, Integer.MAX_VALUE);
2✔
216
    }
217

218
    /**
219
     * Search a character in a range of this Chars instance.
220
     * @param ch Character to search
221
     * @param fromIndex From index (inclusive)
222
     * @param toIndex To index (exclusive)
223
     * @throws AssertionError if fromIndex &gt; toIndex
224
     */
225
    public int indexOf(int ch, int fromIndex, int toIndex) {
226
        assert fromIndex <= toIndex : "Malformed range " + fromIndex + " > " + toIndex;
2!
227

228
        // there is no restriction on fromIndex, if it is negative,
229
        // the whole string should be searched. If it is greater than
230
        // the length of the string, then we should return -1.
231
        fromIndex = max(fromIndex, 0);
2✔
232
        // same for toindex
233
        toIndex = min(toIndex, len);
2✔
234

235
        if (fromIndex >= len || toIndex <= 0) {
2✔
236
            return NOT_FOUND;
2✔
237
        }
238

239
        if (toIndex == len && start + len == str.length()) {
2✔
240
            // If this slice is a suffix of the underlying string,
241
            // then the indexOf of the string is enough and probably
242
            // faster.
243
            int i = str.indexOf(ch, start + fromIndex);
2✔
244
            return i == NOT_FOUND ? NOT_FOUND : i - start;
2✔
245
        }
246

247
        // we want to avoid searching too far in the string
248
        // so we don't use String#indexOf, as it would be looking
249
        // in the rest of the file too, which in the worst case is
250
        // horrible
251

252
        int max = start + toIndex;
2✔
253
        for (int i = start + fromIndex; i < max; i++) {
2✔
254
            char c = str.charAt(i);
2✔
255
            if (c == ch) {
2✔
256
                return i - start;
2✔
257
            }
258
        }
259
        return NOT_FOUND;
2✔
260
    }
261

262
    /**
263
     * See {@link String#lastIndexOf(int, int)}.
264
     */
265
    public int lastIndexOf(int ch, int fromIndex) {
266
        if (fromIndex < 0 || fromIndex >= len) {
2✔
267
            return NOT_FOUND;
2✔
268
        }
269
        // we want to avoid searching too far in the string
270
        // so we don't use String#indexOf, as it would be looking
271
        // in the rest of the file too, which in the worst case is
272
        // horrible
273

274
        for (int i = start + fromIndex; i >= start; i--) {
2✔
275
            char c = str.charAt(i);
2✔
276
            if (c == ch) {
2✔
277
                return i - start;
2✔
278
            }
279
        }
280
        return NOT_FOUND;
2✔
281
    }
282

283
    /**
284
     * See {@link String#startsWith(String, int)}.
285
     */
286
    public boolean startsWith(String prefix, int fromIndex) {
287
        if (fromIndex < 0 || fromIndex + prefix.length() > len) {
2✔
288
            return false;
2✔
289
        }
290
        return str.startsWith(prefix, idx(fromIndex));
2✔
291
    }
292

293
    /**
294
     * See {@link String#startsWith(String)}.
295
     */
296
    public boolean startsWith(String prefix) {
297
        return startsWith(prefix, 0);
2✔
298
    }
299

300

301
    public boolean startsWith(char prefix, int fromIndex) {
302
        if (fromIndex < 0 || fromIndex + 1 > len) {
2!
303
            return false;
2✔
304
        }
305
        return str.charAt(idx(fromIndex)) == prefix;
2✔
306
    }
307

308
    /**
309
     * See {@link String#endsWith(String)}.
310
     */
311
    public boolean endsWith(String suffix) {
UNCOV
312
        return startsWith(suffix, length() - suffix.length());
×
313
    }
314

315
    /**
316
     * Returns a subsequence which does not start with control characters ({@code <= 32}).
317
     * This is consistent with {@link String#trim()}.
318
     */
319
    public Chars trimStart() {
320
        int i = start;
2✔
321
        int maxIdx = start + len;
2✔
322
        while (i < maxIdx && str.charAt(i) <= 32) {
2!
323
            i++;
2✔
324
        }
325
        i -= start;
2✔
326
        return slice(i, len - i);
2✔
327
    }
328

329
    /**
330
     * Returns a subsequence which does not end with control characters ({@code <= 32}).
331
     * This is consistent with {@link String#trim()}.
332
     */
333
    public Chars trimEnd() {
334
        int i = start + len;
2✔
335
        while (i > start && str.charAt(i - 1) <= 32) {
2✔
336
            i--;
2✔
337
        }
338
        return slice(0, i - start);
2✔
339
    }
340

341
    /**
342
     * Like {@link String#trim()}.
343
     */
344
    public Chars trim() {
345
        return trimStart().trimEnd();
2✔
346
    }
347

348
    /**
349
     * Remove trailing and leading blank lines. The resulting string
350
     * does not end with a line terminator.
351
     */
352
    public Chars trimBlankLines() {
353
        int offsetOfFirstNonBlankChar = length();
2✔
354
        for (int i = 0; i < length(); i++) {
2✔
355
            if (!Character.isWhitespace(charAt(i))) {
2✔
356
                offsetOfFirstNonBlankChar = i;
2✔
357
                break;
2✔
358
            }
359
        }
360
        int offsetOfLastNonBlankChar = 0;
2✔
361
        for (int i = length() - 1; i > offsetOfFirstNonBlankChar; i--) {
2✔
362
            if (!Character.isWhitespace(charAt(i))) {
2✔
363
                offsetOfLastNonBlankChar = i;
2✔
364
                break;
2✔
365
            }
366
        }
367

368
        // look backwards before the first non-blank char
369
        int cutFromInclusive = lastIndexOf('\n', offsetOfFirstNonBlankChar);
2✔
370
        // If firstNonBlankLineStart == -1, ie we're on the first line,
371
        // we want to start at zero: then we add 1 to get 0
372
        // If firstNonBlankLineStart >= 0, then it's the index of the
373
        // \n, we want to cut right after that, so we add 1.
374
        cutFromInclusive += 1;
2✔
375

376
        // look forwards after the last non-blank char
377
        int cutUntilExclusive = indexOf('\n', offsetOfLastNonBlankChar);
2✔
378
        if (cutUntilExclusive == StringUtils.INDEX_NOT_FOUND) {
2✔
379
            cutUntilExclusive = length();
2✔
380
        }
381

382
        return subSequence(cutFromInclusive, cutUntilExclusive);
2✔
383
    }
384

385
    /**
386
     * Remove the suffix if it is present, otherwise returns this.
387
     */
388
    public Chars removeSuffix(String charSeq) {
389
        int trimmedLen = length() - charSeq.length();
2✔
390
        if (startsWith(charSeq, trimmedLen)) {
2✔
391
            return slice(0, trimmedLen);
2✔
392
        }
393
        return this;
2✔
394
    }
395

396
    /**
397
     * Remove the prefix if it is present, otherwise returns this.
398
     */
399
    public Chars removePrefix(String charSeq) {
400
        if (startsWith(charSeq)) {
2✔
401
            return subSequence(charSeq.length(), length());
2✔
402
        }
403
        return this;
2✔
404
    }
405

406

407
    /**
408
     * Returns true if this char sequence is logically equal to the
409
     * parameter. This means they're equal character-by-character. This
410
     * is more general than {@link #equals(Object)}, which will only answer
411
     * true if the parameter is a {@link Chars}.
412
     *
413
     * @param cs         Another char sequence
414
     * @param ignoreCase Whether to ignore case
415
     *
416
     * @return True if both sequences are logically equal
417
     */
418
    public boolean contentEquals(CharSequence cs, boolean ignoreCase) {
419
        if (cs instanceof Chars) {
2✔
420
            Chars chars2 = (Chars) cs;
2✔
421
            return len == chars2.len && str.regionMatches(ignoreCase, start, chars2.str, chars2.start, len);
2✔
422
        } else {
423
            return length() == cs.length() && str.regionMatches(ignoreCase, start, cs.toString(), 0, len);
2!
424
        }
425
    }
426

427
    /**
428
     * Like {@link #contentEquals(CharSequence, boolean)}, considering
429
     * case distinctions.
430
     *
431
     * @param cs A char sequence
432
     *
433
     * @return True if both sequences are logically equal, considering case
434
     */
435
    public boolean contentEquals(CharSequence cs) {
436
        return contentEquals(cs, false);
2✔
437
    }
438

439
    @Override
440
    public int length() {
441
        return len;
2✔
442
    }
443

444
    @Override
445
    public char charAt(int index) {
446
        if (index < 0 || index >= len) {
2✔
447
            throw new StringIndexOutOfBoundsException(index);
2✔
448
        }
449
        return str.charAt(idx(index));
2✔
450
    }
451

452
    @Override
453
    public Chars subSequence(int start, int end) {
454
        return slice(start, end - start);
2✔
455
    }
456

457
    /**
458
     * Returns the subsequence that starts at the given offset and ends
459
     * at the end of this string. Similar to {@link String#substring(int)}.
460
     */
461
    public Chars subSequence(int start) {
462
        return slice(start, len - start);
2✔
463
    }
464

465
    /**
466
     * Slice a region of text.
467
     *
468
     * @param region A region
469
     *
470
     * @return A Chars instance
471
     *
472
     * @throws IndexOutOfBoundsException If the region is not a valid range
473
     */
474
    public Chars slice(TextRegion region) {
475
        return slice(region.getStartOffset(), region.getLength());
2✔
476
    }
477

478
    /**
479
     * Like {@link #subSequence(int, int)} but with offset + length instead
480
     * of start + end.
481
     *
482
     * @param off Start of the slice ({@code 0 <= off < this.length()})
483
     * @param len Length of the slice ({@code 0 <= len <= this.length() - off})
484
     *
485
     * @return A Chars instance
486
     *
487
     * @throws IndexOutOfBoundsException If the parameters are not a valid range
488
     */
489
    public Chars slice(int off, int len) {
490
        validateRange(off, len, this.len);
2✔
491
        if (len == 0) {
2✔
492
            return EMPTY;
2✔
493
        } else if (off == 0 && len == this.len) {
2✔
494
            return this;
2✔
495
        }
496
        return new Chars(str, idx(off), len);
2✔
497
    }
498

499
    /**
500
     * Returns the substring between the given offsets.
501
     * given length.
502
     *
503
     * <p>Note: Unlike slice or subSequence, this method will create a
504
     * new String which involves copying the backing char array. Don't
505
     * use it unnecessarily.
506
     *
507
     * @param start Start offset ({@code 0 <= start < this.length()})
508
     * @param end   End offset ({@code start <= end <= this.length()})
509
     *
510
     * @return A substring
511
     *
512
     * @throws IndexOutOfBoundsException If the parameters are not a valid range
513
     * @see String#substring(int, int)
514
     */
515
    public String substring(int start, int end) {
516
        validateRange(start, end - start, this.len);
2✔
517
        return str.substring(idx(start), idx(end));
2✔
518
    }
519

520
    public String substring(int start) {
UNCOV
521
        return substring(start, len);
×
522
    }
523

524
    private static void validateRangeWithAssert(int off, int len, int bound) {
525
        assert len >= 0 && off >= 0 && off + len <= bound : invalidRange(off, len, bound);
2!
526
    }
2✔
527

528
    private static void validateRange(int off, int len, int bound) {
529
        if (len < 0 || off < 0 || off + len > bound) {
2✔
530
            throw new IndexOutOfBoundsException(invalidRange(off, len, bound));
2✔
531
        }
532
    }
2✔
533

534
    private static String invalidRange(int off, int len, int bound) {
535
        return "Invalid range [" + off + ", " + (off + len) + "[ (length " + len + ") in string of length " + bound;
2✔
536
    }
537

538
    @Override
539
    public @NonNull String toString() {
540
        // this already avoids the copy if start == 0 && len == str.length()
541
        return str.substring(start, start + len);
2✔
542
    }
543

544
    @Override
545
    public boolean equals(Object o) {
546
        return this == o || o instanceof Chars && contentEquals((Chars) o);
2!
547
    }
548

549
    @Override
550
    public int hashCode() {
551
        if (isFullString()) {
2✔
552
            return str.hashCode(); // hashcode is cached on strings
2✔
553
        }
554
        int h = 0;
2✔
555
        for (int i = start, end = start + len; i < end; i++) {
2✔
556
            h = h * 31 + str.charAt(i);
2✔
557
        }
558
        return h;
2✔
559
    }
560

561
    // test only
562
    boolean isFullString() {
563
        return start == 0 && len == str.length();
2!
564
    }
565

566
    /**
567
     * Returns an iterable over the lines of this char sequence. The lines
568
     * are yielded without line separators. Like {@link BufferedReader#readLine()},
569
     * a line delimiter is {@code CR}, {@code LF} or {@code CR+LF}.
570
     */
571
    public Iterable<Chars> lines() {
572
        return () -> new Iterator<Chars>() {
2✔
573
            final int max = len;
2✔
574
            int pos = 0;
2✔
575
            // If those are NOT_TRIED, then we should scan ahead to find them
576
            // If the scan fails then they'll stay -1 forever and won't be tried again.
577
            // This is important to scan in documents where we know there are no
578
            // CR characters, as in our normalized TextFileContent.
579
            int nextCr = NOT_TRIED;
2✔
580
            int nextLf = NOT_TRIED;
2✔
581

582
            @Override
583
            public boolean hasNext() {
584
                return pos < max;
2✔
585
            }
586

587
            @Override
588
            public Chars next() {
589
                final int curPos = pos;
2✔
590
                if (nextCr == NOT_TRIED) {
2✔
591
                    nextCr = indexOf('\r', curPos);
2✔
592
                }
593
                if (nextLf == NOT_TRIED) {
2✔
594
                    nextLf = indexOf('\n', curPos);
2✔
595
                }
596
                final int cr = nextCr;
2✔
597
                final int lf = nextLf;
2✔
598

599
                if (cr != NOT_FOUND && lf != NOT_FOUND) {
2✔
600
                    // found both CR and LF
601
                    int min = min(cr, lf);
2✔
602
                    if (lf == cr + 1) {
2✔
603
                        // CRLF
604
                        pos = lf + 1;
2✔
605
                        nextCr = NOT_TRIED;
2✔
606
                        nextLf = NOT_TRIED;
2✔
607
                    } else {
608
                        pos = min + 1;
2✔
609
                        resetLookahead(cr, min);
2✔
610
                    }
611

612
                    return subSequence(curPos, min);
2✔
613
                } else if (cr == NOT_FOUND && lf == NOT_FOUND) {
2✔
614
                    // no following line terminator, cut until the end
615
                    pos = max;
2✔
616
                    return subSequence(curPos, max);
2✔
617
                } else {
618
                    // lf or cr (exactly one is != -1 and max returns that one)
619
                    int idx = max(cr, lf);
2✔
620
                    resetLookahead(cr, idx);
2✔
621
                    pos = idx + 1;
2✔
622
                    return subSequence(curPos, idx);
2✔
623
                }
624
            }
625

626
            private void resetLookahead(int cr, int idx) {
627
                if (idx == cr) {
2✔
628
                    nextCr = NOT_TRIED;
2✔
629
                } else {
630
                    nextLf = NOT_TRIED;
2✔
631
                }
632
            }
2✔
633
        };
634
    }
635

636
    /**
637
     * Returns a stream of lines yielded by {@link #lines()}.
638
     */
639
    public Stream<Chars> lineStream() {
640
        return StreamSupport.stream(lines().spliterator(), false);
2✔
641
    }
642

643
    /**
644
     * Returns a new stringbuilder containing the whole contents of this
645
     * char sequence.
646
     */
647
    public StringBuilder toStringBuilder() {
648
        StringBuilder sb = new StringBuilder(length());
2✔
649
        appendChars(sb);
2✔
650
        return sb;
2✔
651
    }
652

653
    /**
654
     * Split this slice into subslices, like {@link String#split(String)},
655
     * except it's iterated lazily.
656
     */
657
    public Iterable<Chars> splits(Pattern regex) {
658
        return () -> new AbstractIterator<Chars>() {
2✔
659
            final Matcher matcher = regex.matcher(Chars.this);
2✔
660
            int lastPos = 0;
2✔
661

662
            private boolean shouldRetry() {
663
                if (matcher.find()) {
2✔
664
                    if (matcher.start() == 0 && matcher.end() == 0 && lastPos != len) {
2✔
665
                        return true; // zero length match at the start, we should retry once
2✔
666
                    }
667
                    setNext(subSequence(lastPos, matcher.start()));
2✔
668
                    lastPos = matcher.end();
2✔
669
                } else if (lastPos != len) {
2✔
670
                    setNext(subSequence(lastPos, len));
2✔
671
                } else {
672
                    done();
2✔
673
                }
674
                return false;
2✔
675
            }
676

677
            @Override
678
            protected void computeNext() {
679
                if (matcher.hitEnd()) {
2✔
680
                    done();
2✔
681
                } else if (shouldRetry()) {
2✔
682
                    shouldRetry();
2✔
683
                }
684
            }
2✔
685
        };
686
    }
687

688
    /**
689
     * Returns a new reader for the whole contents of this char sequence.
690
     */
691
    public Reader newReader() {
692
        return new CharsReader(this);
2✔
693
    }
694

695
    private static final class CharsReader extends Reader {
696

697
        private Chars chars;
698
        private int pos;
699
        private final int max;
700
        private int mark = -1;
2✔
701

702
        private CharsReader(Chars chars) {
2✔
703
            this.chars = chars;
2✔
704
            this.pos = chars.start;
2✔
705
            this.max = chars.start + chars.len;
2✔
706
        }
2✔
707

708
        @Override
709
        public int read(char @NonNull [] cbuf, int off, int len) throws IOException {
710
            if (len < 0 || off < 0 || off + len > cbuf.length) {
2✔
711
                throw new IndexOutOfBoundsException();
2✔
712
            }
713
            ensureOpen();
2✔
714
            if (pos >= max) {
2✔
715
                return NOT_FOUND;
2✔
716
            }
717
            int toRead = Integer.min(max - pos, len);
2✔
718
            chars.str.getChars(pos, pos + toRead, cbuf, off);
2✔
719
            pos += toRead;
2✔
720
            return toRead;
2✔
721
        }
722

723
        @Override
724
        public int read() throws IOException {
725
            ensureOpen();
2✔
726
            return pos >= max ? NOT_FOUND : chars.str.charAt(pos++);
2✔
727
        }
728

729
        @Override
730
        public long skip(long n) throws IOException {
731
            ensureOpen();
2✔
732
            int oldPos = pos;
2✔
733
            pos = min(max, pos + (int) n);
2✔
734
            return pos - oldPos;
2✔
735
        }
736

737
        private void ensureOpen() throws IOException {
738
            if (chars == null) {
2✔
739
                throw new IOException("Closed");
2✔
740
            }
741
        }
2✔
742

743
        @Override
744
        public void close() {
745
            chars = null;
2✔
746
        }
2✔
747

748
        @Override
749
        public void mark(int readAheadLimit) {
750
            mark = pos;
2✔
751
        }
2✔
752

753
        @Override
754
        public void reset() throws IOException {
755
            ensureOpen();
2✔
756
            if (mark == -1) {
2✔
757
                throw new IOException("Reader was not marked");
2✔
758
            }
759
            pos = mark;
2✔
760
        }
2✔
761

762
        @Override
763
        public boolean markSupported() {
764
            return true;
2✔
765
        }
766
    }
767
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc