Util.java

package de.siegmar.fastcsv.util;

import java.util.HexFormat;

/// Internal utility class.
///
/// It is **not** a part of the API!
public final class Util {

    /// Carriage return.
    public static final char CR = '\r';

    /// Line feed.
    public static final char LF = '\n';

    private static final HexFormat HEX = HexFormat.of();

    private Util() {
    }

    /// Checks if the given array of characters contains any duplicate characters.
    ///
    /// @param chars the array of characters to check for duplicates
    /// @return `true` if any character appears more than once in the array, `false` otherwise
    public static boolean containsDupe(final char... chars) {
        for (int i = 0; i < chars.length; i++) {
            for (int j = i + 1; j < chars.length; j++) {
                if (chars[i] == chars[j]) {
                    return true;
                }
            }
        }
        return false;
    }

    /// Checks if the given character is a line break character.
    ///
    /// @param character character to test
    /// @return `true` if the argument is [#CR] or [#LF], `false` otherwise
    public static boolean isLineBreak(final char character) {
        return character == CR || character == LF;
    }

    /// Checks if the given string contains any line break characters.
    ///
    /// @param str the string to check for line break characters
    /// @return `true` if the string contains either [#CR] or [#LF], `false` otherwise
    public static boolean containsLineBreak(final String str) {
        return str.indexOf(CR) >= 0 || str.indexOf(LF) >= 0;
    }

    /// Replaces every character that is not a letter, mark, number, punctuation, symbol or space with its Unicode
    /// escapes: control and format characters, line and paragraph separators, unpaired surrogates, private-use and
    /// unassigned characters.
    ///
    /// Exception messages that contain text from the CSV data use this, so that the text cannot fake a line in a
    /// log file, reorder the text on display with bidi control characters or send terminal commands. Characters the
    /// running Java version does not know yet are escaped as well.
    ///
    /// @param str the string to escape
    /// @return the escaped string, never `null`
    public static String escapeControlCharacters(final String str) {
        final StringBuilder sb = new StringBuilder(str.length());
        str.codePoints().forEach(codePoint -> {
            if (needsEscape(codePoint)) {
                for (final char ch : Character.toChars(codePoint)) {
                    sb.append("\\u").append(HEX.toHexDigits(ch));
                }
            } else {
                sb.appendCodePoint(codePoint);
            }
        });
        return sb.toString();
    }

    private static boolean needsEscape(final int codePoint) {
        return switch (Character.getType(codePoint)) {
            case Character.CONTROL, Character.FORMAT, Character.SURROGATE, Character.PRIVATE_USE,
                 Character.UNASSIGNED, Character.LINE_SEPARATOR, Character.PARAGRAPH_SEPARATOR -> true;
            default -> false;
        };
    }

}