Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDirk Fauth2013-05-18 11:41:24 -0400
committerTom Schindl2013-05-18 11:41:24 -0400
commit017da40e2c6e8c7a3e48d94eff9c64a729a4705c (patch)
tree7ff54743d457b6beae3fdafc2ad2778072751211
parent003f4c74a65621a11b69e3ab522e15fc6f31d19f (diff)
downloadorg.eclipse.e4.tools-017da40e2c6e8c7a3e48d94eff9c64a729a4705c.tar.gz
org.eclipse.e4.tools-017da40e2c6e8c7a3e48d94eff9c64a729a4705c.tar.xz
org.eclipse.e4.tools-017da40e2c6e8c7a3e48d94eff9c64a729a4705c.zip
Bug 406632 - Add support for Locale changes at runtime
-rw-r--r--bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/IMessageFactoryService.java26
-rw-r--r--bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java66
-rw-r--r--bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/TranslationObjectSupplier.java152
3 files changed, 205 insertions, 39 deletions
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 b5512df7..b93d9b2c 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,8 +1,32 @@
package org.eclipse.e4.tools.services;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
import org.eclipse.osgi.service.localization.BundleLocalization;
+/**
+ * Service that is responsible for creating and managing message class instances.
+ */
public interface IMessageFactoryService {
- public <M> M createInstance(final String locale, final Class<M> messages, BundleLocalization localization)
+
+ /**
+ * Returns an instance of the of a given messages class for the given {@link Locale}.
+ * If configured it caches the created instances and return the already created instances.
+ * Otherwise a new instance will be created.
+ *
+ * @param locale The {@link Locale} for which the message class instance is requested.
+ * @param messages The type of the message class whose instance is requested.
+ * @param localization The service that is needed to retrieve {@link ResourceBundle} objects from a bundle
+ * with a given locale.
+ * @return An instance of the given messages class and {@link Locale}.
+ *
+ * @throws InstantiationException if the requested message class represents an abstract class, an interface,
+ * an array class, a primitive type, or void;
+ * or if the class has no nullary constructor;
+ * or if the instantiation fails for some other reason.
+ * @throws IllegalAccessException if the requested message class or its nullary constructor is not accessible.
+ */
+ public <M> M getMessageInstance(final Locale 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/impl/MessageFactoryServiceImpl.java b/bundles/org.eclipse.e4.tools.services/src/org/eclipse/e4/tools/services/impl/MessageFactoryServiceImpl.java
index e09b23b6..27a23467 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
@@ -7,6 +7,7 @@
*
* Contributors:
* Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
+ * Dirk Fauth <dirk.fauth@gmail.com> - modifications to instance creation
******************************************************************************/
package org.eclipse.e4.tools.services.impl;
@@ -28,16 +29,12 @@ 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>>());
@@ -47,7 +44,8 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService {
private int CLEANUPCOUNT = 0;
- public <M> M createInstance(final String locale, final Class<M> messages, final BundleLocalization localization)
+ @Override
+ public <M> M getMessageInstance(final Locale locale, final Class<M> messages, final BundleLocalization localization)
throws InstantiationException, IllegalAccessException {
String key = messages.getName() + "_" + locale; //$NON-NLS-1$
@@ -95,13 +93,13 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService {
M instance;
if (System.getSecurityManager() == null) {
- instance = doCreateInstance(locale, messages, annotation, localization);
+ instance = createInstance(locale, messages, annotation, localization);
} else {
instance = AccessController.doPrivileged(new PrivilegedAction<M>() {
public M run() {
try {
- return doCreateInstance(locale, messages, annotation, localization);
+ return createInstance(locale, messages, annotation, localization);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
@@ -124,24 +122,48 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService {
return instance;
}
- private static <M> M doCreateInstance(String locale, Class<M> messages,
+ /**
+ * Creates and returns an instance of the of a given messages class for the given {@link Locale}.
+ * The message class gets instantiated and the fields are initialized with values out of a {@link ResourceBundle}.
+ * As there are several options to specify the location of the {@link ResourceBundle} to load, the
+ * following search order is used:
+ * <ol>
+ * <li>URI location<br/>
+ * If the message class is annotated with <code>@Message</code> and the <i>contributorURI</i>
+ * attribute is set, the {@link ResourceBundle} is searched at the specified location</li>
+ * <li>Relative location<br/>
+ * If the message class is not annotated with <code>@Message</code> and a contributorURI
+ * attribute value or there is no {@link ResourceBundle} found at the specified location, a
+ * {@link ResourceBundle} with the same name in the same package as the message class is searched.</li>
+ * <li>Bundle localization<br/>
+ * If there is no {@link ResourceBundle} found by URI or relative location, the OSGi {@link ResourceBundle}
+ * configured in the MANIFEST.MF is tried to load.</li>
+ * </ol>
+ * Note: Even if there is no {@link ResourceBundle} found in any of the mentioned locations, this method will
+ * not break. In this case the fields of the message class will get initialized with values that look
+ * like <code>!key!</code> to indicate that there is no translation value found for that key.
+ *
+ * @param locale The {@link Locale} for which the message class instance is requested.
+ * @param messages The type of the message class whose instance is requested.
+ * @param annotation The annotation that is used in the message class. If specified it is needed
+ * to retrieve the URI of the location to search for the {@link ResourceBundle}.
+ * @param localization The service that is needed to retrieve {@link ResourceBundle} objects from a bundle
+ * with a given locale.
+ * @return The created instance of the given messages class and {@link Locale}.
+ *
+ * @throws InstantiationException if the requested message class represents an abstract class, an interface,
+ * an array class, a primitive type, or void;
+ * or if the class has no nullary constructor;
+ * or if the instantiation fails for some other reason.
+ * @throws IllegalAccessException if the requested message class or its nullary constructor is not accessible.
+ */
+ private static <M> M createInstance(Locale locale, Class<M> messages,
Message annotation, BundleLocalization localization) throws InstantiationException,
IllegalAccessException {
- 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);
+ resourceBundle = ResourceBundleHelper.getResourceBundleForUri(annotation.contributorURI(), locale, localization);
}
if (resourceBundle == null) {
@@ -149,7 +171,7 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService {
String baseName = messages.getName().replace('.', '/');
try {
- resourceBundle = ResourceBundleHelper.getEquinoxResourceBundle(baseName, loc, messages.getClassLoader());
+ resourceBundle = ResourceBundleHelper.getEquinoxResourceBundle(baseName, locale, messages.getClassLoader());
}
catch (MissingResourceException e) {
//do nothing as this just means there is no resource bundle named
@@ -161,7 +183,7 @@ public class MessageFactoryServiceImpl implements IMessageFactoryService {
if (resourceBundle == null) {
//retrieve the OSGi resource bundle
Bundle bundle = FrameworkUtil.getBundle(messages);
- resourceBundle = localization.getLocalization(bundle, locale);
+ resourceBundle = localization.getLocalization(bundle, locale.toString());
}
//always create a provider, if there is no resource bundle found, simply the modified keys will
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 31c06b87..1350a0bf 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
@@ -7,49 +7,170 @@
*
* Contributors:
* Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
+ * Dirk Fauth <dirk.fauth@gmail.com> - modifications to support locale changes at runtime
******************************************************************************/
package org.eclipse.e4.tools.services.impl;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.eclipse.e4.core.di.annotations.Optional;
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.tools.services.IMessageFactoryService;
+import org.eclipse.e4.tools.services.Message;
+import org.eclipse.e4.tools.services.ToolsServicesActivator;
import org.eclipse.osgi.service.localization.BundleLocalization;
+import org.osgi.service.log.LogService;
+@SuppressWarnings("rawtypes")
public class TranslationObjectSupplier extends ExtendedObjectSupplier {
+ private static LogService logService = ToolsServicesActivator.getDefault().getLogService();
+
+ /**
+ * The current active locale that gets injected for updating the message instances.
+ */
+ private Locale locale;
+
+ /**
+ * The service that gets {@link ResourceBundle} objects from a bundle with a given locale.
+ */
+ @Inject
+ private BundleLocalization localization;
+
+ /**
+ * The service that creates instances of message classes based on the current active locale,
+ * the {@link BundleLocalization} and the configuration used in the {@link Message} annotation.
+ */
+ @Inject
+ private IMessageFactoryService factoryService;
+
+ /**
+ * Map that contains all {@link IRequestor} that requested an instance of a messages class.
+ * Used to inform all requestor if the instances have changed due to a locale change.
+ */
+ private Map<Class, Set<IRequestor>> listeners = new HashMap<Class, Set<IRequestor>>();
+
@Override
public Object get(IObjectDescriptor descriptor, IRequestor requestor,
boolean track, boolean group) {
+
Class<?> descriptorsClass = getDesiredClass(descriptor.getDesiredType());
- Requestor req = (Requestor) requestor;
- ContextObjectSupplier sub = (ContextObjectSupplier) req
- .getPrimarySupplier();
-
- 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);
+ if (track)
+ addListener(descriptorsClass, requestor);
+
+ return getMessageInstance(descriptorsClass);
+ }
+
+ /**
+ * Setting the {@link Locale} by using this method will cause to create new instances for all
+ * message classes that were requested before. It also notifys all {@link IRequestor} that requested
+ * those messages instance which causes dynamic reinjection.
+ * @param locale The {@link Locale} to use for creating the message instances.
+ */
+ @Inject
+ public void setLocale(@Optional @Named(TranslationService.LOCALE) String locale) {
+ try {
+ this.locale = locale == null ? Locale.getDefault() : ResourceBundleHelper.toLocale(locale);
+ }
+ catch (IllegalArgumentException e) {
+ //parsing the locale String to a Locale failed because of invalid String, use the default locale
+ if (logService != null)
+ logService.log(LogService.LOG_ERROR, e.getMessage() + " - Default Locale will be used instead."); //$NON-NLS-1$
+ this.locale = Locale.getDefault();
+ }
+ 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$
+ this.locale = Locale.getDefault();
+ }
+ //update listener
+ updateMessages();
+ }
+
+ /**
+ * Notify the {@link IRequestor}s of those instances that they need to update their message class instances.
+ */
+ private void updateMessages() {
+ for (Map.Entry<Class, Set<IRequestor>> entry : this.listeners.entrySet()) {
+ notifyRequestor(entry.getValue());
+ }
+ }
+
+ /**
+ * Checks if for the specified descriptor class there is already an instance in the local
+ * cache. If not a new instance is created using the local configuration on {@link Locale},
+ * {@link BundleLocalization} and given descriptor class.
+ * @param descriptorsClass The class for which an instance is requested.
+ * @return The instance of the requested message class
+ */
+ private Object getMessageInstance(Class<?> descriptorsClass) {
try {
- return factoryService.createInstance(locale, descriptorsClass, localization);
+ return this.factoryService.getMessageInstance(
+ this.locale, descriptorsClass, this.localization);
} catch (InstantiationException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
+ if (logService != null)
+ logService.log(LogService.LOG_ERROR,
+ "Instantiation of messages class failed", e); //$NON-NLS-1$
} catch (IllegalAccessException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
+ if (logService != null)
+ logService.log(LogService.LOG_ERROR,
+ "Failed to access messages class", e); //$NON-NLS-1$
}
return null;
}
+
+ /**
+ * Remember the {@link IRequestor} that requested an instance of the given descriptor class.
+ * This is needed to be able to inform all {@link IRequestor} if the {@link Locale} changes
+ * at runtime.
+ * @param descriptorsClass The class for which an instance was requested.
+ * @param requestor The {@link IRequestor} that requested the instance.
+ */
+ private void addListener(Class<?> descriptorsClass, IRequestor requestor) {
+ Set<IRequestor> registered = this.listeners.get(descriptorsClass);
+ if (registered == null) {
+ registered = new HashSet<IRequestor>();
+ this.listeners.put(descriptorsClass, registered);
+ }
+ registered.add(requestor);
+ }
+
+ /**
+ * Notify all given {@link IRequestor} about changes for their injected values.
+ * This way the dynamic injection is performed.
+ * @param requestors The {@link IRequestor} to inform about the instance changes.
+ */
+ private void notifyRequestor(Collection<IRequestor> requestors) {
+ if (requestors != null) {
+ for (Iterator<IRequestor> it = requestors.iterator(); it.hasNext();) {
+ IRequestor requestor = it.next();
+ if (!requestor.isValid()) {
+ it.remove();
+ continue;
+ }
+ requestor.resolveArguments(false);
+ requestor.execute();
+ }
+ }
+ }
private Class<?> getDesiredClass(Type desiredType) {
if (desiredType instanceof Class<?>)
@@ -61,5 +182,4 @@ public class TranslationObjectSupplier extends ExtendedObjectSupplier {
}
return null;
}
-
} \ No newline at end of file

Back to the top