SegmentsImpl.java

// © 2025 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html

package com.ibm.icu.segmenter;

import com.ibm.icu.text.BreakIterator;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

class SegmentsImpl implements Segments {

    private CharSequence source;

    private BreakIterator breakIterPrototype;

    SegmentsImpl(BreakIterator breakIter, CharSequence source) {
        this.source = source;

        // We are creating a clone of the Segmenter's prototype BreakIterator field so that this
        // concrete Segments object can avoid sharing state with the other Segments object instances
        // that get spawned from the Segmenter. This allows difference source CharSequences to be
        // used
        // in each Segments object.
        //
        // In turn, the cloned BreakIterator becomes a prototype to be stored in the Segments
        // object,
        // which then gets cloned and used in each of the Segments APIs' implementations. The second
        // level of cloning that happens when the Segments object's local BreakIterator prototype
        // gets cloned allows the iteration state to be separate whenever an Segments API is called.
        // Otherwise, there is a chance that multiple API calls on the same Segments object might
        // mutate the same position/index, if done concurrently.
        breakIterPrototype = breakIter.clone();
        // It's okay to perform .setText on the object that we want to clone later because we should
        // then not have to call .setText on the clones.
        breakIterPrototype.setText(source);
    }

    @Override
    public Segment segmentAt(int i) {
        BreakIterator breakIter = breakIterPrototype.clone();
        int start;
        int limit;

        if (i < 0 || i >= source.length()) {
            throw new IndexOutOfBoundsException(i);
        }

        boolean isBoundary = breakIter.isBoundary(i);

        if (isBoundary) {
            start = i;
            limit = breakIter.next();
        } else {
            // BreakIterator.isBoundary(i) will advance forwards to the next boundary if the
            // argument
            // is not a boundary.
            limit = breakIter.current();
            start = breakIter.previous();
        }

        assert start != BreakIterator.DONE && limit != BreakIterator.DONE;

        return new Segment(start, limit, source);
    }

    @Override
    public boolean isBoundary(int i) {
        return breakIterPrototype.clone().isBoundary(i);
    }

    @Override
    public Stream<Segment> segmentsFrom(int i) {
        BreakIterator breakIter = breakIterPrototype.clone();

        // create a Stream from a Spliterator of an Iterable so that the Stream can be lazy, not
        // eager
        SegmentIterable iterable =
                new SegmentIterable(breakIter, IterationDirection.FORWARDS, i, source);
        return StreamSupport.stream(iterable.spliterator(), false);
    }

    @Override
    public Stream<Segment> segmentsBefore(int i) {
        BreakIterator breakIter = breakIterPrototype.clone();

        // create a Stream from a Spliterator of an Iterable so that the Stream can be lazy, not
        // eager
        SegmentIterable iterable =
                new SegmentIterable(breakIter, IterationDirection.BACKWARDS, i, source);
        return StreamSupport.stream(iterable.spliterator(), false);
    }

    @Override
    public IntStream boundariesAfter(int i) {
        BreakIterator breakIter = breakIterPrototype.clone();

        // create a Stream from a Spliterator of an Iterable so that the Stream can be lazy, not
        // eager
        return StreamSupport.intStream(
                new BoundarySpliterator(breakIter, source, IterationDirection.FORWARDS, i), false);
    }

    @Override
    public IntStream boundariesBackFrom(int i) {
        BreakIterator breakIter = breakIterPrototype.clone();
        // create a Stream from a Spliterator of an Iterable so that the Stream can be lazy, not
        // eager
        return StreamSupport.intStream(
                new BoundarySpliterator(breakIter, source, IterationDirection.BACKWARDS, i), false);
    }
}