blob: 5c660eef60f883c8360b08466f5afecab4330f7a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2017 BestSolution.at 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:
* Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - modifications to support locale changes at runtime
******************************************************************************/
package org.eclipse.e4.core.internal.services;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.e4.core.contexts.IEclipseContext;
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.services.nls.IMessageFactoryService;
import org.eclipse.e4.core.services.nls.Message;
import org.eclipse.e4.core.services.translation.ResourceBundleProvider;
import org.eclipse.e4.core.services.translation.TranslationService;
import org.eclipse.osgi.service.localization.BundleLocalization;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
@Component(service = { ExtendedObjectSupplier.class, EventHandler.class }, property = {
"dependency.injection.annotation=org.eclipse.e4.core.services.nls.Translation",
"event.topics=" + IEclipseContext.TOPIC_DISPOSE })
public class TranslationObjectSupplier extends ExtendedObjectSupplier implements EventHandler {
/**
* 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 ResourceBundleProvider provider;
/**
* 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 ConcurrentHashMap<>();
@Override
public Object get(IObjectDescriptor descriptor, IRequestor requestor, boolean track, boolean group) {
Class<?> descriptorsClass = getDesiredClass(descriptor.getDesiredType());
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) Locale locale) {
this.locale = locale == null ? Locale.getDefault() : locale;
// 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) {
return this.factoryService.getMessageInstance(this.locale, descriptorsClass, this.provider);
}
/**
* 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.computeIfAbsent(descriptorsClass, (dc) -> {
return ConcurrentHashMap.newKeySet();
});
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<?>)
return (Class<?>) desiredType;
if (desiredType instanceof ParameterizedType) {
Type rawType = ((ParameterizedType) desiredType).getRawType();
if (rawType instanceof Class<?>)
return (Class<?>) rawType;
}
return null;
}
@Override
public void handleEvent(Event event) {
for (Iterator<Entry<Class<?>, Set<IRequestor>>> it = this.listeners.entrySet().iterator(); it.hasNext();) {
Map.Entry<Class<?>, Set<IRequestor>> entry = it.next();
Set<IRequestor> requestors = entry.getValue();
if (requestors != null) {
Predicate<IRequestor> predicate = IRequestor::isValid;
requestors.removeIf(predicate.negate());
if (requestors.isEmpty()) {
it.remove();
}
}
}
}
}