| /******************************************************************************* |
| * Copyright (c) 2012, 2018 Dirk Fauth and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Dirk Fauth <dirk.fauth@gmail.com> - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.e4.core.internal.services; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.MissingResourceException; |
| import java.util.PropertyResourceBundle; |
| import java.util.ResourceBundle; |
| import java.util.ResourceBundle.Control; |
| import org.eclipse.e4.core.services.translation.ResourceBundleProvider; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.service.log.LogService; |
| import org.osgi.service.log.Logger; |
| import org.osgi.service.log.LoggerFactory; |
| import org.osgi.util.tracker.ServiceTracker; |
| import org.osgi.util.tracker.ServiceTrackerCustomizer; |
| |
| /** |
| * Helper class for retrieving {@link ResourceBundle}s out of OSGi {@link Bundle}s. |
| */ |
| public class ResourceBundleHelper { |
| |
| /** |
| * The schema identifier used for Eclipse platform references |
| */ |
| private static final String PLATFORM_SCHEMA = "platform"; //$NON-NLS-1$ |
| /** |
| * The schema identifier used for Eclipse bundle class references |
| */ |
| private static final String BUNDLECLASS_SCHEMA = "bundleclass"; //$NON-NLS-1$ |
| /** |
| * Identifier part of the Eclipse platform schema to point to a plugin |
| */ |
| private static final String PLUGIN_SEGMENT = "/plugin/"; //$NON-NLS-1$ |
| /** |
| * Identifier part of the Eclipse platform schema to point to a fragment |
| */ |
| private static final String FRAGMENT_SEGMENT = "/fragment/"; //$NON-NLS-1$ |
| /** |
| * The separator character for paths in the platform schema |
| */ |
| private static final String PATH_SEPARATOR = "/"; //$NON-NLS-1$ |
| |
| private static final ServiceTracker<LogService, LogService> logTracker = openLogTracker(); |
| |
| private static ServiceTracker<LogService, LogService> openLogTracker() { |
| try { |
| ServiceTracker<LogService, LogService> st = new ServiceTracker<>( |
| FrameworkUtil.getBundle(ResourceBundleHelper.class).getBundleContext(), LogService.class, null); |
| st.open(); |
| return st; |
| } catch (Throwable t) { |
| return null; |
| } |
| } |
| |
| private static ServiceTracker<LoggerFactory, Logger> loggerTracker = openLoggerTracker(); |
| |
| private static ServiceTracker<LoggerFactory, Logger> openLoggerTracker() { |
| try { |
| BundleContext context = FrameworkUtil.getBundle(ResourceBundleHelper.class).getBundleContext(); |
| ServiceTracker<LoggerFactory, Logger> tracker = new ServiceTracker<>( |
| context, LoggerFactory.class, |
| new ServiceTrackerCustomizer<LoggerFactory, Logger>() { |
| |
| @Override |
| public Logger addingService(ServiceReference<LoggerFactory> reference) { |
| LoggerFactory factory = context.getService(reference); |
| if (factory != null) { |
| return factory.getLogger(ResourceBundleHelper.class); |
| } |
| return null; |
| } |
| |
| @Override |
| public void modifiedService(ServiceReference<LoggerFactory> reference, Logger service) { |
| } |
| |
| @Override |
| public void removedService(ServiceReference<LoggerFactory> reference, Logger service) { |
| context.ungetService(reference); |
| } |
| }); |
| tracker.open(); |
| return tracker; |
| } catch (Throwable t) { |
| return null; |
| } |
| } |
| |
| /** |
| * Parses the specified contribution URI and loads the {@link ResourceBundle} for the specified |
| * {@link Locale} out of an OSGi {@link Bundle}. |
| * <p> |
| * Following URIs are supported: |
| * </p> |
| * <ul> |
| * <li>platform:/[plugin|fragment]/[Bundle-SymbolicName]<br> |
| * Load the OSGi resource bundle out of the bundle/fragment named [Bundle-SymbolicName]</li> |
| * <li>platform:/[plugin|fragment]/[Bundle-SymbolicName]/[Path]/[Basename]<br> |
| * Load the resource bundle specified by [Path] and [Basename] out of the bundle/fragment named |
| * [Bundle-SymbolicName].</li> |
| * <li>bundleclass://[plugin|fragment]/[Full-Qualified-Classname]<br> |
| * Instantiate the class specified by [Full-Qualified-Classname] out of the bundle/fragment |
| * named [Bundle-SymbolicName]. Note that the class needs to be a subtype of |
| * {@link ResourceBundle}.</li> |
| * </ul> |
| * |
| * @param contributionURI |
| * The URI that points to a {@link ResourceBundle} |
| * @param locale |
| * The {@link Locale} to use for loading the {@link ResourceBundle} |
| * @param provider |
| * The service for retrieving a {@link ResourceBundle} for a given {@link Locale} out |
| * of the given {@link Bundle} which is specified by URI. |
| * @return |
| */ |
| public static ResourceBundle getResourceBundleForUri(String contributionURI, Locale locale, |
| ResourceBundleProvider provider) { |
| if (contributionURI == null) |
| return null; |
| |
| Logger logger = loggerTracker.getService(); |
| |
| URI uri; |
| try { |
| uri = new URI(contributionURI); |
| } catch (URISyntaxException e) { |
| if (logger != null) { |
| logger.error("Invalid contribution URI: {}", contributionURI); //$NON-NLS-1$ |
| } |
| return null; |
| } |
| |
| String bundleName = null; |
| Bundle bundle = null; |
| String resourcePath = null; |
| String classPath = null; |
| |
| // the uri follows the platform schema, so we search for .properties files in the bundle |
| if (PLATFORM_SCHEMA.equals(uri.getScheme())) { |
| bundleName = uri.getPath(); |
| if (bundleName.startsWith(PLUGIN_SEGMENT)) |
| bundleName = bundleName.substring(PLUGIN_SEGMENT.length()); |
| else if (bundleName.startsWith(FRAGMENT_SEGMENT)) |
| bundleName = bundleName.substring(FRAGMENT_SEGMENT.length()); |
| |
| resourcePath = ""; //$NON-NLS-1$ |
| if (bundleName.contains(PATH_SEPARATOR)) { |
| resourcePath = bundleName.substring(bundleName.indexOf(PATH_SEPARATOR) + 1); |
| bundleName = bundleName.substring(0, bundleName.indexOf(PATH_SEPARATOR)); |
| } |
| } else if (BUNDLECLASS_SCHEMA.equals(uri.getScheme())) { |
| if (uri.getAuthority() == null) { |
| if (logger != null) { |
| logger.error("Failed to get bundle for: {}", contributionURI); //$NON-NLS-1$ |
| } |
| } |
| bundleName = uri.getAuthority(); |
| // remove the leading / |
| if (uri.getPath() != null && !uri.getPath().isEmpty()) { |
| classPath = uri.getPath().substring(1); |
| } else { |
| if (logger != null) { |
| logger.error("Called with invalid contribution URI: {}", contributionURI); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| ResourceBundle result = null; |
| |
| if (bundleName != null) { |
| bundle = getBundleForName(bundleName); |
| |
| if (bundle != null) { |
| if (resourcePath == null && classPath != null) { |
| // the URI points to a class within the bundle classpath |
| // therefore we are trying to instantiate the class |
| try { |
| Class<?> resourceBundleClass = bundle.loadClass(classPath); |
| result = getEquinoxResourceBundle(classPath, locale, |
| resourceBundleClass.getClassLoader()); |
| } catch (Exception e) { |
| if (logger != null) { |
| logger.error("Failed to load specified ResourceBundle: {}", contributionURI, e); //$NON-NLS-1$ |
| } |
| } |
| } else if (resourcePath != null && !resourcePath.isEmpty()) { |
| // the specified URI points to a resource |
| // therefore we try to load the .properties files into a ResourceBundle |
| result = getEquinoxResourceBundle(resourcePath.replace('.', '/'), locale, |
| bundle); |
| } else { |
| // There is no class and no special resource specified within the URI |
| // therefore we load the OSresource bundle out of the specified Bundle |
| // for the current Locale. Typically this will be the OSGi ResourceBundle |
| result = provider.getResourceBundle(bundle, locale.toString()); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * This method searches for the {@link ResourceBundle} in a modified way by inspecting the |
| * configuration option <code>equinox.root.locale</code>. |
| * <p> |
| * If the value for this system property is set to an empty String the default search order for |
| * ResourceBundles is used: |
| * </p> |
| * <ul> |
| * <li>bn + Ls + "_" + Cs + "_" + Vs</li> |
| * <li>bn + Ls + "_" + Cs</li> |
| * <li>bn + Ls</li> |
| * <li>bn + Ld + "_" + Cd + "_" + Vd</li> |
| * <li>bn + Ld + "_" + Cd</li> |
| * <li>bn + Ld</li> |
| * <li>bn</li> |
| * </ul> |
| * <p> |
| * Where bn is this bundle's localization basename, Ls, Cs and Vs are the specified locale |
| * (language, country, variant) and Ld, Cd and Vd are the default locale (language, country, |
| * variant). |
| * </p> |
| * <p> |
| * If Ls equals the value of <code>equinox.root.locale</code> then the following search order is |
| * used: |
| * </p> |
| * <ul> |
| * <li>bn + Ls + "_" + Cs + "_" + Vs</li> |
| * <li>bn + Ls + "_" + Cs</li> |
| * <li>bn + Ls</li> |
| * <li>bn</li> |
| * <li>bn + Ld + "_" + Cd + "_" + Vd</li> |
| * <li>bn + Ld + "_" + Cd</li> |
| * <li>bn + Ld</li> |
| * <li>bn</li> |
| * </ul> |
| * <p> |
| * If <code>equinox.root.locale=en</code> and en_XX or en is asked for then this allows the root |
| * file to be used instead of falling back to the default locale. |
| * </p> |
| * |
| * @param baseName |
| * the base name of the resource bundle, a fully qualified class name |
| * @param locale |
| * the locale for which a resource bundle is desired |
| * @param loader |
| * the class loader from which to load the resource bundle |
| * @return a resource bundle for the given base name and locale |
| * |
| * @see ResourceBundle#getBundle(String, Locale, ClassLoader) |
| */ |
| public static ResourceBundle getEquinoxResourceBundle(String baseName, Locale locale, |
| ClassLoader loader) { |
| ResourceBundle resourceBundle = null; |
| |
| String equinoxLocale = getEquinoxRootLocale(); |
| // if the equinox.root.locale is not empty and the specified locale equals the |
| // equinox.root.locale |
| // -> use the special search order |
| if (!equinoxLocale.isEmpty() && locale.toString().startsWith(equinoxLocale)) { |
| // there is a equinox.root.locale configured that matches the specified locale |
| // so the special search order is used |
| // to achieve this we first search without a fallback to the default locale |
| try { |
| resourceBundle = ResourceBundle.getBundle(baseName, locale, loader, |
| ResourceBundle.Control.getNoFallbackControl(Control.FORMAT_DEFAULT)); |
| } catch (MissingResourceException e) { |
| // do nothing |
| } |
| // if there is no ResourceBundle found for that path, we will now search for the default |
| // locale ResourceBundle |
| if (resourceBundle == null) { |
| try { |
| resourceBundle = ResourceBundle.getBundle(baseName, Locale.getDefault(), |
| loader, |
| ResourceBundle.Control.getNoFallbackControl(Control.FORMAT_DEFAULT)); |
| } catch (MissingResourceException e) { |
| // do nothing |
| } |
| } |
| } else { |
| // there is either no equinox.root.locale configured or it does not match the specified |
| // locale |
| // -> use the default search order |
| try { |
| resourceBundle = ResourceBundle.getBundle(baseName, locale, loader); |
| } catch (MissingResourceException e) { |
| // do nothing |
| } |
| } |
| |
| return resourceBundle; |
| } |
| |
| /** |
| * This method searches for the {@link ResourceBundle} in a modified way by inspecting the |
| * configuration option <code>equinox.root.locale</code>. It uses the |
| * {@link BundleResourceBundleControl} to load the resources out of a {@link Bundle}. |
| * <p> |
| * <b>Note: This method will only search for ResourceBundles based on properties files.</b> |
| * </p> |
| * <p> |
| * If the value for this system property is set to an empty String the default search order for |
| * ResourceBundles is used: |
| * </p> |
| * <ul> |
| * <li>bn + Ls + "_" + Cs + "_" + Vs</li> |
| * <li>bn + Ls + "_" + Cs</li> |
| * <li>bn + Ls</li> |
| * <li>bn + Ld + "_" + Cd + "_" + Vd</li> |
| * <li>bn + Ld + "_" + Cd</li> |
| * <li>bn + Ld</li> |
| * <li>bn</li> |
| * </ul> |
| * <p> |
| * Where bn is this bundle's localization basename, Ls, Cs and Vs are the specified locale |
| * (language, country, variant) and Ld, Cd and Vd are the default locale (language, country, |
| * variant). |
| * </p> |
| * <p> |
| * If Ls equals the value of <code>equinox.root.locale</code> then the following search order is |
| * used: |
| * </p> |
| * <ul> |
| * <li>bn + Ls + "_" + Cs + "_" + Vs</li> |
| * <li>bn + Ls + "_" + Cs</li> |
| * <li>bn + Ls</li> |
| * <li>bn</li> |
| * <li>bn + Ld + "_" + Cd + "_" + Vd</li> |
| * <li>bn + Ld + "_" + Cd</li> |
| * <li>bn + Ld</li> |
| * <li>bn</li> |
| * </ul> |
| * <p> |
| * If <code>equinox.root.locale=en</code> and en_XX or en is asked for then this allows the root |
| * file to be used instead of falling back to the default locale. |
| * </p> |
| * |
| * @param baseName |
| * the base name of the resource bundle, a fully qualified class name |
| * @param locale |
| * the locale for which a resource bundle is desired |
| * @param bundle |
| * The OSGi {@link Bundle} to lookup the {@link ResourceBundle} |
| * @return a resource bundle for the given base name and locale |
| * |
| * @see ResourceBundle#getBundle(String, Locale, Control) |
| */ |
| public static ResourceBundle getEquinoxResourceBundle(String baseName, Locale locale, |
| Bundle bundle) { |
| return getEquinoxResourceBundle(baseName, locale, new BundleResourceBundleControl(bundle, |
| true), new BundleResourceBundleControl(bundle, false)); |
| } |
| |
| /** |
| * This method searches for the {@link ResourceBundle} in a modified way by inspecting the |
| * configuration option <code>equinox.root.locale</code>. |
| * <p> |
| * <b>Note: This method will only search for ResourceBundles based on properties files.</b> |
| * </p> |
| * <p> |
| * If the value for this system property is set to an empty String the default search order for |
| * ResourceBundles is used: |
| * </p> |
| * <ul> |
| * <li>bn + Ls + "_" + Cs + "_" + Vs</li> |
| * <li>bn + Ls + "_" + Cs</li> |
| * <li>bn + Ls</li> |
| * <li>bn + Ld + "_" + Cd + "_" + Vd</li> |
| * <li>bn + Ld + "_" + Cd</li> |
| * <li>bn + Ld</li> |
| * <li>bn</li> |
| * </ul> |
| * <p> |
| * Where bn is this bundle's localization basename, Ls, Cs and Vs are the specified locale |
| * (language, country, variant) and Ld, Cd and Vd are the default locale (language, country, |
| * variant). |
| * </p> |
| * <p> |
| * If Ls equals the value of <code>equinox.root.locale</code> then the following search order is |
| * used: |
| * </p> |
| * <ul> |
| * <li>bn + Ls + "_" + Cs + "_" + Vs</li> |
| * <li>bn + Ls + "_" + Cs</li> |
| * <li>bn + Ls</li> |
| * <li>bn</li> |
| * <li>bn + Ld + "_" + Cd + "_" + Vd</li> |
| * <li>bn + Ld + "_" + Cd</li> |
| * <li>bn + Ld</li> |
| * <li>bn</li> |
| * </ul> |
| * <p> |
| * If <code>equinox.root.locale=en</code> and en_XX or en is asked for then this allows the root |
| * file to be used instead of falling back to the default locale. |
| * </p> |
| * |
| * @param baseName |
| * the base name of the resource bundle, a fully qualified class name |
| * @param locale |
| * the locale for which a resource bundle is desired |
| * @param withFallback |
| * The {@link Control} that uses the default locale fallback on searching for |
| * resource bundles. |
| * @param withoutFallback |
| * The {@link Control} that doesn't use the default locale fallback on searching for |
| * resource bundles. |
| * @return a resource bundle for the given base name and locale |
| * |
| * @see ResourceBundle#getBundle(String, Locale, Control) |
| */ |
| public static ResourceBundle getEquinoxResourceBundle(String baseName, Locale locale, |
| Control withFallback, Control withoutFallback) { |
| ResourceBundle resourceBundle = null; |
| |
| String equinoxLocale = getEquinoxRootLocale(); |
| // if the equinox.root.locale is not empty and the specified locale equals the |
| // equinox.root.locale |
| // -> use the special search order |
| if (!equinoxLocale.isEmpty() && locale.toString().startsWith(equinoxLocale)) { |
| // there is a equinox.root.locale configured that matches the specified locale |
| // so the special search order is used |
| // to achieve this we first search without a fallback to the default locale |
| try { |
| resourceBundle = ResourceBundle.getBundle(baseName, locale, withoutFallback); |
| } catch (MissingResourceException e) { |
| // do nothing |
| } |
| // if there is no ResourceBundle found for that path, we will now search for the default |
| // locale ResourceBundle |
| if (resourceBundle == null) { |
| try { |
| resourceBundle = ResourceBundle.getBundle(baseName, Locale.getDefault(), |
| withoutFallback); |
| } catch (MissingResourceException e) { |
| // do nothing |
| } |
| } |
| } else { |
| // there is either no equinox.root.locale configured or it does not match the specified |
| // locale |
| // -> use the default search order |
| try { |
| resourceBundle = ResourceBundle.getBundle(baseName, locale, withFallback); |
| } catch (MissingResourceException e) { |
| // do nothing |
| } |
| } |
| |
| return resourceBundle; |
| } |
| |
| /** |
| * @return The value for the system property for key <code>equinox.root.locale</code>. If none |
| * is specified than <b>en</b> will be returned as default. |
| */ |
| private static String getEquinoxRootLocale() { |
| // Logic from FrameworkProperties.getProperty("equinox.root.locale", "en") |
| String root = System.getProperties().getProperty("equinox.root.locale"); //$NON-NLS-1$ |
| if (root == null) { |
| root = "en"; //$NON-NLS-1$ |
| } |
| return root; |
| } |
| |
| /** |
| * This method is copied out of org.eclipse.e4.ui.internal.workbench.Activator because as it is |
| * a internal resource, it is not accessible for us. |
| * |
| * @param bundleName |
| * the bundle id |
| * @return A bundle if found, or <code>null</code> |
| */ |
| public static Bundle getBundleForName(String bundleName) { |
| if (bundleName == null) { |
| return null; |
| } |
| Bundle bundle = FrameworkUtil.getBundle(ResourceBundleHelper.class); |
| BundleContext context = bundle == null ? null : bundle.getBundleContext(); |
| |
| Bundle[] bundles = context.getBundles(); |
| if (bundles == null) { |
| return null; |
| } |
| // Return the first bundle that is not installed or uninstalled |
| for (Bundle localBundle : bundles) { |
| if (bundleName.equals(localBundle.getSymbolicName()) |
| && (localBundle.getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) { |
| return localBundle; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * <p> |
| * Converts a String to a Locale. |
| * </p> |
| * |
| * <p> |
| * This method takes the string format of a locale and creates the locale object from it. |
| * </p> |
| * |
| * <pre> |
| * MessageFactoryServiceImpl.toLocale("en") = new Locale("en", "") |
| * MessageFactoryServiceImpl.toLocale("en_GB") = new Locale("en", "GB") |
| * MessageFactoryServiceImpl.toLocale("en_GB_xxx") = new Locale("en", "GB", "xxx") |
| * </pre> |
| * |
| * <p> |
| * This method validates the input strictly. The language code must be lowercase. The country |
| * code must be uppercase. The separator must be an underscore. The length must be correct. |
| * </p> |
| * |
| * <p> |
| * This method is inspired by <code>org.apache.commons.lang.LocaleUtils.toLocale(String)</code> |
| * by fixing the parsing error for uncommon Locales like having a language and a variant code |
| * but no country code, or a Locale that only consists of a country code. |
| * </p> |
| * |
| * @param str |
| * the locale String to convert |
| * @return a Locale that matches the specified locale String. If the given input String is |
| * <code>null</code> or can not be parsed because of an invalid format, |
| * {@link Locale#getDefault()} will be returned. |
| */ |
| public static Locale toLocale(String str) { |
| return toLocale(str, Locale.getDefault()); |
| } |
| |
| /** |
| * <p> |
| * Converts a String to a Locale. |
| * </p> |
| * |
| * <p> |
| * This method takes the string format of a locale and creates the locale object from it. |
| * </p> |
| * |
| * <pre> |
| * MessageFactoryServiceImpl.toLocale("en") = new Locale("en", "") |
| * MessageFactoryServiceImpl.toLocale("en_GB") = new Locale("en", "GB") |
| * MessageFactoryServiceImpl.toLocale("en_GB_xxx") = new Locale("en", "GB", "xxx") |
| * </pre> |
| * |
| * <p> |
| * This method validates the input strictly. The language code must be lowercase. The country |
| * code must be uppercase. The separator must be an underscore. The length must be correct. |
| * </p> |
| * |
| * <p> |
| * This method is inspired by <code>org.apache.commons.lang.LocaleUtils.toLocale(String)</code> |
| * by fixing the parsing error for uncommon Locales like having a language and a variant code |
| * but no country code, or a Locale that only consists of a country code. |
| * </p> |
| * |
| * <p> |
| * <b>Note:</b> This is the same logic as used in <code>EquinoxConfiguration.toLocale()</code> |
| * </p> |
| * |
| * @param localeString |
| * the locale String to convert |
| * @param defaultLocale |
| * the Locale that should be returned in case of an invalid Locale String |
| * @return a Locale that matches the specified locale String. If the given input String is |
| * <code>null</code> or can not be parsed because of an invalid format, the given |
| * default {@link Locale} will be returned. |
| */ |
| public static Locale toLocale(String localeString, Locale defaultLocale) { |
| Logger logger = loggerTracker.getService(); |
| |
| if (localeString == null) { |
| if (logger != null) { |
| logger.error("Given locale String is null - Default Locale will be used instead."); //$NON-NLS-1$ |
| } |
| return defaultLocale; |
| } |
| |
| String language = ""; //$NON-NLS-1$ |
| String country = ""; //$NON-NLS-1$ |
| String variant = ""; //$NON-NLS-1$ |
| |
| String[] localeParts = localeString.split("_"); //$NON-NLS-1$ |
| if (localeParts.length == 0 || localeParts.length > 3 |
| || (localeParts.length == 1 && localeParts[0].isEmpty())) { |
| logInvalidFormat(localeString, logger); |
| return defaultLocale; |
| } |
| |
| if (!localeParts[0].isEmpty() && !localeParts[0].matches("[a-zA-Z]{2,8}")) { //$NON-NLS-1$ |
| logInvalidFormat(localeString, logger); |
| return defaultLocale; |
| } |
| |
| language = localeParts[0]; |
| |
| if (localeParts.length > 1) { |
| if (!localeParts[1].isEmpty() && !localeParts[1].matches("[a-zA-Z]{2}|[0-9]{3}")) { //$NON-NLS-1$ |
| if (!language.isEmpty()) { |
| if (logger != null) { |
| logger.error( |
| "Invalid locale format: {} - Only language part will be used to create the Locale.", //$NON-NLS-1$ |
| localeString); |
| } |
| return new Locale(language); |
| } |
| logInvalidFormat(localeString, logger); |
| return defaultLocale; |
| } |
| |
| country = localeParts[1]; |
| } |
| |
| if (localeParts.length == 3) { |
| if (localeParts[2].isEmpty()) { |
| if (logger != null) { |
| logger.error( |
| "Invalid locale format: {} - Only language and country part will be used to create the Locale.", //$NON-NLS-1$ |
| localeString); |
| } |
| return new Locale(language, country); |
| } |
| variant = localeParts[2]; |
| } |
| |
| return new Locale(language, country, variant); |
| } |
| |
| private static HashSet<String> invalidLocalesLogged = new HashSet<>(); |
| |
| static void logInvalidFormat(String str, Logger log) { |
| if (log != null && !invalidLocalesLogged.contains(str)) { |
| invalidLocalesLogged.add(str); |
| log.error("Invalid locale format: {} - Default Locale will be used instead.", str); //$NON-NLS-1$ |
| } |
| } |
| |
| @Deprecated |
| public static LogService getLogService() { |
| return logTracker.getService(); |
| } |
| |
| /** |
| * Specialization of {@link Control} which loads the {@link ResourceBundle} out of an OSGi |
| * {@link Bundle} instead of using a classloader. |
| * |
| * <p> |
| * It only supports properties based {@link ResourceBundle}s. If you want to use source based |
| * {@link ResourceBundle}s you have to use the bundleclass URI with the Message annotation. |
| */ |
| static class BundleResourceBundleControl extends ResourceBundle.Control { |
| |
| /** |
| * Flag to determine whether the default locale should be used as fallback locale in case |
| * there is no {@link ResourceBundle} found for the specified locale. |
| */ |
| private final boolean useFallback; |
| |
| /** |
| * The OSGi {@link Bundle} to lookup the {@link ResourceBundle} |
| */ |
| private final Bundle osgiBundle; |
| |
| /** |
| * |
| * @param osgiBundle |
| * The OSGi {@link Bundle} to lookup the {@link ResourceBundle} |
| * @param useFallback |
| * <code>true</code> if the default locale should be used as fallback locale in |
| * the search path or <code>false</code> if there should be no fallback. |
| */ |
| public BundleResourceBundleControl(Bundle osgiBundle, boolean useFallback) { |
| this.osgiBundle = osgiBundle; |
| this.useFallback = useFallback; |
| } |
| |
| @Override |
| public ResourceBundle newBundle(String baseName, Locale locale, String format, |
| ClassLoader loader, boolean reload) throws IllegalAccessException, |
| InstantiationException, IOException { |
| |
| String bundleName = toBundleName(baseName, locale); |
| ResourceBundle bundle = null; |
| if (format.equals("java.properties")) { //$NON-NLS-1$ |
| final String resourceName = toResourceName(bundleName, "properties"); //$NON-NLS-1$ |
| InputStream stream = null; |
| try { |
| stream = AccessController |
| .doPrivileged((PrivilegedExceptionAction<InputStream>) () -> { |
| InputStream is = null; |
| URL url = osgiBundle.getEntry(resourceName); |
| if (url != null) { |
| URLConnection connection = url.openConnection(); |
| if (connection != null) { |
| // Disable caches to get fresh data for |
| // reloading. |
| connection.setUseCaches(false); |
| is = connection.getInputStream(); |
| } |
| } |
| return is; |
| }); |
| } catch (PrivilegedActionException e) { |
| throw (IOException) e.getException(); |
| } |
| if (stream != null) { |
| try { |
| bundle = new PropertyResourceBundle(stream); |
| } finally { |
| stream.close(); |
| } |
| } |
| } else { |
| throw new IllegalArgumentException("unknown format: " + format); //$NON-NLS-1$ |
| } |
| return bundle; |
| } |
| |
| @Override |
| public List<String> getFormats(String baseName) { |
| return FORMAT_PROPERTIES; |
| } |
| |
| @Override |
| public Locale getFallbackLocale(String baseName, Locale locale) { |
| return this.useFallback ? super.getFallbackLocale(baseName, locale) : null; |
| } |
| } |
| } |