
software.amazon.awssdk.utils.StringUtils Maven / Gradle / Ivy
/*
* Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.utils;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Locale;
import software.amazon.awssdk.annotations.ReviewBeforeRelease;
import software.amazon.awssdk.annotations.SdkProtectedApi;
/**
* Operations on {@link java.lang.String} that are
* {@code null} safe.
*
*
* - IsEmpty/IsBlank
* - checks if a String contains text
* - Trim/Strip
* - removes leading and trailing whitespace
* - Equals/Compare
* - compares two strings null-safe
* - startsWith
* - check if a String starts with a prefix null-safe
* - endsWith
* - check if a String ends with a suffix null-safe
* - IndexOf/LastIndexOf/Contains
* - null-safe index-of checks
*
- IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut
* - index-of any of a set of Strings
* - ContainsOnly/ContainsNone/ContainsAny
* - does String contains only/none/any of these characters
* - Substring/Left/Right/Mid
* - null-safe substring extractions
* - SubstringBefore/SubstringAfter/SubstringBetween
* - substring extraction relative to other strings
* - Split/Join
* - splits a String into an array of substrings and vice versa
* - Remove/Delete
* - removes part of a String
* - Replace/Overlay
* - Searches a String and replaces one String with another
* - Chomp/Chop
* - removes the last part of a String
* - AppendIfMissing
* - appends a suffix to the end of the String if not present
* - PrependIfMissing
* - prepends a prefix to the start of the String if not present
* - LeftPad/RightPad/Center/Repeat
* - pads a String
* - UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize
* - changes the case of a String
* - CountMatches
* - counts the number of occurrences of one String in another
* - IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable
* - checks the characters in a String
* - DefaultString
* - protects against a null input String
* - Rotate
* - rotate (circular shift) a String
* - Reverse/ReverseDelimited
* - reverses a String
* - Abbreviate
* - abbreviates a string using ellipsis or another given String
* - Difference
* - compares Strings and reports on their differences
* - LevenshteinDistance
* - the number of changes needed to change one String into another
*
*
* The {@code StringUtils} class defines certain words related to
* String handling.
*
*
* - null - {@code null}
* - empty - a zero-length string ({@code ""})
* - space - the space character ({@code ' '}, char 32)
* - whitespace - the characters defined by {@link Character#isWhitespace(char)}
* - trim - the characters <= 32 as in {@link String#trim()}
*
*
* {@code StringUtils} handles {@code null} input Strings quietly.
* That is to say that a {@code null} input will return {@code null}.
* Where a {@code boolean} or {@code int} is being returned
* details vary by method.
*
* A side effect of the {@code null} handling is that a
* {@code NullPointerException} should be considered a bug in
* {@code StringUtils}.
*
* This class's source was modified from the Apache commons-lang library: https://github.com/apache/commons-lang/
*
* #ThreadSafe#
* @see java.lang.String
*/
@ReviewBeforeRelease("Remove the methods we don't end up using (and we've removed software.amazon.awssdk.core.util.StringUtils).")
@SdkProtectedApi
public final class StringUtils {
// Performance testing notes (JDK 1.4, Jul03, scolebourne)
// Whitespace:
// Character.isWhitespace() is faster than WHITESPACE.indexOf()
// where WHITESPACE is a string of all whitespace characters
//
// Character access:
// String.charAt(n) versus toCharArray(), then array[n]
// String.charAt(n) is about 15% worse for a 10K string
// They are about equal for a length 50 string
// String.charAt(n) is about 4 times better for a length 3 string
// String.charAt(n) is best bet overall
//
// Append:
// String.concat about twice as fast as StringBuffer.append
// (not sure who tested this)
/**
* The empty String {@code ""}.
*/
private static final String EMPTY = "";
/**
* {@code StringUtils} instances should NOT be constructed in
* standard programming. Instead, the class should be used as
* {@code StringUtils.trim(" foo ");}.
*/
private StringUtils() {
}
// Empty checks
//-----------------------------------------------------------------------
/**
* Checks if a CharSequence is empty ("") or null.
*
*
* StringUtils.isEmpty(null) = true
* StringUtils.isEmpty("") = true
* StringUtils.isEmpty(" ") = false
* StringUtils.isEmpty("bob") = false
* StringUtils.isEmpty(" bob ") = false
*
*
* NOTE: This method changed in Lang version 2.0.
* It no longer trims the CharSequence.
* That functionality is available in isBlank().
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is empty or null
* @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence)
*/
public static boolean isEmpty(final CharSequence cs) {
return cs == null || cs.length() == 0;
}
/**
* Checks if a CharSequence is empty (""), null or whitespace only.
*
* Whitespace is defined by {@link Character#isWhitespace(char)}.
*
*
* StringUtils.isBlank(null) = true
* StringUtils.isBlank("") = true
* StringUtils.isBlank(" ") = true
* StringUtils.isBlank("bob") = false
* StringUtils.isBlank(" bob ") = false
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is null, empty or whitespace only
* @since 2.0
* @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence)
*/
public static boolean isBlank(final CharSequence cs) {
if (cs == null || cs.length() == 0) {
return true;
}
for (int i = 0; i < cs.length(); i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
/**
* Checks if a CharSequence is not empty (""), not null and not whitespace only.
*
* Whitespace is defined by {@link Character#isWhitespace(char)}.
*
*
* StringUtils.isNotBlank(null) = false
* StringUtils.isNotBlank("") = false
* StringUtils.isNotBlank(" ") = false
* StringUtils.isNotBlank("bob") = true
* StringUtils.isNotBlank(" bob ") = true
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is not empty and not null and not whitespace only
* @since 2.0
* @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence)
*/
public static boolean isNotBlank(final CharSequence cs) {
return !isBlank(cs);
}
// Trim
//-----------------------------------------------------------------------
/**
* Removes control characters (char <= 32) from both
* ends of this String, handling {@code null} by returning
* {@code null}.
*
* The String is trimmed using {@link String#trim()}.
* Trim removes start and end characters <= 32.
*
*
* StringUtils.trim(null) = null
* StringUtils.trim("") = ""
* StringUtils.trim(" ") = ""
* StringUtils.trim("abc") = "abc"
* StringUtils.trim(" abc ") = "abc"
*
*
* @param str the String to be trimmed, may be null
* @return the trimmed string, {@code null} if null String input
*/
public static String trim(final String str) {
return str == null ? null : str.trim();
}
/**
* Removes control characters (char <= 32) from both
* ends of this String returning {@code null} if the String is
* empty ("") after the trim or if it is {@code null}.
*
*
The String is trimmed using {@link String#trim()}.
* Trim removes start and end characters <= 32.
*
*
* StringUtils.trimToNull(null) = null
* StringUtils.trimToNull("") = null
* StringUtils.trimToNull(" ") = null
* StringUtils.trimToNull("abc") = "abc"
* StringUtils.trimToNull(" abc ") = "abc"
*
*
* @param str the String to be trimmed, may be null
* @return the trimmed String,
* {@code null} if only chars <= 32, empty or null String input
* @since 2.0
*/
public static String trimToNull(final String str) {
final String ts = trim(str);
return isEmpty(ts) ? null : ts;
}
/**
* Removes control characters (char <= 32) from both
* ends of this String returning an empty String ("") if the String
* is empty ("") after the trim or if it is {@code null}.
*
*
The String is trimmed using {@link String#trim()}.
* Trim removes start and end characters <= 32.
*
*
* StringUtils.trimToEmpty(null) = ""
* StringUtils.trimToEmpty("") = ""
* StringUtils.trimToEmpty(" ") = ""
* StringUtils.trimToEmpty("abc") = "abc"
* StringUtils.trimToEmpty(" abc ") = "abc"
*
*
* @param str the String to be trimmed, may be null
* @return the trimmed String, or an empty String if {@code null} input
* @since 2.0
*/
public static String trimToEmpty(final String str) {
return str == null ? EMPTY : str.trim();
}
// Equals
//-----------------------------------------------------------------------
/**
* Compares two Strings, returning {@code true} if they represent
* equal sequences of characters.
*
* {@code null}s are handled without exceptions. Two {@code null}
* references are considered to be equal. The comparison is case sensitive.
*
*
* StringUtils.equals(null, null) = true
* StringUtils.equals(null, "abc") = false
* StringUtils.equals("abc", null) = false
* StringUtils.equals("abc", "abc") = true
* StringUtils.equals("abc", "ABC") = false
*
*
* @see Object#equals(Object)
* @param cs1 the first String, may be {@code null}
* @param cs2 the second String, may be {@code null}
* @return {@code true} if the Strings are equal (case-sensitive), or both {@code null}
*/
public static boolean equals(final String cs1, final String cs2) {
if (cs1 == null || cs2 == null) {
return false;
}
if (cs1.length() != cs2.length()) {
return false;
}
return cs1.equals(cs2);
}
// Substring
//-----------------------------------------------------------------------
/**
* Gets a substring from the specified String avoiding exceptions.
*
* A negative start position can be used to start {@code n}
* characters from the end of the String.
*
* A {@code null} String will return {@code null}.
* An empty ("") String will return "".
*
*
* StringUtils.substring(null, *) = null
* StringUtils.substring("", *) = ""
* StringUtils.substring("abc", 0) = "abc"
* StringUtils.substring("abc", 2) = "c"
* StringUtils.substring("abc", 4) = ""
* StringUtils.substring("abc", -2) = "bc"
* StringUtils.substring("abc", -4) = "abc"
*
*
* @param str the String to get the substring from, may be null
* @param start the position to start from, negative means count back from the end of the String by this many characters
* @return substring from start position, {@code null} if null String input
*/
public static String substring(final String str, int start) {
if (str == null) {
return null;
}
// handle negatives, which means last n characters
if (start < 0) {
start = str.length() + start; // remember start is negative
}
if (start < 0) {
start = 0;
}
if (start > str.length()) {
return EMPTY;
}
return str.substring(start);
}
/**
* Gets a substring from the specified String avoiding exceptions.
*
* A negative start position can be used to start/end {@code n}
* characters from the end of the String.
*
* The returned substring starts with the character in the {@code start}
* position and ends before the {@code end} position. All position counting is
* zero-based -- i.e., to start at the beginning of the string use
* {@code start = 0}. Negative start and end positions can be used to
* specify offsets relative to the end of the String.
*
* If {@code start} is not strictly to the left of {@code end}, ""
* is returned.
*
*
* StringUtils.substring(null, *, *) = null
* StringUtils.substring("", * , *) = "";
* StringUtils.substring("abc", 0, 2) = "ab"
* StringUtils.substring("abc", 2, 0) = ""
* StringUtils.substring("abc", 2, 4) = "c"
* StringUtils.substring("abc", 4, 6) = ""
* StringUtils.substring("abc", 2, 2) = ""
* StringUtils.substring("abc", -2, -1) = "b"
* StringUtils.substring("abc", -4, 2) = "ab"
*
*
* @param str the String to get the substring from, may be null
* @param start the position to start from, negative means count back from the end of the String by this many characters
* @param end the position to end at (exclusive), negative means count back from the end of the String by this many
* characters
* @return substring from start position to end position,
* {@code null} if null String input
*/
public static String substring(final String str, int start, int end) {
if (str == null) {
return null;
}
// handle negatives
if (end < 0) {
end = str.length() + end; // remember end is negative
}
if (start < 0) {
start = str.length() + start; // remember start is negative
}
// check length next
if (end > str.length()) {
end = str.length();
}
// if start is greater than end, return ""
if (start > end) {
return EMPTY;
}
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
return str.substring(start, end);
}
// Case conversion
//-----------------------------------------------------------------------
/**
* Converts a String to upper case as per {@link String#toUpperCase()}.
*
* A {@code null} input String returns {@code null}.
*
*
* StringUtils.upperCase(null) = null
* StringUtils.upperCase("") = ""
* StringUtils.upperCase("aBc") = "ABC"
*
*
* This uses "ENGLISH" as the locale.
*
* @param str the String to upper case, may be null
* @return the upper cased String, {@code null} if null String input
*/
public static String upperCase(final String str) {
if (str == null) {
return null;
}
return str.toUpperCase(Locale.ENGLISH);
}
/**
*
Converts a String to lower case as per {@link String#toLowerCase()}.
*
* A {@code null} input String returns {@code null}.
*
*
* StringUtils.lowerCase(null) = null
* StringUtils.lowerCase("") = ""
* StringUtils.lowerCase("aBc") = "abc"
*
*
* This uses "ENGLISH" as the locale.
*
* @param str the String to lower case, may be null
* @return the lower cased String, {@code null} if null String input
*/
public static String lowerCase(final String str) {
if (str == null) {
return null;
}
return str.toLowerCase(Locale.ENGLISH);
}
/**
*
Capitalizes a String changing the first character to title case as
* per {@link Character#toTitleCase(int)}. No other characters are changed.
*
*
* StringUtils.capitalize(null) = null
* StringUtils.capitalize("") = ""
* StringUtils.capitalize("cat") = "Cat"
* StringUtils.capitalize("cAt") = "CAt"
* StringUtils.capitalize("'cat'") = "'cat'"
*
*
* @param str the String to capitalize, may be null
* @return the capitalized String, {@code null} if null String input
* @see #uncapitalize(String)
* @since 2.0
*/
public static String capitalize(final String str) {
if (str == null || str.length() == 0) {
return str;
}
final int firstCodepoint = str.codePointAt(0);
final int newCodePoint = Character.toTitleCase(firstCodepoint);
if (firstCodepoint == newCodePoint) {
// already capitalized
return str;
}
final int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array
int outOffset = 0;
newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint
for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) {
final int codepoint = str.codePointAt(inOffset);
newCodePoints[outOffset++] = codepoint; // copy the remaining ones
inOffset += Character.charCount(codepoint);
}
return new String(newCodePoints, 0, outOffset);
}
/**
* Uncapitalizes a String, changing the first character to lower case as
* per {@link Character#toLowerCase(int)}. No other characters are changed.
*
*
* StringUtils.uncapitalize(null) = null
* StringUtils.uncapitalize("") = ""
* StringUtils.uncapitalize("cat") = "cat"
* StringUtils.uncapitalize("Cat") = "cat"
* StringUtils.uncapitalize("CAT") = "cAT"
*
*
* @param str the String to uncapitalize, may be null
* @return the uncapitalized String, {@code null} if null String input
* @see #capitalize(String)
* @since 2.0
*/
public static String uncapitalize(final String str) {
if (str == null || str.length() == 0) {
return str;
}
final int firstCodepoint = str.codePointAt(0);
final int newCodePoint = Character.toLowerCase(firstCodepoint);
if (firstCodepoint == newCodePoint) {
// already capitalized
return str;
}
final int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array
int outOffset = 0;
newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint
for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) {
final int codepoint = str.codePointAt(inOffset);
newCodePoints[outOffset++] = codepoint; // copy the remaining ones
inOffset += Character.charCount(codepoint);
}
return new String(newCodePoints, 0, outOffset);
}
/**
* Encode the given bytes as a string using the given charset
* @throws UncheckedIOException with a {@link CharacterCodingException} as the cause if the bytes cannot be encoded using the
* provided charset.
*/
public static String fromBytes(byte[] bytes, Charset charset) throws UncheckedIOException {
try {
return charset.newDecoder().decode(ByteBuffer.wrap(bytes)).toString();
} catch (CharacterCodingException e) {
throw new UncheckedIOException("Cannot encode string.", e);
}
}
}