All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fxmisc.richtext.model.StyleSpans Maven / Gradle / Ivy

There is a newer version: 0.11.3
Show newest version
package org.fxmisc.richtext.model;

import static org.fxmisc.richtext.model.TwoDimensional.Bias.*;

import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Essentially, a list of {@link StyleSpan} objects.
 *
 * @param  the style type
 */
public interface StyleSpans extends Iterable>, TwoDimensional {

    /**
     * Creates a {@link StyleSpans} object that only contains one {@link StyleSpan} object.
     */
    static  StyleSpans singleton(S style, int length) {
        return singleton(new StyleSpan<>(style, length));
    }

    /**
     * Creates a {@link StyleSpans} object that only contains one {@link StyleSpan} object.
     */
    static  StyleSpans singleton(StyleSpan span) {
        return new SingletonSpans<>(span);
    }

    int length();
    int getSpanCount();
    StyleSpan getStyleSpan(int index);

    /**
     * Two {@code StyleSpans} objects are considered equal if they contain equal
     * number of {@code StyleSpan}s and the {@code StyleSpan}s are pairwise
     * equal.
     */
    @Override
    public boolean equals(Object other);

    @Override
    default Iterator> iterator() {
        return new Iterator>() {
            private int nextToReturn = 0;
            private final int spanCount = getSpanCount();

            @Override
            public boolean hasNext() {
                return nextToReturn < spanCount;
            }

            @Override
            public StyleSpan next() {
                return getStyleSpan(nextToReturn++);
            }
        };
    }

    /**
     * Appends the given style to the end of the list of {@link StyleSpan}.
     */
    default StyleSpans append(S style, int length) {
        return append(new StyleSpan<>(style, length));
    }

    /**
     * Appends the given style to the end of the list of {@link StyleSpan}.
     */
    default StyleSpans append(StyleSpan span) {
        if(span.getLength() == 0) {
            return this;
        } else if(length() == 0) {
            return singleton(span);
        }

        int lastIdx = getSpanCount() - 1;
        StyleSpan myLastSpan = getStyleSpan(lastIdx);
        if(Objects.equals(myLastSpan.getStyle(), span.getStyle())) {
            StyleSpan newLastSpan = new StyleSpan<>(span.getStyle(), myLastSpan.getLength() + span.getLength());
            return new UpdatedSpans<>(this, lastIdx, newLastSpan);
        } else {
            return new AppendedSpans<>(this, span);
        }
    }

    /**
     * Prepends the given style to the start of the list of {@link StyleSpan}.
     */
    default StyleSpans prepend(S style, int length) {
        return prepend(new StyleSpan<>(style, length));
    }

    /**
     * Prepends the given style to the start of the list of {@link StyleSpan}.
     */
    default StyleSpans prepend(StyleSpan span) {
        if(span.getLength() == 0) {
            return this;
        } else if(length() == 0) {
            return singleton(span);
        }

        StyleSpan myFirstSpan = getStyleSpan(0);
        if(Objects.equals(span.getStyle(), myFirstSpan.getStyle())) {
            StyleSpan newFirstSpan = new StyleSpan<>(span.getStyle(), span.getLength() + myFirstSpan.getLength());
            return new UpdatedSpans<>(this, 0, newFirstSpan);
        } else {
            return new PrependedSpans<>(this, span);
        }
    }

    /**
     * Same as {@link java.util.List#subList(int, int)}
     */
    default StyleSpans subView(int from, int to) {
        Position start = offsetToPosition(from, Forward);
        Position end = to > from
                ? start.offsetBy(to - from, Backward)
                : start;
        return subView(start, end);
    }

    /**
     * Same as {@link java.util.List#subList(int, int)}, except that the arguments are two dimensional.
     */
    default StyleSpans subView(Position from, Position to) {
        return new SubSpans<>(this, from, to);
    }

    default StyleSpans concat(StyleSpans that) {
        if(that.length() == 0) {
            return this;
        } else if(this.length() == 0) {
            return that;
        }

        int n1 = this.getSpanCount();
        int n2 = that.getSpanCount();

        StyleSpan myLast = this.getStyleSpan(n1 - 1);
        StyleSpan theirFirst = that.getStyleSpan(0);

        StyleSpansBuilder builder;
        if(Objects.equals(myLast.getStyle(), theirFirst.getStyle())) {
            builder = new StyleSpansBuilder<>(n1 + n2 - 1);
            for(int i = 0; i < n1 - 1; ++i) {
                builder.add(this.getStyleSpan(i));
            }
            builder.add(myLast.getStyle(), myLast.getLength() + theirFirst.getLength());
            for(int i = 1; i < n2; ++i) {
                builder.add(that.getStyleSpan(i));
            }
        } else {
            builder = new StyleSpansBuilder<>(n1 + n2);
            builder.addAll(this, n1);
            builder.addAll(that, n2);
        }

        return builder.create();
    }

    /**
     * Returns a new {@code StyleSpans} object that has the same total length
     * as this StyleSpans and style of every span is mapped by the given
     * function. Adjacent style spans whose style mapped to the same value are
     * merged into one. As a consequence, the returned StyleSpans might have
     * fewer style spans than this StyleSpans.
     * @param mapper function to calculate new style
     * @return StyleSpans with replaced styles.
     */
    default StyleSpans mapStyles(UnaryOperator mapper) {
        StyleSpansBuilder builder = new StyleSpansBuilder<>(getSpanCount());
        for(StyleSpan span: this) {
            builder.add(mapper.apply(span.getStyle()), span.getLength());
        }
        return builder.create();
    }

    /**
     * Applies the given bifunction {@code f} to this object's {@link StyleSpan} objects and
     * {@code that} {@link StyleSpan} objects and stores the result in the returned {@link StyleSpans} object.
     */
    default StyleSpans overlay(StyleSpans that, BiFunction f) {
        return StyleSpansBuilder.overlay(this, that, f);
    }

    /**
     * Returns a stream of just this list of {@link StyleSpan}'s styles.
     */
    default Stream styleStream() {
        return stream().map(StyleSpan::getStyle);
    }

    /**
     * Returns a stream of this list' {@link StyleSpan} objects.
     */
    default Stream> stream() {
        Spliterator> spliterator = new Spliterator>() {
            private final Iterator> iterator = iterator();

            @Override
            public boolean tryAdvance(Consumer> action) {
                if(iterator.hasNext()) {
                    action.accept(iterator.next());
                    return true;
                } else {
                    return false;
                }
            }

            @Override
            public Spliterator> trySplit() {
                return null;
            }

            @Override
            public long estimateSize() {
                return getSpanCount();
            }

            @Override
            public int characteristics() {
                return Spliterator.IMMUTABLE | Spliterator.SIZED;
            }
        };

        return StreamSupport.stream(spliterator, false);
    }
}