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

javaslang.collection.CharSeq Maven / Gradle / Ivy

There is a newer version: 0.9.0
Show newest version
/*     / \____  _    _  ____   ______  / \ ____  __    _______
 *    /  /    \/ \  / \/    \ /  /\__\/  //    \/  \  //  /\__\   JΛVΛSLΛNG
 *  _/  /  /\  \  \/  /  /\  \\__\\  \  //  /\  \ /\\/ \ /__\ \   Copyright 2014-2016 Javaslang, http://javaslang.io
 * /___/\_/  \_/\____/\_/  \_/\__\/__/\__\_/  \_//  \__/\_____/   Licensed under the Apache License, Version 2.0
 */
package javaslang.collection;

import javaslang.*;
import javaslang.collection.CharSeqModule.Combinations;
import javaslang.control.Option;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.HashSet;
import java.util.function.*;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collector;

/**
 * The CharSeq (read: character sequence) collection essentially is a rich String wrapper having all operations
 * we know from the functional Javaslang collections.
 *
 * @author Ruslan Sennov, Daniel Dietrich
 * @since 2.0.0
 */
public final class CharSeq implements Kind1, CharSequence, IndexedSeq, Serializable {

    private static final long serialVersionUID = 1L;

    private static final CharSeq EMPTY = new CharSeq("");

    private final String back;

    private CharSeq(String javaString) {
        this.back = javaString;
    }

    public static CharSeq empty() {
        return EMPTY;
    }

    /**
     * Returns a {@link java.util.stream.Collector} which may be used in conjunction with
     * {@link java.util.stream.Stream#collect(java.util.stream.Collector)} to obtain a {@link CharSeq}.
     *
     * @return A {@code CharSeq} Collector.
     */
    public static Collector, CharSeq> collector() {
        final Supplier> supplier = ArrayList::new;
        final BiConsumer, Character> accumulator = ArrayList::add;
        final BinaryOperator> combiner = (left, right) -> {
            left.addAll(right);
            return left;
        };
        final Function, CharSeq> finisher = CharSeq::ofAll;
        return Collector.of(supplier, accumulator, combiner, finisher);
    }

    /**
     * Creates a String of {@code CharSequence}.
     *
     * @param sequence {@code CharSequence} instance.
     * @return A new {@code javaslang.String}
     */
    // DEV-NOTE: Needs to be 'of' instead of 'ofAll' because 'ofAll(CharSeq)' is ambiguous.
    public static CharSeq of(CharSequence sequence) {
        Objects.requireNonNull(sequence, "sequence is null");
        if (sequence instanceof CharSeq) {
            return (CharSeq) sequence;
        } else {
            return sequence.length() == 0 ? empty() : new CharSeq(sequence.toString());
        }
    }

    /**
     * Returns a singleton {@code CharSeq}, i.e. a {@code CharSeq} of one character.
     *
     * @param character A character.
     * @return A new {@code CharSeq} instance containing the given element
     */
    public static CharSeq of(char character) {
        return new CharSeq(new String(new char[] { character }));
    }

    /**
     * Creates a String of the given characters.
     *
     * @param characters Zero or more characters.
     * @return A string containing the given characters in the same order.
     * @throws NullPointerException if {@code elements} is null
     */
    public static CharSeq of(char... characters) {
        Objects.requireNonNull(characters, "characters is null");
        if (characters.length == 0) {
            return empty();
        } else {
            final char[] chrs = new char[characters.length];
            System.arraycopy(characters, 0, chrs, 0, characters.length);
            return new CharSeq(new String(chrs));
        }
    }

    /**
     * Creates a String of the given elements.
     *
     * The resulting string has the same iteration order as the given iterable of elements
     * if the iteration order of the elements is stable.
     *
     * @param elements An Iterable of elements.
     * @return A string containing the given elements in the same order.
     * @throws NullPointerException if {@code elements} is null
     */
    public static CharSeq ofAll(Iterable elements) {
        Objects.requireNonNull(elements, "elements is null");
        final StringBuilder sb = new StringBuilder();
        for (Character character : elements) {
            sb.append(character);
        }
        return sb.length() == 0 ? EMPTY : of(sb.toString());
    }

