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

amaembo / streamex / #677

02 Nov 2024 08:50AM UTC coverage: 99.673%. Remained the same
#677

push

amaembo
Optimize imports

5786 of 5805 relevant lines covered (99.67%)

1.0 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, 2024 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.*;
24
import java.util.stream.Collector;
25
import java.util.stream.Collectors;
26

27
import static one.util.streamex.Internals.alwaysTrue;
28
import static one.util.streamex.Internals.checkNonNegative;
29

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

79
    private static final int CUT_ANYWHERE = 0;
80
    private static final int CUT_CODEPOINT = 1;
81
    private static final int CUT_GRAPHEME = 2;
82
    private static final int CUT_WORD = 3;
83
    private static final int CUT_BEFORE_DELIMITER = 4;
84
    private static final int CUT_AFTER_DELIMITER = 5;
85

86
    private static final int LENGTH_CHARS = 0;
87
    private static final int LENGTH_CODEPOINTS = 1;
88
    private static final int LENGTH_GRAPHEMES = 2;
89
    private static final int LENGTH_ELEMENTS = 3;
90

91
    private final String delimiter, ellipsis, prefix, suffix;
92
    private final int cutStrategy, lenStrategy, maxLength;
93
    private int limit, delimCount = -1;
1✔
94

95
    private Joining(String delimiter, String ellipsis, String prefix, String suffix, int cutStrategy, int lenStrategy,
96
            int maxLength) {
1✔
97
        this.delimiter = delimiter;
1✔
98
        this.ellipsis = ellipsis;
1✔
99
        this.prefix = prefix;
1✔
100
        this.suffix = suffix;
1✔
101
        this.cutStrategy = cutStrategy;
1✔
102
        this.lenStrategy = lenStrategy;
1✔
103
        this.maxLength = maxLength;
1✔
104
    }
1✔
105

106
    private void init() {
107
        if (delimCount == -1) {
1✔
108
            limit = maxLength - length(prefix, false) - length(suffix, false);
1✔
109
            delimCount = length(delimiter, false);
1✔
110
        }
111
    }
1✔
112

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

135
    private static int copy(char[] buf, int pos, String str) {
136
        str.getChars(0, str.length(), buf, pos);
1✔
137
        return pos + str.length();
1✔
138
    }
139

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

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

217
    private Joining withLimit(int lenStrategy, int maxLength) {
218
        checkNonNegative("Length", maxLength);
1✔
219
        return new Joining(delimiter, ellipsis, prefix, suffix, cutStrategy, lenStrategy, maxLength);
1✔
220
    }
221

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

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

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

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

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

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

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

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

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

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

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

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

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

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

510
    @Override
511
    public Supplier<Accumulator> supplier() {
512
        return Accumulator::new;
1✔
513
    }
514

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

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

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

628
    @Override
629
    public Set<Characteristics> characteristics() {
630
        init();
1✔
631
        if (limit <= 0)
1✔
632
            return Collections.singleton(Characteristics.UNORDERED);
1✔
633
        return Collections.emptySet();
1✔
634
    }
635

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