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

amaembo / streamex / #667

02 Sep 2023 01:21PM UTC coverage: 99.462% (-0.2%) from 99.619%
#667

push

amaembo
One more test for Limiter

5733 of 5764 relevant lines covered (99.46%)

0.99 hits per line

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

97.89
/src/main/java/one/util/streamex/Joining.java
1
/*
2
 * Copyright 2015, 2019 StreamEx contributors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package one.util.streamex;
17

18
import java.text.BreakIterator;
19
import java.util.ArrayList;
20
import java.util.Collections;
21
import java.util.List;
22
import java.util.Set;
23
import java.util.function.BiConsumer;
24
import java.util.function.BinaryOperator;
25
import java.util.function.Function;
26
import java.util.function.Predicate;
27
import java.util.function.Supplier;
28
import java.util.stream.Collector;
29
import java.util.stream.Collectors;
30

31
import static one.util.streamex.Internals.alwaysTrue;
32
import static one.util.streamex.Internals.checkNonNegative;
33

34
/**
35
 * An advanced implementation of joining {@link Collector}. This collector is
36
 * capable to join the input {@code CharSequence} elements with given delimiter
37
 * optionally wrapping into given prefix and suffix and optionally limiting the
38
 * length of the resulting string (in Unicode code units, code points or
39
 * grapheme clusters) adding the specified ellipsis sequence. This collector
40
 * supersedes the standard JDK
41
 * {@link Collectors#joining(CharSequence, CharSequence, CharSequence)}
42
 * collectors family.
43
 * 
44
 * <p>
45
 * This collector is <a
46
 * href="package-summary.html#ShortCircuitReduction">short-circuiting</a> when
47
 * the string length is limited in either of ways. Otherwise it's not
48
 * short-circuiting.
49
 * 
50
 * <p>
51
 * Every specific collector represented by this class is immutable, so you can
52
 * share it. A bunch of methods is provided to create a new collector based on
53
 * this one.
54
 * 
55
 * <p>
56
 * To create {@code Joining} collector use {@link #with(CharSequence)} static
57
 * method and specify the delimiter. For further setup use specific instance
58
 * methods which return new {@code Joining} objects like this:
59
 * 
60
 * <pre>{@code
61
 * StreamEx.of(source).collect(Joining.with(", ").wrap("[", "]")
62
 *         .maxCodePoints(100).cutAtWord());
63
 * }</pre>
64
 * 
65
 * <p>
66
 * The intermediate accumulation type of this collector is the implementation
67
 * detail and not exposed to the API. If you want to cast it to
68
 * {@code Collector} type, use ? as accumulator type variable:
69
 * 
70
 * <pre>{@code
71
 * Collector<CharSequence, ?, String> joining = Joining.with(", ");
72
 * }</pre>
73
 * 
74
 * @author Tagir Valeev
75
 * @since 0.4.1
76
 */
