Skip to content

Commit

Permalink
Add more null-safe split methods in KiwiStrings
Browse files Browse the repository at this point in the history
Closes #910
  • Loading branch information
sleberknight committed Mar 9, 2023
1 parent 05fae27 commit 8251cda
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 1 deletion.
155 changes: 154 additions & 1 deletion src/main/java/org/kiwiproject/base/KiwiStrings.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import lombok.experimental.UtilityClass;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

/**
* Utility methods relating to strings or similar.
Expand Down Expand Up @@ -47,6 +50,31 @@ public final class KiwiStrings {
private static final Splitter TRIMMING_AND_EMPTY_OMITTING_NEWLINE_SPLITTER =
Splitter.on(NEWLINE).omitEmptyStrings().trimResults();

private static class EmptyIterator<E> implements Iterator<E> {

@Override
public boolean hasNext() {
return false;
}

@Override
public E next() {
throw new NoSuchElementException();
}
}

private static final Iterator<String> EMPTY_ITERATOR = new EmptyIterator<>();

private static class EmptyStringIterable implements Iterable<String> {

@Override
public Iterator<String> iterator() {
return EMPTY_ITERATOR;
}
}

private static final Iterable<String> EMPTY_ITERABLE = new EmptyStringIterable();

/**
* Splits the given {@link CharSequence}, using a {@link #SPACE} as the separator character, omitting any empty
* strings and trimming leading and trailing whitespace.
Expand All @@ -59,6 +87,21 @@ public static Iterable<String> splitWithTrimAndOmitEmpty(CharSequence sequence)
return splitWithTrimAndOmitEmpty(sequence, SPACE);
}

/**
* Splits the given {@link CharSequence}, using a {@link #SPACE} as the separator character, omitting any empty
* strings and trimming leading and trailing whitespace.
*
* @param sequence the character sequence to be split, may be null
* @return an Iterable over the split strings, or an empty Iterable if {@code sequence} is blank
* @see #splitWithTrimAndOmitEmpty(CharSequence, char)
*/
public static Iterable<String> nullSafeSplitWithTrimAndOmitEmpty(@Nullable CharSequence sequence) {
if (isBlank(sequence)) {
return EMPTY_ITERABLE;
}
return splitWithTrimAndOmitEmpty(sequence);
}

/**
* Splits the given {@link CharSequence}, using the specified separator character, omitting any empty
* strings and trimming leading and trailing whitespace.
Expand All @@ -82,6 +125,21 @@ public static Iterable<String> splitWithTrimAndOmitEmpty(CharSequence sequence,
}
}

/**
* Splits the given {@link CharSequence}, using the specified separator character, omitting any empty
* strings and trimming leading and trailing whitespace.
*
* @param sequence the character sequence to be split, may be null
* @param separator the separator character to use
* @return an Iterable over the split strings, or an empty Iterable if {@code sequence} is blank
*/
public static Iterable<String> nullSafeSplitWithTrimAndOmitEmpty(@Nullable CharSequence sequence, char separator) {
if (isBlank(sequence)) {
return EMPTY_ITERABLE;
}
return splitWithTrimAndOmitEmpty(sequence, separator);
}

/**
* Splits the given {@link CharSequence}, using the specified separator string, omitting any empty
* strings and trimming leading and trailing whitespace.
Expand All @@ -94,6 +152,21 @@ public static Iterable<String> splitWithTrimAndOmitEmpty(CharSequence sequence,
return Splitter.on(separator).omitEmptyStrings().trimResults().split(sequence);
}

/**
* Splits the given {@link CharSequence}, using the specified separator string, omitting any empty
* strings and trimming leading and trailing whitespace.
*
* @param sequence the character sequence to be split, may be null
* @param separator the separator to use, e.g. {@code ", "}
* @return an Iterable over the split strings, or an empty Iterable if {@code sequence} is blank
*/
public static Iterable<String> nullSafeSplitWithTrimAndOmitEmpty(@Nullable CharSequence sequence, String separator) {
if (isBlank(sequence)) {
return EMPTY_ITERABLE;
}
return splitWithTrimAndOmitEmpty(sequence, separator);
}

/**
* Splits the given {@link CharSequence}, using a {@link #SPACE} as the separator character, omitting any empty
* strings and trimming leading and trailing whitespace. Returns an <i>immutable</i> list.
Expand All @@ -106,6 +179,21 @@ public static List<String> splitToList(CharSequence sequence) {
return splitToList(sequence, SPACE);
}

/**
* Splits the given {@link CharSequence}, using a {@link #SPACE} as the separator character, omitting any empty
* strings and trimming leading and trailing whitespace. Returns an <i>immutable</i> list.
*
* @param sequence the character sequence to be split, may be null
* @return an immutable list containing the split strings, or an empty list if {@code sequence} is blank
* @see #splitWithTrimAndOmitEmpty(CharSequence, char)
*/
public static List<String> nullSafeSplitToList(@Nullable CharSequence sequence) {
if (isBlank(sequence)) {
return List.of();
}
return splitToList(sequence);
}

