1 package com.atlassian.refapp.sal.message;
2
3 import com.atlassian.plugin.Plugin;
4 import com.atlassian.plugin.PluginAccessor;
5 import com.atlassian.plugin.elements.ResourceDescriptor;
6 import com.atlassian.plugin.event.PluginEventListener;
7 import com.atlassian.plugin.event.PluginEventManager;
8 import com.atlassian.plugin.event.events.PluginDisabledEvent;
9 import com.atlassian.plugin.event.events.PluginEnabledEvent;
10 import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;
11 import com.atlassian.sal.api.message.I18nResolver;
12 import com.atlassian.sal.core.message.AbstractI18nResolver;
13 import com.atlassian.util.concurrent.ManagedLock;
14 import com.atlassian.util.concurrent.ManagedLocks;
15 import com.atlassian.util.concurrent.Supplier;
16 import org.apache.commons.lang.StringUtils;
17
18 import javax.inject.Inject;
19 import javax.inject.Named;
20 import java.io.Serializable;
21 import java.text.MessageFormat;
22 import java.util.Enumeration;
23 import java.util.HashMap;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.MissingResourceException;
30 import java.util.ResourceBundle;
31 import java.util.concurrent.ConcurrentHashMap;
32 import java.util.concurrent.locks.ReentrantReadWriteLock;
33
34
35
36
37 @ExportAsService(I18nResolver.class)
38 @Named("i18nResolver")
39 public class RefimplI18nResolver extends AbstractI18nResolver {
40 private ManagedLock.ReadWrite locks = ManagedLocks.manageReadWrite(new ReentrantReadWriteLock());
41 private final Map<Plugin, Iterable<String>> pluginResourceBundleNames = new ConcurrentHashMap<Plugin, Iterable<String>>();
42
43 private final ResourceBundleResolver resolver;
44
45 @Inject
46 public RefimplI18nResolver(final PluginAccessor pluginAccessor,
47 PluginEventManager pluginEventManager,
48 ResourceBundleResolver resolver) {
49 pluginEventManager.register(this);
50 this.resolver = assertNotNull(resolver, "resolver");
51
52 locks.write().withLock(new Runnable() {
53 public void run() {
54 addPluginResourceBundles(pluginAccessor.getPlugins());
55 }
56 });
57 }
58
59 @Override
60 public String getRawText(String key) {
61 return StringUtils.defaultString(getPattern(Locale.getDefault(), key), key);
62 }
63
64 @Override
65 public String getRawText(Locale locale, String key) {
66 return StringUtils.defaultString(getPattern(locale, key), key);
67 }
68
69 public String resolveText(String key, Serializable[] arguments) {
70 String pattern = getPattern(Locale.getDefault(), key);
71 if (pattern == null) {
72 return key;
73 }
74 return MessageFormat.format(pattern, (Object[]) arguments);
75 }
76
77 @Override
78 public String resolveText(final Locale locale, final String key, final Serializable[] arguments) {
79 String pattern = StringUtils.defaultString(getPattern(locale, key), getPattern(Locale.getDefault(), key));
80 if (pattern == null) {
81 return key;
82 }
83 return MessageFormat.format(pattern, (Object[]) arguments);
84 }
85
86 private String getPattern(final Locale locale, final String key) {
87 return locks.read().withLock(new Supplier<String>() {
88 public String get() {
89 String bundleString = null;
90 for (Entry<Plugin, Iterable<String>> pluginBundleNames : pluginResourceBundleNames.entrySet()) {
91 for (String bundleName : pluginBundleNames.getValue()) {
92 try {
93 ResourceBundle bundle = getBundle(bundleName, locale, pluginBundleNames.getKey());
94 bundleString = bundle.getString(key);
95 } catch (MissingResourceException e) {
96
97 }
98 }
99 }
100 return bundleString;
101 }
102 });
103 }
104
105 public Map<String, String> getAllTranslationsForPrefix(final String prefix) {
106 assertNotNull(prefix, "prefix");
107
108 return locks.read().withLock(new Supplier<Map<String, String>>() {
109 public Map<String, String> get() {
110 Map<String, String> translationsWithPrefix = new HashMap<String, String>();
111 for (Entry<Plugin, Iterable<String>> pluginBundleNames : pluginResourceBundleNames.entrySet()) {
112
113 addMatchingTranslationsToMap(
114 prefix, Locale.getDefault(), pluginBundleNames.getKey(), pluginBundleNames.getValue(), translationsWithPrefix);
115 }
116 return translationsWithPrefix;
117 }
118 });
119 }
120
121 public Map<String, String> getAllTranslationsForPrefix(final String prefix, final Locale locale) {
122 assertNotNull(prefix, "prefix");
123 assertNotNull(locale, "locale");
124
125 return locks.read().withLock(new Supplier<Map<String, String>>() {
126 public Map<String, String> get() {
127 Map<String, String> translationsWithPrefix = new HashMap<String, String>();
128 for (Entry<Plugin, Iterable<String>> pluginBundleNames : pluginResourceBundleNames.entrySet()) {
129 addMatchingTranslationsToMap(
130 prefix, locale, pluginBundleNames.getKey(), pluginBundleNames.getValue(), translationsWithPrefix);
131 }
132 return translationsWithPrefix;
133 }
134 });
135 }
136
137 private void addMatchingTranslationsToMap(String prefix, Locale locale, Plugin plugin,
138 Iterable<String> bundleNames,
139 Map<String, String> translationsWithPrefix) {
140 for (String bundleName : bundleNames) {
141 try {
142 ResourceBundle bundle = getBundle(bundleName, locale, plugin);
143 if (bundle != null) {
144 addMatchingTranslationsToMap(prefix, bundle, translationsWithPrefix);
145 }
146 } catch (MissingResourceException e) {
147
148 }
149 }
150 }
151
152 private void addMatchingTranslationsToMap(String prefix, ResourceBundle bundle,
153 Map<String, String> translationsWithPrefix) {
154 Enumeration enumeration = bundle.getKeys();
155 while (enumeration.hasMoreElements()) {
156 String key = (String) enumeration.nextElement();
157 if (key.startsWith(prefix)) {
158 translationsWithPrefix.put(key, bundle.getString(key));
159 }
160 }
161 }
162
163 @PluginEventListener
164 public void pluginEnabled(final PluginEnabledEvent event) {
165 locks.write().withLock(new Runnable() {
166 public void run() {
167 addPluginResourceBundles(event.getPlugin());
168 }
169 });
170 }
171
172 @PluginEventListener
173 public void pluginDisabled(final PluginDisabledEvent event) {
174 locks.write().withLock(new Runnable() {
175 public void run() {
176 removePluginResourceBundles(event.getPlugin());
177 }
178 });
179 }
180
181 private void addPluginResourceBundles(Iterable<Plugin> plugins) {
182 for (Plugin plugin : plugins) {
183 addPluginResourceBundles(plugin);
184 }
185 }
186
187 private void addPluginResourceBundles(Plugin plugin) {
188 List<String> bundleNames = new LinkedList<String>();
189 Iterable<ResourceDescriptor> descriptors = plugin.getResourceDescriptors("i18n");
190 for (ResourceDescriptor descriptor : descriptors) {
191 bundleNames.add(descriptor.getLocation());
192 }
193 addPluginResourceBundles(plugin, bundleNames);
194 }
195
196 private void addPluginResourceBundles(Plugin plugin, List<String> bundleNames) {
197 pluginResourceBundleNames.put(plugin, bundleNames);
198 }
199
200 private void removePluginResourceBundles(Plugin plugin) {
201 pluginResourceBundleNames.remove(plugin);
202 }
203
204 private ResourceBundle getBundle(String bundleName, Locale locale, Plugin plugin) {
205 return resolver.getBundle(bundleName, locale, plugin.getClassLoader());
206 }
207
208 private static <T> T assertNotNull(T object, String name) {
209 if (object == null) {
210 throw new NullPointerException(name + " must not be null");
211 }
212 return object;
213 }
214 }