Skip to content

Commit

Permalink
add Strings#replaceEach(StringBuilder,String[],String[])
Browse files Browse the repository at this point in the history
  • Loading branch information
sebthom committed Jun 19, 2024
1 parent 9837474 commit b37fa62
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 10 deletions.
92 changes: 84 additions & 8 deletions jstuff-core/src/main/java/net/sf/jstuff/core/Strings.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
package net.sf.jstuff.core;

import static net.sf.jstuff.core.validation.NullAnalysisHelper.asNonNullUnsafe;
import static net.sf.jstuff.core.validation.NullAnalysisHelper.*;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
Expand Down Expand Up @@ -2737,11 +2737,84 @@ public static String replaceEach(final String searchIn, final String... tokens)
return asNonNullUnsafe(replaceEachNullable(searchIn, tokens));
}

/**
* See {@link StringUtils#replaceEach(String, String[], String[])}
*/
public static String replaceEach(final String searchIn, final String @Nullable [] searchFor, final String @Nullable [] replaceWith) {
return asNonNullUnsafe(StringUtils.replaceEach(searchIn, searchFor, replaceWith));
if (searchFor == null && replaceWith == null || searchIn.length() == 0)
return searchIn;
if (searchFor == null || replaceWith == null || searchFor.length != replaceWith.length)
throw new IllegalArgumentException("searchFor and replaceWith array lengths don't match.");
if (searchFor.length == 0)
return searchIn;

final var sb = new StringBuilder();
int startAt = 0;
String searchString = lateNonNull();
String replaceString = lateNonNull();

while (true) {
int closestIndex = -1;

// Find the closest match at the current position
for (int j = 0; j < searchFor.length; j++) {
final String currSearchFor = searchFor[j];
final String currReplaceWith = replaceWith[j];
if (currSearchFor == null || currReplaceWith == null || currSearchFor.isEmpty()) {
continue;
}
final int index = searchIn.indexOf(currSearchFor, startAt);
if (index != -1 && (closestIndex == -1 || index < closestIndex)) {
closestIndex = index;
searchString = currSearchFor;
replaceString = currReplaceWith;
}
}

if (closestIndex == -1) {
sb.append(searchIn.substring(startAt));
return sb.toString();
}

sb.append(searchIn, startAt, closestIndex);
sb.append(replaceString);
startAt = closestIndex + searchString.length();
}
}

public static void replaceEach(final @Nullable StringBuilder searchIn, final String @Nullable [] searchFor,
final String @Nullable [] replaceWith) {
if (searchIn == null || searchFor == null && replaceWith == null || searchIn.length() == 0)
return;
if (searchFor == null || replaceWith == null || searchFor.length != replaceWith.length)
throw new IllegalArgumentException("searchFor and replaceWith array lengths don't match.");
if (searchFor.length == 0)
return;

int startAt = 0;
String searchString = lateNonNull();
String replaceString = lateNonNull();
while (true) {
int closestIndex = -1;

// Find the closest match at the current position
for (int j = 0; j < searchFor.length; j++) {
final String currSearchFor = searchFor[j];
final String currReplaceWith = replaceWith[j];
if (currSearchFor == null || currReplaceWith == null || currSearchFor.isEmpty()) {
continue;
}
final int foundAt = searchIn.indexOf(currSearchFor, startAt);
if (foundAt != -1 && (closestIndex == -1 || foundAt < closestIndex)) {
closestIndex = foundAt;
searchString = currSearchFor;
replaceString = currReplaceWith;
}
}

if (closestIndex == -1)
return;

searchIn.replace(closestIndex, closestIndex + searchString.length(), replaceString);
startAt = closestIndex + replaceString.length();
}
}

