All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.internal.util.StringUtil Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * Copyright (c) 2008-2023, Hazelcast, Inc. 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.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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 com.hazelcast.internal.util;

import javax.annotation.Nullable;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.lang.Character.isLetter;
import static java.lang.Character.isLowerCase;
import static java.lang.Character.toLowerCase;

/**
 * Utility class for Strings.
 */
public final class StringUtil {

    /**
     * Points to the System property 'line.separator'.
     */
    public static final String LINE_SEPARATOR = System.getProperty("line.separator");

    /**
     * LOCALE_INTERNAL is the default locale for string operations and number formatting. Initialized to
     * {@code java.util.Locale.ROOT} (language neutral).
     */
    public static final Locale LOCALE_INTERNAL = Locale.ROOT;

    /**
     * Pattern used to tokenize version strings.
     */
    public static final Pattern VERSION_PATTERN
            = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?(-\\w+(?:-\\d+)?)?(-SNAPSHOT)?$");

    /**
     * Empty String.
     */
    public static final String EMPTY_STRING = "";

    private static final String GETTER_PREFIX = "get";

    private StringUtil() {
    }

    /**
     * Creates a UTF8_CHARSET string from a byte array.
     *
     * @param bytes  the byte array.
     * @param offset the index of the first byte to decode
     * @param length the number of bytes to decode
     * @return the string created from the byte array.
     */
    public static String bytesToString(byte[] bytes, int offset, int length) {
        return new String(bytes, offset, length, StandardCharsets.UTF_8);
    }

    /**
     * Creates a UTF8_CHARSET string from a byte array.
     *
     * @param bytes the byte array.
     * @return the string created from the byte array.
     */
    public static String bytesToString(byte[] bytes) {

        return new String(bytes, StandardCharsets.UTF_8);
    }

