org.silverpeas.core.util.StringUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.core.util.StringUtil.java

Source

/*
 * Copyright (C) 2000 - 2018 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.silverpeas.core.util;

import org.apache.commons.lang3.StringUtils;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.regex.Pattern;

public class StringUtil extends StringUtils {

    public static String newline = System.getProperty("line.separator");

    private static final String PATTERN_START = "{";
    private static final String PATTERN_END = "}";
    private static final String TRUNCATED_TEXT_SUFFIX = "...";
    private static final String EMAIL_PATTERN = "^[A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,4}$";
    private static final String HOUR_PATTERN = "^([0-1]?[0-9]|2[0-4]):([0-5][0-9])(:[0-5][0-9])?$";

    public static boolean isDefined(String parameter) {
        return (parameter != null && !parameter.trim().isEmpty() && !"null".equalsIgnoreCase(parameter));
    }

    public static boolean isNotDefined(String parameter) {
        return !isDefined(parameter);
    }

    public static void requireDefined(final String name) {
        if (isNotDefined(name)) {
            throw new AssertionError(name + " isn't defined!");
        }
    }

    public static String requireDefined(final String object, final String message) {
        if (isNotDefined(object)) {
            throw new AssertionError(message);
        }
        return object;
    }

    /**
     * Normalizes the given string (which must be encoded into UTF-8) in order that the result
     * contains only unified chars.
     * <p>Indeed, according to the environment of the user, sometimes it is sent data with
     * combined characters which will make the server have a bad behavior, like throw an error on
     * file download.</p>
     * @param string the string to normalize. There is no guarantee when the string is not encoded
     * into UTF8.
     * @return the normalized string.
     */
    public static String normalize(final String string) {
        String normalized = string;
        if (normalized != null) {
            normalized = Normalizer.normalize(normalized, Normalizer.Form.NFC);
        }
        return normalized;
    }

    /**
     * Same treatment as the one of {@link #normalize(String)} but removes also the accented
     * characters.
     * @param string the string to normalize. There is no guarantee when the string is not encoded
     * into UTF8.
     * @return the normalized string.
     */
    public static String normalizeByRemovingAccent(final String string) {
        String normalized = string;
        if (normalized != null) {
            // separating all of the accent marks from the characters
            normalized = Normalizer.normalize(normalized, Normalizer.Form.NFD);
            // removing accent
            normalized = normalized.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
        }
        return normalized;
    }

    /**
     * <p>Returns either the passed in String, or if the String is
     * {@code not defined}, an empty String ("").</p>
     * <p>
     * <pre>
     * StringUtil.defaultStringIfNotDefined(null)   = ""
     * StringUtil.defaultStringIfNotDefined("")     = ""
     * StringUtil.defaultStringIfNotDefined("    ") = ""
     * StringUtil.defaultStringIfNotDefined("bat")  = "bat"
     * </pre>
     * @param string the String to check, may be null, blank or filled by spaces
     * if the input is {@code not defined}, may be null, blank or filled by spaces
     * @return the passed in String, or the default if it was {@code null}
     * @see StringUtil#isNotDefined(String)
     * @see StringUtils#defaultString(String, String)
     */
    public static String defaultStringIfNotDefined(String string) {
        return defaultStringIfNotDefined(string, StringUtils.EMPTY);
    }

    /**
     * <p>Returns either the passed in String, or if the String is
     * {@code not defined}, the value of {@code defaultString}.</p>
     * <p>
     * <pre>
     * StringUtil.defaultStringIfNotDefined(null, "NULL")   = "NULL"
     * StringUtil.defaultStringIfNotDefined("", "NULL")     = "NULL"
     * StringUtil.defaultStringIfNotDefined("    ", "NULL") = "NULL"
     * StringUtil.defaultStringIfNotDefined("bat", "NULL")  = "bat"
     * </pre>
     * @param string the String to check, may be null, blank or filled by spaces
     * @param defaultString the default String to return
     * if the input is {@code not defined}, may be null, blank or filled by spaces
     * @return the passed in String, or the default if it was {@code null}
     * @see StringUtil#isNotDefined(String)
     * @see StringUtils#defaultString(String, String)
     */
    public static String defaultStringIfNotDefined(String string, String defaultString) {
        return StringUtils.defaultString((isDefined(string) ? string : null), defaultString);
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static boolean isInteger(String id) {
        try {
            Integer.parseInt(id);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static boolean isLong(String id) {
        try {
            Long.parseLong(id);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    public static float convertFloat(String value) {
        if (StringUtil.isFloat(value)) {
            return Float.valueOf(value);
        } else if (value != null) {
            String charge = value.replace(',', '.');
            if (StringUtil.isFloat(charge)) {
                return Float.valueOf(charge);
            }
        }
        return 0f;
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static boolean isFloat(String id) {
        try {
            Float.parseFloat(id);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * @param text the from which the quotes must be escaped (replaced by spaces in fact).
     * @return a String with all quotes replaced by spaces
     */
    public static String escapeQuote(String text) {
        return text.replaceAll("'", " ");
    }

    /**
     * Replaces
     *
     * @param name the original filename.
     * @return a String with all quotes replaced by spaces
     */
    public static String toAcceptableFilename(String name) {
        String fileName = name;
        fileName = fileName.replace('\\', '_');
        fileName = fileName.replace('/', '_');
        fileName = fileName.replace('$', '_');
        fileName = fileName.replace('%', '_');
        fileName = fileName.replace('?', '_');
        fileName = fileName.replace(':', '_');
        fileName = fileName.replace('*', '_');
        fileName = fileName.replace('"', '_');
        fileName = fileName.replace('<', '_');
        fileName = fileName.replace('>', '_');
        fileName = fileName.replace('|', '_');
        return fileName;
    }

    /**
     * Format a string by extending the principle of the the method format() of the class
     * java.text.MessageFormat to string arguments. For instance, the string '{key}' contained in the
     * original string to format will be replaced by the value corresponding to this key contained
     * into the values map.
     *
     * @param label The string to format
     * @param values The values to insert into the string
     * @return The formatted string, filled with values of the map.
     */
    public static String format(String label, Map<String, ?> values) {
        StringBuilder sb = new StringBuilder();
        int startIndex = label.indexOf(PATTERN_START);
        int endIndex;
        String patternKey;
        Object value;
        while (startIndex != -1) {
            endIndex = label.indexOf(PATTERN_END, startIndex);
            if (endIndex != -1) {
                patternKey = label.substring(startIndex + 1, endIndex);
                if (values.containsKey(patternKey)) {
                    value = values.get(patternKey);
                    sb.append(label.substring(0, startIndex)).append(value != null ? value.toString() : "");
                } else {
                    sb.append(label.substring(0, endIndex + 1));
                }
                label = label.substring(endIndex + 1);
                startIndex = label.indexOf(PATTERN_START);
            } else {
                sb.append(label);
                label = "";
                startIndex = -1;
            }
        }
        sb.append(label);
        return sb.toString();
    }

    /**
     * @param text The string to truncate if its size is greater than the maximum length given as
     * parameter.
     * @param maxLength The maximum length required.
     * @return The truncated string followed by '...' if needed. Returns the string itself if its
     * length is smaller than the required maximum length.
     */
    public static String truncate(String text, int maxLength) {
        if (text == null || text.length() <= maxLength) {
            return text;
        } else if (maxLength <= 3) {
            return TRUNCATED_TEXT_SUFFIX;
        } else {
            return text.substring(0, maxLength - 3) + TRUNCATED_TEXT_SUFFIX;
        }
    }

    /**
     * Replace parts of a text by an one replacement string. The text to replace is specified by a
     * regex.
     * @param source the original text
     * @param regex the regex that permits to identify parts of text to replace
     * @param replacement the replacement text
     * @return The source text modified
     */
    public static String regexReplace(String source, String regex, String replacement) {
        if (StringUtil.isNotDefined(source) || StringUtil.isNotDefined(regex)) {
            return source;
        }
        return source.replaceAll(regex, replacement);
    }

    /**
     * Validate the form of an email address.
     * <p> Returns <tt>true</tt> only if
     * <ul>
     * <li><tt>aEmailAddress</tt> can successfully construct an
     * {@link javax.mail.internet.InternetAddress}</li>
     * <li>when parsed with "@" as delimiter, <tt>aEmailAddress</tt> contains two tokens which
     * satisfy</li>
     * </ul>
     * <p>
     * The second condition arises since local email addresses, simply of the form "<tt>albert</tt>",
     * for example, are valid for {@link javax.mail.internet.InternetAddress}, but almost always
     * undesired.
     * @param aEmailAddress the address to be validated
     * @return true is the address is a valid email address - false otherwise.
     */
    public static boolean isValidEmailAddress(String aEmailAddress) {
        if (aEmailAddress == null) {
            return false;
        }
        boolean result;
        try {
            new InternetAddress(aEmailAddress);
            result = Pattern.matches(EMAIL_PATTERN, aEmailAddress);
        } catch (AddressException ex) {
            result = false;
        }
        return result;
    }

    public static boolean isValidHour(final String time) {
        return isDefined(time) && Pattern.matches(HOUR_PATTERN, time);
    }

    public static String convertToEncoding(String toConvert, String encoding) {
        try {
            return new String(toConvert.getBytes(Charset.defaultCharset()), encoding);
        } catch (UnsupportedEncodingException ex) {
            return toConvert;
        }
    }

    /**
     * Evaluate the expression and return true if expression equals "true", "yes", "y", "1" or "oui".
     *
     * @param expression the expression to be evaluated
     * @return true if expression equals "true", "yes", "y", "1" or "oui".
     */
    public static boolean getBooleanValue(final String expression) {
        return "true".equalsIgnoreCase(expression) || "yes".equalsIgnoreCase(expression)
                || "y".equalsIgnoreCase(expression) || "oui".equalsIgnoreCase(expression)
                || "1".equalsIgnoreCase(expression);
    }

    /**
     * Indicates if two Strings are equals, managing null.
     *
     * @param s1 the first String.
     * @param s2 the second String.
     * @return true ifthe two Strings are equals.
     */
    public static boolean areStringEquals(String s1, String s2) {
        if (s1 == null) {
            return s2 == null;
        }
        return s1.equals(s2);
    }

    /**
     * Encodes the specified binary data into a text of Base64 characters.
     *
     * @param binaryData the binary data to convert in Base64-based String.
     * @return a String representation of the binary data in Base64 characters.
     */
    public static String asBase64(byte[] binaryData) {
        return Base64.getEncoder().encodeToString(binaryData);
    }

    /**
     * Decodes the specified text with Base64 characters in binary.
     *
     * @param base64Text the text in Base64.
     * @return the binary representation of the text.
     */
    public static byte[] fromBase64(String base64Text) {
        return Base64.getDecoder().decode(base64Text);
    }

    /**
     * <p>Splits the provided text into an array, using whitespace as the separator. Whitespace is
     * defined by {@link Character#isWhitespace(char)}.</p>
     *
     * <p>The separator is not included in the returned String array. Adjacent separators are treated
     * as one separator. For more control over the split use the StrTokenizer class.</p>
     *
     * <p>A {@code null} input String returns {@code null}.</p>
     *
     * <pre>
     * StringUtils.split(null)       = null
     * StringUtils.split("")         = empty list
     * StringUtils.split("abc def")  = ["abc", "def"]
     * StringUtils.split("abc  def") = ["abc", "def"]
     * StringUtils.split(" abc ")    = ["abc"]
     * </pre>
     *
     * @param str the String to parse, may be null
     * @return an array of parsed Strings, {@code null} if null String input
     */
    public static Iterable<String> splitString(String str) {
        return Arrays.asList(StringUtils.split(str));
    }

    /**
     * <p>Splits the provided text into an array, separator specified. This is an alternative to using
     * StringTokenizer.</p>
     *
     * <p>The separator is not included in the returned String array. Adjacent separators are treated
     * as one separator. For more control over the split use the StrTokenizer class.</p>
     *
     * <p>A {@code null} input String returns {@code null}.</p>
     *
     * <pre>
     * StringUtils.split(null, *)         = null
     * StringUtils.split("", *)           = empty list
     * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
     * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
     * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
     * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
     * </pre>
     *
     * @param str the String to parse, may be null
     * @param separatorChar the character used as the delimiter
     * @return an array of parsed Strings, {@code null} if null String input
     */
    public static Iterable<String> splitString(String str, char separatorChar) {
        return Arrays.asList(StringUtils.split(str, separatorChar));
    }

    /**
     * Doubles the anti-slash character in a path value.
     * @param path the String representing a path.
     * @return the path corrected with the anti-slash doubled.
     */
    public static String doubleAntiSlash(String path) {
        StringBuilder res = new StringBuilder(path);
        int k = 0;
        for (int i = 0, j = 1; i < path.length(); i++, j++) {
            if (path.charAt(i) == '\\') {
                boolean hasNotAntiSlashAfter = j < path.length() && path.charAt(j) != '\\';
                boolean hasNotAntiSlashBefore = i > 0 && path.charAt(i - 1) != '\\';
                if (hasNotAntiSlashAfter && hasNotAntiSlashBefore) {
                    res.insert(k + i, '\\');
                    k++;
                }
            }
        }
        return res.toString();
    }

    public static boolean likeIgnoreCase(final String actualValue, String expectedValue) {
        return new Like(actualValue, expectedValue, true).test();
    }

    public static boolean like(final String actualValue, String expectedValue) {
        return new Like(actualValue, expectedValue, false).test();
    }

    private static class Like {
        private final String actual;
        private final String expected;
        private String currentActual;
        private int tokenIndex;

        private Like(final String actual, final String expected, final boolean ignoreCase) {
            if (ignoreCase) {
                this.actual = defaultStringIfNotDefined(actual).toLowerCase();
                this.expected = defaultStringIfNotDefined(expected).toLowerCase();
            } else {
                this.actual = defaultStringIfNotDefined(actual);
                this.expected = defaultStringIfNotDefined(expected);
            }
        }

        boolean test() {
            currentActual = actual;
            tokenIndex = 0;
            boolean like = true;
            boolean mustStart = true;
            String currentToken = nextExpectedToken();
            while (like && currentToken != null) {
                if (currentToken.isEmpty()) {
                    mustStart = false;
                    tokenIndex++;
                } else {
                    like = verifyToken(currentToken, mustStart);
                    mustStart = true;
                }
                currentToken = nextExpectedToken();
            }
            return like && (!mustStart || currentActual.isEmpty());
        }

        private boolean verifyToken(final String token, final boolean mustStart) {
            final String escapedToken = token.replace("\\%", "%");
            final int currentIndex = currentActual.indexOf(escapedToken);
            final int nextActualIndex = currentIndex + escapedToken.length();
            currentActual = nextActualIndex < currentActual.length() ? currentActual.substring(nextActualIndex)
                    : "";
            tokenIndex += token.length();
            return mustStart ? currentIndex == 0 : currentIndex >= 0;
        }

        private String nextExpectedToken() {
            if (tokenIndex >= expected.length()) {
                return null;
            }
            int index = expected.indexOf('%', tokenIndex);
            boolean found = false;
            while (!found) {
                if (index < 0) {
                    index = expected.length();
                    found = true;
                } else if (index > 0 && expected.charAt(index - 1) != '\\') {
                    found = true;
                } else if (index == 0) {
                    found = true;
                } else {
                    index = expected.indexOf('%', index + 1);
                }
            }
            return expected.substring(tokenIndex, index);
        }
    }

    private StringUtil() {
    }
}