diff options
author | Dirk Fauth | 2013-04-17 17:18:35 +0000 |
---|---|---|
committer | Tom Schindl | 2013-04-17 17:18:35 +0000 |
commit | 1fbfddbcd5235a2a01b8eae7ba2aa771335b8add (patch) | |
tree | f09d52bd33705714201e4195841de798ad14e568 | |
parent | de90c3bba7c2477963b268548086979ab97b34bd (diff) | |
download | org.eclipse.e4.tools-I20130417-2200.tar.gz org.eclipse.e4.tools-I20130417-2200.tar.xz org.eclipse.e4.tools-I20130417-2200.zip |
Bug 396198 - Enhancement of translation service in tools.servicesI20130417-2200
9 files changed, 706 insertions, 19 deletions
diff --git a/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF index 9374d6b9..f0ce9437 100644 --- a/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.e4.tools.services/META-INF/MANIFEST.MF @@ -17,3 +17,4 @@ Service-Component: OSGI-INF/resourcepoolfunction.xml, OSGI-INF/resourceservice.x Bundle-ActivationPolicy: lazy Import-Package: javax.annotation;version="1.0.0", javax.inject;version="1.0.0" +Bundle-Activator: org.eclipse.e4.tools.services.ToolsServicesActivator diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java index fc223330..b5512df7 100644 --- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java @@ -1,6 +1,8 @@ package org.eclipse.e4.tools.services; +import org.eclipse.osgi.service.localization.BundleLocalization; + public interface IMessageFactoryService { - public <M> M createInstance(final String locale, final Class<M> messages) + public <M> M createInstance(final String locale, final Class<M> messages, BundleLocalization localization) throws InstantiationException, IllegalAccessException; } diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java index d2948f97..05afcf7f 100644 --- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/Message.java @@ -1,9 +1,14 @@ package org.eclipse.e4.tools.services; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) public @interface Message { public enum ReferenceType { NONE, SOFT, WEAK } ReferenceType referenceType() default ReferenceType.SOFT; + String contributorURI() default ""; } diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/ToolsServicesActivator.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/ToolsServicesActivator.java new file mode 100644 index 00000000..04d63672 --- /dev/null +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/ToolsServicesActivator.java @@ -0,0 +1,63 @@ +package org.eclipse.e4.tools.services; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.service.log.LogService; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.util.tracker.ServiceTracker; + +@SuppressWarnings("deprecation") +public class ToolsServicesActivator implements BundleActivator { + + static private ToolsServicesActivator defaultInstance; + private BundleContext bundleContext; + private ServiceTracker<PackageAdmin, PackageAdmin> pkgAdminTracker; + private ServiceTracker<LogService, LogService> logTracker; + + public ToolsServicesActivator() { + defaultInstance = this; + } + + public static ToolsServicesActivator getDefault() { + return defaultInstance; + } + + public void start(BundleContext context) throws Exception { + bundleContext = context; + } + + public void stop(BundleContext context) throws Exception { + if (pkgAdminTracker != null) { + pkgAdminTracker.close(); + pkgAdminTracker = null; + } + if (logTracker != null) { + logTracker.close(); + logTracker = null; + } + bundleContext = null; + } + + public PackageAdmin getPackageAdmin() { + if (pkgAdminTracker == null) { + if (bundleContext == null) + return null; + pkgAdminTracker = new ServiceTracker<PackageAdmin, PackageAdmin>(bundleContext, + PackageAdmin.class, null); + pkgAdminTracker.open(); + } + return (PackageAdmin) pkgAdminTracker.getService(); + } + + public LogService getLogService() { + if (logTracker == null) { + if (bundleContext == null) + return null; + logTracker = new ServiceTracker<LogService, LogService>(bundleContext, + LogService.class, null); + logTracker.open(); + } + return logTracker.getService(); + } + +} diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java index c255ee13..20d0deb7 100644 --- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/AbstractTranslationProvider.java @@ -20,6 +20,7 @@ import java.util.Locale; import java.util.MissingResourceException; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; + import org.eclipse.osgi.service.localization.BundleLocalization; /** diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java index 2d8d3496..e09b23b6 100644 --- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java @@ -19,15 +19,25 @@ import java.security.PrivilegedAction; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.MissingResourceException; +import java.util.ResourceBundle; import org.eclipse.e4.tools.services.IMessageFactoryService; import org.eclipse.e4.tools.services.Message; import org.eclipse.e4.tools.services.Message.ReferenceType; +import org.eclipse.e4.tools.services.ToolsServicesActivator; +import org.eclipse.osgi.service.localization.BundleLocalization; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.log.LogService; public class MessageFactoryServiceImpl implements IMessageFactoryService { + private static LogService logService = ToolsServicesActivator.getDefault().getLogService(); + // Cache so when multiple instance use the same message class private Map<Object, Reference<Object>> SOFT_CACHE = Collections .synchronizedMap(new HashMap<Object, Reference<Object>>()); @@ -37,9 +47,9 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService { private int CLEANUPCOUNT = 0; - public <M> M createInstance(final String locale, final Class<M> messages) + public <M> M createInstance(final String locale, final Class<M> messages, final BundleLocalization localization) throws InstantiationException, IllegalAccessException { - String key = messages.getName() + "_" + locale; + String key = messages.getName() + "_" + locale; //$NON-NLS-1$ final Message annotation = messages.getAnnotation(Message.class); Map<Object, Reference<Object>> cache = null; @@ -85,13 +95,13 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService { M instance; if (System.getSecurityManager() == null) { - instance = doCreateInstance(locale, messages, annotation); + instance = doCreateInstance(locale, messages, annotation, localization); } else { instance = AccessController.doPrivileged(new PrivilegedAction<M>() { public M run() { try { - return doCreateInstance(locale, messages, annotation); + return doCreateInstance(locale, messages, annotation, localization); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { @@ -115,13 +125,49 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService { } private static <M> M doCreateInstance(String locale, Class<M> messages, - Message annotation) throws InstantiationException, + Message annotation, BundleLocalization localization) throws InstantiationException, IllegalAccessException { - String basename = messages.getName().replace('.', '/'); - PropertiesBundleTranslationProvider provider = new PropertiesBundleTranslationProvider( - messages.getClassLoader(), basename); - + Locale loc = null; + try { + loc = locale == null ? Locale.getDefault() : ResourceBundleHelper.toLocale(locale); + } + catch (Exception e) { + //parsing the locale String to a Locale failed, so we use the default Locale + if (logService != null) + logService.log(LogService.LOG_ERROR, "Invalid locale", e); //$NON-NLS-1$ + loc = Locale.getDefault(); + } + + ResourceBundle resourceBundle = null; + if (annotation != null && annotation.contributorURI().length() > 0) { + resourceBundle = ResourceBundleHelper.getResourceBundleForUri(annotation.contributorURI(), loc, localization); + } + + if (resourceBundle == null) { + //check for the resource bundle relative to the messages class + String baseName = messages.getName().replace('.', '/'); + + try { + resourceBundle = ResourceBundleHelper.getEquinoxResourceBundle(baseName, loc, messages.getClassLoader()); + } + catch (MissingResourceException e) { + //do nothing as this just means there is no resource bundle named + //like the messages class in the same package + //therefore we will go on and search for the OSGi resource bundle + } + } + + if (resourceBundle == null) { + //retrieve the OSGi resource bundle + Bundle bundle = FrameworkUtil.getBundle(messages); + resourceBundle = localization.getLocalization(bundle, locale); + } + + //always create a provider, if there is no resource bundle found, simply the modified keys will + //be returned by this provider to show that there is something wrong on loading it + ResourceBundleTranslationProvider provider = new ResourceBundleTranslationProvider(resourceBundle); + M instance = messages.newInstance(); Field[] fields = messages.getDeclaredFields(); @@ -131,7 +177,7 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService { } fields[i].set(instance, - provider.translate(locale, fields[i].getName())); + provider.translate(fields[i].getName())); } return instance; diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleHelper.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleHelper.java new file mode 100644 index 00000000..fb3f017e --- /dev/null +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleHelper.java @@ -0,0 +1,501 @@ +/******************************************************************************* + * Copyright (c) 2012 Dirk Fauth and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Dirk Fauth <dirk.fauth@gmail.com> - initial API and implementation + ******************************************************************************/ +package org.eclipse.e4.tools.services.impl; + +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.List; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.ResourceBundle.Control; + +import org.eclipse.e4.tools.services.ToolsServicesActivator; +import org.eclipse.osgi.service.localization.BundleLocalization; +import org.osgi.framework.Bundle; +import org.osgi.service.log.LogService; +import org.osgi.service.packageadmin.PackageAdmin; + +/** + * Helper class for retrieving {@link ResourceBundle}s out of OSGi {@link Bundle}s. + * + * @author Dirk Fauth + */ +// There is no replacement for PackageAdmin#getBundles() +@SuppressWarnings("deprecation") +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$ + + /** + * Parses the specified contributor URI and loads the {@link ResourceBundle} for the specified {@link Locale} + * out of an OSGi {@link Bundle}. + * <p>Following URIs are supported: + * <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> + * </p> + * @param contributorURI The URI that points to a {@link ResourceBundle} + * @param locale The {@link Locale} to use for loading the {@link ResourceBundle} + * @param localization 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 contributorURI, Locale locale, BundleLocalization localization) { + if (contributorURI == null) + return null; + + LogService logService = ToolsServicesActivator.getDefault().getLogService(); + + URI uri; + try { + uri = new URI(contributorURI); + } catch (URISyntaxException e) { + if (logService != null) + logService.log(LogService.LOG_ERROR, "Invalid contributor URI: " + contributorURI); //$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 (logService != null) + logService.log(LogService.LOG_ERROR, "Failed to get bundle for: " + contributorURI); //$NON-NLS-1$ + } + bundleName = uri.getAuthority(); + //remove the leading / + classPath = uri.getPath().substring(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 (logService != null) + logService.log(LogService.LOG_ERROR, "Failed to load specified ResourceBundle: " + contributorURI, e); //$NON-NLS-1$ + } + } + else if (resourcePath.length() > 0) { + //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 OSGi resource bundle out of the specified Bundle + //for the current Locale + result = localization.getLocalization(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: + * <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> + * 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: + * <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. + * + * @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.length() > 0 && 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 + resourceBundle = ResourceBundle.getBundle(baseName, locale, loader, + ResourceBundle.Control.getNoFallbackControl(Control.FORMAT_DEFAULT)); + //if there is no ResourceBundle found for that path, we will now search for the default locale ResourceBundle + if (resourceBundle == null) { + resourceBundle = ResourceBundle.getBundle(baseName, Locale.getDefault(), loader, + ResourceBundle.Control.getNoFallbackControl(Control.FORMAT_DEFAULT)); + } + } + else { + //there is either no equinox.root.locale configured or it does not match the specified locale + // -> use the default search order + resourceBundle = ResourceBundle.getBundle(baseName, locale, loader); + } + + return resourceBundle; + } + + /** + * 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: + * <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> + * 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: + * <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. + * + * @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) { + 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.length() > 0 && 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 + resourceBundle = ResourceBundle.getBundle(baseName, locale, new BundleResourceBundleControl(bundle, false)); + //if there is no ResourceBundle found for that path, we will now search for the default locale ResourceBundle + if (resourceBundle == null) { + resourceBundle = ResourceBundle.getBundle(baseName, Locale.getDefault(), new BundleResourceBundleControl(bundle, false)); + } + } + else { + //there is either no equinox.root.locale configured or it does not match the specified locale + // -> use the default search order + resourceBundle = ResourceBundle.getBundle(baseName, locale, new BundleResourceBundleControl(bundle, true)); + } + + 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) { + PackageAdmin packageAdmin = ToolsServicesActivator.getDefault().getPackageAdmin(); + Bundle[] bundles = packageAdmin.getBundles(bundleName, null); + if (bundles == null) + return null; + // Return the first bundle that is not installed or uninstalled + for (int i = 0; i < bundles.length; i++) { + if ((bundles[i].getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) { + return bundles[i]; + } + } + 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 or <code>null</code> + * if the specified String is <code>null</code> + * @throws IllegalArgumentException if the String is an invalid format + */ + public static Locale toLocale(String str) { + if (str == null) { + return null; + } + + String language = ""; //$NON-NLS-1$ + String country = ""; //$NON-NLS-1$ + String variant = ""; //$NON-NLS-1$ + + String[] localeParts = str.split("_"); //$NON-NLS-1$ + if (localeParts.length == 0 || localeParts.length > 3 + || (localeParts.length == 1 && localeParts[0].length() == 0)) { + throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$ + } else { + if (localeParts[0].length() == 1 || localeParts[0].length() > 2) { + throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$ + } + else if (localeParts[0].length() == 2) { + char ch0 = localeParts[0].charAt(0); + char ch1 = localeParts[0].charAt(1); + if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') { + throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$ + } + } + + language = localeParts[0]; + + if (localeParts.length > 1) { + if (localeParts[1].length() == 1 || localeParts[1].length() > 2) { + throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$ + } + else if (localeParts[1].length() == 2) { + char ch3 = localeParts[1].charAt(0); + char ch4 = localeParts[1].charAt(1); + if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') { + throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$ + } + } + + country = localeParts[1]; + } + + if (localeParts.length == 3) { + if (localeParts[0].length() == 0 && localeParts[1].length() == 0) { + throw new IllegalArgumentException("Invalid locale format: " + str); //$NON-NLS-1$ + } + variant = localeParts[2]; + } + } + + return new Locale(language, country, variant); + } + + /** + * 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. + * + * @author Dirk Fauth + * + */ + 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( + new PrivilegedExceptionAction<InputStream>() { + public InputStream run() throws IOException { + 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; + } + } +} diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleTranslationProvider.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleTranslationProvider.java new file mode 100644 index 00000000..163087b0 --- /dev/null +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/ResourceBundleTranslationProvider.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2012 Dirk Fauth and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Dirk Fauth <dirk.fauth@gmail.com> - initial API and implementation + ******************************************************************************/ +package org.eclipse.e4.tools.services.impl; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * Wrapper class for accessing translations out of a {@link ResourceBundle}. + * + * @author Dirk Fauth + * + */ +public class ResourceBundleTranslationProvider { + + /** + * The {@link ResourceBundle} to use for translations. + */ + private ResourceBundle resourceBundle; + + /** + * + * @param resourceBundle The {@link ResourceBundle} to use for translations. + * Can be <code>null</code>, which will lead to simply return the key + * modified by prefixing and suffixing it with "!" when calling translate(String). + */ + public ResourceBundleTranslationProvider(ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + } + + /** + * Tries to retrieve the translation value for the given key out of the {@link ResourceBundle} + * set to this {@link ResourceBundleTranslationProvider}. If there is no {@link ResourceBundle} + * set or there is no translation found for the given key, the key itself prefixed and suffixed + * with "!" will be returned to indicate that there is no translation found. + * <p>This implementation also supports the usage of dot separation for property keys. As in Java + * variables can not be separated with a dot, the underscore needs to be used for separation of + * the variable. This will be replaced automatically to a dot, if there is no translation found + * with an underscore as separator. + * </p> + * @param key The key of the requested translation property. + * @return The translation for the given key or the key itself prefixed and suffixed + * with "!" to indicate that there is no translation available for the + * given key. + */ + public String translate(String key) { + String result = ""; //$NON-NLS-1$ + try { + if (this.resourceBundle == null) { + result = "!" + key + "!"; //$NON-NLS-1$ //$NON-NLS-2$ + } + result = resourceBundle.getString(key); + } catch (MissingResourceException e) { + if (key.contains("_")) { //$NON-NLS-1$ + result = translate(key.replace('_', '.')); + } else { + result = "!" + key + "!"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + return result; + } +} diff --git a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java index a7ee2584..31c06b87 100644 --- a/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java +++ b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java @@ -11,20 +11,17 @@ package org.eclipse.e4.tools.services.impl; import java.lang.reflect.ParameterizedType; - import java.lang.reflect.Type; import java.util.Locale; +import org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier; +import org.eclipse.e4.core.di.suppliers.IObjectDescriptor; +import org.eclipse.e4.core.di.suppliers.IRequestor; import org.eclipse.e4.core.internal.contexts.ContextObjectSupplier; - import org.eclipse.e4.core.internal.di.Requestor; import org.eclipse.e4.core.services.translation.TranslationService; - -import org.eclipse.e4.core.di.suppliers.IObjectDescriptor; -import org.eclipse.e4.core.di.suppliers.IRequestor; - -import org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier; import org.eclipse.e4.tools.services.IMessageFactoryService; +import org.eclipse.osgi.service.localization.BundleLocalization; public class TranslationObjectSupplier extends ExtendedObjectSupplier { @@ -39,10 +36,11 @@ public class TranslationObjectSupplier extends ExtendedObjectSupplier { String locale = (String) sub.getContext().get(TranslationService.LOCALE); locale = locale == null ? Locale.getDefault().toString() : locale; + BundleLocalization localization = sub.getContext().get(BundleLocalization.class); IMessageFactoryService factoryService = sub.getContext().get(IMessageFactoryService.class); try { - return factoryService.createInstance(locale,descriptorsClass); + return factoryService.createInstance(locale, descriptorsClass, localization); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); |