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 { @Override public Locale convert(String source) { - return StringUtils.parseLocaleString(source); + return StringUtils.parseLocale(source); } } diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 37d83b864ab..ca22f467a3c 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.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. @@ -750,23 +750,54 @@ public abstract class StringUtils { /** * 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 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.