sirius.kernel.nls.Babelfish Maven / Gradle / Ivy
Show all versions of sirius-kernel Show documentation
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.kernel.nls;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import sirius.kernel.Classpath;
import sirius.kernel.Sirius;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.Strings;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Internal translation engine used by {@link NLS}.
*
* Loads all .properties files in the given {@link Classpath} which match the pattern
* {@code name_lang.properties}. Where name is any string (must not contain "_") and lang
* is a two letter language code.
*
* @see NLS
*/
public class Babelfish {
/**
* Logs WARN messages if translations are inconsistent (override each other).
*/
protected static final Log LOG = Log.get("babelfish");
/**
* Since the translationMap is not thread-safe, modifying the map requires to acquire this lock
*/
private final Lock translationsWriteLock = new ReentrantLock();
/**
* Contains all known translations
*/
private Map translationMap = Maps.newTreeMap();
/**
* Describes the pattern for .properties files of interest.
*/
private static final Pattern PROPERTIES_FILE = Pattern.compile("(.*?)_([a-z]{2}).properties");
/**
* Contains a list of all loaded resource bundles. Once the framework is booted, this is passed to
* the TimerService.addWatchedResource to reload changes from the development environment.
*/
private List loadedResourceBundles = Lists.newArrayList();
private static final ResourceBundle.Control CONTROL = new NonCachingUTF8Control();
/**
* Enumerates all translations which key starts with the given prefix
*
* @param key the prefix with which the key must start
* @return a stream of all translations matching the given filter
*/
public Stream getEntriesStartingWith(@Nonnull String key) {
return translationMap.values().stream().filter(e -> e.getKey().startsWith(key));
}
/**
* Retrieves the Translation for the given property.
*
* If no matching entry is found, the given fallback is used. If still no match was found, either
* a new one is create (if create is true) or false otherwise.
*
* @param property the property key for which a translation is required
* @param fallback a fallback key, if no translation is found. May be null.
* @param create determines if a new Translation is created if non-existent
* @return a Translation for the given property or fallback.
* Returns null if no translation was found and create is false.
*/
@SuppressWarnings("squid:S2583")
@Explain("Double check for @Nonnull property as it is not enforced by the compiler")
protected Translation get(@Nonnull String property, @Nullable String fallback, boolean create) {
if (property == null) {
throw new IllegalArgumentException("property");
}
Translation entry = translationMap.get(property);
if (entry != null) {
return entry;
}
return getWithFallback(property, fallback, create);
}
private Translation getWithFallback(@Nonnull String property, @Nullable String fallback, boolean create) {
Translation entry = fallback != null ? translationMap.get(fallback) : null;
if (entry == null && create) {
entry = autocreateMissingEntry(property);
}
return entry;
}
private Translation autocreateMissingEntry(@Nonnull String property) {
LOG.INFO("Non-existent translation: %s", property);
Translation entry = new Translation(property);
entry.setAutocreated(true);
inLock(newTranslations -> {
newTranslations.put(entry.getKey(), entry);
});
return entry;
}
/**
* Returns a list of all loaded resource bundles.
*
* @return a list of all loaded resource bundles (.properties files)
*/
public List getLoadedResourceBundles() {
return Collections.unmodifiableList(loadedResourceBundles);
}
private void inLock(Consumer