| /******************************************************************************* |
| * Copyright (c) 2014 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@googlemail.com> - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.e4.core.services.nls; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import javax.annotation.PreDestroy; |
| import org.eclipse.e4.core.internal.services.ResourceBundleHelper; |
| import org.osgi.service.log.LogService; |
| |
| /** |
| * Using this MessageRegistry allows to register controls for attributes in a |
| * Messages class. These controls will automatically get updated in case of |
| * Locale changes. |
| * <p> |
| * When updating the dependencies from Java 7 to Java 8, this class can be |
| * replaced by a more modern variant that makes use of functional interfaces and |
| * method references as shown in the above linked blog post. |
| * </p> |
| * |
| * <p> |
| * To use the registry you need to implement a subclass of |
| * <code>BaseMessageRegistry</code> that is typed to the messages class that it |
| * is related to. The main thing to do is to override |
| * <code>updateMessages(M)</code> while getting the messages instance injected. |
| * </p> |
| * |
| * <pre> |
| * @Creatable |
| * public class ExampleMessageRegistry |
| * extends |
| * BaseMessageRegistry<ExampleMessages> { |
| * |
| * @Override |
| * @Inject |
| * public void updateMessages(@Translation ExampleMessages messages) { |
| * super.updateMessages(messages); |
| * } |
| * } |
| * </pre> |
| * |
| * <p> |
| * Note that the registry instance is annotated with @Creatable so it is |
| * created per requestor and is making use of DI. |
| * </p> |
| * |
| * @param <M> |
| * the message class type |
| * @since 2.0 |
| */ |
| public class BaseMessageRegistry<M> { |
| |
| private M messages; |
| |
| private final Map<MessageConsumer, MessageSupplier> bindings = new HashMap<>(); |
| |
| /** |
| * Register a consumer and a function that is acting as the supplier of the translation value. |
| * <p> |
| * This method allows to register a binding using method references and lambdas if used in an |
| * environment that already uses Java 8. |
| * </p> |
| * |
| * <pre> |
| * @Inject |
| * ExampleMessageRegistry registry; |
| * |
| * Label myFirstLabel = new Label(parent, SWT.WRAP); |
| * registry.register(myFirstLabel::setText, (m) -> m.firstLabelMessage); |
| * </pre> |
| * |
| * @param consumer |
| * The consumer of the message. |
| * @param function |
| * The function that supplies the message. |
| */ |
| public void register(MessageConsumer consumer, final MessageFunction<M> function) { |
| register(consumer, new MessageSupplier() { |
| |
| @Override |
| public String get() { |
| return function.apply(messages); |
| } |
| }); |
| } |
| |
| /** |
| * Register a binding for the given consumer and supplier. |
| * |
| * <p> |
| * Unless you don't want to anonymously implement the consumer and supplier interfaces yourself, |
| * use the register methods that take the Control instance and String(s) as parameters. |
| * </p> |
| * |
| * @param consumer |
| * The consumer of the message. |
| * @param supplier |
| * The supplier of the message. |
| * |
| * @see BaseMessageRegistry#register(Object, String, String) |
| * @see BaseMessageRegistry#registerProperty(Object, String, String) |
| */ |
| public void register(MessageConsumer consumer, MessageSupplier supplier) { |
| //set the value to the control |
| consumer.accept(supplier.get()); |
| //remember the control and the supplier |
| bindings.put(consumer, supplier); |
| } |
| |
| /** |
| * Binds a method of an object to a message. Doing this the specified method will be called on |
| * the instance with the message String as parameter that is retrieved via message key out of |
| * the local Messages instance. |
| * |
| * @param control |
| * The control for which a message binding should be created |
| * @param method |
| * The method that should be bound. Methods that can be bound need to accept one |
| * String parameter. |
| * @param messageKey |
| * The key of the message property that should be bound |
| * |
| * @see BaseMessageRegistry#registerProperty(Object, String, String) |
| */ |
| public void register(final Object control, final String method, final String messageKey) { |
| MessageConsumer consumer = createConsumer(control, method); |
| MessageSupplier supplier = createSupplier(messageKey); |
| //only register if consumer and supplier were created |
| if (consumer != null && supplier != null) |
| register(consumer, supplier); |
| } |
| |
| /** |
| * Binds the setter of a property of an object to a message. Doing this the setter of the given |
| * property will be called on the instance with the message String as parameter that is |
| * retrieved via message key out of the local Messages instance. |
| * |
| * @param control |
| * The control for which a message binding should be created |
| * @param property |
| * The property of the control which should be bound |
| * @param messageKey |
| * The key of the message property that should be bound |
| * |
| * @see BaseMessageRegistry#register(Object, String, String) |
| */ |
| public void registerProperty(final Object control, final String property, final String messageKey) { |
| MessageConsumer consumer = createConsumer(control, "set" + Character.toUpperCase(property.charAt(0)) + property.substring(1)); |
| MessageSupplier supplier = createSupplier(messageKey); |
| //only register if consumer and supplier were created |
| if (consumer != null && supplier != null) |
| register(consumer, supplier); |
| } |
| |
| /** |
| * This method performs the localization update for all bound objects. |
| * <p> |
| * Typically this method is overriden by a concrete implementation where the Messages instance |
| * is injected via @Inject and @Translation. |
| * </p> |
| * |
| * @param messages |
| * The new Messages instance that should be used to update the localization. |
| */ |
| public void updateMessages(M messages) { |
| //remember the current message instance |
| this.messages = messages; |
| //iterate over all registered consumer |
| for (Map.Entry<MessageConsumer, MessageSupplier> entry : bindings.entrySet()) { |
| entry.getKey().accept(entry.getValue().get()); |
| } |
| } |
| |
| /** |
| * |
| * @param control |
| * The control on which the created consumer should operate |
| * @param method |
| * The method the created consumer should call to set the new |
| * value |
| * @return A MessageConsumer that sets a value to the property of the given |
| * control, or {@code null} in case of any exception |
| */ |
| protected MessageConsumer createConsumer(final Object control, final String method) { |
| |
| final LogService logService = ResourceBundleHelper.getLogService(); |
| |
| try { |
| final Method m = control.getClass().getMethod(method, String.class); |
| if (m != null) { |
| return new MessageConsumerImplementation(logService, m, control); |
| } |
| } catch (NoSuchMethodException e) { |
| if (logService != null) |
| logService.log(LogService.LOG_WARNING, |
| "The method '" + e.getMessage() |
| + "' does not exist. Binding is not created!"); |
| } catch (SecurityException e) { |
| if (logService != null) |
| logService.log( |
| LogService.LOG_WARNING, |
| "Error on accessing method '" + method + "' on class '" |
| + control.getClass() + "' with error message '" + e.getMessage() |
| + "'. Binding is not created!"); |
| } |
| |
| return null; |
| } |
| |
| private final class MessageConsumerImplementation implements MessageConsumer { |
| private final LogService logService; |
| private final Method m; |
| private final Object control; |
| |
| private MessageConsumerImplementation(LogService logService, Method m, Object control) { |
| this.logService = logService; |
| this.m = m; |
| this.control = control; |
| } |
| |
| @Override |
| public void accept(final String value) { |
| try { |
| // ensure the method is accessible so the registry |
| // also works well with protected or package |
| // protected classes |
| if (System.getSecurityManager() == null) { |
| m.setAccessible(true); |
| m.invoke(control, value); |
| } else { |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| |
| @Override |
| public Object run() { |
| m.setAccessible(true); |
| try { |
| m.invoke(control, value); |
| } catch (Exception e) { |
| // if anything fails on invoke we unregister the |
| // binding to avoid further issues e.g. this can |
| // happen in case of disposed SWT controls |
| bindings.remove(MessageConsumerImplementation.this); |
| if (logService != null) |
| logService.log(LogService.LOG_INFO, |
| "Error on invoke '" + m.getName() + "' on '" + control.getClass() |
| + "' with error message '" + e.getMessage() |
| + "'. Binding is removed."); |
| } |
| return null; |
| } |
| |
| }); |
| } |
| } catch (Exception e) { |
| // if anything fails on invoke we unregister the binding to |
| // avoid further issues |
| // e.g. this can happen in case of disposed SWT controls |
| bindings.remove(this); |
| if (logService != null) |
| logService.log(LogService.LOG_INFO, |
| "Error on invoke '" + m.getName() + "' on '" + control.getClass() + "' with error message '" |
| + e.getMessage() + "'. Binding is removed."); |
| } |
| } |
| } |
| |
| /** |
| * |
| * @param messageKey |
| * The name of the field that should be accessed |
| * @return A MessageSupplier that returns the message value for the given message key |
| */ |
| protected MessageSupplier createSupplier(final String messageKey) { |
| final LogService logService = ResourceBundleHelper.getLogService(); |
| try { |
| final Field f = messages.getClass().getField(messageKey); |
| if (f != null) { |
| return new MessageSupplierImplementation(logService, f); |
| } |
| } catch (NoSuchFieldException e) { |
| if (logService != null) |
| logService.log(LogService.LOG_WARNING, "The class '" + this.messages.getClass().getName() |
| + "' does not contain a field with name '" + e.getMessage() + "'. Binding is not created!"); |
| } catch (SecurityException e) { |
| if (logService != null) |
| logService.log(LogService.LOG_WARNING, |
| "Error on accessing field '" + messageKey + "' on class '" + messages.getClass() |
| + "' with error message '" + e.getMessage() + "'. Binding is not created!"); |
| } |
| return null; |
| } |
| |
| private final class MessageSupplierImplementation implements MessageSupplier { |
| private final LogService logService; |
| private final Field f; |
| |
| private MessageSupplierImplementation(LogService logService, Field f) { |
| this.logService = logService; |
| this.f = f; |
| } |
| |
| @Override |
| public String get() { |
| String message = null; |
| try { |
| message = (String) f.get(messages); |
| } catch (Exception e) { |
| // if anything fails on invoke we unregister the binding to |
| // avoid further issues |
| // e.g. this can happen in case of disposed SWT controls |
| Iterator<Entry<MessageConsumer, MessageSupplier>> iterator = bindings.entrySet().iterator(); |
| iterator.forEachRemaining(entry -> { |
| if (entry.getValue() == MessageSupplierImplementation.this) { |
| iterator.remove(); |
| } |
| }); |
| if (logService != null) |
| logService.log(LogService.LOG_INFO, |
| "Error on invoke '" + f.getName() + "' on '" + messages.getClass() |
| + "' with error message '" + e.getMessage() + "'. Binding is removed."); |
| } |
| return message; |
| } |
| } |
| |
| @PreDestroy |
| void unregister() { |
| this.bindings.clear(); |
| } |
| } |