/**
* Splits the given {@link CharSequence}, using the specified separator character, omitting any empty
* strings and trimming leading and trailing whitespace. Returns an <i>immutable</i> list.
Expand All @@ -130,6 +218,22 @@ public static List<String> splitToList(CharSequence sequence, char separator) {
}
}

/**
* Splits the given {@link CharSequence}, using the specified separator character, omitting any empty
* strings and trimming leading and trailing whitespace. Returns an <i>immutable</i> list.
*
* @param sequence the character sequence to be split, may be null
* @param separator the separator character to use
* @return an immutable list containing the split strings, or an empty list if {@code sequence} is blank
* @see #splitWithTrimAndOmitEmpty(CharSequence, char)
*/
public static List<String> nullSafeSplitToList(@Nullable CharSequence sequence, char separator) {
if (isBlank(sequence)) {
return List.of();
}
return splitToList(sequence, separator);
}

/**
* Splits the given {@link CharSequence}, using the specified separator character, into the maximum number of groups
* specified omitting any empty strings and trimming leading and trailing whitespace. Returns an
Expand All @@ -144,6 +248,23 @@ public static List<String> splitToList(CharSequence sequence, char separator, in
return Splitter.on(separator).limit(maxGroups).omitEmptyStrings().trimResults().splitToList(sequence);
}

/**
* Splits the given {@link CharSequence}, using the specified separator character, into the maximum number of groups
* specified omitting any empty strings and trimming leading and trailing whitespace. Returns an
* <i>immutable</i> list.
*
* @param sequence the character sequence to be split, may be null
* @param separator the separator character to use
* @param maxGroups the maximum number of groups to separate into
* @return an immutable list containing the split strings, or an empty list if {@code sequence} is blank
*/
public static List<String> nullSafeSplitToList(@Nullable CharSequence sequence, char separator, int maxGroups) {
if (isBlank(sequence)) {
return List.of();
}
return splitToList(sequence, separator, maxGroups);
}

/**
* Splits the given {@link CharSequence}, using the specified separator string, omitting any empty
* strings and trimming leading and trailing whitespace. Returns an <i>immutable</i> list.
Expand All @@ -156,6 +277,21 @@ public static List<String> splitToList(CharSequence sequence, String separator)
return Splitter.on(separator).omitEmptyStrings().trimResults().splitToList(sequence);
}

/**
* Splits the given {@link CharSequence}, using the specified separator string, omitting any empty
* strings and trimming leading and trailing whitespace. Returns an <i>immutable</i> list.
*
* @param sequence the character sequence to be split, may be null
* @param separator the separator string to use
* @return an immutable list containing the split strings, or an empty list if {@code sequence} is blank
*/
public static List<String> nullSafeSplitToList(@Nullable CharSequence sequence, String separator) {
if (isBlank(sequence)) {
return List.of();
}
return splitToList(sequence, separator);
}

/**
* Splits the given {@link CharSequence}, using the specified separator string, into the maximum number of groups
* specified omitting any empty strings and trimming leading and trailing whitespace. Returns an
Expand All @@ -170,6 +306,23 @@ public static List<String> splitToList(CharSequence sequence, String separator,
return Splitter.on(separator).limit(maxGroups).omitEmptyStrings().trimResults().splitToList(sequence);
}

/**
* Splits the given {@link CharSequence}, using the specified separator string, into the maximum number of groups
* specified omitting any empty strings and trimming leading and trailing whitespace. Returns an
* <i>immutable</i> list.
*
* @param sequence the character sequence to be split, may be null
* @param separator the separator string to use
* @param maxGroups the maximum number of groups to separate into
* @return an immutable list containing the split strings, or an empty list if {@code sequence} is blank
*/
public static List<String> nullSafeSplitToList(@Nullable CharSequence sequence, String separator, int maxGroups) {
if (isBlank(sequence)) {
return List.of();
}
return splitToList(sequence, separator, maxGroups);
}