    /**
     * Returns a CharSeq containing {@code n} values of a given Function {@code f}
     * over a range of integer values from 0 to {@code n - 1}.
     *
     * @param n The number of elements in the CharSeq
     * @param f The Function computing element values
     * @return A CharSeq consisting of elements {@code f(0),f(1), ..., f(n - 1)}
     * @throws NullPointerException if {@code f} is null
     */
    public static CharSeq tabulate(int n, Function f) {
        Objects.requireNonNull(f, "f is null");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            sb.append(f.apply(i));
        }
        return of(sb);
    }

    /**
     * Returns a CharSeq containing {@code n} values supplied by a given Supplier {@code s}.
     *
     * @param n The number of elements in the CharSeq
     * @param s The Supplier computing element values
     * @return A CharSeq of size {@code n}, where each element contains the result supplied by {@code s}.
     * @throws NullPointerException if {@code s} is null
     */
    public static CharSeq fill(int n, Supplier s) {
        return tabulate(n, anything -> s.get());
    }

    /**
     * Creates a CharSeq starting from character {@code from}, extending to character {@code toExclusive - 1}.
     * 

* Examples: *

     * 
     * CharSeq.range('a', 'c')  // = "ab"
     * CharSeq.range('c', 'a')  // = ""
     * 
     * 
* * @param from the first character * @param toExclusive the successor of the last character * @return a range of characters as specified or the empty range if {@code from >= toExclusive} */ public static CharSeq range(char from, char toExclusive) { return new CharSeq(Iterator.range(from, toExclusive).mkString()); } public static CharSeq rangeBy(char from, char toExclusive, int step) { return new CharSeq(Iterator.rangeBy(from, toExclusive, step).mkString()); } /** * Creates a CharSeq starting from character {@code from}, extending to character {@code toInclusive}. *

* Examples: *

     * 
     * CharSeq.rangeClosed('a', 'c')  // = "abc"
     * CharSeq.rangeClosed('c', 'a')  // = ""
     * 
     * 
* * @param from the first character * @param toInclusive the last character * @return a range of characters as specified or the empty range if {@code from > toInclusive} */ public static CharSeq rangeClosed(char from, char toInclusive) { return new CharSeq(Iterator.rangeClosed(from, toInclusive).mkString()); } /** * Creates a CharSeq starting from character {@code from}, extending to character {@code toInclusive}, * with {@code step}. *

* Examples: *

     * 
     * CharSeq.rangeClosedBy('a', 'c', 1)  // = ('a', 'b', 'c')
     * CharSeq.rangeClosedBy('a', 'd', 2)  // = ('a', 'c')
     * CharSeq.rangeClosedBy('d', 'a', -2) // = ('d', 'b')
     * CharSeq.rangeClosedBy('d', 'a', 2)  // = ()
     * 
     * 
* * @param from the first character * @param toInclusive the last character * @param step the step * @return a range of characters as specified or the empty range if {@code step * (from - toInclusive) > 0}. * @throws IllegalArgumentException if {@code step} is zero */ public static CharSeq rangeClosedBy(char from, char toInclusive, int step) { return new CharSeq(Iterator.rangeClosedBy(from, toInclusive, step).mkString()); } private Tuple2 splitByBuilder(StringBuilder sb) { if (sb.length() == 0) { return Tuple.of(EMPTY, this); } else if (sb.length() == length()) { return Tuple.of(this, EMPTY); } else { return Tuple.of(of(sb.toString()), of(back.substring(sb.length()))); } } /** * Repeats a character {@code times} times. * * @param character A character * @param times Repetition count * @return A CharSeq representing {@code character * times} */ public static CharSeq repeat(char character, int times) { final int length = Math.max(times, 0); final char[] characters = new char[length]; Arrays.fill(characters, character); return new CharSeq(String.valueOf(characters)); } /** * Repeats this CharSeq {@code times} times. *

* Example: {@code CharSeq.of("ja").repeat(13) = "jajajajajajajajajajajajaja"} * * @param times Repetition count * @return A CharSeq representing {@code this * times} */ public CharSeq repeat(int times) { final StringBuilder builder = new StringBuilder(); for (int i = 0; i < times; i++) { builder.append(back); } return new CharSeq(builder.toString()); } // // // IndexedSeq // // @Override public CharSeq append(Character element) { return of(back + element); } @Override public CharSeq appendAll(Iterable elements) { Objects.requireNonNull(elements, "elements is null"); final StringBuilder sb = new StringBuilder(back); for (char element : elements) { sb.append(element); } return of(sb.toString()); } @Override public IndexedSeq combinations() { return Vector.rangeClosed(0, length()).map(this::combinations).flatMap(Function.identity()); } @Override public IndexedSeq combinations(int k) { return Combinations.apply(this, Math.max(k, 0)); } @Override public Iterator crossProduct(int power) { return Collections.crossProduct(CharSeq.empty(), this, power); } @Override public CharSeq distinct() { return distinctBy(Function.identity()); } @Override public CharSeq distinctBy(Comparator comparator) { Objects.requireNonNull(comparator, "comparator is null"); final java.util.Set seen = new java.util.TreeSet<>(comparator); return filter(seen::add); } @Override public CharSeq distinctBy(Function keyExtractor) { Objects.requireNonNull(keyExtractor, "keyExtractor is null"); final java.util.Set seen = new java.util.HashSet<>(); return filter(t -> seen.add(keyExtractor.apply(t))); } @Override public CharSeq drop(long n) { if (n <= 0) { return this; } else if (n >= length()) { return EMPTY; } else { return of(back.substring((int) n)); } } @Override public CharSeq dropRight(long n) { if (n <= 0) { return this; } else if (n >= length()) { return EMPTY; } else { return of(back.substring(0, length() - (int) n)); } } @Override public CharSeq dropUntil(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); return dropWhile(predicate.negate()); } @Override public CharSeq dropWhile(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); int index = 0; while (index < length() && predicate.test(charAt(index))) { index++; } return index < length() ? (index == 0 ? this : of(back.substring(index))) : empty(); } @Override public CharSeq filter(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < back.length(); i++) { final char ch = back.charAt(i); if (predicate.test(ch)) { sb.append(ch); } } return sb.length() == 0 ? EMPTY : sb.length() == length() ? this : of(sb.toString()); } @Override public IndexedSeq flatMap(Function> mapper) { Objects.requireNonNull(mapper, "mapper is null"); if (isEmpty()) { return Vector.empty(); } else { IndexedSeq result = Vector.empty(); for (int i = 0; i < length(); i++) { for (U u : mapper.apply(get(i))) { result = result.append(u); } } return result; } } public CharSeq flatMapChars(CharFunction mapper) { Objects.requireNonNull(mapper, "mapper is null"); if (isEmpty()) { return this; } else { final StringBuilder builder = new StringBuilder(); back.chars().forEach(c -> builder.append(mapper.apply((char) c))); return new CharSeq(builder.toString()); } } @Override public Map groupBy(Function classifier) { Objects.requireNonNull(classifier, "classifier is null"); return iterator().groupBy(classifier).map((c, it) -> Tuple.of(c, CharSeq.ofAll(it))); } @Override public Iterator grouped(long size) { return sliding(size, size); } @Override public boolean hasDefiniteSize() { return true; } @Override public CharSeq init() { if (isEmpty()) { throw new UnsupportedOperationException("init of empty string"); } else { return of(back.substring(0, length() - 1)); } } @Override public Option initOption() { return isEmpty() ? Option.none() : Option.some(init()); } @Override public CharSeq insert(int index, Character element) { if (index < 0) { throw new IndexOutOfBoundsException("insert(" + index + ", e)"); } if (index > length()) { throw new IndexOutOfBoundsException("insert(" + index + ", e) on String of length " + length()); } return of(new StringBuilder(back).insert(index, element).toString()); } @Override public CharSeq insertAll(int index, Iterable elements) { Objects.requireNonNull(elements, "elements is null"); if (index < 0) { throw new IndexOutOfBoundsException("insertAll(" + index + ", elements)"); } if (index > length()) { throw new IndexOutOfBoundsException("insertAll(" + index + ", elements) on String of length " + length()); } final String javaString = back; final StringBuilder sb = new StringBuilder(javaString.substring(0, index)); for (Character element : elements) { sb.append(element); } sb.append(javaString.substring(index)); return of(sb.toString()); } @Override public Iterator iterator() { return new AbstractIterator() { private int index = 0; @Override public boolean hasNext() { return index < back.length(); } @Override public Character getNext() { return back.charAt(index++); } }; } @Override public CharSeq intersperse(Character element) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < length(); i++) { if (i > 0) { sb.append(element); } sb.append(get(i)); } return sb.length() == 0 ? EMPTY : of(sb.toString()); } @Override public IndexedSeq map(Function mapper) { Objects.requireNonNull(mapper, "mapper is null"); IndexedSeq result = Vector.empty(); for (int i = 0; i < length(); i++) { result = result.append(mapper.apply(get(i))); } return result; } @Override public CharSeq padTo(int length, Character element) { if (length <= back.length()) { return this; } final StringBuilder sb = new StringBuilder(back); final int limit = length - back.length(); for (int i = 0; i < limit; i++) { sb.append(element); } return new CharSeq(sb.toString()); } @Override public CharSeq patch(int from, Iterable that, int replaced) { from = from < 0 ? 0 : from > length() ? length() : from; replaced = replaced < 0 ? 0 : replaced; final StringBuilder sb = new StringBuilder(back.substring(0, from)); for (Character character : that) { sb.append(character); } from += replaced; if (from < length()) { sb.append(back.substring(from)); } return sb.length() == 0 ? EMPTY : new CharSeq(sb.toString()); } public CharSeq mapChars(CharUnaryOperator mapper) { Objects.requireNonNull(mapper, "mapper is null"); if (isEmpty()) { return this; } else { final char[] chars = back.toCharArray(); for (int i = 0; i < chars.length; i++) { chars[i] = mapper.apply(chars[i]); } return CharSeq.of(chars); } } @Override public Tuple2 partition(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); if (isEmpty()) { return Tuple.of(EMPTY, EMPTY); } final StringBuilder left = new StringBuilder(); final StringBuilder right = new StringBuilder(); for (int i = 0; i < length(); i++) { Character t = get(i); (predicate.test(t) ? left : right).append(t); } if (left.length() == 0) { return Tuple.of(EMPTY, of(right.toString())); } else if (right.length() == 0) { return Tuple.of(of(left.toString()), EMPTY); } else { return Tuple.of(of(left.toString()), of(right.toString())); } } @Override public CharSeq peek(Consumer action) { Objects.requireNonNull(action, "action is null"); if (!isEmpty()) { action.accept(back.charAt(0)); } return this; } @Override public IndexedSeq permutations() { if (isEmpty()) { return Vector.empty(); } else { if (length() == 1) { return Vector.of(this); } else { IndexedSeq result = Vector.empty(); for (Character t : distinct()) { for (CharSeq ts : remove(t).permutations()) { result = result.append(CharSeq.of(t).appendAll(ts)); } } return result; } } } @Override public CharSeq prepend(Character element) { return of(element + back); } @Override public CharSeq prependAll(Iterable elements) { Objects.requireNonNull(elements, "elements is null"); final StringBuilder sb = new StringBuilder(); for (Character element : elements) { sb.append(element); } sb.append(back); return sb.length() == 0 ? EMPTY : of(sb.toString()); } @Override public CharSeq remove(Character element) { final StringBuilder sb = new StringBuilder(); boolean found = false; for (int i = 0; i < length(); i++) { char c = get(i); if (!found && c == element) { found = true; } else { sb.append(c); } } return sb.length() == 0 ? EMPTY : sb.length() == length() ? this : of(sb.toString()); } @Override public CharSeq removeFirst(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); final StringBuilder sb = new StringBuilder(); boolean found = false; for (int i = 0; i < back.length(); i++) { final char ch = back.charAt(i); if (predicate.test(ch)) { if (found) { sb.append(ch); } found = true; } else { sb.append(ch); } } return found ? (sb.length() == 0 ? EMPTY : of(sb.toString())) : this; } @Override public CharSeq removeLast(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); for (int i = length() - 1; i >= 0; i--) { if (predicate.test(back.charAt(i))) { return removeAt(i); } } return this; } @Override public CharSeq removeAt(int index) { final String removed = back.substring(0, index) + back.substring(index + 1); return removed.isEmpty() ? EMPTY : of(removed); } @Override public CharSeq removeAll(Character element) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < length(); i++) { final char c = back.charAt(i); if (c != element) { sb.append(c); } } return sb.length() == 0 ? EMPTY : sb.length() == length() ? this : of(sb.toString()); } @Override public CharSeq removeAll(Iterable elements) { Objects.requireNonNull(elements, "elements is null"); final java.util.Set distinct = new HashSet<>(); for (Character element : elements) { distinct.add(element); } final StringBuilder sb = new StringBuilder(); for (int i = 0; i < length(); i++) { final char c = back.charAt(i); if (!distinct.contains(c)) { sb.append(c); } } return sb.length() == 0 ? EMPTY : sb.length() == length() ? this : of(sb.toString()); } @Override public CharSeq replace(Character currentElement, Character newElement) { final StringBuilder sb = new StringBuilder(); boolean found = false; for (int i = 0; i < length(); i++) { final char c = back.charAt(i); if (c == currentElement && !found) { sb.append(newElement); found = true; } else { sb.append(c); } } return found ? of(sb.toString()) : this; } @Override public CharSeq replaceAll(Character currentElement, Character newElement) { final StringBuilder sb = new StringBuilder(); boolean found = false; for (int i = 0; i < length(); i++) { final char c = back.charAt(i); if (c == currentElement) { sb.append(newElement); found = true; } else { sb.append(c); } } return found ? of(sb.toString()) : this; } @Override public CharSeq retainAll(Iterable elements) { Objects.requireNonNull(elements, "elements is null"); final java.util.Set kept = new HashSet<>(); for (Character element : elements) { kept.add(element); } final StringBuilder sb = new StringBuilder(); for (int i = 0; i < length(); i++) { final char c = back.charAt(i); if (kept.contains(c)) { sb.append(c); } } return sb.length() == 0 ? EMPTY : of(sb.toString()); } @Override public CharSeq reverse() { return of(new StringBuilder(back).reverse().toString()); } @Override public IndexedSeq scan(Character zero, BiFunction operation) { return scanLeft(zero, operation); } @Override public IndexedSeq scanLeft(U zero, BiFunction operation) { Objects.requireNonNull(operation, "operation is null"); return Collections.scanLeft(this, zero, operation, Vector.empty(), Vector::append, Function.identity()); } @Override public IndexedSeq scanRight(U zero, BiFunction operation) { Objects.requireNonNull(operation, "operation is null"); return Collections.scanRight(this, zero, operation, Vector.empty(), Vector::prepend, Function.identity()); } @Override public CharSeq slice(long beginIndex, long endIndex) { final long from = beginIndex < 0 ? 0 : beginIndex; final long to = endIndex > length() ? length() : endIndex; if (from >= to) { return EMPTY; } if (from <= 0 && to >= length()) { return this; } return CharSeq.of(back.substring((int) from, (int) to)); } @Override public Iterator sliding(long size) { return sliding(size, 1); } @Override public Iterator sliding(long size, long step) { return iterator().sliding(size, step).map(CharSeq::ofAll); } @Override public CharSeq sorted() { return isEmpty() ? this : toJavaStream().sorted().collect(CharSeq.collector()); } @Override public CharSeq sorted(Comparator comparator) { Objects.requireNonNull(comparator, "comparator is null"); return isEmpty() ? this : toJavaStream().sorted(comparator).collect(CharSeq.collector()); } @Override public > CharSeq sortBy(Function mapper) { return sortBy(U::compareTo, mapper); } @Override public CharSeq sortBy(Comparator comparator, Function mapper) { final Function domain = Function1.of(mapper::apply).memoized(); return toJavaStream() .sorted((e1, e2) -> comparator.compare(domain.apply(e1), domain.apply(e2))) .collect(collector()); } @Override public Tuple2 span(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < length(); i++) { final char c = back.charAt(i); if (predicate.test(c)) { sb.append(c); } else { break; } } return splitByBuilder(sb); } @Override public Spliterator spliterator() { return Spliterators.spliterator(iterator(), length(), Spliterator.ORDERED | Spliterator.IMMUTABLE); } @Override public CharSeq subSequence(int beginIndex) { if (beginIndex < 0 || beginIndex > length()) { throw new IndexOutOfBoundsException("begin index " + beginIndex + " < 0"); } if (beginIndex == 0) { return this; } else if (beginIndex == length()) { return EMPTY; } else { return CharSeq.of(back.substring(beginIndex)); } } @Override public CharSeq tail() { if (isEmpty()) { throw new UnsupportedOperationException("tail of empty string"); } else { return CharSeq.of(back.substring(1)); } } @Override public Option tailOption() { return isEmpty() ? Option.none() : Option.some(tail()); } @Override public CharSeq take(long n) { if (n <= 0) { return EMPTY; } else if (n >= length()) { return this; } else { return CharSeq.of(back.substring(0, (int) n)); } } @Override public CharSeq takeRight(long n) { if (n <= 0) { return EMPTY; } else if (n >= length()) { return this; } else { return CharSeq.of(back.substring(length() - (int) n)); } } @Override public CharSeq takeUntil(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); return takeWhile(predicate.negate()); } @Override public CharSeq takeWhile(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); final StringBuilder sb = new StringBuilder(); for (int i = 0; i < length(); i++) { char c = back.charAt(i); if (!predicate.test(c)) { break; } sb.append(c); } return sb.length() == length() ? this : sb.length() == 0 ? EMPTY : of(sb.toString()); } /** * Transforms this {@code CharSeq}. * * @param f A transformation * @param Type of transformation result * @return An instance of type {@code U} * @throws NullPointerException if {@code f} is null */ public U transform(Function f) { Objects.requireNonNull(f, "f is null"); return f.apply(this); } @Override public IndexedSeq unit(Iterable iterable) { return Vector.ofAll(iterable); } @Override public Tuple2, IndexedSeq> unzip( Function> unzipper) { Objects.requireNonNull(unzipper, "unzipper is null"); IndexedSeq xs = Vector.empty(); IndexedSeq ys = Vector.empty(); for (int i = 0; i < length(); i++) { final Tuple2 t = unzipper.apply(back.charAt(i)); xs = xs.append(t._1); ys = ys.append(t._2); } return Tuple.of(xs, ys); } @Override public Tuple3, IndexedSeq, IndexedSeq> unzip3( Function> unzipper) { Objects.requireNonNull(unzipper, "unzipper is null"); IndexedSeq xs = Vector.empty(); IndexedSeq ys = Vector.empty(); IndexedSeq zs = Vector.empty(); for (int i = 0; i < length(); i++) { final Tuple3 t = unzipper.apply(back.charAt(i)); xs = xs.append(t._1); ys = ys.append(t._2); zs = zs.append(t._3); } return Tuple.of(xs, ys, zs); } @Override public CharSeq update(int index, Character element) { if (index < 0) { throw new IndexOutOfBoundsException("update(" + index + ")"); } if (index >= length()) { throw new IndexOutOfBoundsException("update(" + index + ")"); } return of(back.substring(0, index) + element + back.substring(index + 1)); } @Override public IndexedSeq> zip(Iterable that) { Objects.requireNonNull(that, "that is null"); IndexedSeq> result = Vector.empty(); Iterator list1 = iterator(); java.util.Iterator list2 = that.iterator(); while (list1.hasNext() && list2.hasNext()) { result = result.append(Tuple.of(list1.next(), list2.next())); } return result; } @Override public IndexedSeq> zipAll(Iterable that, Character thisElem, U thatElem) { Objects.requireNonNull(that, "that is null"); IndexedSeq> result = Vector.empty(); Iterator list1 = iterator(); java.util.Iterator list2 = that.iterator(); while (list1.hasNext() || list2.hasNext()) { final Character elem1 = list1.hasNext() ? list1.next() : thisElem; final U elem2 = list2.hasNext() ? list2.next() : thatElem; result = result.append(Tuple.of(elem1, elem2)); } return result; } @Override public IndexedSeq> zipWithIndex() { IndexedSeq> result = Vector.empty(); for (int i = 0; i < length(); i++) { result = result.append(Tuple.of(get(i), (long) i)); } return result; } @Override public Character get(int index) { return back.charAt(index); } @Override public int indexOf(Character element, int from) { return back.indexOf(element, from); } @Override public int lastIndexOf(Character element, int end) { return back.lastIndexOf(element, end); } @Override public Tuple2 splitAt(long n) { if (n <= 0) { return Tuple.of(EMPTY, this); } else if (n >= length()) { return Tuple.of(this, EMPTY); } else { return Tuple.of(of(back.substring(0, (int) n)), of(back.substring((int) n))); } } @Override public Tuple2 splitAt(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); if (isEmpty()) { return Tuple.of(EMPTY, EMPTY); } final StringBuilder left = new StringBuilder(); for (int i = 0; i < length(); i++) { Character t = get(i); if (!predicate.test(t)) { left.append(t); } else { break; } } return splitByBuilder(left); } @Override public Tuple2 splitAtInclusive(Predicate predicate) { Objects.requireNonNull(predicate, "predicate is null"); if (isEmpty()) { return Tuple.of(EMPTY, EMPTY); } final StringBuilder left = new StringBuilder(); for (int i = 0; i < length(); i++) { Character t = get(i); left.append(t); if (predicate.test(t)) { break; } } return splitByBuilder(left); } @Override public boolean startsWith(Iterable that, int offset) { return startsWith(CharSeq.ofAll(that), offset); } @Override public Character head() { if (isEmpty()) { throw new NoSuchElementException("head of empty string"); } else { return back.charAt(0); } } @Override public boolean isEmpty() { return back.isEmpty(); } @Override public boolean isTraversableAgain() { return true; } private Object readResolve() { return isEmpty() ? EMPTY : this; } @Override public boolean equals(Object o) { if (o == this) { return true; } else if (o instanceof CharSeq) { return ((CharSeq) o).back.equals(back); } else { return false; } } @Override public int hashCode() { return back.hashCode(); } // // // java.lang.CharSequence // // /** * Returns the {@code char} value at the * specified index. An index ranges from {@code 0} to * {@code length() - 1}. The first {@code char} value of the sequence * is at index {@code 0}, the next at index {@code 1}, * and so on, as for array indexing. * *

