package restx.i18n; import com.google.common.base.Optional; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Ordering; import com.google.common.io.Resources; import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Template; import restx.common.MoreResources; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map.Entry; import java.util.MissingResourceException; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; import java.util.Set; /** * Date: 25/1/14 * Time: 14:37 */ public class DefaultMessages extends AbstractMessages implements Messages { private final String baseName; private final Charset charset; private final LoadingCache<Locale, Iterable<String>> cachedKeysByLocale = CacheBuilder.newBuilder().build(new CacheLoader<Locale, Iterable<String>>() { @Override public Iterable<String> load(Locale locale) throws Exception { Optional<ResourceBundle> bundle = getBundle(locale); return bundle.isPresent() ? Ordering.natural().sortedCopy(bundle.get().keySet()) : Collections.<String>emptySet(); } }); public DefaultMessages(String baseName) { this(baseName, StandardCharsets.UTF_8); } public DefaultMessages(String baseName, Charset charset) { this.baseName = baseName; this.charset = charset; } public String getBaseName() { return baseName; } public Charset getCharset() { return charset; } @Override public String getMessage(String key, MessageParams params, Locale locale) { return getTemplate(key, locale).execute(params.toMap()); } @Override public Iterable<String> keys(Locale locale) { return cachedKeysByLocale.getUnchecked(locale); } protected void invalidateCachedKeysFor(Locale locale) { cachedKeysByLocale.invalidate(locale); } @Override public Iterable<String> keys() { return keys(Locale.ROOT); } @Override public Iterable<Entry<String, String>> entries(Locale locale) { Optional<ResourceBundle> rootBundle = getBundle(Locale.ROOT); Optional<ResourceBundle> bundle = getBundle(locale); if (rootBundle.isPresent()) { Set<String> keys = rootBundle.get().keySet(); List<Entry<String, String>> entries = new ArrayList<>(keys.size()); for (String key : Ordering.natural().sortedCopy(keys)) { entries.add(new SimpleEntry<>(key, getTemplateString(key, locale, bundle))); } return entries; } else { return Collections.emptySet(); } } @Override public String getMessageTemplate(String key, Locale locale) { return getTemplateString(key, locale); } protected Template getTemplate(String key, Locale locale) { return Mustache.compiler().escapeHTML(false).compile(getTemplateString(key, locale)); } protected String getTemplateString(String key, Locale locale) { return getTemplateString(key, locale, getBundle(locale)); } private String getTemplateString(String key, Locale locale, Optional<ResourceBundle> bundle) { if (!bundle.isPresent()) { return key + "-[" + locale + "]"; } try { return bundle.get().getString(key); } catch (MissingResourceException e) { return key + "-[" + locale + "]"; } } protected Optional<ResourceBundle> getBundle(Locale locale) { ResourceBundle.Control control = new ResourceBundle.Control() { @Override public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException { String bundleName = toBundleName(baseName, locale); String resourceName = toResourceName(bundleName, "properties"); try { final URL resource = getResource(resourceName); try (Reader input = Resources.asCharSource(resource, charset).openStream()) { return newResourceBundle(resource, input); } } catch (IllegalArgumentException e) { return null; } } @Override public List<String> getFormats(String baseName) { return ResourceBundle.Control.FORMAT_PROPERTIES; } @Override public Locale getFallbackLocale(String baseName, Locale locale) { if (baseName == null || locale == null) { throw new NullPointerException(); } return null; } @Override public long getTimeToLive(String baseName, Locale locale) { return DefaultMessages.this.getTimeToLive(); } }; try { return Optional.of(ResourceBundle.getBundle(baseName, locale, control)); } catch (MissingResourceException e) { return Optional.absent(); } } protected PropertyResourceBundle newResourceBundle(URL resource, Reader input) throws IOException { return new PropertyResourceBundle(input); } protected long getTimeToLive() { return ResourceBundle.Control.TTL_NO_EXPIRATION_CONTROL; } protected URL getResource(String resourceName) { return MoreResources.getResource(resourceName, false); } @Override public String toString() { return "DefaultMessages{" + "baseName='" + baseName + '\'' + ", charset=" + charset + '}'; } }