A utility method StringUtils.replace
to replace multiple search strings simultaneously.
I'm looking for a review of any and all aspects, but especially:
- Is there's a better way to do this? (short of external libraries)
- Is the parameter validation OK the way I did?
- Is the way I test adequate and up to good practices in testing?
The code:
import java.util.*;
import java.util.stream.Stream;
public class StringUtils {
public static final String ERR_NULL_PARAM = "none of the parameters should be null";
public static final String ERR_SEARCHSTRINGS_REPLACEMENTS_LENGTH_MISMATCH =
"there must be the same number of search strings and replacements";
public static final String ERR_NULL_OR_EMPTY_SEARCHSTRING = "there must be no null element or empty search string";
public static final String ERR_NULL_REPLACEMENT = "there must be no null element in replacements";
public static final String ERR_DUPLICATE_SEARCHSTRINGS = "search strings must be distinct";
/**
* Replace multiple search strings simultaneously
*
* @param text the source text
* @param searchStrings search strings to replace
* @param replacements texts to replace the corresponding search strings
* @return new text with search strings replaced
*/
public static String replace(String text, String[] searchStrings, String[] replacements) {
validateParams(text, searchStrings, replacements);
StringBuffer buffer = new StringBuffer();
Pattern pattern = Pattern.compile(Stream.of(searchStrings).collect(joining("|")));
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String match = matcher.group();
for (int i = 0; i < searchStrings.length; ++i) {
if (match.equals(searchStrings[i])) {
matcher.appendReplacement(buffer, replacements[i]);
break;
}
}
}
matcher.appendTail(buffer);
return buffer.toString();
}
private static void validateParams(String text, String[] searchStrings, String[] replacements) {
if (text == null || searchStrings == null || replacements == null) {
throw new IllegalArgumentException(ERR_NULL_PARAM);
}
if (searchStrings.length != replacements.length) {
throw new IllegalArgumentException(ERR_SEARCHSTRINGS_REPLACEMENTS_LENGTH_MISMATCH);
}
if (searchStrings.length == 0) {
return;
}
if (anyNullOrEmpty(searchStrings)) {
throw new IllegalArgumentException(ERR_NULL_OR_EMPTY_SEARCHSTRING);
}
if (anyNull(replacements)) {
throw new IllegalArgumentException(ERR_NULL_REPLACEMENT);
}
if (containsDuplicates(searchStrings)) {
throw new IllegalArgumentException(ERR_DUPLICATE_SEARCHSTRINGS);
}
}
private static boolean anyNullOrEmpty(String[] strings) {
return Stream.of(strings).allMatch(x -> x == null || x.isEmpty());
}
private static boolean anyNull(String[] strings) {
return Stream.of(strings).allMatch(x -> x == null);
}
private static boolean containsDuplicates(String[] strings) {
return Stream.of(strings).distinct().count() != strings.length;
}
}
Unit tests:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static com.janosgyerik.tools_wip.StringUtils.*;
import static org.junit.Assert.assertEquals;
public class StringUtilsTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void test_empty_text() {
assertEquals("", replace("", new String[]{"foo"}, new String[]{"bar"}));
}
@Test
public void test_empty_searchstrings_and_replacements() {
assertEquals("", replace("", new String[0], new String[0]));
}
@Test
public void test_replace_one_searchstring_once() {
String before = "foo";
String after = "bar";
assertEquals(after, replace(before, new String[]{before}, new String[]{after}));
}
@Test
public void test_replace_one_searchstring_twice() {
String before = "foo";
String after = "bar";
assertEquals(after + after, replace(before + before, new String[]{before}, new String[]{after}));
}
@Test
public void test_replace_two_searchstrings_simultaneously() {
String before1 = "foo";
String after1 = "bar";
String after2 = "baz";
assertEquals(after1 + after2, replace(before1 + after1,
new String[]{before1, after1},
new String[]{after1, after2}));
}
@Test
public void test_no_matches() {
String text = "foofoo";
assertEquals(text, replace(text, new String[]{"notmatching"}, new String[]{"bar"}));
}
@Test
public void test_source_overlap() {
assertEquals("foovel", replace("applevel", new String[]{"apple", "level"}, new String[]{"foo", "bar"}));
}
@Test
public void test_result_overlap() {
assertEquals("spacemarvel", replace("applevel",
new String[]{"apple", "marvel"},
new String[]{"spacemar", "bar"}));
}
@Test
public void test_replace_multiple_simultaneous_searchstrings() {
assertEquals("Once upon a foo, there was a bar and a baz, and another bar and a cat.",
replace("Once upon a baz, there was a foo and a bar, and another foo and a cat.",
new String[]{"foo", "bar", "baz"},
new String[]{"bar", "baz", "foo"})
);
}
@Test
public void test_circular_replacement() {
assertEquals("barfoo", replace("foobar", new String[]{"foo", "bar"}, new String[]{"bar", "foo"}));
}
@Test
public void test_null_text_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_NULL_PARAM);
replace(null, new String[0], new String[0]);
}
@Test
public void test_null_searchstrings_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_NULL_PARAM);
replace("", null, new String[0]);
}
@Test
public void test_null_replacements_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_NULL_PARAM);
replace("", new String[0], null);
}
@Test
public void test_more_replacements_than_searchstrings_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_SEARCHSTRINGS_REPLACEMENTS_LENGTH_MISMATCH);
replace("", new String[0], new String[]{"bar"});
}
@Test
public void test_more_searchstrings_than_replacements_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_SEARCHSTRINGS_REPLACEMENTS_LENGTH_MISMATCH);
replace("", new String[]{"foo"}, new String[0]);
}
@Test
public void test_null_element_in_searchstrings_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_NULL_OR_EMPTY_SEARCHSTRING);
replace("", new String[]{null}, new String[]{"bar"});
}
@Test
public void test_empty_element_in_searchstrings_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_NULL_OR_EMPTY_SEARCHSTRING);
replace("", new String[]{""}, new String[]{"bar"});
}
@Test
public void test_null_element_in_replacements_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_NULL_REPLACEMENT);
replace("", new String[]{"foo"}, new String[]{null});
}
@Test
public void test_non_distinct_searchstrings_should_throw() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(ERR_DUPLICATE_SEARCHSTRINGS);
String before = "foo";
replace("", new String[]{before, before}, new String[]{"bar", "baz"});
}
}
searchStrings
orreplacements
literal, or not: say so in their@param
comments. (Ifreplacements
are not literal, mentionMatcher.quoteReplacement()
.) \$\endgroup\$