public static CharSequence replaceEachGroup(final @Nullable Pattern regex, final CharSequence searchIn, final int groupToReplace,
Expand Down Expand Up @@ -2784,7 +2857,7 @@ public static CharSequence replaceEachGroup(final @Nullable String regex, final

boolean isNextTokenSearchKey = true;
int idx = 0;
for (final String token : tokens)
for (final String token : tokens) {
if (isNextTokenSearchKey) {
searchFor[idx] = token;
isNextTokenSearchKey = false;
Expand All @@ -2793,15 +2866,18 @@ public static CharSequence replaceEachGroup(final @Nullable String regex, final
idx++;
isNextTokenSearchKey = true;
}
return replaceEach(searchIn, searchFor, replaceWith);
}
return StringUtils.replaceEach(searchIn, searchFor, replaceWith);
}

/**
* See {@link StringUtils#replaceEach(String, String[], String[])}
*/
public static @Nullable String replaceEachNullable(final @Nullable String searchIn, final String @Nullable [] searchFor,
final String @Nullable [] replaceWith) {
return StringUtils.replaceEach(searchIn, searchFor, replaceWith);
if (searchIn == null || searchFor == null || replaceWith == null)
return searchIn;
return replaceEach(searchIn, searchFor, replaceWith);
}

/**
Expand Down
156 changes: 154 additions & 2 deletions jstuff-core/src/test/java/net/sf/jstuff/core/StringsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
package net.sf.jstuff.core;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;

import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -802,14 +802,166 @@ public void testReplaceAt6() {
}

@Test
public void testRepleaceEach() {
public void testReplaceEach() {
assertThat(Strings.replaceEach("There was a man with seven kids.", //
"man", "woman", //
"seven", "three", //
"kids", "cats" //
)).isEqualTo("There was a woman with three cats.");
}

@Test
@SuppressWarnings("null")
public void testReplaceEach_String() {
// Test 1: Basic replacement
{
final var searchIn = "Hello world! This is a test. Hello everyone!";
final @NonNull String[] searchFor = {"Hello", "test"};
final @NonNull String[] replaceWith = {"Hi", "experiment"};

assertThat(Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.hasToString("Hi world! This is a experiment. Hi everyone!");
}

// Test 2: No match
{
final var searchIn = "Hello world! This is a test. Hello everyone!";
final @NonNull String[] searchFor = {"Goodbye", "example"};
final @NonNull String[] replaceWith = {"Farewell", "sample"};

assertThat(Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.hasToString("Hello world! This is a test. Hello everyone!");
}

// Test 3: Partial match
{
final var searchIn = "Hello world! This is a test. Hello everyone!";
final @NonNull String[] searchFor = {"Hello", "example"};
final @NonNull String[] replaceWith = {"Hi", "sample"};

assertThat(Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.hasToString("Hi world! This is a test. Hi everyone!");
}

// Test 4: Empty searchIn
{
final var searchIn = "";
final @NonNull String[] searchFor = {"Hello", "test"};
final @NonNull String[] replaceWith = {"Hi", "experiment"};

assertThat(Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.hasToString("");
}

// Test 5: Empty search list
{
final var searchIn = "Hello world! This is a test. Hello everyone!";
final @NonNull String[] searchFor = {};
final @NonNull String[] replaceWith = {};

assertThat(Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.hasToString("Hello world! This is a test. Hello everyone!");
}

// Test 6: Different lengths for search and replacement lists
{
final var searchIn = "Hello world! This is a test. Hello everyone!";
final @NonNull String[] searchFor = {"Hello", "test"};
final @NonNull String[] replaceWith = {"Hi"};

assertThatThrownBy(() -> Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.isInstanceOf(IllegalArgumentException.class) //
.hasMessage("searchFor and replaceWith array lengths don't match.");
}

// Test 7: Repeated matches
{
// Test: Repeated matches of multiple replacements
final var searchIn = "ababcdcdababcdcd";
final String @NonNull [] searchFor = {"ab", "cd"};
final String @NonNull [] replaceWith = {"xy", "uv"};

assertThat(Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.hasToString("xyxyuvuvxyxyuvuv");
}
}

@Test
@SuppressWarnings("null")
public void testReplaceEach_StringBuilder() {
// Test 1: Basic replacement
{
final var searchIn = new StringBuilder("Hello world! This is a test. Hello everyone!");
final @NonNull String[] searchFor = {"Hello", "test"};
final @NonNull String[] replaceWith = {"Hi", "experiment"};

Strings.replaceEach(searchIn, searchFor, replaceWith);
assertThat(searchIn).hasToString("Hi world! This is a experiment. Hi everyone!");
}

// Test 2: No match
{
final var searchIn = new StringBuilder("Hello world! This is a test. Hello everyone!");
final @NonNull String[] searchFor = {"Goodbye", "example"};
final @NonNull String[] replaceWith = {"Farewell", "sample"};

Strings.replaceEach(searchIn, searchFor, replaceWith);
assertThat(searchIn).hasToString("Hello world! This is a test. Hello everyone!");
}

// Test 3: Partial match
{
final var searchIn = new StringBuilder("Hello world! This is a test. Hello everyone!");
final @NonNull String[] searchFor = {"Hello", "example"};
final @NonNull String[] replaceWith = {"Hi", "sample"};

Strings.replaceEach(searchIn, searchFor, replaceWith);
assertThat(searchIn).hasToString("Hi world! This is a test. Hi everyone!");
}

// Test 4: Empty searchIn
{
final var searchIn = new StringBuilder("");
final @NonNull String[] searchFor = {"Hello", "test"};
final @NonNull String[] replaceWith = {"Hi", "experiment"};

Strings.replaceEach(searchIn, searchFor, replaceWith);
assertThat(searchIn).hasToString("");
}

// Test 5: Empty search list
{
final var searchIn = new StringBuilder("Hello world! This is a test. Hello everyone!");
final @NonNull String[] searchFor = {};
final @NonNull String[] replaceWith = {};

Strings.replaceEach(searchIn, searchFor, replaceWith);
assertThat(searchIn).hasToString("Hello world! This is a test. Hello everyone!");
}

// Test 6: Different lengths for search and replacement lists
{
final var searchIn = new StringBuilder("Hello world! This is a test. Hello everyone!");
final @NonNull String[] searchFor = {"Hello", "test"};
final @NonNull String[] replaceWith = {"Hi"};

assertThatThrownBy(() -> Strings.replaceEach(searchIn, searchFor, replaceWith)) //
.isInstanceOf(IllegalArgumentException.class) //
.hasMessage("searchFor and replaceWith array lengths don't match.");
}

// Test 7: Repeated matches
{
// Test: Repeated matches of multiple replacements
final var searchIn = new StringBuilder("ababcdcdababcdcd");
final String @NonNull [] searchFor = {"ab", "cd"};
final String @NonNull [] replaceWith = {"xy", "uv"};

Strings.replaceEach(searchIn, searchFor, replaceWith);
assertThat(searchIn).hasToString("xyxyuvuvxyxyuvuv");
}
}

@Test
public void testSplitLikeShell() {
assertThat(Strings.splitLikeShell("")).isEmpty();
Expand Down

0 comments on commit b37fa62

Please sign in to comment.