/*******************************************************************************
*
* Copyright 2011-2014 Spiffy UI Team
*
* Licensed 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 org.spiffyui.server.i18n;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.spiffyui.server.JSLocaleUtil;
/**
* A basic implementation for matching a list of locales from the request
* and finding a best corresponding supported locale.
*/
public final class BasicBestLocaleMatcher
{
private BasicBestLocaleMatcher()
{
}
private static final ConcurrentHashMap<String, Locale> LOCALE_CACHE = new ConcurrentHashMap<String, Locale>();
/**
* <p>Return the best match locale for JavaScript dates by getting a list of requested locales from the request.</p>
*
* <p>
* This methods builds the list of supported locales based on the JavaScript date and time localization
* classes included in the Spiffy UI framework. It will loop through the list of locales and attempt to
* match first exactly, then by language and country, then by country. If no match is found it will
* default to English.
* </p>
*
* <p>
* <b>This method should almost never be called by classes outside of the Spiffy UI framework.</b>
* </p>
*
* <p>
* If your application supports a different list of locales, and most of them do, then you should call
* the version of this method passing a list of supported locales.
* </p>
*
* @param request - the HttpServletRequest
* @param response - the HttpServletResponse
* @param context - the ServletContext
*
* @return the best match Locale
*/
public static Locale getBestMatchLocale(HttpServletRequest request, HttpServletResponse response, ServletContext context)
{
// If the Locale wasn't found yet, use the browser's locale list to find the best match
@SuppressWarnings("unchecked")
ArrayList<Locale> requestLocales = Collections.list(request.getLocales());
//loop through the list and see if its a supported locale by
//checking lang, country, variant
//then by checking lang, country
//then by lang
//then by next locale
for (Locale requestLocale : requestLocales) {
Locale matchLocale = matchSupportedLocale(requestLocale, context);
if (matchLocale != null) {
return matchLocale;
}
}
//nothing found, go with application default
//but here we'll assume no application default is defined anywhere, so just go with en
return Locale.ENGLISH;
}
/**
* <p>Return the best match locale by getting a list of requested locales from the request and matching it
* to the provided list of supported locales.</p>
*
* <p>
* This method will loop through the list of locales and attempt to match first exactly, then by language
* and country, then by country. If no match is found it will default to English.
* </p>
*
* @param request - the HttpServletRequest
* @param response - the HttpServletResponse
* @param supportedLocales - the list of available locales to choose from
* @return the best match Locale
*/
public static Locale getBestMatchLocale(HttpServletRequest request, HttpServletResponse response, List<Locale> supportedLocales)
{
// If the Locale wasn't found yet, use the browser's locale list to find the best match
@SuppressWarnings("unchecked")
ArrayList<Locale> requestLocales = Collections.list(request.getLocales());
//loop through the list and see if its a supported locale by
//checking lang, country, variant
//then by checking lang, country
//then by lang
//then by next locale
for (Locale requestLocale : requestLocales) {
Locale matchLocale = matchSupportedLocale(requestLocale, supportedLocales);
if (matchLocale != null) {
return matchLocale;
}
}
//nothing found, go with application default
//but here we'll assume no application default is defined anywhere, so just go with en
return Locale.ENGLISH;
}
private static Locale matchSupportedLocale(Locale loc, ServletContext context)
{
return matchSupportedLocale(loc, JSLocaleUtil.getMinimumSupportedLocales(context));
}
private static Locale matchSupportedLocale(Locale loc, List<Locale> supportedLocales)
{
for (Locale supportedLocale : supportedLocales) {
if (supportedLocale.equals(loc)) {
//exact match found
return supportedLocale;
}
}
Locale supportedLocal = matchSupportedLocaleExact(loc, supportedLocales);
if (supportedLocal != null) {
return supportedLocal;
} else {
return matchSupportedLocaleFuzzy(loc, supportedLocales);
}
}
private static Locale matchSupportedLocaleFuzzy(Locale loc, List<Locale> supportedLocales)
{
//bust on lang-country, try matching on just lang
for (Locale supportedLocale : supportedLocales) {
if (supportedLocale.getLanguage().equals(loc.getLanguage())) {
//lang found
return supportedLocale;
}
}
return null;
}
private static Locale matchSupportedLocaleExact(Locale loc, List<Locale> supportedLocales)
{
String locStr = loc.toString();
int locStrLen = locStr.length();
//bust on exact match, if it's lang-country-variant, try matching on just lang-country
if (locStrLen >= 5) {
for (Locale supportedLocale : supportedLocales) {
if (supportedLocale.getLanguage().equals(loc.getLanguage()) &&
supportedLocale.getCountry().equals(loc.getCountry())) {
//lang-country found
return supportedLocale;
}
}
}
return null;
}
/**
* Creates a Locale object from the xml:lang attribute format or the Java standard (as returned by Locale.toString()) for specifying locales.
*
* @param localeCode String code that conforms to the RFC 3066 standard or the Java standard (as returned by Locale.toString()).
* @return Locale object converted from the localeCode passed in.
*/
public static Locale getLocaleFromCode(final String localeCode)
{
if (localeCode == null || localeCode.length() == 0) {
throw new IllegalArgumentException("Null or empty localeCode passed");
}
if (LOCALE_CACHE.containsKey(localeCode)) {
return LOCALE_CACHE.get(localeCode);
} else {
Locale locale;
String locStr;
String ctryCd = null;
String variant;
switch (localeCode.length()) {
case 2:
locStr = localeCode;
locale = new Locale(locStr.toLowerCase());
break;
case 5:
locStr = localeCode.substring(0, 2);
ctryCd = localeCode.substring(3, 5);
locale = new Locale(locStr.toLowerCase(), ctryCd.toUpperCase());
break;
default:
locStr = localeCode.substring(0, 2);
ctryCd = localeCode.substring(3, 5);
variant = localeCode.substring(6);
locale = new Locale(locStr.toLowerCase(), ctryCd.toUpperCase(), variant);
}
LOCALE_CACHE.putIfAbsent(localeCode, locale);
return locale;
}
}
}