com.aspectran.core.util.LocaleUtils Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2023 The Aspectran Project
*
* 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.aspectran.core.util;
import java.util.Locale;
import java.util.TimeZone;
/**
* This class has utility methods useful for parsing locale and timezone strings.
*/
public class LocaleUtils {
/**
* Parse the given {@code String} value into a {@link Locale}, accepting
* the {@link Locale#toString} format as well as BCP 47 language tags.
* @param localeValue the locale value: following either {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
* separators (as an alternative to underscores), or BCP 47 (e.g. "en-UK")
* as specified by {@link Locale#forLanguageTag} on Java 7+
* @return a corresponding {@code Locale} instance, or {@code null} if none
* @throws IllegalArgumentException in case of an invalid locale specification
* @see #parseLocaleString
* @see Locale#forLanguageTag
*/
public static Locale parseLocale(String localeValue) {
String[] tokens = tokenizeLocaleSource(localeValue);
if (tokens.length == 1) {
validateLocalePart(localeValue);
Locale resolved = Locale.forLanguageTag(localeValue);
if (resolved.getLanguage().length() > 0) {
return resolved;
}
}
return parseLocaleTokens(localeValue, tokens);
}
/**
* Parse the given {@code String} representation into a {@link Locale}.
* For many parsing scenarios, this is an inverse operation of
* {@link Locale#toString Locale's toString}, in a lenient sense.
* This method does not aim for strict {@code Locale} design compliance;
* it is rather specifically tailored for typical Spring parsing needs.
* Note: This delegate does not accept the BCP 47 language tag format.
* Please use {@link #parseLocale} for lenient parsing of both formats.
* @param localeString the locale {@code String}: following {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
* separators (as an alternative to underscores)
* @return a corresponding {@code Locale} instance, or {@code null} if none
* @throws IllegalArgumentException in case of an invalid locale specification
*/
public static Locale parseLocaleString(String localeString) {
return parseLocaleTokens(localeString, tokenizeLocaleSource(localeString));
}
private static String[] tokenizeLocaleSource(String localeSource) {
return StringUtils.tokenize(localeSource, "_ ", false);
}
private static Locale parseLocaleTokens(String localeString, String[] tokens) {
String language = (tokens.length > 0 ? tokens[0] : StringUtils.EMPTY);
String country = (tokens.length > 1 ? tokens[1] : StringUtils.EMPTY);
validateLocalePart(language);
validateLocalePart(country);
String variant = StringUtils.EMPTY;
if (tokens.length > 2) {
// There is definitely a variant, and it is everything after the country
// code sans the separator between the country code and the variant.
int endIndexOfCountryCode = localeString.indexOf(country, language.length()) + country.length();
// Strip off any leading '_' and whitespace, what's left is the variant.
variant = StringUtils.trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
if (variant.startsWith("_")) {
variant = StringUtils.trimLeadingCharacter(variant, '_');
}
}
if (variant.isEmpty() && country.startsWith("#")) {
variant = country;
country = StringUtils.EMPTY;
}
return (language.length() > 0 ? new Locale(language, country, variant) : null);
}
private static void validateLocalePart(String localePart) {
for (int i = 0; i < localePart.length(); i++) {
char ch = localePart.charAt(i);
if (ch != ' ' && ch != '_' && ch != '-' && ch != '#' && !Character.isLetterOrDigit(ch)) {
throw new IllegalArgumentException(
"Locale part \"" + localePart + "\" contains invalid characters");
}
}
}
/**
* Parse the given {@code timeZoneString} value into a {@link TimeZone}.
* @param timeZoneString the time zone {@code String}, following {@link TimeZone#getTimeZone(String)}
* but throwing {@link IllegalArgumentException} in case of an invalid time zone specification
* @return a corresponding {@link TimeZone} instance
* @throws IllegalArgumentException in case of an invalid time zone specification
*/
public static TimeZone parseTimeZoneString(String timeZoneString) {
TimeZone timeZone = TimeZone.getTimeZone(timeZoneString);
if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) {
// We don't want that GMT fallback...
throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'");
}
return timeZone;
}
}