All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.commons.locale.LocaleCache Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.commons.locale;

import java.util.Locale;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.annotation.Singleton;
import com.helger.commons.collection.impl.CommonsHashSet;
import com.helger.commons.collection.impl.CommonsLinkedHashMap;
import com.helger.commons.collection.impl.CommonsLinkedHashSet;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.impl.ICommonsOrderedMap;
import com.helger.commons.collection.impl.ICommonsOrderedSet;
import com.helger.commons.collection.impl.ICommonsSet;
import com.helger.commons.concurrent.SimpleReadWriteLock;
import com.helger.commons.debug.GlobalDebug;
import com.helger.commons.log.ConditionalLogger;
import com.helger.commons.log.IHasConditionalLogger;
import com.helger.commons.string.StringHelper;

/**
 * This is a global cache for Locale objects to avoid too many object flowing
 * around.
* This cache is application independent. * * @author Philip Helger */ @ThreadSafe @Singleton public class LocaleCache implements IHasConditionalLogger { /** * Internal interface for a callback handler to be invoked, if a non-existing * locale is found. * * @author Philip Helger * @since 9.3.9 */ @FunctionalInterface public interface IMissingLocaleHandler { /** * Called if a Locale is not yet present. * * @param sLocaleKey * The key for the internal map. Never null. * @param sLanguage * Language to use. Never null. * @param sCountry * Country to use. Never null. * @param sVariant * variant to use. Never null. * @return The created Locale or null. */ @Nullable Locale onMissingLocale (@Nonnull String sLocaleKey, @Nonnull String sLanguage, @Nonnull String sCountry, @Nonnull String sVariant); } private static final class SingletonHolder { private static final LocaleCache INSTANCE = new LocaleCache (); } private static final Logger LOGGER = LoggerFactory.getLogger (LocaleCache.class); private static final ConditionalLogger CONDLOG = new ConditionalLogger (LOGGER, !GlobalDebug.DEFAULT_SILENT_MODE); private static boolean s_bDefaultInstantiated = false; private final SimpleReadWriteLock m_aRWLock = new SimpleReadWriteLock (); /** maps a string to a locale. */ @GuardedBy ("m_aRWLock") private final ICommonsOrderedMap m_aLocales = new CommonsLinkedHashMap <> (); private final IMissingLocaleHandler m_aMissingLocaleHandlerInsert = (sLocaleKey, l, c, v) -> { // Insert in write lock if (StringHelper.hasNoText (sLocaleKey)) return null; return m_aRWLock.writeLockedGet ( () -> m_aLocales.computeIfAbsent (sLocaleKey, k -> new Locale (l, c, v))); }; protected LocaleCache () { reinitialize (); } /** * @return true if logging is disabled, false if it * is enabled. * @since 9.4.0 */ public static boolean isSilentMode () { return CONDLOG.isDisabled (); } /** * Enable or disable certain regular log messages. * * @param bSilentMode * true to disable logging, false to enable * logging * @return The previous value of the silent mode. * @since 9.4.0 */ public static boolean setSilentMode (final boolean bSilentMode) { return !CONDLOG.setEnabled (!bSilentMode); } public static boolean isInstantiated () { return s_bDefaultInstantiated; } @Nonnull public static LocaleCache getInstance () { final LocaleCache ret = SingletonHolder.INSTANCE; s_bDefaultInstantiated = true; return ret; } /** * @return The {@link IMissingLocaleHandler} implementation of this instance * that adds a missing locale to the set. Never null. * @since 9.4.2 */ @Nonnull public final IMissingLocaleHandler getDefaultMissingLocaleHandler () { return m_aMissingLocaleHandlerInsert; } /** * Get the {@link Locale} object matching the given language. * * @param sLanguage * The language to use. May be null or empty. * @return null if the passed language string is * null or empty */ @Nullable public Locale getLocale (@Nullable final String sLanguage) { return getLocaleExt (sLanguage, m_aMissingLocaleHandlerInsert); } /** * Get the {@link Locale} object matching the given language. * * @param sLanguage * The language to use. May be null or empty. * @param aMissingHandler * An optional handler to be invoked if the provided locale is not yet * contained. May be null. * @return null if the passed language string is * null or empty * @since 9.3.9 */ @Nullable public Locale getLocaleExt (@Nullable final String sLanguage, @Nullable final IMissingLocaleHandler aMissingHandler) { if (sLanguage != null && sLanguage.length () > 2) { // parse final String [] aParts = StringHelper.getExplodedArray (LocaleHelper.LOCALE_SEPARATOR, sLanguage, 3); if (aParts.length == 3) return getLocale (aParts[0], aParts[1], aParts[2], aMissingHandler); if (aParts.length == 2) return getLocale (aParts[0], aParts[1], "", aMissingHandler); // else fall through } return getLocale (sLanguage, "", "", aMissingHandler); } /** * Get the {@link Locale} object matching the given language and country. * * @param sLanguage * The language to use. May be null or empty. * @param sCountry * The country to use. May be null. * @return null if the passed language string is * null or empty */ @Nullable public Locale getLocale (@Nullable final String sLanguage, @Nullable final String sCountry) { return getLocale (sLanguage, sCountry, ""); } /** * Get the {@link Locale} object matching the given locale string * * @param sLanguage * The language to use. May be null or empty. * @param sCountry * Optional country to use. May be null. * @param sVariant * Optional variant. May be null. * @return null if all the passed parameters are * null or empty */ @Nullable public Locale getLocale (@Nullable final String sLanguage, @Nullable final String sCountry, @Nullable final String sVariant) { // Try fetching again in writeLock // not yet in cache, create a new one // -> may lead to illegal locales, but simpler than the error handling // for all the possible illegal values return getLocale (sLanguage, sCountry, sVariant, m_aMissingLocaleHandlerInsert); } /** * Build the locale key internally used. Note: this is not the same string as * returned by {@link Locale#toString()}!! * * @param sLanguage * Language to use * @param sCountry * Country to use * @param sVariant * Variant to use * @return String */ @Nonnull private static String _buildLocaleString (@Nonnull final String sLanguage, @Nonnull final String sCountry, @Nonnull final String sVariant) { final StringBuilder aLocaleSB = new StringBuilder (); if (sLanguage.length () > 0) aLocaleSB.append (sLanguage); if (sCountry.length () > 0) aLocaleSB.append (LocaleHelper.LOCALE_SEPARATOR).append (sCountry); if (sVariant.length () > 0) aLocaleSB.append (LocaleHelper.LOCALE_SEPARATOR).append (sVariant); return aLocaleSB.toString (); } /** * Get the {@link Locale} object matching the given locale string * * @param sLanguage * The language to use. May be null or empty. * @param sCountry * Optional country to use. May be null. * @param sVariant * Optional variant. May be null. * @param aMissingHandler * An optional handler to be invoked if the provided locale is not yet * contained. May be null. * @return null if all the passed parameters are * null or empty * @since 9.3.9 */ @Nullable public Locale getLocale (@Nullable final String sLanguage, @Nullable final String sCountry, @Nullable final String sVariant, @Nullable final IMissingLocaleHandler aMissingHandler) { final String sRealLanguage = StringHelper.getNotNull (LocaleHelper.getValidLanguageCode (sLanguage)); final String sRealCountry = StringHelper.getNotNull (LocaleHelper.getValidCountryCode (sCountry)); final String sRealVariant = StringHelper.getNotNull (sVariant); final String sLocaleKey = _buildLocaleString (sRealLanguage, sRealCountry, sRealVariant); Locale aLocale = null; if (sLocaleKey.length () > 0) { // try to resolve locale aLocale = m_aRWLock.readLockedGet ( () -> m_aLocales.get (sLocaleKey)); } if (aLocale == null && aMissingHandler != null) aLocale = aMissingHandler.onMissingLocale (sLocaleKey, sRealLanguage, sRealCountry, sRealVariant); return aLocale; } /** * Get all contained locales except the locales "all" and "independent" * * @return a set with all contained locales, except "all" and "independent". * Never null. */ @Nonnull @ReturnsMutableCopy public ICommonsList getAllLocales () { final ICommonsList ret = m_aRWLock.readLockedGet (m_aLocales::copyOfValues); ret.remove (LocaleHelper.LOCALE_ALL); ret.remove (LocaleHelper.LOCALE_INDEPENDENT); return ret; } /** * Get all contained locales that consist only of a non-empty language. * * @return a set with all contained languages, except "all" and "independent" */ @Nonnull @ReturnsMutableCopy public ICommonsSet getAllLanguages () { final ICommonsSet ret = new CommonsHashSet <> (); for (final Locale aLocale : getAllLocales ()) { final String sLanguage = aLocale.getLanguage (); if (StringHelper.hasText (sLanguage)) ret.add (getLocale (sLanguage, null, null)); } return ret; } /** * Check if the passed language is in the cache. * * @param sLanguage * The language to check. * @return true if it is in the cache, false * otherwise. */ public boolean containsLocale (@Nullable final String sLanguage) { if (sLanguage != null && sLanguage.length () > 2) { // parse final String [] aParts = StringHelper.getExplodedArray (LocaleHelper.LOCALE_SEPARATOR, sLanguage, 3); if (aParts.length == 3) return containsLocale (aParts[0], aParts[1], aParts[2]); if (aParts.length == 2) return containsLocale (aParts[0], aParts[1], ""); // else fall through } return containsLocale (sLanguage, "", ""); } /** * Check if the passed language is in the cache. * * @param sLanguage * The language to check. * @param sCountry * The country to check. * @return true if it is in the cache, false * otherwise. */ public boolean containsLocale (@Nullable final String sLanguage, @Nullable final String sCountry) { return containsLocale (sLanguage, sCountry, ""); } @Nonnull private static String _createLocaleKey (@Nullable final String sLanguage, @Nullable final String sCountry, @Nullable final String sVariant) { final String sRealLanguage = StringHelper.getNotNull (LocaleHelper.getValidLanguageCode (sLanguage)); final String sRealCountry = StringHelper.getNotNull (LocaleHelper.getValidCountryCode (sCountry)); final String sRealVariant = StringHelper.getNotNull (sVariant); return _buildLocaleString (sRealLanguage, sRealCountry, sRealVariant); } /** * Check if the passed language is in the cache. * * @param sLanguage * The language to check. * @param sCountry * The country to check. * @param sVariant * The variant to check. * @return true if it is in the cache, false * otherwise. */ public boolean containsLocale (@Nullable final String sLanguage, @Nullable final String sCountry, @Nullable final String sVariant) { final String sLocaleKey = _createLocaleKey (sLanguage, sCountry, sVariant); if (sLocaleKey.length () == 0) return false; return m_aRWLock.readLockedBoolean ( () -> m_aLocales.containsKey (sLocaleKey)); } /** * @return A set of all system default locales. Never null. */ @Nonnull @ReturnsMutableCopy public static ICommonsOrderedSet getAllDefaultLocales () { final ICommonsOrderedSet ret = new CommonsLinkedHashSet <> (1024); // add pseudo locales ret.add (LocaleHelper.LOCALE_ALL); ret.add (LocaleHelper.LOCALE_INDEPENDENT); // add all predefined languages for (final Locale aLocale : Locale.getAvailableLocales ()) { ret.add (aLocale); final String sCountry = aLocale.getCountry (); final String sLanguage = aLocale.getLanguage (); if (StringHelper.hasText (sCountry) && StringHelper.hasText (sLanguage)) { // Add as country-only and as language-only locales as well ret.add (new Locale ("", sCountry)); ret.add (new Locale (sLanguage, "")); } } // http://forums.sun.com/thread.jspa?threadID=525482&tstart=1411 for (final String sCountry : Locale.getISOCountries ()) ret.add (new Locale ("", sCountry)); for (final String sLanguage : Locale.getISOLanguages ()) ret.add (new Locale (sLanguage, "")); return ret; } /** * Reset the cache to the initial state. */ public final void reinitialize () { final ICommonsOrderedSet aDefLocales = getAllDefaultLocales (); // Update map m_aRWLock.writeLocked ( () -> { m_aLocales.clear (); for (final Locale aLocale : aDefLocales) m_aLocales.put (aLocale.toString (), aLocale); }); CONDLOG.debug ( () -> "Reinitialized " + LocaleCache.class.getName ()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy