diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java index f4b6f53802d..7212068a83d 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java @@ -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}. * + *
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 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}.
+ * 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)
+ * 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() : "");
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java
index 8d1048301cf..dc8ae554a74 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java
@@ -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));
diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
index e6110c54405..a63bb7d10f8 100644
--- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
+++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
@@ -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;
}
diff --git a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
index d48e41614ca..172752b6795 100644
--- a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
+++ b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
@@ -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());
}
}
diff --git a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java
index d07bedd0409..69270cac4d6 100644
--- a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java
+++ b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java
@@ -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.
*
- * 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.
+ * 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}.
* 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.
* Default implementation rejects any HTTP status code beyond 2xx, to avoid
* parsing the response body and trying to deserialize from a corrupted stream.