/**
  * 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<S> mapStyles(UnaryOperator<S> mapper) {
   StyleSpansBuilder<S> builder = new StyleSpansBuilder<>(getSpanCount());
   for (StyleSpan<S> span : this) {
     builder.add(mapper.apply(span.getStyle()), span.getLength());
   }
   return builder.create();
 }
  default StyleSpans<S> concat(StyleSpans<S> that) {
    if (that.length() == 0) {
      return this;
    } else if (this.length() == 0) {
      return that;
    }

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

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

    StyleSpansBuilder<S> 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();
  }
  default StyleSpans<S> prepend(StyleSpan<S> span) {
    if (span.getLength() == 0) {
      return this;
    } else if (length() == 0) {
      return singleton(span);
    }

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

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