/**
* Convenience method that splits the given comma-delimited {@link CharSequence}, omitting any empty strings and
* trimming leading and trailing whitespace. Returns an <i>immutable</i> list.
Expand All @@ -190,7 +343,7 @@ public static List<String> splitOnCommas(CharSequence sequence) {
* @return an immutable list containing the split strings, or an empty list if {@code sequence} is blank
* @see #splitWithTrimAndOmitEmpty(CharSequence, char)
*/
public static List<String> nullSafeSplitOnCommas(CharSequence sequence) {
public static List<String> nullSafeSplitOnCommas(@Nullable CharSequence sequence) {
if (isBlank(sequence)) {
return List.of();
}
Expand Down
116 changes: 116 additions & 0 deletions src/test/java/org/kiwiproject/base/KiwiStringsTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.kiwiproject.base;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.util.Lists.newArrayList;
import static org.kiwiproject.base.KiwiStrings.COMMA;
Expand All @@ -11,6 +12,8 @@
import static org.kiwiproject.base.KiwiStrings.f;
import static org.kiwiproject.base.KiwiStrings.format;
import static org.kiwiproject.base.KiwiStrings.nullSafeSplitOnCommas;
import static org.kiwiproject.base.KiwiStrings.nullSafeSplitToList;
import static org.kiwiproject.base.KiwiStrings.nullSafeSplitWithTrimAndOmitEmpty;
import static org.kiwiproject.base.KiwiStrings.splitOnCommas;
import static org.kiwiproject.base.KiwiStrings.splitToList;
import static org.kiwiproject.base.KiwiStrings.splitWithTrimAndOmitEmpty;
Expand All @@ -26,6 +29,7 @@
import org.kiwiproject.util.BlankStringArgumentsProvider;

import java.util.List;
import java.util.NoSuchElementException;

@DisplayName("KiwiStrings")
@ExtendWith(SoftAssertionsExtension.class)
Expand Down Expand Up @@ -109,6 +113,58 @@ void shouldSplitOnStringSeparator() {
}
}

@Nested
class NullSafeWithTrimAndOmitEmpty {

@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyIterable_WithBlankArgument(String string) {
var iterable = nullSafeSplitWithTrimAndOmitEmpty(string);
assertThat(iterable).isEmpty();
}

@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyIterable_WithBlankArgument_AndCharSeparator(String string) {
var iterable = nullSafeSplitWithTrimAndOmitEmpty(string, COMMA);
assertThat(iterable).isEmpty();
}

@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyIterable_WithBlankArgument_AndStringSeparator(String string) {
var iterable = nullSafeSplitWithTrimAndOmitEmpty(string, String.valueOf(COMMA));
assertThat(iterable).isEmpty();
}

@Test
void shouldReturnEmptyIterableWithIteratorThatThrowsNoSuchElementException() {
var iterable = nullSafeSplitWithTrimAndOmitEmpty(null);
var iterator = iterable.iterator();

assertThat(iterator.hasNext()).isFalse();
assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> iterator.next());
}

@Test
void shouldSplit() {
var iterable = nullSafeSplitWithTrimAndOmitEmpty("a b c d");
assertThat(iterable).containsExactly("a", "b", "c", "d");
}

@Test
void shouldSplitUsingCharSeparator() {
var iterable = nullSafeSplitWithTrimAndOmitEmpty("a|b|c|d", '|');
assertThat(iterable).containsExactly("a", "b", "c", "d");
}

@Test
void shouldSplitUsingStringSeparator() {
var iterable = nullSafeSplitWithTrimAndOmitEmpty("a/b/c/d", "/");
assertThat(iterable).containsExactly("a", "b", "c", "d");
}
}

@Nested
class ToList {

Expand Down Expand Up @@ -171,6 +227,66 @@ void shouldSplitWithMaxGroups_WithStringSeparator() {
}
}

@Nested
class NullSafeToList {

@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyList_WithBlankArgument(String string) {
assertThat(nullSafeSplitToList(string)).isEmpty();
}

@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyList_WithBlankArgument_AndCharSeparator(String string) {
assertThat(nullSafeSplitToList(string, COMMA)).isEmpty();
}

@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyList_WithBlankArgument_AndStringSeparator(String string) {
assertThat(nullSafeSplitToList(string, String.valueOf(COMMA))).isEmpty();
}

@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyList_WithBlankArgument_AndCharSeparator_AndMaxGroups(String string) {
assertThat(nullSafeSplitToList(string, NEWLINE, 5)).isEmpty();
}


@ParameterizedTest
@ArgumentsSource(BlankStringArgumentsProvider.class)
void shouldReturnEmptyList_WithBlankArgument_AndStringSeparator_AndMaxGroups(String string) {
assertThat(nullSafeSplitToList(string, String.valueOf(COMMA), 10)).isEmpty();
}

@Test
void shouldSplit() {
assertThat(nullSafeSplitToList("a b c d")).containsExactly("a", "b", "c", "d");
}

@Test
void shouldSplitUsingCharSeparator() {
assertThat(nullSafeSplitToList("a|b|c|d", '|')).containsExactly("a", "b", "c", "d");
}

@Test
void shouldSplitUsingCharSeparatorAndMaxGroups() {
assertThat(nullSafeSplitToList("a:b:c:d:e:f", ':', 4)).containsExactly("a", "b", "c", "d:e:f");
}

@Test
void shouldSplitUsingStringSeparator() {
assertThat(nullSafeSplitToList("a/b/c/d", "/")).containsExactly("a", "b", "c", "d");
}

@Test
void shouldSplitUsingStringSeparatorAndMaxGroups() {
assertThat(nullSafeSplitToList("a/b/c/d/e/f", "/", 4)).containsExactly("a", "b", "c", "d/e/f");
}
}

@Nested
class OnCommas {

Expand Down

0 comments on commit 8251cda

Please sign in to comment.