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

LearnLib / automatalib / 13138848026

04 Feb 2025 02:53PM UTC coverage: 92.108% (+2.2%) from 89.877%
13138848026

push

github

mtf90
[maven-release-plugin] prepare release automatalib-0.12.0

16609 of 18032 relevant lines covered (92.11%)

1.7 hits per line

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

88.0
/api/src/main/java/net/automatalib/word/Word.java
1
/* Copyright (C) 2013-2025 TU Dortmund University
2
 * This file is part of AutomataLib <https://automatalib.net>.
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 net.automatalib.word;
17

18
import java.io.IOException;
19
import java.util.AbstractList;
20
import java.util.Arrays;
21
import java.util.Collection;
22
import java.util.Collections;
23
import java.util.Comparator;
24
import java.util.List;
25
import java.util.NoSuchElementException;
26
import java.util.Objects;
27
import java.util.Spliterator;
28
import java.util.Spliterators;
29
import java.util.function.Function;
30
import java.util.function.ToIntFunction;
31
import java.util.stream.Collector;
32
import java.util.stream.Stream;
33
import java.util.stream.StreamSupport;
34

35
import net.automatalib.alphabet.Alphabet;
36
import net.automatalib.common.setting.AutomataLibProperty;
37
import net.automatalib.common.setting.AutomataLibSettings;
38
import net.automatalib.common.smartcollection.AWUtil;
39
import net.automatalib.common.smartcollection.ArrayWritable;
40
import net.automatalib.common.smartcollection.IntSeq;
41
import net.automatalib.common.util.string.AbstractPrintable;
42
import org.checkerframework.checker.nullness.qual.Nullable;
43

44
/**
45
 * A word is an ordered sequence of symbols. {@link Word}s are generally immutable, i.e., a single {@link Word} object
46
 * will never change (unless symbol objects are modified, which is however highly discouraged).
47
 * <p>
48
 * This class provides the following static methods for creating words in the most common scenarios: <ul> <li>
49
 * {@link #epsilon()} returns the empty word of length 0 <li> {@link #fromLetter(Object)} turns a single letter into a
50
 * word of length 1 <li> {@link #fromSymbols(Object...)} creates a word from an array of symbols <li>
51
 * {@link #fromArray(Object[], int, int)} creates a word from a subrange of a symbols array <li> {@link #fromList(List)}
52
 * creates a word from a {@link List} of symbols </ul>
53
 * <p>
54
 * Modification operations like {@link #append(Object)} or {@link #concat(Word...)} create new objects, subsequently
55
 * invoking these operations on the respective objects returned is therefore highly inefficient. If words need to be
56
 * dynamically created, a {@link WordBuilder} should be used.
57
 * <p>
58
 * This is an abstract base class for word representations. Implementing classes only need to implement <ul> <li>
59
 * {@link #getSymbol(int)} <li> {@link #length()} </ul>
60
 * <p>
61
 * However, for the sake of efficiency it is highly encouraged to overwrite the other methods as well, providing
62
 * specialized realizations.
63
 *
64
 * @param <I>
65
 *         symbol type
66
 */
