WW-5422 Adds dedicate unit test to cover DefaultLocaleProvider
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
index 8c7e15e..ab1a180 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
@@ -91,6 +91,11 @@
     }
 
     @Override
+    public Locale toLocale(String localeStr) {
+        return getLocaleProvider().toLocale(localeStr);
+    }
+
+    @Override
     public boolean hasKey(String key) {
         return getTextProvider().hasKey(key);
     }
diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultLocaleProvider.java b/core/src/main/java/com/opensymphony/xwork2/DefaultLocaleProvider.java
index da89306..35f1619 100644
--- a/core/src/main/java/com/opensymphony/xwork2/DefaultLocaleProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/DefaultLocaleProvider.java
@@ -46,17 +46,23 @@
 
     @Override
     public boolean isValidLocaleString(String localeStr) {
+        Locale locale = this.toLocale(localeStr);
+        return isValidLocale(locale);
+    }
+
+    @Override
+    public boolean isValidLocale(Locale locale) {
+        return locale != null && LocaleUtils.isAvailableLocale(locale);
+    }
+
+    @Override
+    public Locale toLocale(String localeStr) {
         Locale locale = null;
         try {
             locale = LocaleUtils.toLocale(StringUtils.trimToNull(localeStr));
         } catch (IllegalArgumentException e) {
             LOG.warn(new ParameterizedMessage("Cannot convert [{}] to proper locale", localeStr), e);
         }
-        return isValidLocale(locale);
-    }
-
-    @Override
-    public boolean isValidLocale(Locale locale) {
-        return LocaleUtils.isAvailableLocale(locale);
+        return locale;
     }
 }
diff --git a/core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java b/core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java
index 67972af..00a41a2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/LocaleProvider.java
@@ -18,6 +18,9 @@
  */
 package com.opensymphony.xwork2;
 
+import org.apache.commons.lang3.LocaleUtils;
+import org.apache.commons.lang3.StringUtils;
+
 import java.util.Locale;
 
 
@@ -58,4 +61,17 @@
      */
     boolean isValidLocale(Locale locale);
 
+    /**
+     * Tries to convert provided locale string into {@link Locale} or returns null
+     * @param localeStr a String representing locale, e.g.: en_EN
+     * @return instance of {@link Locale} or null
+     * @since Struts 6.5.0
+     */
+    default Locale toLocale(String localeStr) {
+        try {
+            return LocaleUtils.toLocale(StringUtils.trimToNull(localeStr));
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
 }
diff --git a/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java b/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java
index 5c7f2c1..bc8c888 100644
--- a/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java
+++ b/core/src/main/java/com/opensymphony/xwork2/validator/DelegatingValidatorContext.java
@@ -122,10 +122,15 @@
         return localeProvider.isValidLocale(locale);
     }
 
+    @Override
+    public Locale toLocale(String localeStr) {
+        return localeProvider.toLocale(localeStr);
+    }
+
     public boolean hasKey(String key) {
     	return textProvider.hasKey(key);
     }
-    
+
     public String getText(String aTextName) {
         return textProvider.getText(aTextName);
     }
@@ -280,6 +285,11 @@
         public boolean isValidLocale(Locale locale) {
             return getLocaleProvider().isValidLocale(locale);
         }
+
+        @Override
+        public Locale toLocale(String localeStr) {
+            return getLocaleProvider().toLocale(localeStr);
+        }
     }
 
     /**
diff --git a/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
index e0f978f..6bce042 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/I18nInterceptor.java
@@ -24,7 +24,6 @@
 import com.opensymphony.xwork2.inject.Inject;
 import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
 import com.opensymphony.xwork2.util.TextParseUtil;
-import org.apache.commons.lang3.LocaleUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.message.ParameterizedMessage;
@@ -85,7 +84,7 @@
     }
 
     public void setLocaleStorage(String storageName) {
-        if (storageName == null || "".equals(storageName)) {
+        if (storageName == null || storageName.isEmpty()) {
             this.storage = Storage.ACCEPT_LANGUAGE;
         } else {
             try {
@@ -169,27 +168,21 @@
     }
 
     /**
-     * Creates a Locale object from the request param, which might
-     * be already a Local or a String
+     * Creates a Locale object from the request param
      *
      * @param requestedLocale the parameter from the request
-     * @return the Locale
+     * @return instance of {@link Locale} or null
      */
