| /******************************************************************************* |
| * Copyright (c) 2011, 2018 BestSolution.at 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: |
| * Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation |
| * Dirk Fauth <dirk.fauth@googlemail.com> - modifications to instance creation |
| * Dirk Fauth <dirk.fauth@googlemail.com> - Bug 460308 |
| ******************************************************************************/ |
| package org.eclipse.e4.core.internal.services; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.security.AccessController; |
| 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.ResourceBundle; |
| import javax.annotation.PostConstruct; |
| import org.eclipse.e4.core.services.nls.IMessageFactoryService; |
| import org.eclipse.e4.core.services.nls.Message; |
| import org.eclipse.e4.core.services.nls.Message.ReferenceType; |
| import org.eclipse.e4.core.services.translation.ResourceBundleProvider; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.osgi.service.component.annotations.ReferencePolicy; |
| import org.osgi.service.log.Logger; |
| import org.osgi.service.log.LoggerFactory; |
| |
| @Component |
| public class MessageFactoryServiceImpl implements IMessageFactoryService { |
| |
| private LoggerFactory factory; |
| private Logger logger; |
| |
| // Cache so when multiple instance use the same message class |
| private Map<Object, Reference<Object>> SOFT_CACHE = Collections |
| .synchronizedMap(new HashMap<>()); |
| |
| private Map<Object, Reference<Object>> WEAK_CACHE = Collections |
| .synchronizedMap(new HashMap<>()); |
| |
| private int CLEANUPCOUNT = 0; |
| |
| @Override |
| public <M> M getMessageInstance(final Locale locale, final Class<M> messages, |
| final ResourceBundleProvider provider) { |
| String key = messages.getName() + "_" + locale; //$NON-NLS-1$ |
| |
| final Message annotation = messages.getAnnotation(Message.class); |
| Map<Object, Reference<Object>> cache = null; |
| ReferenceType type = ReferenceType.NONE; |
| |
| if (++CLEANUPCOUNT > 1000) { |
| Iterator<Entry<Object, Reference<Object>>> it = WEAK_CACHE.entrySet().iterator(); |
| while (it.hasNext()) { |
| if (it.next().getValue().get() == null) { |
| it.remove(); |
| } |
| } |
| |
| it = SOFT_CACHE.entrySet().iterator(); |
| while (it.hasNext()) { |
| if (it.next().getValue().get() == null) { |
| it.remove(); |
| } |
| } |
| CLEANUPCOUNT = 0; |
| } |
| |
| if (annotation == null || annotation.referenceType() == ReferenceType.SOFT) { |
| cache = SOFT_CACHE; |
| type = ReferenceType.SOFT; |
| } else if (annotation.referenceType() == ReferenceType.WEAK) { |
| cache = WEAK_CACHE; |
| type = ReferenceType.WEAK; |
| } |
| |
| if (cache != null && cache.containsKey(key)) { |
| @SuppressWarnings("unchecked") |
| Reference<M> ref = (Reference<M>) cache.get(key); |
| M o = ref.get(); |
| if (o != null) { |
| return o; |
| } |
| cache.remove(key); |
| } |
| |
| M instance; |
| |
| if (System.getSecurityManager() == null) { |
| instance = createInstance(locale, messages, annotation, provider); |
| } else { |
| instance = AccessController.doPrivileged((PrivilegedAction<M>) () -> createInstance(locale, messages, annotation, provider)); |
| } |
| |
| if (cache != null) { |
| if (type == ReferenceType.SOFT) { |
| cache.put(key, new SoftReference<>(instance)); |
| } else if (type == ReferenceType.WEAK) { |
| cache.put(key, new WeakReference<>(instance)); |
| } |
| } |
| |
| return instance; |
| } |
| |
| /** |
| * 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 rbProvider |
| * 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} or |
| * <code>null</code> if an error occured on creating the instance. |
| */ |
| @SuppressWarnings("deprecation") |
| private <M> M createInstance(Locale locale, Class<M> messages, Message annotation, |
| ResourceBundleProvider rbProvider) { |
| |
| ResourceBundle resourceBundle = null; |
| if (annotation != null) { |
| if (!annotation.contributionURI().isEmpty()) { |
| resourceBundle = ResourceBundleHelper.getResourceBundleForUri( |
| annotation.contributionURI(), locale, rbProvider); |
| } else if (!annotation.contributorURI().isEmpty()) { |
| Logger log = this.logger; |
| if (log != null) { |
| log.warn( |
| "Usage of @Message#contributorURI detected! Please use @Message#contributionURI instead!"); //$NON-NLS-1$ |
| } |
| resourceBundle = ResourceBundleHelper.getResourceBundleForUri( |
| annotation.contributorURI(), locale, rbProvider); |
| } |
| } |
| |
| if (resourceBundle == null) { |
| // check for the resource bundle relative to the messages class |
| String baseName = messages.getName().replace('.', '/'); |
| |
| resourceBundle = ResourceBundleHelper.getEquinoxResourceBundle(baseName, locale, |
| messages.getClassLoader()); |
| |
| if (resourceBundle == null) { |
| // check for the resource bundle relative to the messages class by searching |
| // the properties file lower case |
| // this is a fix for Linux environments |
| resourceBundle = ResourceBundleHelper.getEquinoxResourceBundle( |
| baseName.toLowerCase(), locale, messages.getClassLoader()); |
| } |
| } |
| |
| if (resourceBundle == null) { |
| // retrieve the OSGi resource bundle |
| Bundle bundle = FrameworkUtil.getBundle(messages); |
| resourceBundle = rbProvider.getResourceBundle(bundle, locale.toString()); |
| } |
| |
| // 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 = null; |
| try { |
| instance = messages.newInstance(); |
| Field[] fields = messages.getDeclaredFields(); |
| |
| for (Field field : fields) { |
| if (!field.isAccessible()) { |
| field.setAccessible(true); |
| } |
| if (field.getType().isAssignableFrom(String.class)) { |
| field.set(instance, provider.translate(field.getName())); |
| } |
| } |
| } catch (InstantiationException e) { |
| Logger log = this.logger; |
| if (log != null) { |
| log.error("Instantiation of messages class failed", e); //$NON-NLS-1$ |
| } |
| } catch (IllegalAccessException e) { |
| Logger log = this.logger; |
| if (log != null) { |
| log.error("Failed to access messages class", e); //$NON-NLS-1$ |
| } |
| } |
| |
| // invoke the method annotated with @PostConstruct |
| processPostConstruct(instance, messages); |
| |
| return instance; |
| } |
| |
| /** |
| * Searches for the method annotated {@link PostConstruct} in the messages class. If there is |
| * one found it will be executed. |
| * <p> |
| * Note: The method annotated with {@link PostConstruct} does not support method injection |
| * because we are not using the injection mechanism to call. |
| * |
| * @param messageObject |
| * The message instance of the given class where the method annotated with |
| * {@link PostConstruct} should be called |
| * @param messageClass |
| * The type of the message class whose instance is requested. |
| */ |
| private void processPostConstruct(Object messageObject, Class<?> messageClass) { |
| if (messageObject != null) { |
| Method[] methods = messageClass.getDeclaredMethods(); |
| for (Method method : methods) { |
| if (!method.isAnnotationPresent(PostConstruct.class)) { |
| continue; |
| } else { |
| try { |
| method.invoke(messageObject); |
| } catch (Exception e) { |
| Logger log = this.logger; |
| if (log != null) { |
| log.error( |
| "Exception on trying to execute the @PostConstruct annotated method in {}", //$NON-NLS-1$ |
| messageClass, e); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @org.osgi.service.component.annotations.Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) |
| void setLogger(LoggerFactory factory) { |
| this.factory = factory; |
| this.logger = factory.getLogger(getClass()); |
| } |
| |
| void unsetLogger(LoggerFactory loggerFactory) { |
| if (this.factory == loggerFactory) { |
| // if the factory we referenced locally is unset, we set the logger reference to |
| // null |
| this.factory = null; |
| this.logger = null; |
| } |
| } |
| |
| } |