    /**
     * Creates a byte array from a string.
     *
     * @param s the string.
     * @return the byte array created from the string.
     */
    public static byte[] stringToBytes(String s) {
        return s.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * Checks if a string is empty or not.
     *
     * @param s the string to check.
     * @return true if the string is {@code null} or empty, false otherwise
     */

    public static boolean isNullOrEmpty(String s) {
        if (s == null) {
            return true;
        }
        return s.isEmpty();
    }

    /**
     * Checks if a string is empty or not after trim operation
     *
     * @param s the string to check.
     * @return true if the string is {@code null} or empty, false otherwise
     */
    public static boolean isNullOrEmptyAfterTrim(String s) {
        if (s == null) {
            return true;
        }
        return s.trim().isEmpty();
    }

    /**
     * Check if all Strings are not blank
     * @param values the strings to check
     * @return true if all the strings are not {@code null} and not blank, false otherwise
     */
    public static boolean isAllNullOrEmptyAfterTrim(String... values) {
        if (values == null) {
            return false;
        }
        return Arrays.stream(values).noneMatch(StringUtil::isNullOrEmptyAfterTrim);
    }

    /**
     * Check if any String from the provided Strings
     * @param values the strings to check
     * @return true if at least one string of the {@param values} are not {@code null} and not blank
     */
    public static boolean isAnyNullOrEmptyAfterTrim(String... values) {
        if (values == null) {
            return false;
        }
        return Arrays.stream(values).anyMatch(s -> !isNullOrEmptyAfterTrim(s));
    }

    /**
     * HC specific settings, operands etc. use this method.
     * Creates an uppercase string from the given string.
     *
     * @param s the given string
     * @return an uppercase string, or {@code null}/empty if the string is {@code null}/empty
     */
    public static String upperCaseInternal(String s) {
        if (isNullOrEmpty(s)) {
            return s;
        }
        return s.toUpperCase(LOCALE_INTERNAL);
    }

    /**
     * Converts the first character to lower case.
     *
     * Empty strings are ignored.
     *
     * @param s the given string
     * @return the converted string.
     */
    public static String lowerCaseFirstChar(String s) {
        if (s.isEmpty()) {
            return s;
        }

        char first = s.charAt(0);
        if (isLowerCase(first)) {
            return s;
        }

        return toLowerCase(first) + s.substring(1);
    }

    /**
     * HC specific settings, operands etc. use this method.
     * Creates a lowercase string from the given string.
     *
     * @param s the given string
     * @return a lowercase string, or {@code null}/empty if the string is {@code null}/empty
     */
    public static String lowerCaseInternal(String s) {
        if (isNullOrEmpty(s)) {
            return s;
        }
        return s.toLowerCase(LOCALE_INTERNAL);
    }

    /**
     * Returns a String representation of the time.
     * 

* This method is not particularly efficient since it generates a ton of litter. * * @param timeMillis time in millis * @return the String */ public static String timeToString(long timeMillis) { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); return dateFormat.format(new Date(timeMillis)); } /** * Returns a String representation of the time. If time is 0, then 'never' is returned. *

* This method is not particularly efficient since it generates a ton of litter. * * @param timeMillis time in millis * @return the String */ public static String timeToStringFriendly(long timeMillis) { return timeMillis == 0 ? "never" : timeToString(timeMillis); } /** * Like a String.indexOf but without MIN_SUPPLEMENTARY_CODE_POINT handling * * @param input to check the indexOf on * @param ch character to find the index of * @param offset offset to start the reading from * @return index of the character, or -1 if not found */ public static int indexOf(String input, char ch, int offset) { for (int i = offset; i < input.length(); i++) { if (input.charAt(i) == ch) { return i; } } return -1; } /** * Like a String.indexOf but without MIN_SUPPLEMENTARY_CODE_POINT handling * * @param input to check the indexOf on * @param ch character to find the index of * @return index of the character, or -1 if not found */ public static int indexOf(String input, char ch) { return indexOf(input, ch, 0); } /** * Like a String.lastIndexOf but without MIN_SUPPLEMENTARY_CODE_POINT handling * * @param input to check the indexOf on * @param ch character to find the index of * @param offset offset to start the reading from the end * @return index of the character, or -1 if not found */ public static int lastIndexOf(String input, char ch, int offset) { for (int i = input.length() - 1 - offset; i >= 0; i--) { if (input.charAt(i) == ch) { return i; } } return -1; } /** * Like a String.lastIndexOf but without MIN_SUPPLEMENTARY_CODE_POINT handling * * @param input to check the indexOf on * @param ch character to find the index of * @return index of the character, or -1 if not found */ public static int lastIndexOf(String input, char ch) { return lastIndexOf(input, ch, 0); } /** * Tokenizes a version string and returns the tokens with the following grouping: * (1) major version, eg "3" * (2) minor version, eg "8" * (3) patch version prefixed with ".", if exists, otherwise {@code null} (eg ".0") * (4) patch version, eg "0" * (5) 1st -qualifier, if exists * (6) -SNAPSHOT qualifier, if exists * * @param version * @return */ public static String[] tokenizeVersionString(String version) { Matcher matcher = VERSION_PATTERN.matcher(version); if (matcher.matches()) { String[] tokens = new String[matcher.groupCount()]; for (int i = 0; i < matcher.groupCount(); i++) { tokens[i] = matcher.group(i + 1); } return tokens; } else { return null; } } /** * Convert getter into a property name * Example: 'getFoo' is converted into 'foo' * * It's written defensively, when output is not a getter then it * returns the original name. * * It only converts names starting with a get- prefix. When a getter * starts with an is- prefix (=boolean) then it does not convert it. * * @param getterName * @return property matching the given getter */ public static String getterIntoProperty(String getterName) { if (getterName == null) { return getterName; } int length = getterName.length(); if (!getterName.startsWith(GETTER_PREFIX) || length <= GETTER_PREFIX.length()) { return getterName; } String propertyName = getterName.substring(GETTER_PREFIX.length(), length); char firstChar = propertyName.charAt(0); if (isLetter(firstChar)) { if (isLowerCase(firstChar)) { //ok, apparently this is not a JavaBean getter, better leave it untouched return getterName; } propertyName = toLowerCase(firstChar) + propertyName.substring(1, propertyName.length()); } return propertyName; } /** * Trim whitespaces. This method (compared to {@link String#trim()}) doesn't limit to space character. * * @param input string to trim * @return {@code null} if provided value was {@code null}, input with removed leading and trailing whitespaces */ public static String trim(String input) { if (input == null) { return null; } return input.replaceAll("^\\s+|\\s+$", ""); } /** * Splits String value with comma "," used as a separator. The whitespaces around values are trimmed. * * @param input string to split * @return {@code null} if provided value was {@code null}, split parts otherwise (trimmed) */ public static String[] splitByComma(String input, boolean allowEmpty) { if (input == null) { return null; } String[] splitWithEmptyValues = trim(input).split("\\s*,\\s*", -1); return allowEmpty ? splitWithEmptyValues : subtraction(splitWithEmptyValues, new String[]{""}); } /** * Returns intersection of given String arrays. If either array is {@code null}, then {@code null} is returned. * * @param arr1 first array * @param arr2 second array * @return arr1 without values which are not present in arr2 */ public static String[] intersection(String[] arr1, String[] arr2) { if (arr1 == null || arr2 == null) { return null; } if (arr1.length == 0 || arr2.length == 0) { return new String[0]; } List list = new ArrayList<>(Arrays.asList(arr1)); list.retainAll(Arrays.asList(arr2)); return list.toArray(new String[0]); } /** * Returns subtraction between given String arrays. * * @param arr1 first array * @param arr2 second array * @return arr1 without values which are not present in arr2 */ public static String[] subtraction(String[] arr1, String[] arr2) { if (arr1 == null || arr1.length == 0 || arr2 == null || arr2.length == 0) { return arr1; } List list = new ArrayList<>(Arrays.asList(arr1)); list.removeAll(Arrays.asList(arr2)); return list.toArray(new String[0]); } /** * Returns true if two strings are equals ignoring the letter case in {@link #LOCALE_INTERNAL} locale. * * @param str1 first string to compare * @param str2 second string to compare * @return true if the strings are equals ignoring the case */ public static boolean equalsIgnoreCase(String str1, String str2) { return (str1 == null || str2 == null) ? false : (str1 == str2 || lowerCaseInternal(str1).equals(lowerCaseInternal(str2))); } /** * Strips the trailing slash from the input string, if it is present * * @param str * @return the string with trailing slash removed */ public static String stripTrailingSlash(String str) { if (isNullOrEmpty(str)) { return str; } if (str.charAt(str.length() - 1) == '/') { return str.substring(0, str.length() - 1); } return str; } /** * Returns a string where named placeholders are replaced by values from the * given {@code variableValues} map. The placeholder is defined as the * variable name prefixed by ${@code placeholderNamespace}{ and followed * by }. For example, if the {@code placeholderNamespace} is {@code HZ_TEST} * the placeholder for "instance_name" would be $HZ_TEST{instance_name}. *

* The variable replacement is fail-safe which means any incorrect syntax such * as missing closing brackets or missing variable values is ignored. * * @param pattern the pattern in which placeholders should be replaced * @param placeholderNamespace the string inserted into the placeholder prefix to distinguish between * different types of placeholders * @param variableValues the placeholder variable values * @return the formatted string */ public static String resolvePlaceholders(String pattern, String placeholderNamespace, Map variableValues) { StringBuilder sb = new StringBuilder(pattern); String placeholderPrefix = "$" + placeholderNamespace + "{"; int endIndex; int startIndex = sb.indexOf(placeholderPrefix); while (startIndex > -1) { endIndex = sb.indexOf("}", startIndex); if (endIndex == -1) { // ignore bad syntax, search finished break; } String variableName = sb.substring(startIndex + placeholderPrefix.length(), endIndex); Object variableValue = variableValues.get(variableName); // ignore missing values if (variableValue != null) { String valueStr = variableValue.toString(); sb.replace(startIndex, endIndex + 1, valueStr); endIndex = startIndex + valueStr.length(); } startIndex = sb.indexOf(placeholderPrefix, endIndex); } return sb.toString(); } /** * Converts the provided collection to string, joined by LINE_SEPARATOR * @param collection collection to convert to string * @return string */ public static String toString(Collection collection) { return collection.stream() .map(Objects::toString) .collect(Collectors.joining(LINE_SEPARATOR)); } /** * Converts the provided array to string, joined by LINE_SEPARATOR * @param arr array to convert to string * @return string */ public static String toString(T[] arr) { return Arrays.stream(arr) .map(Objects::toString) .collect(Collectors.joining(LINE_SEPARATOR)); } /** * Formats given XML String with the given indentation used. If the {@code input} XML string is {@code null}, or * {@code indent} parameter is negative, or XML transformation fails, then the original value is returned unchanged. The * {@link IllegalArgumentException} is thrown when {@code indent==0}. * * @param input the XML String * @param indent indentation (number of spaces used for one indentation level) * @return formatted XML String or the original String if the formatting fails. * @throws IllegalArgumentException when indentation is equal to zero * @deprecated Use directly {@link XmlUtil#format(String, int)} */ @Deprecated public static String formatXml(@Nullable String input, int indent) throws IllegalArgumentException { return XmlUtil.format(input, indent); } /** * Ensures that the returned string is at most {@code maxLength} long. If * it's longer, trims it to one char less (not taking word boundaries into * account), and appends an ellipsis. Returns {@code null} for null input. * * @param s The string to shorten * @param maxLength Maximum length the returned string must have * @return Shortened string */ public static String shorten(String s, int maxLength) { Preconditions.checkPositive("maxLength", maxLength); if (s == null || s.length() <= maxLength) { return s; } return s.substring(maxLength - 1) + '…'; } /** * Removes all occurrence of {@code charToRemove} from {@code str}. This method is more efficient than * {@link String#replaceAll(String, String)} which compiles a regex from the first parameter every invocation. */ public static String removeCharacter(String str, char charToRemove) { if (str == null || str.indexOf(charToRemove) == -1) { return str; } char[] chars = str.toCharArray(); int pos = 0; for (int i = 0; i < chars.length; i++) { if (chars[i] != charToRemove) { chars[pos++] = chars[i]; } } return new String(chars, 0, pos); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy