001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023
002package io.cui.tools.lang;
003
004import java.util.Locale;
005import io.cui.tools.string.MoreStrings;
006
007/**
008 * <p>
009 * Operations to assist when working with a {@link Locale}.
010 * </p>
011 *
012 * <p>
013 * This class tries to handle {@code null} input gracefully.
014 * An exception will not be thrown for a {@code null} input.
015 * Each method documents its behavior in more detail.
016 * </p>
017 *
018 * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/LocaleUtils.java
019 */
020public final class LocaleUtils {
021    private static final String INVALID_LOCALE_FORMAT = "Invalid locale format: ";
022
023    // owolff: Original code
024    /**
025     * <p>
026     * Converts a String to a Locale.
027     * </p>
028     *
029     * <p>
030     * This method takes the string format of a locale and creates the
031     * locale object from it.
032     * </p>
033     *
034     * <pre>
035     *   LocaleUtils.toLocale("")           = new Locale("", "")
036     *   LocaleUtils.toLocale("en")         = new Locale("en", "")
037     *   LocaleUtils.toLocale("en_GB")      = new Locale("en", "GB")
038     *   LocaleUtils.toLocale("en_001")     = new Locale("en", "001")
039     *   LocaleUtils.toLocale("en_GB_xxx")  = new Locale("en", "GB", "xxx")   (#)
040     * </pre>
041     *
042     * <p>
043     * This method validates the input strictly.
044     * The language code must be lowercase.
045     * The country code must be uppercase.
046     * The separator must be an underscore.
047     * The length must be correct.
048     * </p>
049     *
050     * @param str the locale String to convert, null returns null
051     * @return a Locale, null if null input
052     * @throws IllegalArgumentException if the string is an invalid format
053     * @see Locale#forLanguageTag(String)
054     */
055    @SuppressWarnings("squid:S3776")
056    public static Locale toLocale(final String str) {
057        if (str == null) {
058            return null;
059        }
060        if (str.isEmpty()) {
061            // LANG-941 - JDK 8 introduced an empty locale where all fields are
062            // blank
063            return new Locale(MoreStrings.EMPTY, MoreStrings.EMPTY);
064        }
065        if (str.contains("#")) {
066            // LANG-879 - Cannot handle Java 7 script & extensions
067            throw new IllegalArgumentException(INVALID_LOCALE_FORMAT + str);
068        }
069        final var len = str.length();
070        if (len < 2) {
071            throw new IllegalArgumentException(INVALID_LOCALE_FORMAT + str);
072        }
073        final var ch0 = str.charAt(0);
074        if (ch0 == '_') {
075            if (len < 3) {
076                throw new IllegalArgumentException(INVALID_LOCALE_FORMAT + str);
077            }
078            final var ch1 = str.charAt(1);
079            final var ch2 = str.charAt(2);
080            if (!Character.isUpperCase(ch1) || !Character.isUpperCase(ch2)) {
081                throw new IllegalArgumentException(INVALID_LOCALE_FORMAT + str);
082            }
083            if (len == 3) {
084                return new Locale(MoreStrings.EMPTY, str.substring(1, 3));
085            }
086            if (len < 5 || str.charAt(3) != '_') {
087                throw new IllegalArgumentException(INVALID_LOCALE_FORMAT + str);
088            }
089            return new Locale(MoreStrings.EMPTY, str.substring(1, 3), str.substring(4));
090        }
091        return parseLocale(str);
092    }
093
094    /**
095     * Tries to parse a locale from the given String.
096     *
097     * @param str the String to parse a locale from.
098     *
099     * @return a Locale instance parsed from the given String.
100     * @throws IllegalArgumentException if the given String can not be parsed.
101     */
102    private static Locale parseLocale(final String str) {
103        if (isISO639LanguageCode(str)) {
104            return new Locale(str);
105        }
106        final var segments = str.split("_", -1);
107        final var language = segments[0];
108        if (segments.length == 2) {
109            final var country = segments[1];
110            if (isISO639LanguageCode(language) && isISO3166CountryCode(country) || isNumericAreaCode(country)) {
111                return new Locale(language, country);
112            }
113        } else if (segments.length == 3) {
114            final var country = segments[1];
115            final var variant = segments[2];
116            if (isISO639LanguageCode(language) && (country.isEmpty() || isISO3166CountryCode(country) || isNumericAreaCode(country)) && !variant.isEmpty()) {
117                return new Locale(language, country, variant);
118            }
119        }
120        throw new IllegalArgumentException(INVALID_LOCALE_FORMAT + str);
121    }
122
123    /**
124     * Checks whether the given String is a ISO 639 compliant language code.
125     *
126     * @param str the String to check.
127     *
128     * @return true, if the given String is a ISO 639 compliant language code.
129     */
130    private static boolean isISO639LanguageCode(final String str) {
131        return MoreStrings.isAllLowerCase(str) && (str.length() == 2 || str.length() == 3);
132    }
133
134    /**
135     * Checks whether the given String is a ISO 3166 alpha-2 country code.
136     *
137     * @param str the String to check
138     *
139     * @return true, is the given String is a ISO 3166 compliant country code.
140     */
141    private static boolean isISO3166CountryCode(final String str) {
142        return MoreStrings.isAllUpperCase(str) && str.length() == 2;
143    }
144
145    /**
146     * Checks whether the given String is a UN M.49 numeric area code.
147     *
148     * @param str the String to check
149     *
150     * @return true, is the given String is a UN M.49 numeric area code.
151     */
152    private static boolean isNumericAreaCode(final String str) {
153        return MoreStrings.isNumeric(str) && str.length() == 3;
154    }
155
156    @java.lang.SuppressWarnings("all")
157    @lombok.Generated
158    private LocaleUtils() {
159        throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated");
160    }
161}