Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/main/java/org/apache/commons/lang3/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -9332,6 +9332,51 @@ public static String wrapIfMissing(final String str, final String wrapWith) {
return builder.toString();
}

/**
* Converts a camelCase string to snake_case.
* <pre>
* StringUtils.toSnakeCase(null) = null
* StringUtils.toSnakeCase("") = ""
* StringUtils.toSnakeCase("simple") = "simple"
* StringUtils.toSnakeCase("camelCase") = "camel_case"
* StringUtils.toSnakeCase("CamelCase") = "camel_case"
* StringUtils.toSnakeCase("thisIsATest") = "this_is_a_test"
* StringUtils.toSnakeCase("JSONParser") = "json_parser"
* StringUtils.toSnakeCase("already_snake_case") = "already_snake_case"
* StringUtils.toSnakeCase("A") = "a"
* StringUtils.toSnakeCase("HelloWorld") = "hello_world"
* StringUtils.toSnakeCase("multipleUPPERCases") = "multiple_upper_cases"
* </pre>
* @param str the camelCase string to be converted, may be {@code null}
* @return the snake_case version of the input string
*/
public static String toSnakeCase(String str) {
if (str == null || str.isEmpty()) {
return str;
}

StringBuilder result = new StringBuilder();
char prevChar = str.charAt(0);
result.append(Character.toLowerCase(prevChar)); // Initialize with the first character in lowercase

for (int i = 1; i < str.length(); i++) {
char currentChar = str.charAt(i);

if (Character.isUpperCase(currentChar)) {
// Add underscore if the previous character is not uppercase or if the next character is lowercase
if (!Character.isUpperCase(prevChar) || (i + 1 < str.length() && Character.isLowerCase(str.charAt(i + 1)))) {
result.append('_');
}
result.append(Character.toLowerCase(currentChar));
} else {
result.append(currentChar);
}

prevChar = currentChar;
}
return result.toString();
}

/**
* {@link StringUtils} instances should NOT be constructed in
* standard programming. Instead, the class should be used as
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/org/apache/commons/lang3/StringUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3431,4 +3431,21 @@ public void testWrapIfMissing_StringString() {
assertSame("ab/ab", StringUtils.wrapIfMissing("ab/ab", "ab"));
assertSame("//x//", StringUtils.wrapIfMissing("//x//", "//"));
}

/**
* Tests {@code toSnakeCase}.
*/
@Test
public void testToSnakeCase() {
assertEquals("camel_case", StringUtils.toSnakeCase("camelCase"));
assertEquals("json_parser", StringUtils.toSnakeCase("JSONParser"));
assertEquals("simple", StringUtils.toSnakeCase("simple"));
assertEquals("this_is_a_test_string", StringUtils.toSnakeCase("thisIsATestString"));
assertEquals("", StringUtils.toSnakeCase(""));
assertEquals("a", StringUtils.toSnakeCase("A"));
assertEquals("hello_world", StringUtils.toSnakeCase("HelloWorld"));
assertEquals("multiple_upper_cases", StringUtils.toSnakeCase("multipleUPPERCases"));
assertNull(StringUtils.toSnakeCase(null));
assertEquals("already_snake_case", StringUtils.toSnakeCase("already_snake_case"));
}
}