-    protected Locale getLocaleFromParam(Object requestedLocale) {
+    protected Locale getLocaleFromParam(String requestedLocale) {
         LocaleProvider localeProvider = localeProviderFactory.createLocaleProvider();
 
         Locale locale = null;
         if (requestedLocale != null) {
-            if (requestedLocale instanceof Locale) {
-                locale = (Locale) requestedLocale;
-            } else {
-                String localeStr = requestedLocale.toString();
-                if (localeProvider.isValidLocaleString(localeStr)) {
-                    locale = LocaleUtils.toLocale(localeStr);
-                } else {
-                    locale = localeProvider.getLocale();
-                }
+            locale = localeProvider.toLocale(requestedLocale);
+            if (locale == null) {
+                locale = localeProvider.getLocale();
             }
+
             if (locale != null) {
                 LOG.debug("Found locale: {}", locale);
             }
@@ -285,7 +278,7 @@
         @Override
         @SuppressWarnings("rawtypes")
         public Locale find() {
-            if (supportedLocale.size() > 0) {
+            if (!supportedLocale.isEmpty()) {
                 Enumeration locales = actionInvocation.getInvocationContext().getServletRequest().getLocales();
                 while (locales.hasMoreElements()) {
                     Locale locale = (Locale) locales.nextElement();
diff --git a/core/src/test/java/com/opensymphony/xwork2/DefaultLocaleProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/DefaultLocaleProviderTest.java
new file mode 100644
index 0000000..bb5178a
--- /dev/null
+++ b/core/src/test/java/com/opensymphony/xwork2/DefaultLocaleProviderTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.opensymphony.xwork2;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class DefaultLocaleProviderTest {
+
+    private DefaultLocaleProvider provider;
+
+    @Before
+    public void setUp() throws Exception {
+        provider = new DefaultLocaleProvider();
+    }
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        ActionContext.of().bind();
+    }
+
+    @AfterClass
+    public static void afterClass() throws Exception {
+        ActionContext.clear();
+    }
+
+    @Test
+    public void getLocale() {
+        // given
+        ActionContext.getContext().withLocale(Locale.ITALY);
+
+        // when
+        Locale actual = provider.getLocale();
+
+        // then
+        assertEquals(Locale.ITALY, actual);
+    }
+
+    @Test
+    public void getLocaleNull() {
+        // given
+        ActionContext backup = ActionContext.getContext();
+        ActionContext.clear();
+
+        // when
+        Locale actual = provider.getLocale();
+
+        // then
+        assertNull(actual);
+        ActionContext.bind(backup);
+    }
+
+    @Test
+    public void toLocale() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        Locale actual = provider.toLocale("it");
+
+        // then
+        assertEquals(Locale.ITALIAN, actual);
+    }
+
+    @Test
+    public void toLocaleFull() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        Locale actual = provider.toLocale("it_IT");
+
+        // then
+        assertEquals(Locale.ITALY, actual);
+    }
+
+    @Test
+    public void toLocaleTrimEndOfLine() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        Locale actual = provider.toLocale("it_IT\n");
+
+        // then
+        assertEquals(Locale.ITALY, actual);
+    }
+
+    @Test
+    public void toLocaleTrimEmptySpace() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        Locale actual = provider.toLocale(" it_IT ");
+
+        // then
+        assertEquals(Locale.ITALY, actual);
+    }
+
+    @Test
+    public void isValidLocaleNull() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        boolean actual = provider.isValidLocale(null);
+
+        // then
+        assertFalse(actual);
+    }
+
+    @Test
+    public void isValidLocale() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        boolean actual = provider.isValidLocale(Locale.ITALIAN);
+
+        // then
+        assertTrue(actual);
+    }
+
+    @Test
+    public void isValidLocaleString() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        boolean actual = provider.isValidLocaleString("it");
+
+        // then
+        assertTrue(actual);
+    }
+
+    @Test
+    public void isValidLocaleStringNot() {
+        // given
+        ActionContext.getContext().withLocale(Locale.GERMAN);
+
+        // when
+        boolean actual = provider.isValidLocaleString("italy");
+
+        // then
+        assertFalse(actual);
+    }
+
+}
diff --git a/core/src/test/java/com/opensymphony/xwork2/LocaleProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/LocaleProviderTest.java
new file mode 100644
index 0000000..03e05f5
--- /dev/null
+++ b/core/src/test/java/com/opensymphony/xwork2/LocaleProviderTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.opensymphony.xwork2;
+
+import org.junit.Test;
+
+import java.util.Locale;
+
+import static org.junit.Assert.*;
+
+public class LocaleProviderTest {
+
+    @Test
+    public void toLocale() {
+        // given
+        DummyLocale locale = new DummyLocale();
+
+        // when
+        Locale actual = locale.toLocale("de");
+
+        // then
+        assertEquals(Locale.GERMAN, actual);
+    }
+
+    @Test
+    public void toLocaleTrim() {
+        // given
+        DummyLocale locale = new DummyLocale();
+
+        // when
+        Locale actual = locale.toLocale(" de_DE ");
+
+        // then
+        assertEquals(Locale.GERMANY, actual);
+    }
+
+    @Test
+    public void toLocaleNull() {
+        // given
+        DummyLocale locale = new DummyLocale();
+
+        // when
+        Locale actual = locale.toLocale("germany");
+
+        // then
+        assertNull(actual);
+    }
+
+}
+
+class DummyLocale implements LocaleProvider {
+    @Override
+    public Locale getLocale() {
+        return null;
+    }
+
+    @Override
+    public boolean isValidLocaleString(String localeStr) {
+        return false;
+    }
+
+    @Override
+    public boolean isValidLocale(Locale locale) {
+        return false;
+    }
+}
diff --git a/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
index a8fd842..604d61b 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/I18nInterceptorTest.java
@@ -147,6 +147,26 @@
         assertEquals(Locale.getDefault(), session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
     }
 
+    public void testTrimableLocaleString1() throws Exception {
+        prepare(I18nInterceptor.DEFAULT_PARAMETER, "de\n");
+
+        interceptor.intercept(mai);
+
+        assertFalse(mai.getInvocationContext().getParameters().get(I18nInterceptor.DEFAULT_PARAMETER).isDefined()); // should have been removed
+        assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+        assertEquals(Locale.GERMAN, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+    }
+
+    public void testTrimableLocaleString2() throws Exception {
+        prepare(I18nInterceptor.DEFAULT_PARAMETER, "de ");
+
+        interceptor.intercept(mai);
+
+        assertFalse(mai.getInvocationContext().getParameters().get(I18nInterceptor.DEFAULT_PARAMETER).isDefined()); // should have been removed
+        assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
+        assertEquals(Locale.GERMAN, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
+    }
+
     public void testWithVariant() throws Exception {
         prepare(I18nInterceptor.DEFAULT_PARAMETER, "ja_JP_JP");
         interceptor.intercept(mai);