/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (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.civilian-framework.org/license.txt
*
* 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.civilian.text;
import java.util.HashMap;
import java.util.Locale;
import org.civilian.text.keys.KeyList;
import org.civilian.text.keys.KeyListBuilder;
import org.civilian.text.keys.serialize.KeySerializers;
import org.civilian.text.msg.EmptyMsgBundleFactory;
import org.civilian.text.msg.MsgBundle;
import org.civilian.text.msg.MsgBundleFactory;
import org.civilian.type.fn.LocaleSerializer;
import org.civilian.util.Check;
/**
* LocaleServiceList provides LocaleService objects for Locales.
* The list possesses a non empty list of LocaleServices for "supported" locales, defined by the
* application setup. (A non localized application will just use a single
* supported locale). The first of these supported locales is defined as the default locale.
*/
public class LocaleServiceList
{
/**
* Creates a new LocaleServiceList.
* @param msgBundleFactory a factory to create localized MsgBundles or null, if the service
* should only provide empty message bundles
* @param allowUnsupportedLocales true if the service also constructs LocaleServices for unsupported
* locales or false, if it falls back to a supported locale.
* @param supportedLocales the list of supported locales. Must at least contain one entry.
*/
public LocaleServiceList(MsgBundleFactory msgBundleFactory, boolean allowUnsupportedLocales, Locale... supportedLocales)
{
allowUnsupportedLocales_ = allowUnsupportedLocales;
msgBundleFactory_ = msgBundleFactory != null ? msgBundleFactory : new EmptyMsgBundleFactory();
supportedLocales_ = normLocales(supportedLocales);
supportedServices_ = new LocaleService[supportedLocales_.length];
KeyListBuilder<LocaleService> klBuilder = new KeyListBuilder<>();
klBuilder.setSerializer(KeySerializers.TO_STRING);
for (int i=0; i<supportedLocales_.length; i++)
{
Locale locale = supportedLocales_[i];
LocaleService service = supportedServices_[i] = createService(locale);
klBuilder.add(service, locale.getDisplayName(locale));
}
defaultService_ = supportedServices_[0];
serviceKeys_ = klBuilder.end();
if (allowUnsupportedLocales)
localeMap_ = new AllowUnsupportedLocaleMap();
else if (supportedLocales_.length == 1)
localeMap_ = new FixedLocaleMap();
else
localeMap_ = new FallbackLocaleMap();
}
private static Locale[] normLocales(Locale[] locales)
{
if ((locales != null && locales.length > 0))
{
for (Locale locale : locales)
Check.notNull(locale, "locale");
return locales;
}
else
return new Locale[] { Locale.ENGLISH };
}
/**
* Returns the MsgBundleFactory.
*/
public MsgBundleFactory getMsgBundleFactory()
{
return msgBundleFactory_;
}
/**
* Returns the number of supported locales.
*/
public int size()
{
return supportedLocales_.length;
}
/**
* Returns the i-th supported locale.
*/
public Locale getLocale(int i)
{
return supportedLocales_[i];
}
/**
* Returns the first supported locale.
*/
public Locale getDefaultLocale()
{
return getDefaultService().getLocale();
}
/**
* Returns if LocaleServices for unsupported locales
* are returned, or if the service falls back to a supported locale.
*/
public boolean allowUnsupportedLocales()
{
return allowUnsupportedLocales_;
}
/**
* Tests if the given locale is supported.
*/
public boolean isSupported(Locale locale)
{
return toSupported(locale) != null;
}
private Locale toSupported(Locale locale)
{
for (Locale supported : supportedLocales_)
{
if (supported.equals(locale))
return supported;
}
return null;
}
/**
* Returns a supported locale. If the locale is included
* in the supported locales it is returned.
* If its language matches the language of a supported locale
* that locale is returned. Else the default locale is returned.
*/
public Locale normLocale(Locale locale)
{
if ((locale != null) && (size() > 1))
{
Locale supported = toSupported(locale);
if (supported != null)
return supported;
// fall back to first locale with same language
String language = locale.getLanguage();
for (Locale sl : supportedLocales_)
{
if (sl.getLanguage().equals(language))
return sl;
}
}
// fall back to default locale
return getDefaultLocale();
}
/**
* Returns the LocaleService of the default locale.
*/
public LocaleService getDefaultService()
{
return defaultService_;
}
/**
* Returns the LocaleService for a locale.
* If the locale is not supported, it depends on the policy of
* the locale service what LocaleService is returned:
* If unsupported locales are allowed, a LocaleService for the locale
* is constructed and returned. Such a LocaleService is not cached and
* therefore has a small performance penalty.
* If unsupported locales are not allowed, a LocaleService for fallback locale
* is returned.
*/
public LocaleService getService(Locale locale)
{
Check.notNull(locale, "locale");
return localeMap_.getService(locale);
}
public LocaleService getService(String locale)
{
Check.notNull(locale, "locale");
for (LocaleService service : supportedServices_)
{
if (service.toString().equals(locale))
return service;
}
return getDefaultService();
}
/**
* Returns the LocaleService for the i-th locale.
*/
public LocaleService getService(int i)
{
return supportedServices_[i];
}
/**
* Returns a KeyList for the defined LocaleService objects.
*/
public KeyList<LocaleService> getServiceKeys()
{
return serviceKeys_;
}
private LocaleService createService(Locale locale)
{
MsgBundle msgBundle = msgBundleFactory_.getMsgBundle(locale);
LocaleSerializer first = supportedServices_[0] != null ? supportedServices_[0].getSerializer() : null;
LocaleSerializer serializer = new LocaleSerializer(locale, first);
return new LocaleService(locale, msgBundle, serializer);
}
/**
* Clears the cache of the MsgBundleFactory and reloads the MsgBundles of all services.
*/
public void reloadServiceMsgBundles()
{
msgBundleFactory_.clearCache();
for (LocaleService service : supportedServices_)
service.setMsgBundle(msgBundleFactory_.getMsgBundle(service.getLocale()));
}
/**
* A LocaleMap maps locales to LocaleServices.
*/
private abstract class LocaleMap
{
public abstract LocaleService getService(Locale locale);
}
/**
* A LocaleMap with a single entry.
*/
private class FixedLocaleMap extends LocaleMap
{
@Override public LocaleService getService(Locale locale)
{
return defaultService_;
}
}
/**
* A base class for LocaleMaps with multiple entries and a default entry.
*/
private abstract class MultiLocaleMap extends LocaleMap
{
public MultiLocaleMap()
{
for (LocaleService service : supportedServices_)
map_.put(service.getLocale(), service);
}
protected LocaleService tryGet(Locale locale)
{
return map_.get(locale);
}
private HashMap<Locale,LocaleService> map_ = new HashMap<>();
}
/**
* A LocaleMap with multiple entries which returns a LocaleService
* from its supported locale-list for unknown locales
*/
private class FallbackLocaleMap extends MultiLocaleMap
{
@Override public LocaleService getService(Locale locale)
{
LocaleService service = tryGet(locale);
if (service == null)
{
service = tryGet(normLocale(locale));
if (service == null)
service = defaultService_;
}
return service;
}
}
/**
* A LocaleMap with multiple entries which creates uncached
* LocaleServices when it encounters an unsupported locale.
*/
private class AllowUnsupportedLocaleMap extends MultiLocaleMap
{
@Override public LocaleService getService(Locale locale)
{
LocaleService service = tryGet(locale);
if (service == null)
service = createService(locale);
return service;
}
}
private final Locale[] supportedLocales_;
private final LocaleService defaultService_;
private final LocaleService[] supportedServices_;
private final MsgBundleFactory msgBundleFactory_;
private final LocaleMap localeMap_;
private final KeyList<LocaleService> serviceKeys_;
private final boolean allowUnsupportedLocales_;
}