Language tag parsing support in StringUtils and StringToLocaleConverter
Issue: SPR-16188
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,15 +24,19 @@ import org.springframework.util.StringUtils;
|
||||
/**
|
||||
* Converts from a String to a {@link java.util.Locale}.
|
||||
*
|
||||
* <p>Accepts the classic {@link Locale} String format ({@link Locale#toString()})
|
||||
* as well as BCP 47 language tags ({@link Locale#forLanguageTag} on Java 7+).
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see StringUtils#parseLocaleString
|
||||
* @see StringUtils#parseLocale
|
||||
*/
|
||||
final class StringToLocaleConverter implements Converter<String, Locale> {
|
||||
|
||||
@Override
|
||||
public Locale convert(String source) {
|
||||
return StringUtils.parseLocaleString(source);
|
||||
return StringUtils.parseLocale(source);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -750,23 +750,54 @@ public abstract class StringUtils {
|
||||
/**
|
||||
* Parse the given {@code localeString} value into a {@link Locale}.
|
||||
* <p>This is the inverse operation of {@link Locale#toString Locale's toString}.
|
||||
* @param localeString the locale {@code String}, following {@code Locale's}
|
||||
* {@code toString()} format ("en", "en_UK", etc);
|
||||
* also accepts spaces as separators, as an alternative to underscores
|
||||
* @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
|
||||
* @since 5.0.4
|
||||
* @see #parseLocaleString
|
||||
* @see Locale#forLanguageTag
|
||||
*/
|
||||
@Nullable
|
||||
public static Locale parseLocale(String localeValue) {
|
||||
String[] tokens = tokenizeLocaleSource(localeValue);
|
||||
if (tokens.length == 1) {
|
||||
return Locale.forLanguageTag(localeValue);
|
||||
}
|
||||
return parseLocaleTokens(localeValue, tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given {@code localeString} value into a {@link Locale}.
|
||||
* <p>This is the inverse operation of {@link Locale#toString Locale's toString}.
|
||||
* @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)
|
||||
* <p>Note: This variant does not accept the BCP 47 language tag format.
|
||||
* Please use {@link #parseLocale} for lenient parsing of both formats.
|
||||
* @return a corresponding {@code Locale} instance, or {@code null} if none
|
||||
* @throws IllegalArgumentException in case of an invalid locale specification
|
||||
*/
|
||||
@Nullable
|
||||
public static Locale parseLocaleString(String localeString) {
|
||||
String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
|
||||
String language = (parts.length > 0 ? parts[0] : "");
|
||||
String country = (parts.length > 1 ? parts[1] : "");
|
||||
return parseLocaleTokens(localeString, tokenizeLocaleSource(localeString));
|
||||
}
|
||||
|
||||
private static String[] tokenizeLocaleSource(String localeSource) {
|
||||
return tokenizeToStringArray(localeSource, "_ ", false, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Locale parseLocaleTokens(String localeString, String[] tokens) {
|
||||
String language = (tokens.length > 0 ? tokens[0] : "");
|
||||
String country = (tokens.length > 1 ? tokens[1] : "");
|
||||
validateLocalePart(language);
|
||||
validateLocalePart(country);
|
||||
|
||||
String variant = "";
|
||||
if (parts.length > 2) {
|
||||
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();
|
||||
@@ -794,7 +825,9 @@ public abstract class StringUtils {
|
||||
* as used for the HTTP "Accept-Language" header.
|
||||
* @param locale the Locale to transform to a language tag
|
||||
* @return the RFC 3066 compliant language tag as {@code String}
|
||||
* @deprecated as of 5.0.4, in favor of {@link Locale#toLanguageTag()}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String toLanguageTag(Locale locale) {
|
||||
return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -275,6 +275,16 @@ public class DefaultConversionServiceTests {
|
||||
assertEquals(Locale.ENGLISH, conversionService.convert("en", Locale.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringToLocaleWithCountry() {
|
||||
assertEquals(Locale.US, conversionService.convert("en_US", Locale.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringToLocaleWithLanguageTag() {
|
||||
assertEquals(Locale.US, conversionService.convert("en-US", Locale.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringToCharset() {
|
||||
assertEquals(StandardCharsets.UTF_8, conversionService.convert("UTF-8", Charset.class));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -218,7 +218,7 @@ public class Jackson2ObjectMapperBuilder {
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public Jackson2ObjectMapperBuilder locale(String localeString) {
|
||||
this.locale = StringUtils.parseLocaleString(localeString);
|
||||
this.locale = StringUtils.parseLocale(localeString);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -44,7 +44,6 @@ import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.remoting.httpinvoker.HttpInvokerRequestExecutor} implementation that uses
|
||||
@@ -223,7 +222,7 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
|
||||
if (localeContext != null) {
|
||||
Locale locale = localeContext.getLocale();
|
||||
if (locale != null) {
|
||||
httpPost.addHeader(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale));
|
||||
httpPost.addHeader(HTTP_HEADER_ACCEPT_LANGUAGE, locale.toLanguageTag());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,17 +28,15 @@ import java.util.zip.GZIPInputStream;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.remoting.support.RemoteInvocationResult;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* HttpInvokerRequestExecutor implementation that uses standard J2SE facilities
|
||||
* to execute POST requests, without support for HTTP authentication or
|
||||
* advanced configuration options.
|
||||
* {@link org.springframework.remoting.httpinvoker.HttpInvokerRequestExecutor} implementation
|
||||
* that uses standard Java facilities to execute POST requests, without support for HTTP
|
||||
* authentication or advanced configuration options.
|
||||
*
|
||||
* <p>Designed for easy subclassing, customizing specific template methods.
|
||||
* However, consider {@code HttpComponentsHttpInvokerRequestExecutor} for
|
||||
* more sophisticated needs: The J2SE HttpURLConnection is rather limited
|
||||
* in its capabilities.
|
||||
* <p>Designed for easy subclassing, customizing specific template methods. However,
|
||||
* consider {@code HttpComponentsHttpInvokerRequestExecutor} for more sophisticated needs:
|
||||
* The standard {@link HttpURLConnection} class is rather limited in its capabilities.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
@@ -73,7 +71,7 @@ public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequest
|
||||
|
||||
|
||||
/**
|
||||
* Execute the given request through a standard J2SE HttpURLConnection.
|
||||
* Execute the given request through a standard {@link HttpURLConnection}.
|
||||
* <p>This method implements the basic processing workflow:
|
||||
* The actual work happens in this class's template methods.
|
||||
* @see #openConnection
|
||||
@@ -97,7 +95,7 @@ public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an HttpURLConnection for the given remote invocation request.
|
||||
* Open an {@link HttpURLConnection} for the given remote invocation request.
|
||||
* @param config the HTTP invoker configuration that specifies the
|
||||
* target service
|
||||
* @return the HttpURLConnection for the given request
|
||||
@@ -141,7 +139,7 @@ public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequest
|
||||
if (localeContext != null) {
|
||||
Locale locale = localeContext.getLocale();
|
||||
if (locale != null) {
|
||||
connection.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale));
|
||||
connection.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, locale.toLanguageTag());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +169,7 @@ public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given response as contained in the HttpURLConnection object,
|
||||
* Validate the given response as contained in the {@link HttpURLConnection} object,
|
||||
* throwing an exception if it does not correspond to a successful HTTP response.
|
||||
* <p>Default implementation rejects any HTTP status code beyond 2xx, to avoid
|
||||
* parsing the response body and trying to deserialize from a corrupted stream.
|
||||
|
||||
Reference in New Issue
Block a user