67
public abstract class Word<I> extends AbstractPrintable implements ArrayWritable<I>, Iterable<I> {
2✔
68

69
    private static final String EMPTY_WORD_REP;
70
    private static final String WORD_DELIM_LEFT;
71
    private static final String WORD_DELIM_RIGHT;
72
    private static final String WORD_SYMBOL_SEPARATOR;
73
    private static final String WORD_SYMBOL_DELIM_LEFT;
74
    private static final String WORD_SYMBOL_DELIM_RIGHT;
75

76
    static {
77
        AutomataLibSettings settings = AutomataLibSettings.getInstance();
2✔
78
        EMPTY_WORD_REP = settings.getProperty(AutomataLibProperty.WORD_EMPTY_REP, "ε");
2✔
79
        WORD_DELIM_LEFT = settings.getProperty(AutomataLibProperty.WORD_DELIM_LEFT, "");
2✔
80
        WORD_DELIM_RIGHT = settings.getProperty(AutomataLibProperty.WORD_DELIM_RIGHT, "");
2✔
81
        WORD_SYMBOL_SEPARATOR = settings.getProperty(AutomataLibProperty.WORD_SYMBOL_SEPARATOR, " ");
2✔
82
        WORD_SYMBOL_DELIM_LEFT = settings.getProperty(AutomataLibProperty.WORD_SYMBOL_DELIM_LEFT, "");
2✔
83
        WORD_SYMBOL_DELIM_RIGHT = settings.getProperty(AutomataLibProperty.WORD_SYMBOL_DELIM_RIGHT, "");
2✔
84
    }
2✔
85

86
    public static <I> Comparator<Word<I>> canonicalComparator(Comparator<? super I> symComparator) {
87
        return new CanonicalWordComparator<>(symComparator);
1✔
88
    }
89

90
    /**
91
     * Creates a word from an array of symbols.
92
     *
93
     * @param symbols
94
     *         the symbol array
95
     * @param <I>
96
     *         symbol type
97
     *
98
     * @return a word containing the symbols in the specified array
99
     */
100
    @SafeVarargs
101
    public static <I> Word<I> fromSymbols(I... symbols) {
102
        if (symbols.length == 0) {
2✔
103
            return epsilon();
2✔
104
        }
105
        if (symbols.length == 1) {
2✔
106
            return fromLetter(symbols[0]);
2✔
107
        }
108
        return new SharedWord<>(symbols.clone());
2✔
109
    }
110

111
    /**
112
     * Retrieves the empty word.
113
     *
114
     * @param <I>
115
     *         symbol type
116
     *
117
     * @return the empty word.
118
     *
119
     * @see Collections#emptyList()
120
     */
121
    @SuppressWarnings("unchecked")
122
    public static <I> Word<I> epsilon() {
123
        return (Word<I>) EmptyWord.INSTANCE;
2✔
124
    }
125

126
    /**
127
     * Constructs a word from a single letter.
128
     *
129
     * @param letter
130
     *         the letter
131
     * @param <I>
132
     *         symbol type
133
     *
134
     * @return a word consisting of only this letter
135
     */
136
    public static <I> Word<I> fromLetter(I letter) {
137
        return new LetterWord<>(letter);
2✔
138
    }
139

140
    /**
141
     * Creates a word from a subrange of an array of symbols. Note that to ensure immutability, internally a copy of the
142
     * array is made.
143
     *
144
     * @param symbols
145
     *         the symbols array
146
     * @param offset
147
     *         the starting index in the array
148
     * @param length
149
     *         the length of the resulting word (from the starting index on)
150
     * @param <I>
151
     *         symbol type
152
     *
153
     * @return the word consisting of the symbols in the range
154
     */
155
    public static <I> Word<I> fromArray(I[] symbols, int offset, int length) {
156
        if (length == 0) {
2✔
157
            return epsilon();
2✔
158
        }
159
        if (length == 1) {
2✔
160
            return fromLetter(symbols[offset]);
×
161
        }
162
        Object[] array = new Object[length];
2✔
163
        System.arraycopy(symbols, offset, array, 0, length);
2✔
164
        return new SharedWord<>(array);
2✔
165
    }
166

167
    /**
168
     * Creates a word from a list of symbols.
169
     *
170
     * @param symbolList
171
     *         the list of symbols
172
     * @param <I>
173
     *         symbol type
174
     *
175
     * @return the resulting word
176
     */
177
    public static <I> Word<I> fromList(List<? extends I> symbolList) {
178
        int siz = symbolList.size();
2✔
179
        if (siz == 0) {
2✔
180
            return epsilon();
2✔
181
        }
182
        if (siz == 1) {
2✔
183
            return Word.fromLetter(symbolList.get(0));
1✔
184
        }
185
        return new SharedWord<>(symbolList);
2✔
186
    }
187

188
    public static Word<Character> fromString(String str) {
189
        return fromCharSequence(str);
2✔
190
    }
191

192
    public static Word<Character> fromCharSequence(CharSequence cs) {
193
        int len = cs.length();
2✔
194
        Character[] chars = new Character[len];
2✔
195
        for (int i = 0; i < len; i++) {
2✔
196
            chars[i] = cs.charAt(i);
2✔
197
        }
198
        return new SharedWord<>(chars);
2✔
199
    }
200

201
    @SafeVarargs
202
    public static <I> Word<I> fromWords(Word<? extends I>... words) {
203
        return fromWords(Arrays.asList(words));
2✔
204
    }
205

206
    public static <I> Word<I> fromWords(Collection<? extends Word<? extends I>> words) {
207
        int totalLength = 0;
2✔
208
        for (Word<?> w : words) {
2✔
209
            totalLength += w.length();
2✔
210
        }
2✔
211

212
        if (totalLength == 0) {
2✔
213
            return epsilon();
2✔
214
        }
215

216
        Object[] array = new Object[totalLength];
2✔
217

218
        int currOfs = 0;
2✔
219
        for (Word<? extends I> w : words) {
2✔
220
            AWUtil.safeWrite(w, array, currOfs);
2✔
221
            currOfs += w.length();
2✔
222
        }
2✔
223

224
        return new SharedWord<>(array);
2✔
225
    }
226

227
    /**
228
     * Retrieves the length of this word.
229
     *
230
     * @return the length of this word.
231
     */
232
    public abstract int length();
233

234
    /**
235
     * Performs an upcast of the generic type parameter of the word. Since words are immutable, the type parameter
236
     * {@code <I>} is covariant (even though it is not possible to express this in Java), making this a safe operation.
237
     *
238
     * @param word
239
     *         the word to upcast
240
     * @param <I>
241
     *         symbol type
242
     *
243
     * @return the upcasted word (reference identical to {@code word})
244
     */
245
    @SuppressWarnings("unchecked")
246
    public static <I> Word<I> upcast(Word<? extends I> word) {
247
        return (Word<I>) word;
1✔
248
    }
249

250
    @Override
251
    public int hashCode() {
252
        int hash = 5;
1✔
253
        for (I sym : this) {
1✔
254
            hash *= 89;
1✔
255
            hash += (sym != null) ? sym.hashCode() : 0;
1✔
256
        }
1✔
257
        return hash;
1✔
258
    }
259

260
    @Override
261
    public boolean equals(@Nullable Object other) {
262
        if (this == other) {
2✔
263
            return true;
2✔
264
        }
265
        if (!(other instanceof Word)) {
2✔
266
            return false;
2✔
267
        }
268
        Word<?> otherWord = (Word<?>) other;
2✔
269
        int len = otherWord.length();
2✔
270
        if (len != length()) {
2✔
271
            return false;
2✔
272
        }
273
        java.util.Iterator<I> thisIt = iterator();
2✔
274
        java.util.Iterator<?> otherIt = otherWord.iterator();
2✔
275
        while (thisIt.hasNext()) {
2✔
276
            I thisSym = thisIt.next();
2✔
277
            Object otherSym = otherIt.next();
2✔
278
            if (!Objects.equals(thisSym, otherSym)) {
2✔
279
                return false;
2✔
280
            }
281
        }
2✔
282
        return true;
2✔
283
    }
284

285
    @Override
286
    public java.util.Iterator<I> iterator() {
287
        return new Iterator();
2✔
288
    }
289

290
    @Override
291
    public Spliterator<I> spliterator() {
292
        return Spliterators.spliterator(iterator(),
×
293
                                        length(),
×
294
                                        Spliterator.IMMUTABLE | Spliterator.ORDERED | Spliterator.SUBSIZED);
295
    }
296

297
    @Override
298
    public void print(Appendable a) throws IOException {
299
        if (isEmpty()) {
2✔
300
            a.append(EMPTY_WORD_REP);
2✔
301
        } else {
302
            a.append(WORD_DELIM_LEFT);
2✔
303
            java.util.Iterator<? extends I> symIt = iterator();
2✔
304
            assert symIt.hasNext();
2✔
305
            appendSymbol(a, symIt.next());
2✔
306
            while (symIt.hasNext()) {
2✔
307
                a.append(WORD_SYMBOL_SEPARATOR);
2✔
308
                appendSymbol(a, symIt.next());
2✔
309
            }
310
            a.append(WORD_DELIM_RIGHT);
2✔
311
        }
312
    }
2✔
313

314
    /**
315
     * Checks if this word is empty, i.e., contains no symbols.
316
     *
317
     * @return {@code true} if this word is empty, {@code false} otherwise.
318
     */
319
    public boolean isEmpty() {
320
        return length() == 0;
2✔
321
    }
322

323
    private static void appendSymbol(Appendable a, @Nullable Object symbol) throws IOException {
324
        a.append(WORD_SYMBOL_DELIM_LEFT);
2✔
325
        a.append(String.valueOf(symbol));
2✔
326
        a.append(WORD_SYMBOL_DELIM_RIGHT);
2✔
327
    }
2✔
328

329
    public Stream<I> stream() {
330
        return StreamSupport.stream(spliterator(), false);
1✔
331
    }
332

333
    /**
334
     * Retrieves the subword of this word starting at the given index and extending until the end of this word. Calling
335
     * this method is equivalent to calling
336
     * <pre>w.subWord(fromIndex, w.length())</pre>
337
     *
338
     * @param fromIndex
339
     *         the first index, inclusive
340
     *
341
     * @return the word representing the specified subrange
342
     */
343
    public final Word<I> subWord(int fromIndex) {
344
        if (fromIndex <= 0) {
2✔
345
            if (fromIndex == 0) {
2✔
346
                return this;
2✔
347
            }
348
            throw new IndexOutOfBoundsException("Invalid subword range [" + fromIndex + ",)");
×
349
        }
350
        return subWordInternal(fromIndex, length());
2✔
351
    }
352

353
    /**
354
     * Retrieves a word representing the specified subrange of this word. As words are immutable, this function usually
355
     * can be realized quite efficient (implementing classes should take care of this).
356
     *
357
     * @param fromIndex
358
     *         the first index, inclusive.
359
     * @param toIndex
360
     *         the last index, exclusive.
361
     *
362
     * @return the word representing the specified subrange.
363
     */
364
    public final Word<I> subWord(int fromIndex, int toIndex) {
365
        if (fromIndex < 0 || toIndex < fromIndex || toIndex > length()) {
2✔
366
            throw new IndexOutOfBoundsException("Invalid subword range [" + fromIndex + ", " + toIndex + ")");
2✔
367
        }
368

369
        return subWordInternal(fromIndex, toIndex);
2✔
370
    }
371

372
    /**
373
     * Internal subword operation implementation. In contrast to {@link #subWord(int, int)}, no range checks need to be
374
     * performed. As this method is flagged as {@code protected}, implementations may rely on the specified indices
375
     * being valid.
376
     *
377
     * @param fromIndex
378
     *         the first index, inclusive (guaranteed to be valid)
379
     * @param toIndex
380
     *         the last index, exclusive (guaranteed to be valid)
381
     *
382
     * @return the word representing the specified subrange
383
     */
384
    protected Word<I> subWordInternal(int fromIndex, int toIndex) {
385
        int len = toIndex - fromIndex;
×
386
        Object[] array = new Object[len];
×
387
        writeToArray(fromIndex, array, 0, len);
×
388
        return new SharedWord<>(array);
×
389
    }
390

391
    @Override
392
    public void writeToArray(int offset, @Nullable Object[] array, int tgtOffset, int length) {
393
        int idx = offset, arrayIdx = tgtOffset;
×
394

395
        for (int i = length; i > 0; i--) {
×
396
            array[arrayIdx++] = getSymbol(idx++);
×
397
        }
398
    }
×
399

400
    /**
401
     * Return symbol that is at the specified position.
402
     *
403
     * @param index
404
     *         the position
405
     *
406
     * @return symbol at position i.
407
     *
408
     * @throws IndexOutOfBoundsException
409
     *         if there is no symbol with this index.
410
     */
411
    public abstract I getSymbol(int index);
412

413
    @Override
414
    public final int size() {
415
        return length();
2✔
416
    }
417

418
    /**
419
     * Retrieves a {@link List} view on the contents of this word.
420
     *
421
     * @return an unmodifiable list of the contained symbols.
422
     */
423
    public List<I> asList() {
424
        return new AsList();
2✔
425
    }
426

427
    /**
428
     * Retrieves a {@link IntSeq} view on the contents of this word for a given indexing function (e.g. an
429
     * {@link Alphabet}).
430
     *
431
     * @param indexFunction
432
     *         the mapping from symbols to indices
433
     *
434
     * @return an {@link IntSeq} view of the contained symbols.
435
     */
436
    public IntSeq asIntSeq(ToIntFunction<I> indexFunction) {
437
        return new AsIntSeq(indexFunction);
2✔
438
    }
439

440
    /**
441
     * Retrieves the list of all prefixes of this word. In the default implementation, the prefixes are lazily
442
     * instantiated upon the respective calls of {@link List#get(int)} or {@link Iterator#next()}.
443
     *
444
     * @param longestFirst
445
     *         whether to start with the longest prefix (otherwise, the first prefix in the list will be the shortest).
446
     *
447
     * @return a (non-materialized) list containing all prefixes
448
     */
449
    public List<Word<I>> prefixes(boolean longestFirst) {
450
        return new SubwordList<>(this, true, longestFirst);
2✔
451
    }
452

453
    /**
454
     * Retrieves the list of all suffixes of this word. In the default implementation, the suffixes are lazily
455
     * instantiated upon the respective calls of {@link List#get(int)} or {@link Iterator#next()}.
456
     *
457
     * @param longestFirst
458
     *         whether to start with the longest suffix (otherwise, the first suffix in the list will be the shortest).
459
     *
460
     * @return a (non-materialized) list containing all suffix
461
     */
462
    public List<Word<I>> suffixes(boolean longestFirst) {
463
        return new SubwordList<>(this, false, longestFirst);
2✔
464
    }
465

466
    /**
467
     * Retrieves the next word after this in canonical order. Figuratively speaking, if there are {@code k} alphabet
468
     * symbols, one can think of a word of length {@code n} as an {@code n}-digit radix-{@code k} representation of the
469
     * number. The next word in canonical order is the representation for the number represented by this word plus one.
470
     *
471
     * @param sigma
472
     *         the alphabet
473
     *
474
     * @return the next word in canonical order
475
     */
476
    public Word<I> canonicalNext(Alphabet<I> sigma) {
477
        int len = length();
1✔
478
        @Nullable Object[] symbols = new Object[len];
1✔
479
        writeToArray(0, symbols, 0, len);
1✔
480

481
        int alphabetSize = sigma.size();
1✔
482

483
        int i = 0;
1✔
484
        boolean overflow = true;
1✔
485
        for (I sym : this) {
1✔
486
            int nextIdx = (sigma.getSymbolIndex(sym) + 1) % alphabetSize;
1✔
487
            symbols[i++] = sigma.getSymbol(nextIdx);
1✔
488
            if (nextIdx != 0) {
1✔
489
                overflow = false;
1✔
490
                break;
1✔
491
            }
492
        }
1✔
493

494
        while (i < len) {
1✔
495
            symbols[i] = getSymbol(i);
1✔
496
            i++;
1✔
497
        }
498

499
        if (overflow) {
1✔
500
            @Nullable Object[] newSymbols = new Object[len + 1];
1✔
501
            newSymbols[0] = sigma.getSymbol(0);
1✔
502
            System.arraycopy(symbols, 0, newSymbols, 1, len);
1✔
503
            symbols = newSymbols;
1✔
504
        }
505

506
        return new SharedWord<>(symbols);
1✔
507
    }
508

509
    /**
510
     * Retrieves the last symbol of this word.
511
     *
512
     * @return the last symbol of this word.
513
     */
514
    public I lastSymbol() {
515
        return getSymbol(length() - 1);
×
516
    }
517

518
    /**
519
     * Retrieves the first symbol of this word.
520
     *
521
     * @return the first symbol of this word
522
     */
523
    public I firstSymbol() {
524
        return getSymbol(0);
1✔
525
    }
526

527
    /**
528
     * Appends a symbol to this word and returns the result as a new word.
529
     *
530
     * @param symbol
531
     *         the symbol to append
532
     *
533
     * @return the word plus the given symbol
534
     */
535
    public Word<I> append(I symbol) {
536
        int len = length();
2✔
537
        @Nullable Object[] array = new Object[len + 1];
2✔
538
        writeToArray(0, array, 0, len);
2✔
539
        array[len] = symbol;
2✔
540
        return new SharedWord<>(array);
2✔
541
    }
542

543
    /**
544
     * Prepends a symbol to this word and returns the result as a new word.
545
     *
546
     * @param symbol
547
     *         the symbol to prepend
548
     *
549
     * @return the given symbol plus to word.
550
     */
551
    public Word<I> prepend(I symbol) {
552
        int len = length();
2✔
553
        @Nullable Object[] array = new Object[len + 1];
2✔
554
        array[0] = symbol;
2✔
555
        writeToArray(0, array, 1, len);
2✔
556

557
        return new SharedWord<>(array);
2✔
558
    }
559

560
    /**
561
     * Concatenates this word with several other words and returns the result as a new word.
562
     * <p>
563
     * Note that this method cannot be overridden. Implementing classes need to override the
564
     * {@link #concatInternal(Word...)} method instead.
565
     *
566
     * @param words
567
     *         the words to concatenate with this word
568
     *
569
     * @return the result of the concatenation
570
     *
571
     * @see #concatInternal(Word...)
572
     */
573
    @SafeVarargs
574
    public final Word<I> concat(Word<? extends I>... words) {
575
        return concatInternal(words);
2✔
576
    }
577

578
    /**
579
     * Realizes the concatenation of this word with several other words.
580
     *
581
     * @param words
582
     *         the words to concatenate
583
     *
584
     * @return the results of the concatenation
585
     */
586
    @SuppressWarnings("unchecked")
587
    protected Word<I> concatInternal(Word<? extends I>... words) {
588
        if (words.length == 0) {
2✔
589
            return this;
2✔
590
        }
591

592
        int len = length();
2✔
593

594
        int totalSize = len;
2✔
595
        for (Word<? extends I> word : words) {
2✔
596
            totalSize += word.length();
2✔
597
        }
598

599
        Object[] array = new Object[totalSize];
2✔
600
        writeToArray(0, array, 0, len);
2✔
601
        int currOfs = len;
2✔
602
        for (Word<? extends I> w : words) {
2✔
603
            int wLen = w.length();
2✔
604
            w.writeToArray(0, array, currOfs, wLen);
2✔
605
            currOfs += wLen;
2✔
606
        }
607

608
        return new SharedWord<>(array);
2✔
609
    }
610

611
    /**
612
     * Checks if this word is a prefix of another word.
613
     *
614
     * @param other
615
     *         the other word
616
     *
617
     * @return {@code true} if this word is a prefix of the other word, {@code false} otherwise.
618
     */
619
    public boolean isPrefixOf(Word<?> other) {
620
        int len = length(), otherLen = other.length();
2✔
621
        if (otherLen < len) {
2✔
622
            return false;
×
623
        }
624

625
        for (int i = 0; i < len; i++) {
2✔
626
            I sym1 = getSymbol(i);
2✔
627
            Object sym2 = other.getSymbol(i);
2✔
628

629
            if (!Objects.equals(sym1, sym2)) {
2✔
630
                return false;
×
631
            }
632
        }
633
        return true;
2✔
634
    }
635

636
    /**
637
     * Determines the longest common prefix of this word and another word.
638
     *
639
     * @param other
640
     *         the other word
641
     *
642
     * @return the longest common prefix of this word and the other word
643
     */
644
    public Word<I> longestCommonPrefix(Word<?> other) {
645
        int len = length(), otherLen = other.length();
2✔
646
        int maxIdx = Math.min(len, otherLen);
2✔
647

648
        int i = 0;
2✔
649
        while (i < maxIdx) {
2✔
650
            I sym1 = getSymbol(i);
2✔
651
            Object sym2 = other.getSymbol(i);
2✔
652

653
            if (!Objects.equals(sym1, sym2)) {
2✔
654
                break;
1✔
655
            }
656
            i++;
2✔
657
        }
2✔
658

659
        return prefix(i);
2✔
660
    }
661

662
    /**
663
     * Retrieves a prefix of the given length. If {@code length} is negative, then a prefix consisting of all but the
664
     * last {@code -length} symbols is returned.
665
     *
666
     * @param prefixLen
667
     *         the length of the prefix (may be negative, see above).
668
     *
669
     * @return the prefix of the given length.
670
     */
671
    public final Word<I> prefix(int prefixLen) {
672
        final int length = prefixLen < 0 ? length() + prefixLen : prefixLen;
2✔
673
        return subWord(0, length);
2✔
674
    }
675

676
    /**
677
     * Checks if this word is a suffix of another word.
678
     *
679
     * @param other
680
     *         the other word
681
     *
682
     * @return {@code true} if this word is a suffix of the other word, {@code false} otherwise.
683
     */
684
    public boolean isSuffixOf(Word<?> other) {
685
        int len = length(), otherLen = other.length();
2✔
686
        if (otherLen < len) {
2✔
687
            return false;
×
688
        }
689

690
        int ofs = otherLen - len;
2✔
691
        for (int i = 0; i < len; i++) {
2✔
692
            I sym1 = getSymbol(i);
2✔
693
            Object sym2 = other.getSymbol(ofs + i);
2✔
694
            if (!Objects.equals(sym1, sym2)) {
2✔
695
                return false;
×
696
            }
697
        }
698
        return true;
2✔
699
    }
700

701
    /**
702
     * Determines the longest common suffix of this word and another word.
703
     *
704
     * @param other
705
     *         the other word
706
     *
707
     * @return the longest common suffix
708
     */
709
    public Word<I> longestCommonSuffix(Word<?> other) {
710
        int len = length(), otherLen = other.length();
2✔
711
        int minLen = Math.min(len, otherLen);
2✔
712

713
        int idx1 = len, idx2 = otherLen;
2✔
714
        int i = 0;
2✔
715
        while (i < minLen) {
2✔
716
            I sym1 = getSymbol(--idx1);
2✔
717
            Object sym2 = other.getSymbol(--idx2);
2✔
718

719
            if (!Objects.equals(sym1, sym2)) {
2✔
720
                break;
×
721
            }
722

723
            i++;
2✔
724
        }
2✔
725

726
        return suffix(i);
2✔
727
    }
728

729
    /**
730
     * Retrieves a suffix of the given length. If {@code length} is negative, then a suffix consisting of all but the
731
     * first {@code -length} symbols is returned.
732
     *
733
     * @param suffixLen
734
     *         the length of the suffix (may be negative, see above).
735
     *
736
     * @return the suffix of the given length.
737
     */
738
    public final Word<I> suffix(int suffixLen) {
739
        int wordLen = length();
2✔
740
        int startIdx = suffixLen < 0 ? -suffixLen : wordLen - suffixLen;
2✔
741

742
        return subWord(startIdx, wordLen);
2✔
743
    }
744

745
    /**
746
     * Retrieves a "flattened" version of this word, i.e., without any hierarchical structure attached. This can be
747
     * helpful if {@link Word} is subclassed to allow representing, e.g., a concatenation dynamically, but due to
748
     * performance concerns not too many levels of indirection should be introduced.
749
     *
750
     * @return a flattened version of this word.
751
     */
752
    public Word<I> flatten() {
753
        int len = length();
×
754
        Object[] array = new Object[len];
×
755
        writeToArray(0, array, 0, len);
×
756
        return new SharedWord<>(array);
×
757
    }
758

759
    public Word<I> trimmed() {
760
        int len = length();
×
761
        Object[] array = new Object[len];
×
762
        writeToArray(0, array, 0, len);
×
763
        return new SharedWord<>(array);
×
764
    }
765

766
    /**
767
     * Transforms this word into an array of integers, using the specified function for translating an individual symbol
768
     * to an integer.
769
     *
770
     * @param toInt
771
     *         the function for translating symbols to integers
772
     *
773
     * @return an integer-array representation of the word, according to the specified translation function
774
     */
775
    public int[] toIntArray(ToIntFunction<? super I> toInt) {
776
        int len = length();
2✔
777
        int[] result = new int[len];
2✔
778
        int i = 0;
2✔
779
        for (I sym : this) {
2✔
780
            int symIdx = toInt.applyAsInt(sym);
2✔
781
            result[i++] = symIdx;
2✔
782
        }
2✔
783
        return result;
2✔
784
    }
785

786
    /**
787
     * Transforms a word symbol-by-symbol, using the specified transformation function.
788
     *
789
     * @param transformer
790
     *         the transformation function
791
     * @param <T>
792
     *         the target type
793
     *
794
     * @return the transformed word
795
     */
796
    public <T> Word<T> transform(Function<? super I, ? extends T> transformer) {
797
        int len = length();
2✔
798
        @Nullable Object[] array = new Object[len];
2✔
799
        int i = 0;
2✔
800
        for (I symbol : this) {
2✔
801
            array[i++] = transformer.apply(symbol);
2✔
802
        }
2✔
803
        return new SharedWord<>(array);
2✔
804
    }
805

806
    /**
807
     * Returns a {@link Collector} that collects individual symbols (in order) and aggregates them in a {@link Word}.
808
     *
809
     * @param <I>
810
     *         input symbol type
811
     *
812
     * @return a {@link Collector} that collects individual symbols in order and aggregates them in a {@link Word}
813
     */
814
    public static <I> Collector<I, ?, Word<I>> collector() {
815
        return new WordCollector<>();
1✔
816
    }
817

818
    /*
819
     * General word iterator
820
     */
821
    private final class Iterator implements java.util.Iterator<I> {
2✔
822

823
        private int index;
824

825
        @Override
826
        public boolean hasNext() {
827
            return index < Word.this.length();
2✔
828
        }
829

830
        @Override
831
        public I next() {
832
            if (index >= Word.this.length()) {
×
833
                throw new NoSuchElementException();
×
834
            }
835
            return Word.this.getSymbol(index++);
×
836
        }
837
    }
838

839
    /*
840
     * Representing a word as a list.
841
     */
842
    private final class AsList extends AbstractList<I> {
2✔
843

844
        @Override
845
        public I get(int index) {
846
            return Word.this.getSymbol(index);
2✔
847
        }
848

849
        @Override
850
        public java.util.Iterator<I> iterator() {
851
            return Word.this.iterator();
2✔
852
        }
853

854
        @Override
855
        public int size() {
856
            return Word.this.length();
2✔
857
        }
858
    }
859

860
    /*
861
     * Representing a word as an IntSeq.
862
     */
863
    private class AsIntSeq implements IntSeq {
864

865
        private final ToIntFunction<I> indexFunction;
866

867
        AsIntSeq(ToIntFunction<I> indexFunction) {
2✔
868
            this.indexFunction = indexFunction;
2✔
869
        }
2✔
870

871
        @Override
872
        public int size() {
873
            return Word.this.size();
2✔
874
        }
875

876
        @Override
877
        public int get(int index) {
878
            return indexFunction.applyAsInt(Word.this.getSymbol(index));
2✔
879
        }
880

881
        @Override
882
        public String toString() {
883
            return Word.this.toString();
×
884
        }
885
    }
886
}
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