77
public class Joining extends CancellableCollector<CharSequence, Joining.Accumulator, String> {
78
    static final class Accumulator {
1✔
79
        final List<CharSequence> data = new ArrayList<>();
1✔
80
        int chars = 0, count = 0;
1✔
81
    }
82

83
    private static final int CUT_ANYWHERE = 0;
84
    private static final int CUT_CODEPOINT = 1;
85
    private static final int CUT_GRAPHEME = 2;
86
    private static final int CUT_WORD = 3;
87
    private static final int CUT_BEFORE_DELIMITER = 4;
88
    private static final int CUT_AFTER_DELIMITER = 5;
89

90
    private static final int LENGTH_CHARS = 0;
91
    private static final int LENGTH_CODEPOINTS = 1;
92
    private static final int LENGTH_GRAPHEMES = 2;
93
    private static final int LENGTH_ELEMENTS = 3;
94

95
    private final String delimiter, ellipsis, prefix, suffix;
96
    private final int cutStrategy, lenStrategy, maxLength;
97
    private int limit, delimCount = -1;
1✔
98

99
    private Joining(String delimiter, String ellipsis, String prefix, String suffix, int cutStrategy, int lenStrategy,
100
            int maxLength) {
1✔
101
        this.delimiter = delimiter;
1✔
102
        this.ellipsis = ellipsis;
1✔
103
        this.prefix = prefix;
1✔
104
        this.suffix = suffix;
1✔
105
        this.cutStrategy = cutStrategy;
1✔
106
        this.lenStrategy = lenStrategy;
1✔
107
        this.maxLength = maxLength;
1✔
108
    }
1✔
109

110
    private void init() {
111
        if (delimCount == -1) {
1✔
112
            limit = maxLength - length(prefix, false) - length(suffix, false);
1✔
113
            delimCount = length(delimiter, false);
1✔
114
        }
115
    }
1✔
116

117
    private int length(CharSequence s, boolean content) {
118
        switch (lenStrategy) {
1✔
119
            case LENGTH_CHARS:
120
                return s.length();
1✔
121
            case LENGTH_CODEPOINTS:
122
                if (s instanceof String)
1✔
123
                    return ((String) s).codePointCount(0, s.length());
1✔
124
                return (int) s.codePoints().count();
1✔
125
            case LENGTH_GRAPHEMES:
126
                BreakIterator bi = BreakIterator.getCharacterInstance();
1✔
127
                bi.setText(s.toString());
1✔
128
                int count = 0;
1✔
129
                for (int end = bi.next(); end != BreakIterator.DONE; end = bi.next())
1✔
130
                    count++;
1✔
131
                return count;
1✔
132
            case LENGTH_ELEMENTS:
133
                return content ? 1 : 0;
1✔
134
            default:
135
                throw new InternalError();
×
136
        }
137
    }
138

139
    private static int copy(char[] buf, int pos, String str) {
140
        str.getChars(0, str.length(), buf, pos);
1✔
141
        return pos + str.length();
1✔
142
    }
143

144
    private int copyCut(char[] buf, int pos, String str, int limit, int cutStrategy) {
145
        if (limit <= 0)
1✔
146
            return pos;
1✔
147
        int endPos = str.length();
1✔
148
        switch (lenStrategy) {
1✔
149
            case LENGTH_CHARS:
150
                if (limit < str.length())
1✔
151
                    endPos = limit;
1✔
152
                break;
153
            case LENGTH_CODEPOINTS:
154
                if (limit < str.codePointCount(0, str.length()))
1✔
155
                    endPos = str.offsetByCodePoints(0, limit);
1✔
156
                break;
157
            case LENGTH_GRAPHEMES:
158
                BreakIterator bi = BreakIterator.getCharacterInstance();
1✔
159
                bi.setText(str);
1✔
160
                int count = limit, end;
1✔
161
                while (true) {
162
                    end = bi.next();
1✔
163
                    if (end == BreakIterator.DONE)
1✔
164
                        break;
1✔
165
                    if (--count == 0) {
1✔
166
                        endPos = end;
1✔
167
                        break;
1✔
168
                    }
169
                }
170
                break;
171
            case LENGTH_ELEMENTS:
172
                break;
×
173
            default:
174
                throw new InternalError();
×
175
        }
176
        if (endPos < str.length()) {
1✔
177
            BreakIterator bi;
178
            switch (cutStrategy) {
1✔
179
            case CUT_BEFORE_DELIMITER:
180
            case CUT_AFTER_DELIMITER:
181
                endPos = 0;
1✔
182
                break;
1✔
183
            case CUT_WORD:
184
                bi = BreakIterator.getWordInstance();
1✔
185
                bi.setText(str);
1✔
186
                endPos = bi.preceding(endPos + 1);
1✔
187
                break;
1✔
188
            case CUT_GRAPHEME:
189
                bi = BreakIterator.getCharacterInstance();
1✔
190
                bi.setText(str);
1✔
191
                endPos = bi.preceding(endPos + 1);
1✔
192
                break;
1✔
193
            case CUT_ANYWHERE:
194
                break;
1✔
195
            case CUT_CODEPOINT:
196
                if (Character.isHighSurrogate(str.charAt(endPos - 1)) && Character.isLowSurrogate(str.charAt(endPos)))
1✔
197
                    endPos--;
1✔
198
                break;
199
            default:
200
                throw new InternalError();
×
201
            }
202
        }
203
        str.getChars(0, endPos, buf, pos);
1✔
204
        return pos + endPos;
1✔
205
    }
206

207
    private String finisherNoOverflow(Accumulator acc) {
208
        char[] buf = new char[acc.chars + prefix.length() + suffix.length()];
1✔
209
        int size = acc.data.size();
1✔
210
        int pos = copy(buf, 0, prefix);
1✔
211
        for (int i = 0; i < size; i++) {
1✔
212
            if (i > 0) {
1✔
213
                pos = copy(buf, pos, delimiter);
1✔
214
            }
215
            pos = copy(buf, pos, acc.data.get(i).toString());
1✔
216
        }
217
        copy(buf, pos, suffix);
1✔
218
        return new String(buf);
1✔
219
    }
220

221
    private Joining withLimit(int lenStrategy, int maxLength) {
222
        checkNonNegative("Length", maxLength);
1✔
223
        return new Joining(delimiter, ellipsis, prefix, suffix, cutStrategy, lenStrategy, maxLength);
1✔
224
    }
225

226
    private Joining withCut(int cutStrategy) {
227
        return new Joining(delimiter, ellipsis, prefix, suffix, cutStrategy, lenStrategy, maxLength);
1✔
228
    }
229

230
    /**
231
     * Returns a {@code Collector} that concatenates the input elements,
232
     * separated by the specified delimiter, in encounter order.
233
     * 
234
     * <p>
235
     * This collector is similar to {@link Collectors#joining(CharSequence)},
236
     * but can be further set up in a flexible way, for example, specifying the
237
     * maximal allowed length of the resulting {@code String}.
238
     *
239
     * @param delimiter the delimiter to be used between each element
240
     * @return A {@code Collector} which concatenates CharSequence elements,
241
     *         separated by the specified delimiter, in encounter order
242
     * @see Collectors#joining(CharSequence)
243
     */
244
    public static Joining with(CharSequence delimiter) {
245
        return new Joining(delimiter.toString(), "...", "", "", CUT_GRAPHEME, LENGTH_CHARS, -1);
1✔
246
    }
247

248
    /**
249
     * Returns a {@code Collector} which behaves like this collector, but
250
     * additionally wraps the result with the specified prefix and suffix.
251
     * 
252
     * <p>
253
     * The collector returned by
254
     * {@code Joining.with(delimiter).wrap(prefix, suffix)} is equivalent to
255
     * {@link Collectors#joining(CharSequence, CharSequence, CharSequence)}, but
256
     * can be further set up in a flexible way, for example, specifying the
257
     * maximal allowed length of the resulting {@code String}.
258
     * 
259
     * <p>
260
     * If length limit is specified for the collector, the prefix length and the
261
     * suffix length are also counted towards this limit. If the length of the
262
     * prefix and the suffix exceed the limit, the resulting collector will not
263
     * accumulate any elements and produce the same output. For example,
264
     * {@code stream.collect(Joining.with(",").wrap("prefix", "suffix").maxChars(9))}
265
     * will produce {@code "prefixsuf"} string regardless of the input stream
266
     * content.
267
     * 
268
     * <p>
269
     * You may wrap several times:
270
     * {@code Joining.with(",").wrap("[", "]").wrap("(", ")")} is equivalent to
271
     * {@code Joining.with(",").wrap("([", "])")}.
272
     * 
273
     * @param prefix the sequence of characters to be used at the beginning of
274
     *        the joined result
275
     * @param suffix the sequence of characters to be used at the end of the
276
     *        joined result
277
     * @return a new {@code Collector} which wraps the result with the specified
278
     *         prefix and suffix.
279
     */
280
    public Joining wrap(CharSequence prefix, CharSequence suffix) {
281
        return new Joining(delimiter, ellipsis, prefix.toString().concat(this.prefix), this.suffix.concat(suffix
1✔
282
                .toString()), cutStrategy, lenStrategy, maxLength);
1✔
283
    }
284

285
    /**
286
     * Returns a {@code Collector} which behaves like this collector, but uses
287
     * the specified ellipsis {@code CharSequence} instead of default
288
     * {@code "..."} when the string limit (if specified) is reached.
289
     * 
290
     * @param ellipsis the sequence of characters to be used at the end of the
291
     *        joined result to designate that not all of the input elements are
292
     *        joined due to the specified string length restriction.
293
     * @return a new {@code Collector} which will use the specified ellipsis
294
     *         instead of current setting.
295
     */
296
    public Joining ellipsis(CharSequence ellipsis) {
297
        return new Joining(delimiter, ellipsis.toString(), prefix, suffix, cutStrategy, lenStrategy, maxLength);
1✔
298
    }
299

300
    /**
301
     * Returns a {@code Collector} which behaves like this collector, but sets
302
     * the maximal length of the resulting string to the specified number of
303
     * UTF-16 characters (or Unicode code units). This setting overwrites any
304
     * limit previously set by {@code maxChars(int)},
305
     * {@link #maxCodePoints(int)}, {@link #maxGraphemes(int)} or
306
     * {@link #maxElements(int)} call.
307
     * 
308
     * <p>
309
     * The {@code String} produced by the resulting collector is guaranteed to
310
     * have {@link String#length() length} which does not exceed the specified
311
     * limit. An ellipsis sequence (by default {@code "..."}) is used to
312
     * designate whether the limit was reached. Use
313
     * {@link #ellipsis(CharSequence)} to set custom ellipsis sequence.
314
     * 
315
     * <p>
316
     * The collector returned by this method is <a
317
     * href="package-summary.html#ShortCircuitReduction">short-circuiting</a>:
318
     * it may not process all the input elements if the limit is reached.
319
     * 
320
     * @param limit the maximal number of UTF-16 characters in the resulting
321
     *        String.
322
     * @return a new {@code Collector} which will produce String no longer than
323
     *         given limit.
324
     */
325
    public Joining maxChars(int limit) {
326
        return withLimit(LENGTH_CHARS, limit);
1✔
327
    }
328

329
    /**
330
     * Returns a {@code Collector} which behaves like this collector, but sets
331
     * the maximal number of Unicode code points of the resulting string. This
332
     * setting overwrites any limit previously set by {@link #maxChars(int)},
333
     * {@code maxCodePoints(int)}, {@link #maxGraphemes(int)} or
334
     * {@link #maxElements(int)} call.
335
     * 
336
     * <p>
337
     * The {@code String} produced by the resulting collector is guaranteed to
338
     * have no more code points than the specified limit. An ellipsis sequence
339
     * (by default {@code "..."}) is used to designate whether the limit was
340
     * reached. Use {@link #ellipsis(CharSequence)} to set custom ellipsis
341
     * sequence.
342
     * 
343
     * <p>
344
     * The collector returned by this method is <a
345
     * href="package-summary.html#ShortCircuitReduction">short-circuiting</a>:
346
     * it may not process all the input elements if the limit is reached.
347
     * 
348
     * @param limit the maximal number of code points in the resulting String.
349
     * @return a new {@code Collector} which will produce String no longer than
350
     *         given limit.
351
     */
352
    public Joining maxCodePoints(int limit) {
353
        return withLimit(LENGTH_CODEPOINTS, limit);
1✔
354
    }
355

356
    /**
357
     * Returns a {@code Collector} which behaves like this collector, but sets
358
     * the maximal number of grapheme clusters. This setting overwrites any
359
     * limit previously set by {@link #maxChars(int)}, {@link #maxCodePoints(int)},
360
     * {@code maxGraphemes(int)} or {@link #maxElements(int)} call.
361
     * 
362
     * <p>
363
     * The grapheme cluster is defined in <a
364
     * href="http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries"
365
     * >Unicode Text Segmentation</a> technical report. Basically, it counts
366
     * base character and the following combining characters as single object.
367
     * The {@code String} produced by the resulting collector is guaranteed to
368
     * have no more grapheme clusters than the specified limit. An ellipsis
369
     * sequence (by default {@code "..."}) is used to designate whether the
370
     * limit was reached. Use {@link #ellipsis(CharSequence)} to set custom
371
     * ellipsis sequence.
372
     * 
373
     * <p>
374
     * The collector returned by this method is <a
375
     * href="package-summary.html#ShortCircuitReduction">short-circuiting</a>:
376
     * it may not process all the input elements if the limit is reached.
377
     * 
378
     * @param limit the maximal number of grapheme clusters in the resulting
379
     *        String.
380
     * @return a new {@code Collector} which will produce String no longer than
381
     *         given limit.
382
     */
383
    public Joining maxGraphemes(int limit) {
384
        return withLimit(LENGTH_GRAPHEMES, limit);
1✔
385
    }
386

387
    /**
388
     * Returns a {@code Collector} which behaves like this collector, but sets
389
     * the maximal number of elements to join. This setting overwrites any limit
390
     * previously set by {@link #maxChars(int)}, {@link #maxCodePoints(int)} or
391
     * {@link #maxGraphemes(int)} or {@code maxElements(int)} call.
392
     * <p>
393
     * The {@code String} produced by the resulting collector is guaranteed to
394
     * have no more input elements than the specified limit. An ellipsis
395
     * sequence (by default {@code "..."}) is used to designate whether the
396
     * limit was reached. Use {@link #ellipsis(CharSequence)} to set custom
397
     * ellipsis sequence. The cutting strategy is mostly irrelevant for this
398
     * mode except {@link #cutBeforeDelimiter()}.
399
     * <p>
400
     * The collector returned by this method is
401
     * <a href="package-summary.html#ShortCircuitReduction">short-circuiting</a>:
402
     * it may not process all the input elements if the limit is reached.
403
     *
404
     * @param limit the maximal number of input elements in the resulting String.
405
     * @return a new {@code Collector} which will produce String no longer than
406
     *         given limit.
407
     * @since 0.6.7
408
     */
409
    public Joining maxElements(int limit) {
410
        return withLimit(LENGTH_ELEMENTS, limit);
1✔
411
    }
412

413
    /**
414
     * Returns a {@code Collector} which behaves like this collector, but cuts
415
     * the resulting string at any point when limit is reached.
416
     * 
417
     * <p>
418
     * The resulting collector will produce {@code String} which length is
419
     * exactly equal to the specified limit if the limit is reached. If used
420
     * with {@link #maxChars(int)}, the resulting string may be cut in the
421
     * middle of surrogate pair.
422
     * 
423
     * @return a new {@code Collector} which cuts the resulting string at any
424
     *         point when limit is reached.
425
     */
426
    public Joining cutAnywhere() {
427
        return withCut(CUT_ANYWHERE);
1✔
428
    }
429

430
    /**
431
     * Returns a {@code Collector} which behaves like this collector, but cuts
432
     * the resulting string between any code points when limit is reached.
433
     * 
434
     * <p>
435
     * The resulting collector will not split the surrogate pair when used with
436
     * {@link #maxChars(int)} or {@link #maxCodePoints(int)}. However it may
437
     * remove the combining character which may result in incorrect rendering of
438
     * the last displayed grapheme.
439
     * 
440
     * @return a new {@code Collector} which cuts the resulting string between
441
     *         code points.
442
     */
443
    public Joining cutAtCodePoint() {
444
        return withCut(CUT_CODEPOINT);
1✔
445
    }
446

447
    /**
448
     * Returns a {@code Collector} which behaves like this collector, but cuts
449
     * the resulting string at grapheme cluster boundary when limit is reached.
450
     * This is the default behavior.
451
     * 
452
     * <p>
453
     * The grapheme cluster is defined in <a
454
     * href="http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries"
455
     * >Unicode Text Segmentation</a> technical report. Thus the resulting
456
     * collector will not split the surrogate pair and will preserve any
457
     * combining characters or remove them with the base character.
458
     * 
459
     * @return a new {@code Collector} which cuts the resulting string at
460
     *         grapheme cluster boundary.
461
     */
462
    public Joining cutAtGrapheme() {
463
        return withCut(CUT_GRAPHEME);
1✔
464
    }
465

466
    /**
467
     * Returns a {@code Collector} which behaves like this collector, but cuts
468
     * the resulting string at word boundary when limit is reached.
469
     * 
470
     * <p>
471
     * The beginning and end of every input stream element or delimiter is
472
     * always considered as word boundary, so the stream of
473
     * {@code "one", "two three"} collected with
474
     * {@code Joining.with("").maxChars(n).ellipsis("").cutAtWord()} may produce
475
     * the following strings depending on {@code n}:
476
     * 
477
     * <pre>{@code
478
     * ""
479
     * "one"
480
     * "onetwo"
481
     * "onetwo "
482
     * "onetwo three"
483
     * }</pre>
484
     * 
485
     * @return a new {@code Collector} which cuts the resulting string at word
486
     *         boundary.
487
     */
488
    public Joining cutAtWord() {
489
        return withCut(CUT_WORD);
1✔
490
    }
491

492
    /**
493
     * Returns a {@code Collector} which behaves like this collector, but cuts
494
     * the resulting string before the delimiter when limit is reached.
495
     * 
496
     * @return a new {@code Collector} which cuts the resulting string at before
497
     *         the delimiter.
498
     */
499
    public Joining cutBeforeDelimiter() {
500
        return withCut(CUT_BEFORE_DELIMITER);
1✔
501
    }
502

503
    /**
504
     * Returns a {@code Collector} which behaves like this collector, but cuts
505
     * the resulting string after the delimiter when limit is reached.
506
     * 
507
     * @return a new {@code Collector} which cuts the resulting string at after
508
     *         the delimiter.
509
     */
510
    public Joining cutAfterDelimiter() {
511
        return withCut(CUT_AFTER_DELIMITER);
1✔
512
    }
513

514
    @Override
515
    public Supplier<Accumulator> supplier() {
516
        return Accumulator::new;
1✔
517
    }
518

519
    @Override
520
    public BiConsumer<Accumulator, CharSequence> accumulator() {
521
        if (maxLength == -1)
1✔
522
            return (acc, str) -> {
1✔
523
                if (!acc.data.isEmpty())
1✔
524
                    acc.chars += delimiter.length();
1✔
525
                acc.chars += str.length();
1✔
526
                acc.data.add(str);
1✔
527
            };
1✔
528
        init();
1✔
529
        return (acc, str) -> {
1✔
530
            if (acc.count <= limit) {
1✔
531
                if (!acc.data.isEmpty()) {
1✔
532
                    acc.chars += delimiter.length();
1✔
533
                    acc.count += delimCount;
1✔
534
                }
535
                acc.chars += str.length();
1✔
536
                acc.count += length(str, true);
1✔
537
                acc.data.add(str);
1✔
538
            }
539
        };
1✔
540
    }
541

542
    @Override
543
    public BinaryOperator<Accumulator> combiner() {
544
        if (maxLength == -1)
1✔
545
            return (acc1, acc2) -> {
1✔
546
                if (acc1.data.isEmpty())
1✔
547
                    return acc2;
1✔
548
                if (acc2.data.isEmpty())
1✔
549
                    return acc1;
1✔
550
                acc1.chars += delimiter.length() + acc2.chars;
1✔
551
                acc1.data.addAll(acc2.data);
1✔
552
                return acc1;
1✔
553
            };
554
        init();
1✔
555
        BiConsumer<Accumulator, CharSequence> accumulator = accumulator();
1✔
556
        return (acc1, acc2) -> {
1✔
557
            if (acc1.data.isEmpty())
1✔
558
                return acc2;
1✔
559
            if (acc2.data.isEmpty())
1✔
560
                return acc1;
1✔
561
            int len = acc1.count + acc2.count + delimCount;
1✔
562
            if (len <= limit) {
1✔
563
                acc1.count = len;
1✔
564
                acc1.chars += delimiter.length() + acc2.chars;
1✔
565
                acc1.data.addAll(acc2.data);
1✔
566
            } else {
567
                for (CharSequence s : acc2.data) {
1✔
568
                    if (acc1.count > limit)
1✔
569
                        break;
1✔
570
                    accumulator.accept(acc1, s);
1✔
571
                }
1✔
572
            }
573
            return acc1;
1✔
574
        };
575
    }
576

577
    @Override
578
    public Function<Accumulator, String> finisher() {
579
        if (maxLength == -1) {
1✔
580
            return this::finisherNoOverflow;
1✔
581
        }
582
        init();
1✔
583
        if (limit <= 0 && lenStrategy != LENGTH_ELEMENTS) {
1✔
584
            char[] buf = new char[prefix.length() + suffix.length()];
1✔
585
            int pos = copyCut(buf, 0, prefix, maxLength, cutStrategy);
1✔
586
            pos = copyCut(buf, pos, suffix, maxLength - length(prefix, false), cutStrategy);
1✔
587
            String result = new String(buf, 0, pos);
1✔
588
            return acc -> result;
1✔
589
        }
590
        return acc -> {
1✔
591
            if (acc.count <= limit)
1✔
592
                return finisherNoOverflow(acc);
1✔
593
            char[] buf = new char[acc.chars + prefix.length() + suffix.length()];
1✔
594
            int size = acc.data.size();
1✔
595
            int pos = copy(buf, 0, prefix);
1✔
596
            int ellipsisCount = length(ellipsis, false);
1✔
597
            int rest = limit - ellipsisCount;
1✔
598
            if (rest < 0) {
1✔
599
                pos = copyCut(buf, pos, ellipsis, limit, CUT_ANYWHERE);
1✔
600
            } else {
601
                for (int i = 0; i < size; i++) {
1✔
602
                    String s = acc.data.get(i).toString();
1✔
603
                    int count = length(s, true);
1✔
604
                    if (i > 0) {
1✔
605
                        if (cutStrategy == CUT_BEFORE_DELIMITER && delimCount + count > rest) {
1✔
606
                            break;
1✔
607
                        }
608
                        if (delimCount > rest) {
1✔
609
                            pos = copyCut(buf, pos, delimiter, rest, cutStrategy);
1✔
610
                            break;
1✔
611
                        }
612
                        rest -= delimCount;
1✔
613
                        pos = copy(buf, pos, delimiter);
1✔
614
                    }
615
                    if (cutStrategy == CUT_AFTER_DELIMITER && delimCount + count > rest) {
1✔
616
                        break;
1✔
617
                    }
618
                    if (count > rest) {
1✔
619
                        pos = copyCut(buf, pos, s, rest, cutStrategy);
1✔
620
                        break;
1✔
621
                    }
622
                    pos = copy(buf, pos, s);
1✔
623
                    rest -= count;
1✔
624
                }
625
                pos = copy(buf, pos, ellipsis);
1✔
626
            }
627
            pos = copy(buf, pos, suffix);
1✔
628
            return new String(buf, 0, pos);
1✔
629
        };
630
    }
631

632
    @Override
633
    public Set<Characteristics> characteristics() {
634
        init();
1✔
635
        if (limit <= 0)
1✔
636
            return Collections.singleton(Characteristics.UNORDERED);
1✔
637
        return Collections.emptySet();
1✔
638
    }
639

640
    @Override
641
    Predicate<Accumulator> finished() {
642
        if (maxLength == -1)
1✔
643
            return null;
1✔
644
        init();
1✔
645
        if (limit <= 0 && lenStrategy != LENGTH_ELEMENTS)
1✔
646
            return alwaysTrue();
1✔
647
        return acc -> acc.count > limit;
1✔
648
    }
649
}
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

© 2025 Coveralls, Inc