package net.contextfw.web.commons.i18n;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import net.contextfw.web.application.configuration.Configuration;
import static net.contextfw.web.commons.i18n.LocaleConf.*;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class LocaleServiceImpl implements LocaleService {
private Map<String, LocaleMessage> messages = new HashMap<String, LocaleMessage>();
private final Locale defaultLocale;
private Map<Locale, ResourceBundle> bundles;
private final Set<Locale> supportedLocales;
private final String baseName;
private final boolean strictValidation;
@Inject
public LocaleServiceImpl(Configuration conf) {
defaultLocale = conf.get(DEFAULT_LOCALE);
supportedLocales = conf.get(SUPPORTED_LOCALE);
baseName = conf.get(BASE_NAME);
strictValidation = conf.get(STRICT_VALIDATION);
reset();
}
private final ThreadLocal<Locale> current = new ThreadLocal<Locale>() {
@Override
protected Locale initialValue() {
return defaultLocale;
}
};
@Override
public void setCurrentLocale(Locale current) {
this.current.set(current);
}
@Override
public final void reset() {
bundles = new HashMap<Locale, ResourceBundle>();
messages = new HashMap<String, LocaleMessage>();
ResourceBundle.clearCache(Thread.currentThread().getContextClassLoader());
for (Locale locale : supportedLocales) {
bundles.put(locale, ResourceBundle.getBundle(
baseName,
locale,
Thread.currentThread().getContextClassLoader()));
}
}
@Override
public void process(Document document) {
reset();
List<Element> texts = getI18nElements(document);
addLocalizations(document.getRootElement(),
getLocalizations(getNames(texts)));
addConversions(texts);
}
private void addConversions(List<Element> texts) {
for (Element element : texts) {
if (LocaleConf.NS.equals(element.getNamespaceURI())) {
toCallTemplate(element);
} else {
@SuppressWarnings("unchecked")
Iterator<Attribute> attributes = element.attributeIterator();
int i = 0;
while (attributes.hasNext()) {
Attribute attr = attributes.next();
if (LocaleConf.NS.equals(attr.getNamespaceURI())) {
toCallTemplate(i, element, attr);
attributes.remove();
i++;
}
}
}
}
}
@SuppressWarnings("unchecked")
private void toCallTemplate(int i, Element element, Attribute attr) {
String name = attr.getName();
String value = attr.getValue();
Element attribute = element
.addElement("xsl:attribute")
.addAttribute("name", name);
attribute.addElement("xsl:call-template")
.addAttribute("name", LocaleConf.PREFIX + ":" + value);
attribute.detach();
element.elements().add(i, attribute);
}
private void toCallTemplate(Element element) {
String name = element.getName();
element.setName("xsl:call-template");
element.addAttribute("name", LocaleConf.PREFIX + ":" + name);
}
@SuppressWarnings("unchecked")
private List<Element> getI18nElements(Document document) {
return document.getRootElement()
.selectNodes("//*[namespace-uri()='" + LocaleConf.NS + "' or "
+ "@*[namespace-uri()='" + LocaleConf.NS + "']]");
}
@SuppressWarnings("unchecked")
private Set<String> getNames(List<Element> elements) {
Set<String> names = new HashSet<String>();
for (Element element : elements) {
if (LocaleConf.NS.equals(element.getNamespaceURI())) {
names.add(element.getName());
}
for (Attribute attribute : (List<Attribute>) element.attributes()) {
if (LocaleConf.NS.equals(attribute.getNamespaceURI())) {
names.add(attribute.getValue());
}
}
}
return names;
}
private void addLocalizations(Element root,
Map<String, Map<Locale, String>> localizations) {
for (Entry<String, Map<Locale, String>> entry : localizations.entrySet()) {
Element template = root.addElement("xsl:template");
template.addAttribute("name", LocaleConf.PREFIX + ":" + entry.getKey());
//template.addAttribute("match", LocalizationConf.PREFIX + ":" + entry.getKey());
Element choose = template.addElement("xsl:choose");
for (Entry<Locale, String> text : entry.getValue().entrySet()) {
choose.addElement("xsl:when")
.addAttribute("test", "$lang='" + text.getKey().getLanguage() + "'")
.addText(text.getValue());
}
}
}
private Map<String, Map<Locale, String>> getLocalizations(
Set<String> names) {
Map<String, Map<Locale, String>> localizations =
new HashMap<String, Map<Locale, String>>();
for (String name : names) {
localizations.put(name, getTexts(name));
}
return localizations;
}
private Map<Locale, String> getTexts(String name) {
Map<Locale, String> texts = new HashMap<Locale, String>();
for (Locale locale : supportedLocales) {
texts.put(locale, getText(name, locale));
}
return texts;
}
public LocaleMessage getMessage(String name) {
LocaleMessage msg = messages.get(name);
if (msg == null) {
msg = new LocaleMessageImpl(this, name);
messages.put(name, msg);
}
return msg;
}
public String getText(String name) {
return getText(name, current.get());
}
public String getText(String name, Locale locale) {
String text = null;
try {
text = bundles.get(locale).getString(name);
} catch (MissingResourceException e) {
if (defaultLocale != null) {
try {
text = bundles.get(defaultLocale).getString(name);
} catch (MissingResourceException e1) {
// Just ignore
}
}
}
if (text != null) {
return text;
} else if (strictValidation) {
throw new MissingResourceException("Localization missing: " + name,
baseName, name);
} else {
return "[missing(" + locale.getLanguage() + "):" + name + "]";
}
}
}