Browse Source

Language tag parsing support in StringUtils and StringToLocaleConverter

Issue: SPR-16188
pull/1653/head
Juergen Hoeller 8 years ago
parent
commit
ef3f93e84a
  1. 10
      spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java
  2. 49
      spring-core/src/main/java/org/springframework/util/StringUtils.java
  3. 12
      spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java
  4. 4
      spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
  5. 5
      spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
  6. 24
      spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java

10
spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java

@ -1,5 +1,5 @@ @@ -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; @@ -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);
}
}

49
spring-core/src/main/java/org/springframework/util/StringUtils.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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() : "");
}

12
spring-core/src/test/java/org/springframework/core/convert/converter/DefaultConversionServiceTests.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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));

4
spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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;
}

5
spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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());
}
}

24
spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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.

Loading…
Cancel
Save