If the {@code char} value specified by the index is a * surrogate, the surrogate * value is returned. * * @param index the index of the {@code char} value. * @return the {@code char} value at the specified index of this string. * The first {@code char} value is at index {@code 0}. * @throws IndexOutOfBoundsException if the {@code index} * argument is negative or not less than the length of this * string. */ @Override public char charAt(int index) { return back.charAt(index); } /** * Returns the length of this string. * The length is equal to the number of Unicode * code units in the string. * * @return the length of the sequence of characters represented by this * object. */ @Override public int length() { return back.length(); } // // // String // // /** * Returns the character (Unicode code point) at the specified * index. The index refers to {@code char} values * (Unicode code units) and ranges from {@code 0} to * {@link #length()}{@code - 1}. * *

If the {@code char} value specified at the given index * is in the high-surrogate range, the following index is less * than the length of this {@code CharSeq}, and the * {@code char} value at the following index is in the * low-surrogate range, then the supplementary code point * corresponding to this surrogate pair is returned. Otherwise, * the {@code char} value at the given index is returned. * * @param index the index to the {@code char} values * @return the code point value of the character at the * {@code index} * @throws IndexOutOfBoundsException if the {@code index} * argument is negative or not less than the length of this * string. */ public int codePointAt(int index) { return back.codePointAt(index); } /** * Returns the character (Unicode code point) before the specified * index. The index refers to {@code char} values * (Unicode code units) and ranges from {@code 1} to {@link * CharSequence#length() length}. * *

If the {@code char} value at {@code (index - 1)} * is in the low-surrogate range, {@code (index - 2)} is not * negative, and the {@code char} value at {@code (index - * 2)} is in the high-surrogate range, then the * supplementary code point value of the surrogate pair is * returned. If the {@code char} value at {@code index - * 1} is an unpaired low-surrogate or a high-surrogate, the * surrogate value is returned. * * @param index the index following the code point that should be returned * @return the Unicode code point value before the given index. * @throws IndexOutOfBoundsException if the {@code index} * argument is less than 1 or greater than the length * of this string. */ public int codePointBefore(int index) { return back.codePointBefore(index); } /** * Returns the number of Unicode code points in the specified text * range of this {@code CharSeq}. The text range begins at the * specified {@code beginIndex} and extends to the * {@code char} at index {@code endIndex - 1}. Thus the * length (in {@code char}s) of the text range is * {@code endIndex-beginIndex}. Unpaired surrogates within * the text range count as one code point each. * * @param beginIndex the index to the first {@code char} of * the text range. * @param endIndex the index after the last {@code char} of * the text range. * @return the number of Unicode code points in the specified text * range * @throws IndexOutOfBoundsException if the * {@code beginIndex} is negative, or {@code endIndex} * is larger than the length of this {@code CharSeq}, or * {@code beginIndex} is larger than {@code endIndex}. */ public int codePointCount(int beginIndex, int endIndex) { return back.codePointCount(beginIndex, endIndex); } /** * Returns the index within this {@code CharSeq} that is * offset from the given {@code index} by * {@code codePointOffset} code points. Unpaired surrogates * within the text range given by {@code index} and * {@code codePointOffset} count as one code point each. * * @param index the index to be offset * @param codePointOffset the offset in code points * @return the index within this {@code CharSeq} * @throws IndexOutOfBoundsException if {@code index} * is negative or larger then the length of this * {@code CharSeq}, or if {@code codePointOffset} is positive * and the substring starting with {@code index} has fewer * than {@code codePointOffset} code points, * or if {@code codePointOffset} is negative and the substring * before {@code index} has fewer than the absolute value * of {@code codePointOffset} code points. */ public int offsetByCodePoints(int index, int codePointOffset) { return back.offsetByCodePoints(index, codePointOffset); } /** * Copies characters from this string into the destination character * array. *

* The first character to be copied is at index {@code srcBegin}; * the last character to be copied is at index {@code srcEnd-1} * (thus the total number of characters to be copied is * {@code srcEnd-srcBegin}). The characters are copied into the * subarray of {@code dst} starting at index {@code dstBegin} * and ending at index: *

     *     dstbegin + (srcEnd-srcBegin) - 1
     * 
* * @param srcBegin index of the first character in the string * to copy. * @param srcEnd index after the last character in the string * to copy. * @param dst the destination array. * @param dstBegin the start offset in the destination array. * @throws IndexOutOfBoundsException If any of the following * is true: *
  • {@code srcBegin} is negative. *
  • {@code srcBegin} is greater than {@code srcEnd} *
  • {@code srcEnd} is greater than the length of this * string *
  • {@code dstBegin} is negative *
  • {@code dstBegin+(srcEnd-srcBegin)} is larger than * {@code dst.length}
*/ public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { back.getChars(srcBegin, srcEnd, dst, dstBegin); } /** * Encodes this {@code CharSeq} into a sequence of bytes using the named * charset, storing the result into a new byte array. * *

The behavior of this method when this string cannot be encoded in * the given charset is unspecified. The {@link * java.nio.charset.CharsetEncoder} class should be used when more control * over the encoding process is required. * * @param charsetName The name of a supported {@linkplain java.nio.charset.Charset * charset} * @return The resultant byte array * @throws UnsupportedEncodingException If the named charset is not supported */ public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { return back.getBytes(charsetName); } /** * Encodes this {@code CharSeq} into a sequence of bytes using the given * {@linkplain java.nio.charset.Charset charset}, storing the result into a * new byte array. * *

This method always replaces malformed-input and unmappable-character * sequences with this charset's default replacement byte array. The * {@link java.nio.charset.CharsetEncoder} class should be used when more * control over the encoding process is required. * * @param charset The {@linkplain java.nio.charset.Charset} to be used to encode * the {@code CharSeq} * @return The resultant byte array */ public byte[] getBytes(Charset charset) { return back.getBytes(charset); } /** * Encodes this {@code CharSeq} into a sequence of bytes using the * platform's default charset, storing the result into a new byte array. * *

The behavior of this method when this string cannot be encoded in * the default charset is unspecified. The {@link * java.nio.charset.CharsetEncoder} class should be used when more control * over the encoding process is required. * * @return The resultant byte array */ public byte[] getBytes() { return back.getBytes(); } /** * Compares this string to the specified {@code StringBuffer}. The result * is {@code true} if and only if this {@code CharSeq} represents the same * sequence of characters as the specified {@code StringBuffer}. This method * synchronizes on the {@code StringBuffer}. * * @param sb The {@code StringBuffer} to compare this {@code CharSeq} against * @return {@code true} if this {@code CharSeq} represents the same * sequence of characters as the specified {@code StringBuffer}, * {@code false} otherwise */ public boolean contentEquals(StringBuffer sb) { return back.contentEquals(sb); } /** * Compares this string to the specified {@code CharSequence}. The * result is {@code true} if and only if this {@code CharSeq} represents the * same sequence of char values as the specified sequence. Note that if the * {@code CharSequence} is a {@code StringBuffer} then the method * synchronizes on it. * * @param cs The sequence to compare this {@code CharSeq} against * @return {@code true} if this {@code CharSeq} represents the same * sequence of char values as the specified sequence, {@code * false} otherwise */ public boolean contentEquals(CharSequence cs) { return back.contentEquals(cs); } /** * Compares this {@code CharSeq} to another {@code CharSeq}, ignoring case * considerations. Two strings are considered equal ignoring case if they * are of the same length and corresponding characters in the two strings * are equal ignoring case. * *

Two characters {@code c1} and {@code c2} are considered the same * ignoring case if at least one of the following is true: *

    *
  • The two characters are the same (as compared by the * {@code ==} operator) *
  • Applying the method {@link * java.lang.Character#toUpperCase(char)} to each character * produces the same result *
  • Applying the method {@link * java.lang.Character#toLowerCase(char)} to each character * produces the same result *
* * @param anotherString The {@code CharSeq} to compare this {@code CharSeq} against * @return {@code true} if the argument is not {@code null} and it * represents an equivalent {@code CharSeq} ignoring case; {@code * false} otherwise * @see #equals(Object) */ public boolean equalsIgnoreCase(CharSeq anotherString) { return back.equalsIgnoreCase(anotherString.back); } /** * Compares two strings lexicographically. * The comparison is based on the Unicode value of each character in * the strings. The character sequence represented by this * {@code CharSeq} object is compared lexicographically to the * character sequence represented by the argument string. The result is * a negative integer if this {@code CharSeq} object * lexicographically precedes the argument string. The result is a * positive integer if this {@code CharSeq} object lexicographically * follows the argument string. The result is zero if the strings * are equal; {@code compareTo} returns {@code 0} exactly when * the {@link #equals(Object)} method would return {@code true}. *

* This is the definition of lexicographic ordering. If two strings are * different, then either they have different characters at some index * that is a valid index for both strings, or their lengths are different, * or both. If they have different characters at one or more index * positions, let k be the smallest such index; then the string * whose character at position k has the smaller value, as * determined by using the < operator, lexicographically precedes the * other string. In this case, {@code compareTo} returns the * difference of the two character values at position {@code k} in * the two string -- that is, the value: *

     * this.charAt(k)-anotherString.charAt(k)
     * 
* If there is no index position at which they differ, then the shorter * string lexicographically precedes the longer string. In this case, * {@code compareTo} returns the difference of the lengths of the * strings -- that is, the value: *
     * this.length()-anotherString.length()
     * 
* * @param anotherString the {@code CharSeq} to be compared. * @return the value {@code 0} if the argument string is equal to * this string; a value less than {@code 0} if this string * is lexicographically less than the string argument; and a * value greater than {@code 0} if this string is * lexicographically greater than the string argument. */ public int compareTo(CharSeq anotherString) { return back.compareTo(anotherString.back); } /** * Compares two strings lexicographically, ignoring case * differences. This method returns an integer whose sign is that of * calling {@code compareTo} with normalized versions of the strings * where case differences have been eliminated by calling * {@code Character.toLowerCase(Character.toUpperCase(character))} on * each character. *

* Note that this method does not take locale into account, * and will result in an unsatisfactory ordering for certain locales. * The java.text package provides collators to allow * locale-sensitive ordering. * * @param str the {@code CharSeq} to be compared. * @return a negative integer, zero, or a positive integer as the * specified String is greater than, equal to, or less * than this String, ignoring case considerations. */ public int compareToIgnoreCase(CharSeq str) { return back.compareToIgnoreCase(str.back); } /** * Tests if two string regions are equal. *

* A substring of this {@code CharSeq} object is compared to a substring * of the argument other. The result is true if these substrings * represent identical character sequences. The substring of this * {@code CharSeq} object to be compared begins at index {@code toffset} * and has length {@code len}. The substring of other to be compared * begins at index {@code ooffset} and has length {@code len}. The * result is {@code false} if and only if at least one of the following * is true: *

  • {@code toffset} is negative. *
  • {@code ooffset} is negative. *
  • {@code toffset+len} is greater than the length of this * {@code CharSeq} object. *
  • {@code ooffset+len} is greater than the length of the other * argument. *
  • There is some nonnegative integer k less than {@code len} * such that: * {@code this.charAt(toffset + }k{@code ) != other.charAt(ooffset + } * k{@code )} *
* * @param toffset the starting offset of the subregion in this string. * @param other the string argument. * @param ooffset the starting offset of the subregion in the string * argument. * @param len the number of characters to compare. * @return {@code true} if the specified subregion of this string * exactly matches the specified subregion of the string argument; * {@code false} otherwise. */ public boolean regionMatches(int toffset, CharSeq other, int ooffset, int len) { return back.regionMatches(toffset, other.back, ooffset, len); } /** * Tests if two string regions are equal. *

* A substring of this {@code CharSeq} object is compared to a substring * of the argument {@code other}. The result is {@code true} if these * substrings represent character sequences that are the same, ignoring * case if and only if {@code ignoreCase} is true. The substring of * this {@code CharSeq} object to be compared begins at index * {@code toffset} and has length {@code len}. The substring of * {@code other} to be compared begins at index {@code ooffset} and * has length {@code len}. The result is {@code false} if and only if * at least one of the following is true: *

  • {@code toffset} is negative. *
  • {@code ooffset} is negative. *
  • {@code toffset+len} is greater than the length of this * {@code CharSeq} object. *
  • {@code ooffset+len} is greater than the length of the other * argument. *
  • {@code ignoreCase} is {@code false} and there is some nonnegative * integer k less than {@code len} such that: *
         * this.charAt(toffset+k) != other.charAt(ooffset+k)
         * 
    *
  • {@code ignoreCase} is {@code true} and there is some nonnegative * integer k less than {@code len} such that: *
         * Character.toLowerCase(this.charAt(toffset+k)) !=
         * Character.toLowerCase(other.charAt(ooffset+k))
         * 
    * and: *
         * Character.toUpperCase(this.charAt(toffset+k)) !=
         *         Character.toUpperCase(other.charAt(ooffset+k))
         * 
    *
* * @param ignoreCase if {@code true}, ignore case when comparing * characters. * @param toffset the starting offset of the subregion in this * string. * @param other the string argument. * @param ooffset the starting offset of the subregion in the string * argument. * @param len the number of characters to compare. * @return {@code true} if the specified subregion of this string * matches the specified subregion of the string argument; * {@code false} otherwise. Whether the matching is exact * or case insensitive depends on the {@code ignoreCase} * argument. */ public boolean regionMatches(boolean ignoreCase, int toffset, CharSeq other, int ooffset, int len) { return back.regionMatches(ignoreCase, toffset, other.back, ooffset, len); } @Override public CharSeq subSequence(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new IndexOutOfBoundsException("begin index " + beginIndex + " < 0"); } if (endIndex > length()) { throw new IndexOutOfBoundsException("endIndex " + endIndex + " > length " + length()); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new IndexOutOfBoundsException("beginIndex " + beginIndex + " > endIndex " + endIndex); } if (beginIndex == 0 && endIndex == length()) { return this; } else { return CharSeq.of(back.subSequence(beginIndex, endIndex)); } } /** * Tests if the substring of this string beginning at the * specified index starts with the specified prefix. * * @param prefix the prefix. * @param toffset where to begin looking in this string. * @return {@code true} if the character sequence represented by the * argument is a prefix of the substring of this object starting * at index {@code toffset}; {@code false} otherwise. * The result is {@code false} if {@code toffset} is * negative or greater than the length of this * {@code CharSeq} object; otherwise the result is the same * as the result of the expression *
     *          this.substring(toffset).startsWith(prefix)
     *          
*/ public boolean startsWith(CharSeq prefix, int toffset) { return back.startsWith(prefix.back, toffset); } /** * Tests if this string starts with the specified prefix. * * @param prefix the prefix. * @return {@code true} if the character sequence represented by the * argument is a prefix of the character sequence represented by * this string; {@code false} otherwise. * Note also that {@code true} will be returned if the * argument is an empty string or is equal to this * {@code CharSeq} object as determined by the * {@link #equals(Object)} method. */ public boolean startsWith(CharSeq prefix) { return back.startsWith(prefix.back); } /** * Tests if this string ends with the specified suffix. * * @param suffix the suffix. * @return {@code true} if the character sequence represented by the * argument is a suffix of the character sequence represented by * this object; {@code false} otherwise. Note that the * result will be {@code true} if the argument is the * empty string or is equal to this {@code CharSeq} object * as determined by the {@link #equals(Object)} method. */ public boolean endsWith(CharSeq suffix) { return back.endsWith(suffix.back); } /** * Returns the index within this string of the first occurrence of * the specified character. If a character with value * {@code ch} occurs in the character sequence represented by * this {@code CharSeq} object, then the index (in Unicode * code units) of the first such occurrence is returned. For * values of {@code ch} in the range from 0 to 0xFFFF * (inclusive), this is the smallest value k such that: *
     * this.charAt(k) == ch
     * 
* is true. For other values of {@code ch}, it is the * smallest value k such that: *
     * this.codePointAt(k) == ch
     * 
* is true. In either case, if no such character occurs in this * string, then {@code -1} is returned. * * @param ch a character (Unicode code point). * @return the index of the first occurrence of the character in the * character sequence represented by this object, or * {@code -1} if the character does not occur. */ public int indexOf(int ch) { return back.indexOf(ch); } /** * Returns the index within this string of the first occurrence of the * specified character, starting the search at the specified index. *

* If a character with value {@code ch} occurs in the * character sequence represented by this {@code CharSeq} * object at an index no smaller than {@code fromIndex}, then * the index of the first such occurrence is returned. For values * of {@code ch} in the range from 0 to 0xFFFF (inclusive), * this is the smallest value k such that: *

     * (this.charAt(k) == ch) {@code &&} (k >= fromIndex)
     * 
* is true. For other values of {@code ch}, it is the * smallest value k such that: *
     * (this.codePointAt(k) == ch) {@code &&} (k >= fromIndex)
     * 
* is true. In either case, if no such character occurs in this * string at or after position {@code fromIndex}, then * {@code -1} is returned. * *

* There is no restriction on the value of {@code fromIndex}. If it * is negative, it has the same effect as if it were zero: this entire * string may be searched. If it is greater than the length of this * string, it has the same effect as if it were equal to the length of * this string: {@code -1} is returned. * *

All indices are specified in {@code char} values * (Unicode code units). * * @param ch a character (Unicode code point). * @param fromIndex the index to start the search from. * @return the index of the first occurrence of the character in the * character sequence represented by this object that is greater * than or equal to {@code fromIndex}, or {@code -1} * if the character does not occur. */ public int indexOf(int ch, int fromIndex) { return back.indexOf(ch, fromIndex); } /** * Returns the index within this string of the last occurrence of * the specified character. For values of {@code ch} in the * range from 0 to 0xFFFF (inclusive), the index (in Unicode code * units) returned is the largest value k such that: *

     * this.charAt(k) == ch
     * 
* is true. For other values of {@code ch}, it is the * largest value k such that: *
     * this.codePointAt(k) == ch
     * 
* is true. In either case, if no such character occurs in this * string, then {@code -1} is returned. The * {@code CharSeq} is searched backwards starting at the last * character. * * @param ch a character (Unicode code point). * @return the index of the last occurrence of the character in the * character sequence represented by this object, or * {@code -1} if the character does not occur. */ public int lastIndexOf(int ch) { return back.lastIndexOf(ch); } /** * Returns the index within this string of the last occurrence of * the specified character, searching backward starting at the * specified index. For values of {@code ch} in the range * from 0 to 0xFFFF (inclusive), the index returned is the largest * value k such that: *
     * (this.charAt(k) == ch) {@code &&} (k <= fromIndex)
     * 
* is true. For other values of {@code ch}, it is the * largest value k such that: *
     * (this.codePointAt(k) == ch) {@code &&} (k <= fromIndex)
     * 
* is true. In either case, if no such character occurs in this * string at or before position {@code fromIndex}, then * {@code -1} is returned. * *

All indices are specified in {@code char} values * (Unicode code units). * * @param ch a character (Unicode code point). * @param fromIndex the index to start the search from. There is no * restriction on the value of {@code fromIndex}. If it is * greater than or equal to the length of this string, it has * the same effect as if it were equal to one less than the * length of this string: this entire string may be searched. * If it is negative, it has the same effect as if it were -1: * -1 is returned. * @return the index of the last occurrence of the character in the * character sequence represented by this object that is less * than or equal to {@code fromIndex}, or {@code -1} * if the character does not occur before that point. */ public int lastIndexOf(int ch, int fromIndex) { return back.lastIndexOf(ch, fromIndex); } /** * Returns the index within this string of the first occurrence of the * specified substring. * *

The returned index is the smallest value k for which: *

     * this.startsWith(str, k)
     * 
* If no such value of k exists, then {@code -1} is returned. * * @param str the substring to search for. * @return the index of the first occurrence of the specified substring, * or {@code -1} if there is no such occurrence. */ public int indexOf(CharSeq str) { return back.indexOf(str.back); } /** * Returns the index within this string of the first occurrence of the * specified substring, starting at the specified index. * *

The returned index is the smallest value k for which: *

     * k >= fromIndex {@code &&} this.startsWith(str, k)
     * 
* If no such value of k exists, then {@code -1} is returned. * * @param str the substring to search for. * @param fromIndex the index from which to start the search. * @return the index of the first occurrence of the specified substring, * starting at the specified index, * or {@code -1} if there is no such occurrence. */ public int indexOf(CharSeq str, int fromIndex) { return back.indexOf(str.back, fromIndex); } /** * Returns the index within this string of the last occurrence of the * specified substring. The last occurrence of the empty string "" * is considered to occur at the index value {@code this.length()}. * *

The returned index is the largest value k for which: *

     * this.startsWith(str, k)
     * 
* If no such value of k exists, then {@code -1} is returned. * * @param str the substring to search for. * @return the index of the last occurrence of the specified substring, * or {@code -1} if there is no such occurrence. */ public int lastIndexOf(CharSeq str) { return back.lastIndexOf(str.back); } /** * Returns the index within this string of the last occurrence of the * specified substring, searching backward starting at the specified index. * *

The returned index is the largest value k for which: *

     * k {@code <=} fromIndex {@code &&} this.startsWith(str, k)
     * 
* If no such value of k exists, then {@code -1} is returned. * * @param str the substring to search for. * @param fromIndex the index to start the search from. * @return the index of the last occurrence of the specified substring, * searching backward from the specified index, * or {@code -1} if there is no such occurrence. */ public int lastIndexOf(CharSeq str, int fromIndex) { return back.lastIndexOf(str.back, fromIndex); } /** * Returns a string that is a substring of this string. The * substring begins with the character at the specified index and * extends to the end of this string.

* Examples: *

     * "unhappy".substring(2) returns "happy"
     * "Harbison".substring(3) returns "bison"
     * "emptiness".substring(9) returns "" (an empty string)
     * 
* * @param beginIndex the beginning index, inclusive. * @return the specified substring. * @throws IndexOutOfBoundsException if * {@code beginIndex} is negative or larger than the * length of this {@code CharSeq} object. */ public CharSeq substring(int beginIndex) { return CharSeq.of(back.substring(beginIndex)); } /** * Returns a string that is a substring of this string. The * substring begins at the specified {@code beginIndex} and * extends to the character at index {@code endIndex - 1}. * Thus the length of the substring is {@code endIndex-beginIndex}. *

* Examples: *

     * "hamburger".substring(4, 8) returns "urge"
     * "smiles".substring(1, 5) returns "mile"
     * 
* * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified substring. * @throws IndexOutOfBoundsException if the * {@code beginIndex} is negative, or * {@code endIndex} is larger than the length of * this {@code CharSeq} object, or * {@code beginIndex} is larger than * {@code endIndex}. */ public CharSeq substring(int beginIndex, int endIndex) { return CharSeq.of(back.substring(beginIndex, endIndex)); } @Override public String stringPrefix() { return "CharSeq"; } /** * Returns a string containing the characters in this sequence in the same * order as this sequence. The length of the string will be the length of * this sequence. * * @return a string consisting of exactly this sequence of characters */ @Override public String toString() { return back; } /** * Concatenates the specified string to the end of this string. *

* If the length of the argument string is {@code 0}, then this * {@code CharSeq} object is returned. Otherwise, a * {@code CharSeq} object is returned that represents a character * sequence that is the concatenation of the character sequence * represented by this {@code CharSeq} object and the character * sequence represented by the argument string.

* Examples: *

     * "cares".concat("s") returns "caress"
     * "to".concat("get").concat("her") returns "together"
     * 
* * @param str the {@code CharSeq} that is concatenated to the end * of this {@code CharSeq}. * @return a string that represents the concatenation of this object's * characters followed by the string argument's characters. */ public CharSeq concat(CharSeq str) { return CharSeq.of(back.concat(str.back)); } /** * Tells whether or not this string matches the given regular expression. * *

An invocation of this method of the form * str{@code .matches(}regex{@code )} yields exactly the * same result as the expression * *

* {@link java.util.regex.Pattern}.{@link java.util.regex.Pattern#matches(String, CharSequence) * matches(regex, str)} *
* * @param regex the regular expression to which this string is to be matched * @return {@code true} if, and only if, this string matches the * given regular expression * @throws PatternSyntaxException if the regular expression's syntax is invalid * @see java.util.regex.Pattern */ public boolean matches(String regex) { return back.matches(regex); } /** * Returns true if and only if this string contains the specified * sequence of char values. * * @param s the sequence to search for * @return true if this string contains {@code s}, false otherwise */ public boolean contains(CharSequence s) { return back.contains(s); } /** * Replaces the first substring of this string that matches the given regular expression with the * given replacement. * *

An invocation of this method of the form * str{@code .replaceFirst(}regex{@code ,} repl{@code )} * yields exactly the same result as the expression * *

* * {@link java.util.regex.Pattern}.{@link * java.util.regex.Pattern#compile compile}(regex).{@link * java.util.regex.Pattern#matcher(java.lang.CharSequence) matcher}(str).{@link * java.util.regex.Matcher#replaceFirst replaceFirst}(repl) * *
* *

* Note that backslashes ({@code \}) and dollar signs ({@code $}) in the * replacement string may cause the results to be different than if it were * being treated as a literal replacement string; see * {@link java.util.regex.Matcher#replaceFirst}. * Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special * meaning of these characters, if desired. * * @param regex the regular expression to which this string is to be matched * @param replacement the string to be substituted for the first match * @return The resulting {@code CharSeq} * @throws PatternSyntaxException if the regular expression's syntax is invalid * @see java.util.regex.Pattern */ public CharSeq replaceFirst(String regex, String replacement) { return CharSeq.of(back.replaceFirst(regex, replacement)); } /** * Replaces each substring of this string that matches the given regular expression with the * given replacement. * *

An invocation of this method of the form * str{@code .replaceAll(}regex{@code ,} repl{@code )} * yields exactly the same result as the expression * *

* * {@link java.util.regex.Pattern}.{@link * java.util.regex.Pattern#compile compile}(regex).{@link * java.util.regex.Pattern#matcher(java.lang.CharSequence) matcher}(str).{@link * java.util.regex.Matcher#replaceAll replaceAll}(repl) * *
* *

* Note that backslashes ({@code \}) and dollar signs ({@code $}) in the * replacement string may cause the results to be different than if it were * being treated as a literal replacement string; see * {@link java.util.regex.Matcher#replaceAll Matcher.replaceAll}. * Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special * meaning of these characters, if desired. * * @param regex the regular expression to which this string is to be matched * @param replacement the string to be substituted for each match * @return The resulting {@code CharSeq} * @throws PatternSyntaxException if the regular expression's syntax is invalid * @see java.util.regex.Pattern */ public CharSeq replaceAll(String regex, String replacement) { return CharSeq.of(back.replaceAll(regex, replacement)); } /** * Replaces each substring of this string that matches the literal target * sequence with the specified literal replacement sequence. The * replacement proceeds from the beginning of the string to the end, for * example, replacing "aa" with "b" in the string "aaa" will result in * "ba" rather than "ab". * * @param target The sequence of char values to be replaced * @param replacement The replacement sequence of char values * @return The resulting string */ public CharSeq replace(CharSequence target, CharSequence replacement) { return CharSeq.of(back.replace(target, replacement)); } /** * Splits this string around matches of the given * regular expression. * *

The array returned by this method contains each substring of this * string that is terminated by another substring that matches the given * expression or is terminated by the end of the string. The substrings in * the array are in the order in which they occur in this string. If the * expression does not match any part of the input then the resulting array * has just one element, namely this string. * *

When there is a positive-width match at the beginning of this * string then an empty leading substring is included at the beginning * of the resulting array. A zero-width match at the beginning however * never produces such empty leading substring. * *

The {@code limit} parameter controls the number of times the * pattern is applied and therefore affects the length of the resulting * array. If the limit n is greater than zero then the pattern * will be applied at most n - 1 times, the array's * length will be no greater than n, and the array's last entry * will contain all input beyond the last matched delimiter. If n * is non-positive then the pattern will be applied as many times as * possible and the array can have any length. If n is zero then * the pattern will be applied as many times as possible, the array can * have any length, and trailing empty strings will be discarded. * *

The string {@code "boo:and:foo"}, for example, yields the * following results with these parameters: * *

* * * * * * * * * * * * * * * * * * * * * * * *
RegexLimitResult
:2{@code { "boo", "and:foo" }}
:5{@code { "boo", "and", "foo" }}
:-2{@code { "boo", "and", "foo" }}
o5{@code { "b", "", ":and:f", "", "" }}
o-2{@code { "b", "", ":and:f", "", "" }}
o0{@code { "b", "", ":and:f" }}
* *

An invocation of this method of the form * str.{@code split(}regex{@code ,} n{@code )} * yields the same result as the expression * *

* * {@link java.util.regex.Pattern}.{@link * java.util.regex.Pattern#compile compile}(regex).{@link * java.util.regex.Pattern#split(java.lang.CharSequence, int) split}(strn) * *
* * @param regex the delimiting regular expression * @param limit the result threshold, as described above * @return the array of strings computed by splitting this string * around matches of the given regular expression * @throws PatternSyntaxException if the regular expression's syntax is invalid * @see java.util.regex.Pattern */ public CharSeq[] split(String regex, int limit) { final String[] javaStrings = back.split(regex, limit); final CharSeq[] strings = new CharSeq[javaStrings.length]; for (int i = 0; i < strings.length; i++) { strings[i] = of(javaStrings[i]); } return strings; } /** * Splits this string around matches of the given regular expression. * *

This method works as if by invoking the two-argument {@link * #split(String, int) split} method with the given expression and a limit * argument of zero. Trailing empty strings are therefore not included in * the resulting array. * *

The string {@code "boo:and:foo"}, for example, yields the following * results with these expressions: * *

* * * * * * * * *
RegexResult
:{@code { "boo", "and", "foo" }}
o{@code { "b", "", ":and:f" }}
* * @param regex the delimiting regular expression * @return the array of strings computed by splitting this string * around matches of the given regular expression * @throws PatternSyntaxException if the regular expression's syntax is invalid * @see java.util.regex.Pattern */ public CharSeq[] split(String regex) { return split(regex, 0); } /** * Converts all of the characters in this {@code CharSeq} to lower * case using the rules of the given {@code Locale}. Case mapping is based * on the Unicode Standard version specified by the {@link java.lang.Character Character} * class. Since case mappings are not always 1:1 char mappings, the resulting * {@code CharSeq} may be a different length than the original {@code CharSeq}. *

* Examples of lowercase mappings are in the following table: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Language Code of LocaleUpper CaseLower CaseDescription
tr (Turkish)\u0130\u0069capital letter I with dot above -> small letter i
tr (Turkish)\u0049\u0131capital letter I -> small letter dotless i
(all)French Friesfrench frieslowercased all chars in String
(all)capiotacapchi * capthetacapupsil * capsigmaiotachi * thetaupsilon * sigmalowercased all chars in String
* * @param locale use the case transformation rules for this locale * @return the {@code CharSeq}, converted to lowercase. * @see String#toLowerCase() * @see String#toUpperCase() * @see String#toUpperCase(Locale) */ public CharSeq toLowerCase(Locale locale) { return CharSeq.of(back.toLowerCase(locale)); } /** * Converts all of the characters in this {@code CharSeq} to lower * case using the rules of the default locale. This is equivalent to calling * {@code toLowerCase(Locale.getDefault())}. *

* Note: This method is locale sensitive, and may produce unexpected * results if used for strings that are intended to be interpreted locale * independently. * Examples are programming language identifiers, protocol keys, and HTML * tags. * For instance, {@code "TITLE".toLowerCase()} in a Turkish locale * returns {@code "t\u005Cu0131tle"}, where '\u005Cu0131' is the * LATIN SMALL LETTER DOTLESS I character. * To obtain correct results for locale insensitive strings, use * {@code toLowerCase(Locale.ROOT)}. *

* * @return the {@code CharSeq}, converted to lowercase. * @see String#toLowerCase(Locale) */ public CharSeq toLowerCase() { return CharSeq.of(back.toLowerCase(Locale.getDefault())); } /** * Converts all of the characters in this {@code CharSeq} to upper * case using the rules of the given {@code Locale}. Case mapping is based * on the Unicode Standard version specified by the {@link java.lang.Character Character} * class. Since case mappings are not always 1:1 char mappings, the resulting * {@code CharSeq} may be a different length than the original {@code CharSeq}. *

* Examples of locale-sensitive and 1:M case mappings are in the following table. * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Language Code of LocaleLower CaseUpper CaseDescription
tr (Turkish)\u0069\u0130small letter i -> capital letter I with dot above
tr (Turkish)\u0131\u0049small letter dotless i -> capital letter I
(all)\u00df\u0053 \u0053small letter sharp s -> two letters: SS
(all)FahrvergnügenFAHRVERGNÜGEN
* * @param locale use the case transformation rules for this locale * @return the {@code CharSeq}, converted to uppercase. * @see String#toUpperCase() * @see String#toLowerCase() * @see String#toLowerCase(Locale) */ public CharSeq toUpperCase(Locale locale) { return CharSeq.of(back.toUpperCase(locale)); } /** * Converts all of the characters in this {@code CharSeq} to upper * case using the rules of the default locale. This method is equivalent to * {@code toUpperCase(Locale.getDefault())}. *

* Note: This method is locale sensitive, and may produce unexpected * results if used for strings that are intended to be interpreted locale * independently. * Examples are programming language identifiers, protocol keys, and HTML * tags. * For instance, {@code "title".toUpperCase()} in a Turkish locale * returns {@code "T\u005Cu0130TLE"}, where '\u005Cu0130' is the * LATIN CAPITAL LETTER I WITH DOT ABOVE character. * To obtain correct results for locale insensitive strings, use * {@code toUpperCase(Locale.ROOT)}. *

* * @return the {@code CharSeq}, converted to uppercase. * @see String#toUpperCase(Locale) */ public CharSeq toUpperCase() { return CharSeq.of(back.toUpperCase(Locale.getDefault())); } /** * Returns a string whose value is this string, with any leading and trailing * whitespace removed. *

* If this {@code CharSeq} object represents an empty character * sequence, or the first and last characters of character sequence * represented by this {@code CharSeq} object both have codes * greater than {@code '\u005Cu0020'} (the space character), then a * reference to this {@code CharSeq} object is returned. *

* Otherwise, if there is no character with a code greater than * {@code '\u005Cu0020'} in the string, then a * {@code CharSeq} object representing an empty string is * returned. *

* Otherwise, let k be the index of the first character in the * string whose code is greater than {@code '\u005Cu0020'}, and let * m be the index of the last character in the string whose code * is greater than {@code '\u005Cu0020'}. A {@code CharSeq} * object is returned, representing the substring of this string that * begins with the character at index k and ends with the * character at index m-that is, the result of * {@code this.substring(k, m + 1)}. *

* This method may be used to trim whitespace (as defined above) from * the beginning and end of a string. * * @return A string whose value is this string, with any leading and trailing white * space removed, or this string if it has no leading or * trailing white space. */ public CharSeq trim() { return of(back.trim()); } /** * Converts this string to a new character array. * * @return a newly allocated character array whose length is the length * of this string and whose contents are initialized to contain * the character sequence represented by this string. */ public char[] toCharArray() { return back.toCharArray(); } @FunctionalInterface public interface CharUnaryOperator { char apply(char c); } @FunctionalInterface public interface CharFunction { R apply(char c); } } interface CharSeqModule { interface Combinations { static IndexedSeq apply(CharSeq elements, int k) { if (k == 0) { return Vector.of(CharSeq.empty()); } else { return elements.zipWithIndex().flatMap( t -> apply(elements.drop(t._2 + 1), (k - 1)).map((CharSeq c) -> c.prepend(t._1)) ); } } } }