Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 6175a49451977f58de818b948da8d7db46204d89 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*******************************************************************************
 * Copyright (c) 2011 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@gmail.com> - modifications to instance creation
 ******************************************************************************/
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.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 = ServicesActivator.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>>());

	private Map<Object, Reference<Object>> WEAK_CACHE = Collections
			.synchronizedMap(new HashMap<Object, Reference<Object>>());

	private int CLEANUPCOUNT = 0;

	@Override
	public <M> M getMessageInstance(final Locale locale, final Class<M> messages,
			final BundleLocalization localization) {
		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, localization);
		} else {
			instance = AccessController.doPrivileged(new PrivilegedAction<M>() {

				public M run() {
					return createInstance(locale, messages, annotation, localization);
				}

			});
		}

		if (cache != null) {
			if (type == ReferenceType.SOFT) {
				cache.put(key, new SoftReference<Object>(instance));
			} else if (type == ReferenceType.WEAK) {
				cache.put(key, new WeakReference<Object>(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 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} or
	 *         <code>null</code> if an error occured on creating the instance.
	 */
	private static <M> M createInstance(Locale locale, Class<M> messages, Message annotation,
			BundleLocalization localization) {

		ResourceBundle resourceBundle = null;
		if (annotation != null && annotation.contributorURI().length() > 0) {
			resourceBundle = ResourceBundleHelper.getResourceBundleForUri(
					annotation.contributorURI(), locale, localization);
		}

		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 = localization.getLocalization(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 (int i = 0; i < fields.length; i++) {
				if (!fields[i].isAccessible()) {
					fields[i].setAccessible(true);
				}

				fields[i].set(instance, provider.translate(fields[i].getName()));
			}
		} catch (InstantiationException e) {
			if (logService != null)
				logService.log(LogService.LOG_ERROR, "Instantiation of messages class failed", e); //$NON-NLS-1$
		} catch (IllegalAccessException e) {
			if (logService != null)
				logService.log(LogService.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 static void processPostConstruct(Object messageObject, Class<?> messageClass) {
		if (messageObject != null) {
			Method[] methods = messageClass.getDeclaredMethods();
			for (int i = 0; i < methods.length; i++) {
				Method method = methods[i];
				if (!method.isAnnotationPresent(PostConstruct.class)) {
					continue;
				} else {
					try {
						method.invoke(messageObject);
					} catch (Exception e) {
						if (logService != null)
							logService
									.log(LogService.LOG_ERROR,
											"Exception on trying to execute the @PostConstruct annotated method in " + messageClass, e); //$NON-NLS-1$
					}
				}
			}
		}
	}

}

Back to the top