View Javadoc
1   package com.atlassian.plugin.refimpl.saldeps;
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.sal.api.message.I18nResolver;
11  import com.atlassian.sal.api.message.Message;
12  import com.atlassian.sal.api.message.MessageCollection;
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 java.io.Serializable;
19  import java.text.MessageFormat;
20  import java.util.Enumeration;
21  import java.util.HashMap;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.MissingResourceException;
28  import java.util.ResourceBundle;
29  import java.util.concurrent.ConcurrentHashMap;
30  import java.util.concurrent.locks.ReentrantReadWriteLock;
31  
32  import static com.google.common.base.Preconditions.checkNotNull;
33  
34  /**
35   * This is a copy of com.atlassian.refapp.sal.message.RefimplI18nResolver. It's been copied because it's required
36   * by {@link com.atlassian.plugin.refimpl.ContainerManager} *before* the plugin system is available.
37   *
38   * This is not a direct clone of RefimplI18nResolver; it copies in both
39   * com.atlassian.sal.core.message.AbstractI18nResolver and RefimplI18nResolver as sal-core is not available either.
40   */
41  public class CoreRefimplI18nResolver implements I18nResolver {
42      private ManagedLock.ReadWrite locks = ManagedLocks.manageReadWrite(new ReentrantReadWriteLock());
43      private final Map<Plugin, Iterable<String>> pluginResourceBundleNames = new ConcurrentHashMap<Plugin, Iterable<String>>();
44  
45      public CoreRefimplI18nResolver(final PluginAccessor pluginAccessor,
46                                     PluginEventManager pluginEventManager) {
47          pluginEventManager.register(this);
48  
49          locks.write().withLock(new Runnable() {
50              public void run() {
51                  addPluginResourceBundles(pluginAccessor.getPlugins());
52              }
53          });
54      }
55  
56      private static final Serializable[] EMPTY_SERIALIZABLE = new Serializable[0];
57  
58      public String getText(String key, Serializable... arguments) {
59          Serializable[] resolvedArguments = new Serializable[arguments.length];
60          for (int i = 0; i < arguments.length; i++) {
61              Serializable argument = arguments[i];
62              if (argument instanceof Message) {
63                  resolvedArguments[i] = getText((Message) argument);
64              } else {
65                  resolvedArguments[i] = arguments[i];
66              }
67          }
68          return resolveText(key, resolvedArguments);
69      }
70  
71      public String getText(Locale locale, String key, Serializable... arguments) {
72          checkNotNull(locale);
73          Serializable[] resolvedArguments = new Serializable[arguments.length];
74          for (int i = 0; i < arguments.length; i++) {
75              Serializable argument = arguments[i];
76              if (argument instanceof Message) {
77                  resolvedArguments[i] = getText(locale, (Message) argument);
78              } else {
79                  resolvedArguments[i] = arguments[i];
80              }
81          }
82          return resolveText(locale, key, resolvedArguments);
83      }
84  
85      public String getText(String key) {
86          return resolveText(key, EMPTY_SERIALIZABLE);
87      }
88  
89      public String getText(Locale locale, String key) {
90          checkNotNull(locale);
91          return resolveText(locale, key, EMPTY_SERIALIZABLE);
92      }
93  
94      public String getText(Message message) {
95          return getText(message.getKey(), message.getArguments());
96      }
97  
98      public String getText(Locale locale, Message message) {
99          return getText(locale, message.getKey(), message.getArguments());
100     }
101 
102     public Message createMessage(String key, Serializable... arguments) {
103         return new DefaultMessage(key, arguments);
104     }
105 
106     public MessageCollection createMessageCollection() {
107         return new DefaultMessageCollection();
108     }
109 
110     @Override
111     public String getRawText(String key) {
112         return StringUtils.defaultString(getPattern(Locale.getDefault(), key), key);
113     }
114 
115     @Override
116     public String getRawText(Locale locale, String key) {
117         return StringUtils.defaultString(getPattern(locale, key), key);
118     }
119 
120     public String resolveText(String key, Serializable[] arguments) {
121         String pattern = getPattern(Locale.getDefault(), key);
122         if (pattern == null) {
123             return key;
124         }
125         return MessageFormat.format(pattern, (Object[]) arguments);
126     }
127 
128     public String resolveText(final Locale locale, final String key, final Serializable[] arguments) {
129         String pattern = StringUtils.defaultString(getPattern(locale, key), getPattern(Locale.getDefault(), key));
130         if (pattern == null) {
131             return key;
132         }
133         return MessageFormat.format(pattern, (Object[]) arguments);
134     }
135 
136     private String getPattern(final Locale locale, final String key) {
137         return locks.read().withLock(new Supplier<String>() {
138             public String get() {
139                 String bundleString = null;
140                 for (Entry<Plugin, Iterable<String>> pluginBundleNames : pluginResourceBundleNames.entrySet()) {
141                     for (String bundleName : pluginBundleNames.getValue()) {
142                         try {
143                             ResourceBundle bundle = getBundle(bundleName, locale, pluginBundleNames.getKey());
144                             bundleString = bundle.getString(key);
145                         } catch (MissingResourceException e) {
146                             // ignore, try next bundle
147                         }
148                     }
149                 }
150                 return bundleString;
151             }
152         });
153     }
154 
155     public Map<String, String> getAllTranslationsForPrefix(final String prefix) {
156         assertNotNull(prefix, "prefix");
157 
158         return locks.read().withLock(new Supplier<Map<String, String>>() {
159             public Map<String, String> get() {
160                 Map<String, String> translationsWithPrefix = new HashMap<String, String>();
161                 for (Entry<Plugin, Iterable<String>> pluginBundleNames : pluginResourceBundleNames.entrySet()) {
162 
163                     addMatchingTranslationsToMap(
164                             prefix, Locale.getDefault(), pluginBundleNames.getKey(), pluginBundleNames.getValue(), translationsWithPrefix);
165                 }
166                 return translationsWithPrefix;
167             }
168         });
169     }
170 
171     public Map<String, String> getAllTranslationsForPrefix(final String prefix, final Locale locale) {
172         assertNotNull(prefix, "prefix");
173         assertNotNull(locale, "locale");
174 
175         return locks.read().withLock(new Supplier<Map<String, String>>() {
176             public Map<String, String> get() {
177                 Map<String, String> translationsWithPrefix = new HashMap<String, String>();
178                 for (Entry<Plugin, Iterable<String>> pluginBundleNames : pluginResourceBundleNames.entrySet()) {
179                     addMatchingTranslationsToMap(
180                             prefix, locale, pluginBundleNames.getKey(), pluginBundleNames.getValue(), translationsWithPrefix);
181                 }
182                 return translationsWithPrefix;
183             }
184         });
185     }
186 
187     private void addMatchingTranslationsToMap(String prefix, Locale locale, Plugin plugin,
188                                               Iterable<String> bundleNames,
189                                               Map<String, String> translationsWithPrefix) {
190         for (String bundleName : bundleNames) {
191             try {
192                 ResourceBundle bundle = getBundle(bundleName, locale, plugin);
193                 if (bundle != null) {
194                     addMatchingTranslationsToMap(prefix, bundle, translationsWithPrefix);
195                 }
196             } catch (MissingResourceException e) {
197                 // OK, just ignore
198             }
199         }
200     }
201 
202     private void addMatchingTranslationsToMap(String prefix, ResourceBundle bundle,
203                                               Map<String, String> translationsWithPrefix) {
204         Enumeration enumeration = bundle.getKeys();
205         while (enumeration.hasMoreElements()) {
206             String key = (String) enumeration.nextElement();
207             if (key.startsWith(prefix)) {
208                 translationsWithPrefix.put(key, bundle.getString(key));
209             }
210         }
211     }
212 
213     @PluginEventListener
214     public void pluginEnabled(final PluginEnabledEvent event) {
215         locks.write().withLock(new Runnable() {
216             public void run() {
217                 addPluginResourceBundles(event.getPlugin());
218             }
219         });
220     }
221 
222     @PluginEventListener
223     public void pluginDisabled(final PluginDisabledEvent event) {
224         locks.write().withLock(new Runnable() {
225             public void run() {
226                 removePluginResourceBundles(event.getPlugin());
227             }
228         });
229     }
230 
231     private void addPluginResourceBundles(Iterable<Plugin> plugins) {
232         for (Plugin plugin : plugins) {
233             addPluginResourceBundles(plugin);
234         }
235     }
236 
237     private void addPluginResourceBundles(Plugin plugin) {
238         List<String> bundleNames = new LinkedList<String>();
239         Iterable<ResourceDescriptor> descriptors = plugin.getResourceDescriptors("i18n");
240         for (ResourceDescriptor descriptor : descriptors) {
241             bundleNames.add(descriptor.getLocation());
242         }
243         addPluginResourceBundles(plugin, bundleNames);
244     }
245 
246     private void addPluginResourceBundles(Plugin plugin, List<String> bundleNames) {
247         pluginResourceBundleNames.put(plugin, bundleNames);
248     }
249 
250     private void removePluginResourceBundles(Plugin plugin) {
251         pluginResourceBundleNames.remove(plugin);
252     }
253 
254     private ResourceBundle getBundle(String bundleName, Locale locale, Plugin plugin) {
255         return ResourceBundle.getBundle(bundleName, locale, plugin.getClassLoader());
256     }
257 
258     private static <T> T assertNotNull(T object, String name) {
259         if (object == null) {
260             throw new NullPointerException(name + " must not be null");
261         }
262         return object;
263     }
264 }