Java tutorial
/* * Weblounge: Web Content Management System * Copyright (c) 2003 - 2011 The Weblounge Team * http://entwinemedia.com/weblounge * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package ch.entwine.weblounge.common.impl.language; import ch.entwine.weblounge.common.content.Resource; import ch.entwine.weblounge.common.impl.util.xml.XPathHelper; import ch.entwine.weblounge.common.language.Language; import ch.entwine.weblounge.common.language.Localizable; import ch.entwine.weblounge.common.language.UnknownLanguageException; import ch.entwine.weblounge.common.site.Site; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathFactory; /** * <code>LanguageSupport</code> is a helper class the facilitates the handling * of languages in numerous ways. */ public final class LanguageUtils { /** Logging facility */ private static final Logger logger = LoggerFactory.getLogger(LanguageUtils.class); /** Regular expression to extract CH_de style Accept-Language headers */ private static final Pattern ACCEPT_LANGUAGE_HEADER = Pattern.compile("([\\w][\\w])_([\\w][\\w])"); /** Globally available languages */ private static final Map<String, Language> systemLanguages = new HashMap<String, Language>(); /** * This class is not meant to be instantiated. */ private LanguageUtils() { // Nothing to be done here } /** * Returns the language object that represents the given locale. * * @param locale * the locale * @return the language * @throws UnknownLanguageException * if there is no language for the given locale */ public static Language getLanguage(Locale locale) throws UnknownLanguageException { // Do we know this language already? Language language = systemLanguages.get(locale.getLanguage()); if (language != null) return language; // Makes sure we get the locale in the right format (might be hand crafted) Matcher matcher = ACCEPT_LANGUAGE_HEADER.matcher(locale.getLanguage()); if (matcher.matches()) { locale = new Locale(matcher.group(2), matcher.group(1)); } // Check the system locales for a match Locale systemLocale = null; try { for (Locale l : Locale.getAvailableLocales()) { if (l.getISO3Language().equals(locale.getISO3Language())) { systemLocale = l; break; } else if (l.getLanguage().equals(l.getLanguage())) { systemLocale = l; break; } } } catch (MissingResourceException e) { logger.debug("No 3 found for '{}': {}", locale, e.getMessage()); } // Is there a matching system locale? if (systemLocale != null) { language = new LanguageImpl(locale); systemLanguages.put(locale.getLanguage(), language); return language; } // Apparently not... throw new UnknownLanguageException(locale.getLanguage()); } /** * Returns the language object identified by the language identifier. * * @param languageCode * the language identifier * @return the language * @throws UnknownLanguageException * if there is no language for the given locale */ public static Language getLanguage(String languageCode) throws UnknownLanguageException { Language language = systemLanguages.get(languageCode); if (language != null) return language; for (Locale locale : Locale.getAvailableLocales()) { if (locale.getLanguage().equals(languageCode)) { language = new LanguageImpl(new Locale(languageCode, "", "")); systemLanguages.put(languageCode, language); break; } } if (language == null) throw new UnknownLanguageException(languageCode); return language; } /** * Reads the names of an object described in a weblounge configuration file in * various languages and applies them to the multilingual object * <code>o</code>. * <p> * The localized content is looked up in the tags specified by parameter * <code>tagName</code>, language identifier are expected in the * <code>language</code> attribute of these tags. * <p> * If the description is found in the default language, then * {@link LocalizableContent#setDefaultLanguage(Language)} is called. * <p> * The required format of the input node is as follows: * * <pre> * <role> * <id>editor</id> * <name language="de">Editor</name> * <name language="fr">Editeur</name> * <name language="it">Editore</name> * </role> * </pre> * <p> * The method throws a <code>ConfigurationException</code> if no * name is provided the site default language. * * @param configuration * the XML configuration node containing the descriptions * @param tagName * the tag name containing the localized content * @param defaultLanguage * the default language * @param o * the localizable object * @param escape * <code>true</code> to filter out " and ' * @return the localized content */ public static LocalizableContent<String> addDescriptions(Node configuration, String tagName, Language defaultLanguage, LocalizableContent<String> o, boolean escape) { XPath xpath = XPathFactory.newInstance().newXPath(); return addDescriptions(configuration, tagName, defaultLanguage, o, escape, xpath); } /** * Reads the names of an object described in a weblounge configuration file in * various languages and applies them to the multilingual object * <code>o</code>. * <p> * The localized content is looked up in the tags specified by parameter * <code>tagName</code>, language identifier are expected in the * <code>language</code> attribute of these tags. * <p> * If the description is found in the default language, then * {@link LocalizableContent#setDefaultLanguage(Language)} is called. * <p> * The required format of the input node is as follows: * * <pre> * <role> * <id>editor</id> * <name language="de">Editor</name> * <name language="fr">Editeur</name> * <name language="it">Editore</name> * </role> * </pre> * <p> * The method throws a <code>ConfigurationException</code> if no * name is provided the site default language. * * @param configuration * the XML configuration node containing the descriptions * @param tagName * the tag name containing the localized content * @param defaultLanguage * the default language * @param o * the localizable object * @param escape * <code>true</code> to filter out " and ' * @param xpath * the xpath processor * @return the localized content */ public static LocalizableContent<String> addDescriptions(Node configuration, String tagName, Language defaultLanguage, LocalizableContent<String> o, boolean escape, XPath xpath) { if (configuration == null) throw new IllegalArgumentException("Cannot extract from empty configuration"); if (tagName == null) throw new IllegalArgumentException("Tagname must be specified"); if (o == null) o = new LocalizableContent<String>(); NodeList nodes = XPathHelper.selectList(configuration, tagName, xpath); for (int i = 0; i < nodes.getLength(); i++) { Node name = nodes.item(i); String description = XPathHelper.valueOf(name, "text()", xpath); String lAttrib = XPathHelper.valueOf(name, "@language", xpath); Language language = LanguageUtils.getLanguage(lAttrib); if (language == null) { logger.debug("Found name in unsupported language {}", lAttrib); continue; } // Escape? if (escape) { description = description.replaceAll("\"", ""); description = description.replaceAll("'", ""); } // Add the entry logger.debug("Found description {}", description); o.put(description, language); if (language.equals(defaultLanguage)) { o.setDefaultLanguage(defaultLanguage); } } return o; } /** * Returns the localized variant for the given language. * <p> * For example, if <code>s</code> is <tt>file.jsp</tt> and * <code>language</code> is <tt>German</tt>, then this method returns * <tt>file_de.jsp</tt>. * * @param s * the file name * @param language * the language variant to obtain * @return the localized variant of the text */ public static String getLanguageVariant(String s, Language language) { int suffixPosition = s.lastIndexOf("."); String suffix = ""; if (suffixPosition > -1) { suffix = s.substring(suffixPosition); s = s.substring(0, suffixPosition); } s += "_" + language.getIdentifier() + suffix; return s; } /** * Returns all language variants of the filename <code>s</code>, including * <code>s</code> itself (last in line). * * @param s * the filename * @param languages * the languages used to build the variants */ public static String[] getLanguageVariants(String s, Language... languages) { String[] result = new String[languages.length + 1]; result[languages.length] = s; for (int i = 0; i < languages.length; i++) { Language language = languages[i]; result[i] = getLanguageVariant(s, language); } return result; } /** * Returns the original version string of text <code>s</code>. * <p> * For example, if <tt>s</tt> equals <tt>file_de.jsp</tt> then this method * returns <tt>file.jsp</tt>. * * @param s * the language filename * @param languages * the languages * @return the original filename */ public static String getBaseVersion(String s) { Language l = extractLanguage(s); if (l == null) { return s; } int languagePos = s.indexOf("_" + l.getIdentifier()); String original = s.substring(0, languagePos); if (s.length() > languagePos + 3) { String suffix = s.substring(languagePos + 3); original += suffix; } return original; } /** * Returns the language of this file. For example, if <tt>s</tt> is * <tt>file_de.jsp</tt> then this method returns the German language object. * <p> * <b>Note:</b> This method returns <code>null</code> if the string contains * an unknown language identifier or no language identifier at all. * * @param s * the filename * @return the language object or <code>null</code> */ public static Language extractLanguage(String s) { int languagePosition = s.lastIndexOf("_"); if ((languagePosition < 0) || (languagePosition + 1 > s.length())) return null; Language l = null; String languageId = s.substring(languagePosition + 1, languagePosition + 3); l = getLanguage(languageId); return l; } /** * Returns the language out of <code>choices</code> that matches the client's * requirements as indicated through the <code>Accept-Language</code> header. * If no match is possible, <code>null</code> is returned. * * @param choices * the available locales * @param request * the http request */ public static Language getPreferredLanguage(Set<Language> choices, HttpServletRequest request) { if (request.getHeader("Accept-Language") != null) { Enumeration<?> locales = request.getLocales(); while (locales.hasMoreElements()) { try { Language l = getLanguage((Locale) locales.nextElement()); if (choices.contains(l)) return l; } catch (UnknownLanguageException e) { // never mind, some clients will send stuff like "*" as the locale } } } return null; } /** * Returns the preferred one out of of those languages that are requested by * the client through the <code>Accept-Language</code> header and are * supported by both the localizable and the site. * <p> * The preferred one is defined by the following priorities: * <ul> * <li>Requested by the client</li> * <li>The localizable's original language</li> * <li>The site default language</li> * <li>The first language of what is supported by both the localizable and the * site</li> * </ul> * * @param localizable * the localizable * @param request * the http request * @param site * the site */ public static Language getPreferredLanguage(Localizable localizable, HttpServletRequest request, Site site) { // Path String[] pathElements = StringUtils.split(request.getRequestURI(), "/"); for (String element : pathElements) { for (Language l : localizable.languages()) { if (l.getIdentifier().equals(element)) { return l; } } } // Accept-Language header if (request.getHeader("Accept-Language") != null) { Enumeration<?> locales = request.getLocales(); while (locales.hasMoreElements()) { try { Language l = getLanguage((Locale) locales.nextElement()); if (localizable != null && !localizable.supportsLanguage(l)) continue; if (!site.supportsLanguage(l)) continue; return l; } catch (UnknownLanguageException e) { // never mind, some clients will send stuff like "*" as the locale } } } // The localizable's original language if (localizable != null && localizable instanceof Resource) { Resource<?> r = (Resource<?>) localizable; if (r.getOriginalContent() != null) { if (site.supportsLanguage(r.getOriginalContent().getLanguage())) return r.getOriginalContent().getLanguage(); } } // Site default language if (localizable != null && localizable.supportsLanguage(site.getDefaultLanguage())) { return site.getDefaultLanguage(); } // Any match if (localizable != null) { for (Language l : site.getLanguages()) { if (localizable.supportsLanguage(l)) { return l; } } } return null; } /** * Returns the preferred one out of of those languages that are requested by * the client through the <code>Accept-Language</code> header and are * supported by both the resource in that there is resource content in that * language and the site. * <p> * The preferred one is defined by the following priorities: * <ul> * <li>Requested by the client</li> * <li>The resource's original language</li> * <li>The site default language</li> * <li>The first language of what is supported by both the resource and the * site</li> * </ul> * * @param resource * the resource * @param request * the http request * @param site * the site */ public static Language getPreferredContentLanguage(Resource<?> resource, HttpServletRequest request, Site site) { if (resource == null) throw new IllegalArgumentException("Resource must not be null"); // Path String[] pathElements = StringUtils.split(request.getRequestURI(), "/"); for (String element : pathElements) { for (Language l : resource.contentLanguages()) { if (l.getIdentifier().equals(element)) { return l; } } } // Accept-Language header if (request.getHeader("Accept-Language") != null) { Enumeration<?> locales = request.getLocales(); while (locales.hasMoreElements()) { try { Language l = getLanguage((Locale) locales.nextElement()); if (l == null) continue; if (!resource.supportsContentLanguage(l)) continue; if (!site.supportsLanguage(l)) continue; return l; } catch (UnknownLanguageException e) { // never mind, some clients will send stuff like "*" as the locale } } } // Original content if (resource.getOriginalContent() != null) { if (site.supportsLanguage(resource.getOriginalContent().getLanguage())) return resource.getOriginalContent().getLanguage(); } // Site default language if (resource.supportsContentLanguage(site.getDefaultLanguage())) { return site.getDefaultLanguage(); } // Any match for (Language l : site.getLanguages()) { if (resource.supportsContentLanguage(l)) { return l; } } return null; } /** * Returns the preferred one out of of those languages that are requested by * the client through the <code>Accept-Language</code> header and are * supported by the site. If there is no match, the site's default language is * returned. * <p> * The preferred one is defined by the following priorities: * <ul> * <li>Requested by the client</li> * <li>The site default language</li> * </ul> * * @param request * the http request * @param site * the site */ public static Language getPreferredLanguage(HttpServletRequest request, Site site) { // Accept-Language header if (request.getHeader("Accept-Language") != null) { Enumeration<?> locales = request.getLocales(); while (locales.hasMoreElements()) { try { Language l = getLanguage((Locale) locales.nextElement()); if (site.supportsLanguage(l)) return l; } catch (UnknownLanguageException e) { // never mind, some clients will send stuff like "*" as the locale } } } return site.getDefaultLanguage(); } /** * Returns the first language out of <code>choices</code> that is supported by * the <code>localizable</code>. If there is no such language, * <code>null</code> is returned. * * @param localizable * the localizable object * @param languages * the prioritized list of possible languages * @return the first matching language or <code>null</code> * @throws IllegalArgumentException * if <code>localizable</code> is <code>null</code> */ public static Language getPreferredLanguage(Localizable localizable, Language... languages) { if (localizable == null) throw new IllegalArgumentException("Localizable cannot be null"); if (languages == null) return null; for (Language l : languages) { if (localizable.supportsLanguage(l)) return l; } return null; } /** * Returns the first language out of <code>choices</code> that is supported by * the <code>localizable</code>. If there is no such language, * <code>null</code> is returned. * * @param localizable * the localizable object * @param languages * the prioritized list of possible languages * @return the first matching language or <code>null</code> * @throws IllegalArgumentException * if <code>localizable</code> is <code>null</code> */ public static Language getPreferredContentLanguage(Resource<?> resource, Language... languages) { if (resource == null) throw new IllegalArgumentException("Resource cannot be null"); if (languages == null) return null; for (Language l : languages) { if (resource.supportsContentLanguage(l)) return l; } return null; } }