/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library 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.1 of the License, or (at your option) * any later version. * * This library 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. */ package com.liferay.portal.language; import com.liferay.portal.kernel.language.LanguageUtil; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.util.CharPool; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.LocaleUtil; import com.liferay.portal.kernel.util.PropertiesUtil; import com.liferay.portal.kernel.util.ResourceBundleLoader; import com.liferay.portal.kernel.util.ResourceBundleLoaderUtil; import com.liferay.portal.kernel.util.ResourceBundleUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.tools.LangBuilder; import com.liferay.registry.Filter; import com.liferay.registry.Registry; import com.liferay.registry.RegistryUtil; import com.liferay.registry.ServiceReference; import com.liferay.registry.ServiceTracker; import com.liferay.registry.ServiceTrackerCustomizer; import java.io.InputStream; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * @author Shuyang Zhou * @author Kamesh Sampath */ public class LanguageResources { public static ResourceBundleLoader RESOURCE_BUNDLE_LOADER = new ResourceBundleLoader() { @Override public ResourceBundle loadResourceBundle(Locale locale) { return LanguageResources.getResourceBundle(locale); } /** * @deprecated As of 7.0.0, replaced by {@link #loadResourceBundle( * Locale)} */ @Deprecated public ResourceBundle loadResourceBundle(String languageId) { return loadResourceBundle( LocaleUtil.fromLanguageId(languageId)); } }; public static String fixValue(String value) { if (value.endsWith(LangBuilder.AUTOMATIC_COPY)) { value = value.substring( 0, value.length() - LangBuilder.AUTOMATIC_COPY.length()); } if (value.endsWith(LangBuilder.AUTOMATIC_TRANSLATION)) { value = value.substring( 0, value.length() - LangBuilder.AUTOMATIC_TRANSLATION.length()); } return value; } public static void fixValues( Map<String, String> languageMap, Properties properties) { for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = (String)entry.getKey(); String value = (String)entry.getValue(); value = fixValue(value); languageMap.put(key, value); } } public static String getMessage(Locale locale, String key) { if (locale == null) { return null; } Map<String, String> languageMap = _languageMaps.get(locale); if (languageMap == null) { languageMap = _loadLocale(locale); } String value = languageMap.get(key); if (value == null) { return getMessage(getSuperLocale(locale), key); } else { return value; } } public static ResourceBundle getResourceBundle(Locale locale) { return new LanguageResourcesBundle(locale); } public static Locale getSuperLocale(Locale locale) { Locale superLocale = _superLocales.get(locale); if (superLocale != null) { if (superLocale == _nullLocale) { return null; } return superLocale; } superLocale = _getSuperLocale(locale); if (superLocale == null) { _superLocales.put(locale, _nullLocale); } else { _superLocales.put(locale, superLocale); } return superLocale; } public void afterPropertiesSet() { Registry registry = RegistryUtil.getRegistry(); Filter languageResourceFilter = registry.getFilter( "(&(!(javax.portlet.name=*))(language.id=*)(objectClass=" + ResourceBundle.class.getName() + "))"); _serviceTracker = registry.trackServices( languageResourceFilter, new LanguageResourceServiceTrackerCustomizer()); _serviceTracker.open(); ResourceBundleLoaderUtil.setPortalResourceBundleLoader( RESOURCE_BUNDLE_LOADER); } public void setConfig(String config) { _configNames = StringUtil.split( config.replace(CharPool.PERIOD, CharPool.SLASH)); } private static Locale _getSuperLocale(Locale locale) { String variant = locale.getVariant(); if (variant.length() > 0) { return new Locale(locale.getLanguage(), locale.getCountry()); } String country = locale.getCountry(); if (country.length() > 0) { Locale priorityLocale = LanguageUtil.getLocale( locale.getLanguage()); if (priorityLocale != null) { variant = priorityLocale.getVariant(); } if ((priorityLocale != null) && !locale.equals(priorityLocale) && (variant.length() <= 0)) { return new Locale( priorityLocale.getLanguage(), priorityLocale.getCountry()); } return LocaleUtil.fromLanguageId(locale.getLanguage(), false, true); } String language = locale.getLanguage(); if (language.length() > 0) { return _blankLocale; } return null; } private static Map<String, String> _loadLocale(Locale locale) { Map<String, String> languageMap = null; if (_configNames.length > 0) { String localeName = locale.toString(); languageMap = new HashMap<>(); for (String name : _configNames) { StringBundler sb = new StringBundler(4); sb.append(name); if (localeName.length() > 0) { sb.append(StringPool.UNDERLINE); sb.append(localeName); } sb.append(".properties"); Properties properties = _loadProperties(sb.toString()); fixValues(languageMap, properties); } } else { languageMap = Collections.emptyMap(); } _languageMaps.put(locale, languageMap); return languageMap; } private static Properties _loadProperties(String name) { Properties properties = new Properties(); try { ClassLoader classLoader = LanguageResources.class.getClassLoader(); Enumeration<URL> enu = classLoader.getResources(name); if (_log.isDebugEnabled() && !enu.hasMoreElements()) { _log.debug("No resources found for " + name); } while (enu.hasMoreElements()) { URL url = enu.nextElement(); if (_log.isInfoEnabled()) { _log.info("Loading " + name + " from " + url); } try (InputStream inputStream = url.openStream()) { Properties inputStreamProperties = PropertiesUtil.load( inputStream, StringPool.UTF8); properties.putAll(inputStreamProperties); if (_log.isInfoEnabled()) { _log.info( "Loading " + url + " with " + inputStreamProperties.size() + " values"); } } } } catch (Exception e) { if (_log.isWarnEnabled()) { _log.warn(e, e); } } return properties; } private static Map<String, String> _putLanguageMap( Locale locale, Map<String, String> languageMap) { Map<String, String> oldLanguageMap = _languageMaps.get(locale); if (oldLanguageMap == null) { _loadLocale(locale); oldLanguageMap = _languageMaps.get(locale); } Map<String, String> newLanguageMap = new HashMap<>(); if (oldLanguageMap != null) { newLanguageMap.putAll(oldLanguageMap); } Map<String, String> diffLanguageMap = new HashMap<>(); for (Map.Entry<String, String> entry : languageMap.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); String oldValue = null; if (value == null) { oldValue = newLanguageMap.remove(key); } else { oldValue = newLanguageMap.put(key, value); } diffLanguageMap.put(entry.getKey(), oldValue); } _languageMaps.put(locale, newLanguageMap); return diffLanguageMap; } private static final Log _log = LogFactoryUtil.getLog( LanguageResources.class); private static final Locale _blankLocale = new Locale(StringPool.BLANK); private static String[] _configNames; private static final Map<Locale, Map<String, String>> _languageMaps = new ConcurrentHashMap<>(64); private static final Locale _nullLocale = new Locale(StringPool.BLANK); private static final Map<Locale, Locale> _superLocales = new ConcurrentHashMap<>(); private ServiceTracker<ResourceBundle, ResourceBundle> _serviceTracker; private static class LanguageResourcesBundle extends ResourceBundle { @Override public Enumeration<String> getKeys() { Set<String> keySet = _languageMap.keySet(); if (parent == null) { return Collections.enumeration(keySet); } return new ResourceBundleEnumeration(keySet, parent.getKeys()); } @Override public Locale getLocale() { return _locale; } @Override protected Object handleGetObject(String key) { return _languageMap.get(key); } @Override protected Set<String> handleKeySet() { return _languageMap.keySet(); } private LanguageResourcesBundle(Locale locale) { _locale = locale; Map<String, String> languageMap = _languageMaps.get(locale); if (languageMap == null) { languageMap = _loadLocale(locale); } _languageMap = languageMap; Locale superLocale = getSuperLocale(locale); if (superLocale != null) { setParent(new LanguageResourcesBundle(superLocale)); } } private final Map<String, String> _languageMap; private final Locale _locale; } private static class LanguageResourceServiceTrackerCustomizer implements ServiceTrackerCustomizer<ResourceBundle, ResourceBundle> { @Override public ResourceBundle addingService( ServiceReference<ResourceBundle> serviceReference) { Registry registry = RegistryUtil.getRegistry(); ResourceBundle resourceBundle = registry.getService( serviceReference); String languageId = GetterUtil.getString( serviceReference.getProperty("language.id")); Map<String, String> languageMap = new HashMap<>(); Locale locale = null; if (Validator.isNotNull(languageId)) { locale = LocaleUtil.fromLanguageId(languageId, true); } else { locale = new Locale(StringPool.BLANK); } Enumeration<String> keys = resourceBundle.getKeys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); String value = ResourceBundleUtil.getString( resourceBundle, key); languageMap.put(key, value); } Map<String, String> diffLanguageMap = _putLanguageMap( locale, languageMap); _diffLanguageMap.put(serviceReference, diffLanguageMap); return resourceBundle; } @Override public void modifiedService( ServiceReference<ResourceBundle> serviceReference, ResourceBundle resourceBundle) { } @Override public void removedService( ServiceReference<ResourceBundle> serviceReference, ResourceBundle resourceBundle) { Registry registry = RegistryUtil.getRegistry(); registry.ungetService(serviceReference); String languageId = GetterUtil.getString( serviceReference.getProperty("language.id")); Locale locale = null; if (Validator.isNotNull(languageId)) { locale = LocaleUtil.fromLanguageId(languageId, true); } else { locale = new Locale(StringPool.BLANK); } Map<String, String> languageMap = _diffLanguageMap.remove( serviceReference); _putLanguageMap(locale, languageMap); } private final Map<ServiceReference<?>, Map<String, String>> _diffLanguageMap = new HashMap<>(); } }