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

church.i18n.resources.bundles.PolyglotMultiResourceBundle Maven / Gradle / Ivy

Go to download

Project name: church.i18n.resources.bundles Improved Resource bundles that are able to hold multiple different localization files and provide functionality to handle multiple language mutations at once. .

The newest version!
/*
 * Copyright (c) 2019 Juraj Jurčo
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package church.i18n.resources.bundles;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Holder of different resource bundles for multiple languages. The class allows adding resource
 * bundles 'on the fly' and loads locales lazily, when they are required.
 */
public class PolyglotMultiResourceBundle implements MultiResourceBundle {

  private static final Logger log = LoggerFactory.getLogger(PolyglotMultiResourceBundle.class);
  private final Map locales = new HashMap<>();
  private final List resources = new ArrayList<>();
  private final ReadWriteLock lock = new ReentrantReadWriteLock();
  private final Locale defaultLocale;

  /**
   * Load multiple resource bundles with messages.
   *
   * @param defaultLocale     Set default locale that will be used by this class. If {@code null},
   *                          {@link Locale#getDefault()} is used instead.
   * @param resourceFilenames List of resource bundle names to load. If you do not specify any file,
   *                          no message will be retrieved. You can freely add resource files later.
   *                          The order of added resource bundles is respected during message
   *                          lookup.
   */
  public PolyglotMultiResourceBundle(final Locale defaultLocale,
      final String... resourceFilenames) {
    log.trace("PolyglotMultiResourceBundle(defaultLocale = [{}], resourceFilenames = [{}])",
        defaultLocale, resourceFilenames);
    lock.writeLock().lock();
    try {
      this.defaultLocale = (defaultLocale == null) ? Locale.getDefault() : defaultLocale;
      if (resourceFilenames != null) {
        resources.addAll(Arrays.asList(resourceFilenames));
      }
      //by default, always load root locale - no language, no country
      loadLocale(Locale.ROOT);
      loadLocale(this.defaultLocale);
    } finally {
      lock.writeLock().unlock();
    }
  }

  /**
   * Load multiple resource bundles with messages. When you call this constructor without any
   * specific locale, the default locale {@link Locale#getDefault()} will be used.
   *
   * @param resourceFilenames List of resource bundles names to load. If you do not specify any
   *                          file, no message will be retrieved. You can freely add resource files
   *                          later. The order of added resource bundles is respected during message
   *                          lookup.
   */
  public PolyglotMultiResourceBundle(final String... resourceFilenames) {
    this(Locale.getDefault(), resourceFilenames);
  }

  /**
   * Retrieve the first localized message. The order of locales is respected and the first found
   * localized message is returned. If the specified locale was not loaded yet, it tries to load
   * messages with required locale first and then it looks for a message. It always tries to load
   * also the message from the default locale if none from specified one was found.
   *
   * @param key                Message code reference from resource bundle.
   * @param prioritizedLocales Ordered list of locales. If the enumeration is {@code null} or empty,
   *                           default and root locale are used instead
   * @return {@link Optional#empty()} is returned if the code is null or the message was not found
   *     in any locale in any loaded resource bundle. Otherwise it returns the message in the first
   *     locale and the first first resource bundle file found.
   */
  public Optional getString(final String key, final List prioritizedLocales) {
    log.trace("getString(key = [{}], prioritizedLocales = [{}])", key, prioritizedLocales);
    if (key == null) {
      return Optional.empty();
    }
    List searchLocales = new ArrayList<>();
    if (prioritizedLocales != null) {
      searchLocales.addAll(prioritizedLocales);
    }
    searchLocales.add(defaultLocale);
    searchLocales.add(Locale.ROOT);
    lock.writeLock().lock();
    Optional result;
    try {
      result = searchLocales.stream()
          .filter(Objects::nonNull)
          .map(locale -> getString(key, locale))
          .flatMap(Optional::stream)
          .findFirst();
    } finally {
      lock.writeLock().unlock();
    }
    return result;
  }

  /**
   * Retrieve the first localized message. The order of locales is respected and the first found
   * localized message is returned. If the specified locale was not loaded yet, it tries to load
   * messages with required locale first and then it looks for a message. It always tries to load
   * also the message from the default locale if none from specified one was found.
   *
   * @param key     Message code reference from resource bundle.
   * @param locales Ordered list of locales. If the enumeration is {@code null} or empty, default
   *                and root locale are used instead
   * @return {@link Optional#empty()} is returned if the code is {@code null} or the message was not
   *     found in any locale in any loaded resource bundle. Otherwise it returns the message in the
   *     first locale and the first first resource bundle file found.
   */
  public Optional getString(final String key, final Enumeration locales) {
    log.trace("getString(key = [{}], locales = [{}])", key, locales);
    if (locales == null) {
      return getString(key, Collections.emptyList());
    } else {
      return getString(key, Collections.list(locales));
    }
  }

  /**
   * Retrieve a localized message from the message bundle. If the specified locale was not loaded
   * yet, it tries to load messages first and then it looks for a message.
   *
   * @param key    Message code reference from resource bundle.
   * @param locale Locale to use for message lookup.
   * @return {@link Optional#empty()} is returned if the code is {@code null} or the message was not
   *     found in default locale in any loaded resource bundle. Otherwise it returns the message in
   *     the default locale and the first first resource bundle file found.
   */
  public Optional getString(final String key, final Locale locale) {
    log.trace("getString(key = [{}], locale = [{}])", key, locale);
    if (locale == null || key == null) {
      return Optional.empty();
    }
    if (locales.get(locale) == null) {
      loadLocale(locale);
    }
    lock.readLock().lock();
    try {
      try {
        return Optional.of(locales.get(locale).getString(key));
      } catch (final MissingResourceException ex) {
        log.info("Cannot find key {} for locale: {}", key, locale, ex);
      }
    } finally {
      lock.readLock().unlock();
    }
    return Optional.empty();
  }

  /**
   * Add locale among other locales. This loads all resource files that were previously added with
   * specified locale and all resource files added later will be loaded with this new locale as
   * well.
   *
   * @param locale Locale you want to add.
   * @return This instance.
   */
  public PolyglotMultiResourceBundle loadLocale(final Locale locale) {
    log.trace("loadLocale(locale = [{}])", locale);
    lock.writeLock().lock();
    try {
      if (locales.containsKey(locale)) {
        return this;
      }
      SingleLocaleMultiResourceBundle singleLocaleMultiResourceBundle =
          new SingleLocaleMultiResourceBundle(locale);
      singleLocaleMultiResourceBundle.addMessageSources(resources.toArray(new String[0]));
      locales.put(locale, singleLocaleMultiResourceBundle);
    } finally {
      lock.writeLock().unlock();
    }
    return this;
  }

  /**
   * Get list of loaded locales.
   *
   * @return Set of locales that this instance has already loaded.
   */
  public Set getLocales() {
    log.trace("getLocales()");
    return locales.keySet();
  }

  @Override
  public MultiResourceBundle addMessageSources(final String... fileLocations) {
    log.trace("addMessageSources(fileLocations = [{}])", (Object) fileLocations);
    if (fileLocations == null) {
      return this;
    }
    lock.writeLock().lock();
    try {
      locales.forEach((k, v) -> v.addMessageSources(fileLocations));
      Arrays.stream(fileLocations).filter(Objects::nonNull).forEach(resources::add);
    } finally {
      lock.writeLock().unlock();
    }
    return this;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy