/*
* #%L
* Nazgul Project: nazgul-core-resource-impl-resourcebundle
* %%
* Copyright (C) 2010 - 2017 jGuru Europe AB
* %%
* Licensed under the jGuru Europe AB license (the "License"), based
* on Apache License, Version 2.0; you may not use this file except
* in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.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.
* #L%
*
*/
package se.jguru.nazgul.core.resource.impl.resourcebundle;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.jguru.nazgul.core.resource.api.LocalResources;
import se.jguru.nazgul.core.resource.impl.resourcebundle.parser.CompoundParser;
import se.jguru.nazgul.core.resource.impl.resourcebundle.parser.Utf8ResourceBundle;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
/**
* ResourceBundle-delegating implementation of the LocalResources
* specification using a backing ResourceBundle to realize the protocol.
*
* @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB
*/
public class ResourceBundleLocalResources implements LocalResources {
// Our Log
private static final Logger log = LoggerFactory.getLogger(ResourceBundleLocalResources.class);
// Internal state
private final Object[] lock = new Object[0];
private String resourceBundleBaseName;
private Locale defaultLocale;
private Map<Locale, ResourceBundle> resourceBundleCache = new HashMap<Locale, ResourceBundle>();
/**
* Creates a new ResourceBundleLocalResources delegating typed calls to the provided ResourceBundle.
*
* @param resourceBundle A non-null ResourceBundle.
* @param resourceBundleBaseName The non-null baseName of the provided ResourceBundle.
* @param defaultLocale The default/fallback locale for all lookups.
*/
public ResourceBundleLocalResources(final ResourceBundle resourceBundle,
final String resourceBundleBaseName,
final Locale defaultLocale) {
// Check sanity
Validate.notNull(resourceBundle, "Cannot handle null resourceBundle argument.");
Validate.notNull(defaultLocale, "Cannot handle null defaultLocale argument.");
Validate.notEmpty(resourceBundleBaseName, "Cannot handle null resourceBundleBaseName argument.");
// Assign internal state
this.resourceBundleBaseName = resourceBundleBaseName;
this.defaultLocale = defaultLocale;
final Locale internalLocale = resourceBundle.getLocale();
if (internalLocale == null || !internalLocale.equals(defaultLocale)) {
if (!setDefaultLocale(defaultLocale)) {
throw new IllegalArgumentException("Improper resourceBundleBaseName [" + resourceBundleBaseName
+ "] given (not found).");
}
} else {
resourceBundleCache.put(this.defaultLocale, resourceBundle);
}
}
/**
* Creates a new ResourceBundleLocalResources delegating typed calls to the provided ResourceBundle.
*
* @param resourceBundleBaseName The non-null baseName to generate a ResourceBundle using
* <code>ResourceBundle.getBundle(resourceBundleBaseName, defaultLocale)</code>
* @param defaultLocale The default/fallback locale for all lookups. Also used to create the
* internal ResourceBundle using the call
* <code>ResourceBundle.getBundle(resourceBundleBaseName, defaultLocale)</code>.
*/
public ResourceBundleLocalResources(final String resourceBundleBaseName,
final Locale defaultLocale) {
this(Utf8ResourceBundle.getBundle(resourceBundleBaseName, defaultLocale),
resourceBundleBaseName,
defaultLocale);
}
/**
* Creates a new ResourceBundleLocalResources delegating typed calls to the provided ResourceBundle.
*
* @param resourceBundleBaseName The non-null baseName to generate a ResourceBundle using
* <code>ResourceBundle.getBundle(resourceBundleBaseName, Locale.getDefault())</code>
*/
public ResourceBundleLocalResources(final String resourceBundleBaseName) {
this(resourceBundleBaseName, Locale.getDefault());
}
/**
* Retrieves a localized String from the underlying local resources, using the currently active Locale.
*
* @param key The key within the underlying ResourceBundle structure for the value to acquire.
* @param defaultValue The fallback/default value to return if no value was present within the underlying
* ResourceBundle structure.
* @return The localized T instance acquired from the underlying ResourceBundle structure at key
* <code>key</code>, or the defaultValue should no value exist for the given key within the underlying
* ResourceBundle structure.
*/
@Override
public final String getLocalized(final String key, final String defaultValue) {
return getLocalized(key, defaultValue, defaultLocale);
}
/**
* Retrieves a localized T instance from the underlying local resources.
*
* @param key The key within the underlying ResourceBundle structure for the value to acquire.
* @param defaultValue The fallback/default value to return if no value was present within the underlying
* ResourceBundle structure.
* @param locale The locale for which the resource should be retrieved.
* @return The localized T instance acquired from the underlying ResourceBundle structure at key
* <code>key</code>, or the defaultValue should no value exist for the given key within the underlying
* ResourceBundle structure.
*/
@Override
public final String getLocalized(final String key, final String defaultValue, final Locale locale) {
return getLocalized(key, defaultValue, locale, null);
}
/**
* Retrieves a localized T instance from the underlying local resources,
* using the currently active Locale.
*
* @param key The key within the underlying ResourceBundle structure for the value to acquire.
* @param defaultValue The fallback/default value to return if no value was present within the underlying
* ResourceBundle structure.
* @param keyValueTokens Either <code>null</code>, or a list holding strings on the form key=value where
* both key and value must be non-empty. The keyValueTokens will be substituted before
* the result is returned.
* @return The localized T instance acquired from the underlying ResourceBundle structure at key
* <code>key</code>, or the defaultValue should no value exist for the given key within the underlying
* ResourceBundle structure.
*/
@Override
public final String getLocalized(final String key, final String defaultValue, final String... keyValueTokens) {
return getLocalized(key, defaultValue, defaultLocale, keyValueTokens);
}
/**
* Retrieves a localized T instance from the underlying local resources, and using the provided arguments
* as token placeholders.
*
* @param key The key within the underlying ResourceBundle structure for the value to acquire.
* @param defaultValue The fallback/default value to return if no value was present within the underlying
* ResourceBundle structure.
* @param locale The locale for which the resource should be retrieved.
* @param keyValueTokens Either <code>null</code>, or a list holding strings on the form key=value where
* both key and value must be non-empty.
* @return The localized T instance acquired from the underlying ResourceBundle structure at key
* <code>key</code>, or the defaultValue should no value exist for the given key within the underlying
* ResourceBundle structure.
*/
@Override
public String getLocalized(final String key,
final String defaultValue,
final Locale locale,
final String... keyValueTokens) {
// Create the parser
final CompoundParser parser = CompoundParser.create(keyValueTokens);
// Should we acquire the localized ResourceBundle?
if (!resourceBundleCache.keySet().contains(locale)) {
cacheResourceBundleForLocale(locale);
}
// Acquire and return the result.
String toReturn = defaultValue;
try {
toReturn = resourceBundleCache.get(locale).getString(key);
} catch (MissingResourceException e) {
// Just ignore this.
}
// All done.
return toReturn == null ? null : parser.substituteTokens(toReturn);
}
/**
* (Re-)assigns the locale used should no Locale be submitted with
* the lookup call. The defaultLocale is kept as long as the LocalResources
* instance lives.
*
* @param defaultLocale The new default locale, used if no Locale is submitted with calls.
* @return <code>true</code> if the defaultLocale was successfully set, and false otherwise.
*/
@Override
public final boolean setDefaultLocale(final Locale defaultLocale) {
// Check sanity
Validate.notNull(defaultLocale, "Cannot handle null defaultLocale argument.");
// Is the defaultLocale already present within the cache?
if (this.resourceBundleCache.keySet().contains(defaultLocale)) {
this.defaultLocale = defaultLocale;
return true;
}
// Nopes. Let's acquire the ResourceBundle for the provided defaultLocale.
final boolean success = cacheResourceBundleForLocale(defaultLocale);
if (success) {
this.defaultLocale = defaultLocale;
}
// All done.
return success;
}
//
// Private helpers
//
private boolean cacheResourceBundleForLocale(final Locale locale) {
synchronized (lock) {
// We need to re-set the ResourceBundle, after manipulating the Default Locale briefly.
final Locale previousLocale = Locale.getDefault();
try {
// The ResourceBundle loader always uses the default
// locale to load the ResourceBundle internally ...
Locale.setDefault(locale);
try {
final ResourceBundle bundle = Utf8ResourceBundle.getBundle(resourceBundleBaseName, locale);
resourceBundleCache.put(locale, bundle);
} catch (MissingResourceException e) {
// Ignore this error... and note that the locale is not set.
log.warn("Not caching nonexistent ResourceBundle for missing base name ["
+ resourceBundleBaseName + "]", e);
return false;
}
} finally {
Locale.setDefault(previousLocale);
}
}
// All done.
return true;
}
}