1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   * 
13   */
14  
15  package com.liferay.util;
16  
17  import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
18  import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
19  import com.liferay.portal.kernel.language.LanguageUtil;
20  import com.liferay.portal.kernel.log.Log;
21  import com.liferay.portal.kernel.log.LogFactoryUtil;
22  import com.liferay.portal.kernel.util.LocaleUtil;
23  import com.liferay.portal.kernel.util.ParamUtil;
24  import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
25  import com.liferay.portal.kernel.util.StringPool;
26  import com.liferay.portal.kernel.util.StringUtil;
27  import com.liferay.portal.kernel.util.Tuple;
28  import com.liferay.portal.kernel.util.Validator;
29  
30  import java.util.HashMap;
31  import java.util.Locale;
32  import java.util.Map;
33  
34  import javax.portlet.ActionRequest;
35  import javax.portlet.PortletPreferences;
36  import javax.portlet.PortletRequest;
37  
38  import javax.xml.stream.XMLInputFactory;
39  import javax.xml.stream.XMLOutputFactory;
40  import javax.xml.stream.XMLStreamConstants;
41  import javax.xml.stream.XMLStreamException;
42  import javax.xml.stream.XMLStreamReader;
43  import javax.xml.stream.XMLStreamWriter;
44  
45  import org.apache.commons.collections.map.ReferenceMap;
46  
47  /**
48   * <a href="LocalizationUtil.java.html"><b><i>View Source</i></b></a>
49   *
50   * <p>
51   * This class is used to localize values stored in XML and is often used to add
52   * localization behavior to value objects.
53   * </p>
54   *
55   * <p>
56   * Caching of the localized values is done in this class rather than in the
57   * value object since value objects get flushed from cache fairly quickly.
58   * Though lookups performed on a key based on an XML file is slower than lookups
59   * done at the value object level in general, the value object will get flushed
60   * at a rate which works against the performance gain. The cache is a soft hash
61   * map which prevents memory leaks within the system while enabling the cache to
62   * live longer than in a weak hash map.
63   * </p>
64   *
65   * @author Alexander Chow
66   * @author Jorge Ferrer
67   * @author Mauro Mariuzzo
68   */
69  public class LocalizationUtil {
70  
71      public static String[] getAvailableLocales(String xml) {
72          String attributeValue = _getRootAttribute(
73              xml, _AVAILABLE_LOCALES, StringPool.BLANK);
74  
75          return StringUtil.split(attributeValue);
76      }
77  
78      public static String getDefaultLocale(String xml) {
79          String defaultLanguageId = LocaleUtil.toLanguageId(
80              LocaleUtil.getDefault());
81  
82          return _getRootAttribute(xml, _DEFAULT_LOCALE, defaultLanguageId);
83      }
84  
85      public static String getLocalization(
86          String xml, String requestedLanguageId) {
87  
88          return getLocalization(xml, requestedLanguageId, true);
89      }
90  
91      public static String getLocalization(
92          String xml, String requestedLanguageId, boolean useDefault) {
93  
94          String value = _getCachedValue(xml, requestedLanguageId, useDefault);
95  
96          if (value != null) {
97              return value;
98          }
99          else {
100             value = StringPool.BLANK;
101         }
102 
103         String systemDefaultLanguageId = LocaleUtil.toLanguageId(
104             LocaleUtil.getDefault());
105 
106         String defaultValue = StringPool.BLANK;
107 
108         if (!Validator.isXml(xml)) {
109             if (requestedLanguageId.equals(systemDefaultLanguageId)) {
110                 value = xml;
111             }
112             else {
113                 value = defaultValue;
114             }
115 
116             _setCachedValue(xml, requestedLanguageId, useDefault, value);
117 
118             return value;
119         }
120 
121         XMLStreamReader xmlStreamReader = null;
122 
123         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
124 
125         Thread currentThread = Thread.currentThread();
126 
127         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
128 
129         try {
130             if (contextClassLoader != portalClassLoader) {
131                 currentThread.setContextClassLoader(portalClassLoader);
132             }
133 
134             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
135 
136             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
137                 new UnsyncStringReader(xml));
138 
139             String defaultLanguageId = StringPool.BLANK;
140 
141             // Skip root node
142 
143             if (xmlStreamReader.hasNext()) {
144                 xmlStreamReader.nextTag();
145 
146                 defaultLanguageId = xmlStreamReader.getAttributeValue(
147                     null, _DEFAULT_LOCALE);
148 
149                 if (Validator.isNull(defaultLanguageId)) {
150                     defaultLanguageId = systemDefaultLanguageId;
151                 }
152             }
153 
154             // Find specified language and/or default language
155 
156             while (xmlStreamReader.hasNext()) {
157                 int event = xmlStreamReader.next();
158 
159                 if (event == XMLStreamConstants.START_ELEMENT) {
160                     String languageId = xmlStreamReader.getAttributeValue(
161                         null, _LANGUAGE_ID);
162 
163                     if (Validator.isNull(languageId)) {
164                         languageId = defaultLanguageId;
165                     }
166 
167                     if (languageId.equals(defaultLanguageId) ||
168                         languageId.equals(requestedLanguageId)) {
169 
170                         while (xmlStreamReader.hasNext()) {
171                             event = xmlStreamReader.next();
172 
173                             if (event == XMLStreamConstants.CHARACTERS ||
174                                 event == XMLStreamConstants.CDATA) {
175 
176                                 String text = xmlStreamReader.getText();
177 
178                                 if (languageId.equals(defaultLanguageId)) {
179                                     defaultValue = text;
180                                 }
181 
182                                 if (languageId.equals(requestedLanguageId)) {
183                                     value = text;
184                                 }
185 
186                                 break;
187                             }
188                             else if (event == XMLStreamConstants.END_ELEMENT) {
189                                 break;
190                             }
191                         }
192 
193                         if (Validator.isNotNull(value)) {
194                             break;
195                         }
196                     }
197                 }
198                 else if (event == XMLStreamConstants.END_DOCUMENT) {
199                     break;
200                 }
201             }
202 
203             if (useDefault && Validator.isNull(value)) {
204                 value = defaultValue;
205             }
206         }
207         catch (Exception e) {
208             if (_log.isWarnEnabled()) {
209                 _log.warn(e, e);
210             }
211         }
212         finally {
213             if (contextClassLoader != portalClassLoader) {
214                 currentThread.setContextClassLoader(contextClassLoader);
215             }
216 
217             if (xmlStreamReader != null) {
218                 try {
219                     xmlStreamReader.close();
220                 }
221                 catch (Exception e) {
222                 }
223             }
224         }
225 
226         _setCachedValue(xml, requestedLanguageId, useDefault, value);
227 
228         return value;
229     }
230 
231     public static Map<Locale, String> getLocalizationMap(
232         PortletRequest portletRequest, String parameter) {
233 
234         Locale[] locales = LanguageUtil.getAvailableLocales();
235 
236         Map<Locale, String> map = new HashMap<Locale, String>();
237 
238         for (Locale locale : locales) {
239             String languageId = LocaleUtil.toLanguageId(locale);
240 
241             String localeParameter =
242                 parameter + StringPool.UNDERLINE + languageId;
243 
244             map.put(
245                 locale, ParamUtil.getString(portletRequest, localeParameter));
246         }
247 
248         return map;
249     }
250 
251     public static Map<Locale, String> getLocalizationMap(String xml) {
252         Locale[] locales = LanguageUtil.getAvailableLocales();
253 
254         Map<Locale, String> map = new HashMap<Locale, String>();
255 
256         for (Locale locale : locales) {
257             String languageId = LocaleUtil.toLanguageId(locale);
258 
259             map.put(locale, getLocalization(xml, languageId));
260         }
261 
262         return map;
263     }
264 
265     /**
266      * @deprecated Use <code>getLocalizationMap</code>.
267      */
268     public static Map<Locale, String> getLocalizedParameter(
269         PortletRequest portletRequest, String parameter) {
270 
271         return getLocalizationMap(portletRequest, parameter);
272     }
273 
274     public static String getPreferencesValue(
275         PortletPreferences preferences, String key, String languageId) {
276 
277         return getPreferencesValue(preferences, key, languageId, true);
278     }
279 
280     public static String getPreferencesValue(
281         PortletPreferences preferences, String key, String languageId,
282         boolean useDefault) {
283 
284         String localizedKey = _getPreferencesKey(key, languageId);
285 
286         String value = preferences.getValue(localizedKey, StringPool.BLANK);
287 
288         if (useDefault && Validator.isNull(value)) {
289             value = preferences.getValue(key, StringPool.BLANK);
290         }
291 
292         return value;
293     }
294 
295     public static String[] getPreferencesValues(
296         PortletPreferences preferences, String key, String languageId) {
297 
298         return getPreferencesValues(preferences, key, languageId, true);
299     }
300 
301     public static String[] getPreferencesValues(
302         PortletPreferences preferences, String key, String languageId,
303         boolean useDefault) {
304 
305         String localizedKey = _getPreferencesKey(key, languageId);
306 
307         String[] values = preferences.getValues(localizedKey, new String[0]);
308 
309         if (useDefault && Validator.isNull(values)) {
310             values = preferences.getValues(key, new String[0]);
311         }
312 
313         return values;
314     }
315 
316     public static String removeLocalization(
317         String xml, String key, String requestedLanguageId) {
318 
319         return removeLocalization(xml, key, requestedLanguageId, false);
320     }
321 
322     public static String removeLocalization(
323         String xml, String key, String requestedLanguageId, boolean cdata) {
324 
325         if (Validator.isNull(xml)) {
326             return StringPool.BLANK;
327         }
328 
329         xml = _sanitizeXML(xml);
330 
331         String systemDefaultLanguageId = LocaleUtil.toLanguageId(
332             LocaleUtil.getDefault());
333 
334         XMLStreamReader xmlStreamReader = null;
335         XMLStreamWriter xmlStreamWriter = null;
336 
337         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
338 
339         Thread currentThread = Thread.currentThread();
340 
341         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
342 
343         try {
344             if (contextClassLoader != portalClassLoader) {
345                 currentThread.setContextClassLoader(portalClassLoader);
346             }
347 
348             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
349 
350             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
351                 new UnsyncStringReader(xml));
352 
353             String availableLocales = StringPool.BLANK;
354             String defaultLanguageId = StringPool.BLANK;
355 
356             // Read root node
357 
358             if (xmlStreamReader.hasNext()) {
359                 xmlStreamReader.nextTag();
360 
361                 availableLocales = xmlStreamReader.getAttributeValue(
362                     null, _AVAILABLE_LOCALES);
363                 defaultLanguageId = xmlStreamReader.getAttributeValue(
364                     null, _DEFAULT_LOCALE);
365 
366                 if (Validator.isNull(defaultLanguageId)) {
367                     defaultLanguageId = systemDefaultLanguageId;
368                 }
369             }
370 
371             if ((availableLocales != null) &&
372                 (availableLocales.indexOf(requestedLanguageId) != -1)) {
373 
374                 availableLocales = StringUtil.remove(
375                     availableLocales, requestedLanguageId, StringPool.COMMA);
376 
377                 UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter(
378                     true);
379 
380                 XMLOutputFactory xmlOutputFactory =
381                     XMLOutputFactory.newInstance();
382 
383                 xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(
384                     unsyncStringWriter);
385 
386                 xmlStreamWriter.writeStartDocument();
387                 xmlStreamWriter.writeStartElement(_ROOT);
388                 xmlStreamWriter.writeAttribute(
389                     _AVAILABLE_LOCALES, availableLocales);
390                 xmlStreamWriter.writeAttribute(
391                     _DEFAULT_LOCALE, defaultLanguageId);
392 
393                 _copyNonExempt(
394                     xmlStreamReader, xmlStreamWriter, requestedLanguageId,
395                     defaultLanguageId, cdata);
396 
397                 xmlStreamWriter.writeEndElement();
398                 xmlStreamWriter.writeEndDocument();
399 
400                 xmlStreamWriter.close();
401                 xmlStreamWriter = null;
402 
403                 xml = unsyncStringWriter.toString();
404             }
405         }
406         catch (Exception e) {
407             if (_log.isWarnEnabled()) {
408                 _log.warn(e, e);
409             }
410         }
411         finally {
412             if (contextClassLoader != portalClassLoader) {
413                 currentThread.setContextClassLoader(contextClassLoader);
414             }
415 
416             if (xmlStreamReader != null) {
417                 try {
418                     xmlStreamReader.close();
419                 }
420                 catch (Exception e) {
421                 }
422             }
423 
424             if (xmlStreamWriter != null) {
425                 try {
426                     xmlStreamWriter.close();
427                 }
428                 catch (Exception e) {
429                 }
430             }
431         }
432 
433         return xml;
434     }
435 
436     public static void setLocalizedPreferencesValues (
437             ActionRequest actionRequest, PortletPreferences preferences,
438             String parameter)
439         throws Exception {
440 
441         Map<Locale, String> map = getLocalizedParameter(
442             actionRequest, parameter);
443 
444         for (Locale locale : map.keySet()) {
445             String languageId = LocaleUtil.toLanguageId(locale);
446 
447             String key = parameter + StringPool.UNDERLINE + languageId;
448             String value = map.get(locale);
449 
450             preferences.setValue(key, value);
451         }
452     }
453 
454     public static void setPreferencesValue(
455             PortletPreferences preferences, String key, String languageId,
456             String value)
457         throws Exception {
458 
459         preferences.setValue(_getPreferencesKey(key, languageId), value);
460     }
461 
462     public static void setPreferencesValues(
463             PortletPreferences preferences, String key, String languageId,
464             String[] values)
465         throws Exception {
466 
467         preferences.setValues(_getPreferencesKey(key, languageId), values);
468     }
469 
470     public static String updateLocalization(
471         String xml, String key, String value) {
472 
473         String defaultLanguageId = LocaleUtil.toLanguageId(
474             LocaleUtil.getDefault());
475 
476         return updateLocalization(
477             xml, key, value, defaultLanguageId, defaultLanguageId);
478     }
479 
480     public static String updateLocalization(
481         String xml, String key, String value, String requestedLanguageId) {
482 
483         String defaultLanguageId = LocaleUtil.toLanguageId(
484             LocaleUtil.getDefault());
485 
486         return updateLocalization(
487             xml, key, value, requestedLanguageId, defaultLanguageId);
488     }
489 
490     public static String updateLocalization(
491         String xml, String key, String value, String requestedLanguageId,
492         String defaultLanguageId) {
493 
494         return updateLocalization(
495             xml, key, value, requestedLanguageId, defaultLanguageId, false);
496     }
497 
498     public static String updateLocalization(
499         String xml, String key, String value, String requestedLanguageId,
500         String defaultLanguageId, boolean cdata) {
501 
502         xml = _sanitizeXML(xml);
503 
504         XMLStreamReader xmlStreamReader = null;
505         XMLStreamWriter xmlStreamWriter = null;
506 
507         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
508 
509         Thread currentThread = Thread.currentThread();
510 
511         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
512 
513         try {
514             if (contextClassLoader != portalClassLoader) {
515                 currentThread.setContextClassLoader(portalClassLoader);
516             }
517 
518             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
519 
520             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
521                 new UnsyncStringReader(xml));
522 
523             String availableLocales = StringPool.BLANK;
524 
525             // Read root node
526 
527             if (xmlStreamReader.hasNext()) {
528                 xmlStreamReader.nextTag();
529 
530                 availableLocales = xmlStreamReader.getAttributeValue(
531                     null, _AVAILABLE_LOCALES);
532 
533                 if (Validator.isNull(availableLocales)) {
534                     availableLocales = defaultLanguageId;
535                 }
536 
537                 if (availableLocales.indexOf(requestedLanguageId) == -1) {
538                     availableLocales = StringUtil.add(
539                         availableLocales, requestedLanguageId,
540                         StringPool.COMMA);
541                 }
542             }
543 
544             UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter(
545                 true);
546 
547             XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
548 
549             xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(
550                 unsyncStringWriter);
551 
552             xmlStreamWriter.writeStartDocument();
553             xmlStreamWriter.writeStartElement(_ROOT);
554             xmlStreamWriter.writeAttribute(
555                 _AVAILABLE_LOCALES, availableLocales);
556             xmlStreamWriter.writeAttribute(_DEFAULT_LOCALE, defaultLanguageId);
557 
558             _copyNonExempt(
559                 xmlStreamReader, xmlStreamWriter, requestedLanguageId,
560                 defaultLanguageId, cdata);
561 
562             if (cdata) {
563                 xmlStreamWriter.writeStartElement(key);
564                 xmlStreamWriter.writeAttribute(
565                     _LANGUAGE_ID, requestedLanguageId);
566                 xmlStreamWriter.writeCData(value);
567                 xmlStreamWriter.writeEndElement();
568             }
569             else {
570                 xmlStreamWriter.writeStartElement(key);
571                 xmlStreamWriter.writeAttribute(
572                     _LANGUAGE_ID, requestedLanguageId);
573                 xmlStreamWriter.writeCharacters(value);
574                 xmlStreamWriter.writeEndElement();
575             }
576 
577             xmlStreamWriter.writeEndElement();
578             xmlStreamWriter.writeEndDocument();
579 
580             xmlStreamWriter.close();
581             xmlStreamWriter = null;
582 
583             xml = unsyncStringWriter.toString();
584         }
585         catch (Exception e) {
586             if (_log.isWarnEnabled()) {
587                 _log.warn(e, e);
588             }
589         }
590         finally {
591             if (contextClassLoader != portalClassLoader) {
592                 currentThread.setContextClassLoader(contextClassLoader);
593             }
594 
595             if (xmlStreamReader != null) {
596                 try {
597                     xmlStreamReader.close();
598                 }
599                 catch (Exception e) {
600                 }
601             }
602 
603             if (xmlStreamWriter != null) {
604                 try {
605                     xmlStreamWriter.close();
606                 }
607                 catch (Exception e) {
608                 }
609             }
610         }
611 
612         return xml;
613     }
614 
615     private static void _copyNonExempt(
616             XMLStreamReader xmlStreamReader, XMLStreamWriter xmlStreamWriter,
617             String exemptLanguageId, String defaultLanguageId, boolean cdata)
618         throws XMLStreamException {
619 
620         while (xmlStreamReader.hasNext()) {
621             int event = xmlStreamReader.next();
622 
623             if (event == XMLStreamConstants.START_ELEMENT) {
624                 String languageId = xmlStreamReader.getAttributeValue(
625                     null, _LANGUAGE_ID);
626 
627                 if (Validator.isNull(languageId)) {
628                     languageId = defaultLanguageId;
629                 }
630 
631                 if (!languageId.equals(exemptLanguageId)) {
632                     xmlStreamWriter.writeStartElement(
633                         xmlStreamReader.getLocalName());
634                     xmlStreamWriter.writeAttribute(_LANGUAGE_ID, languageId);
635 
636                     while (xmlStreamReader.hasNext()) {
637                         event = xmlStreamReader.next();
638 
639                         if (event == XMLStreamConstants.CHARACTERS ||
640                             event == XMLStreamConstants.CDATA) {
641 
642                             String text = xmlStreamReader.getText();
643 
644                             if (cdata) {
645                                 xmlStreamWriter.writeCData(text);
646                             }
647                             else {
648                                 xmlStreamWriter.writeCharacters(
649                                     xmlStreamReader.getText());
650                             }
651 
652                             break;
653                         }
654                         else if (event == XMLStreamConstants.END_ELEMENT) {
655                             break;
656                         }
657                     }
658 
659                     xmlStreamWriter.writeEndElement();
660                 }
661             }
662             else if (event == XMLStreamConstants.END_DOCUMENT) {
663                 break;
664             }
665         }
666     }
667 
668     private static String _getCachedValue(
669         String xml, String requestedLanguageId, boolean useDefault) {
670 
671         String value = null;
672 
673         Map<Tuple, String> valueMap = _cache.get(xml);
674 
675         if (valueMap != null) {
676             Tuple subkey = new Tuple(useDefault, requestedLanguageId);
677 
678             value = valueMap.get(subkey);
679         }
680 
681         return value;
682     }
683 
684     private static String _getPreferencesKey(String key, String languageId) {
685         String defaultLanguageId = LocaleUtil.toLanguageId(
686             LocaleUtil.getDefault());
687 
688         if (!languageId.equals(defaultLanguageId)) {
689             key += StringPool.UNDERLINE + languageId;
690         }
691 
692         return key;
693     }
694 
695     private static String _getRootAttribute(
696         String xml, String name, String defaultValue) {
697 
698         String value = null;
699 
700         XMLStreamReader xmlStreamReader = null;
701 
702         ClassLoader portalClassLoader = PortalClassLoaderUtil.getClassLoader();
703 
704         Thread currentThread = Thread.currentThread();
705 
706         ClassLoader contextClassLoader = currentThread.getContextClassLoader();
707 
708         try {
709             if (contextClassLoader != portalClassLoader) {
710                 currentThread.setContextClassLoader(portalClassLoader);
711             }
712 
713             XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
714 
715             xmlStreamReader = xmlInputFactory.createXMLStreamReader(
716                 new UnsyncStringReader(xml));
717 
718             if (xmlStreamReader.hasNext()) {
719                 xmlStreamReader.nextTag();
720 
721                 value = xmlStreamReader.getAttributeValue(null, name);
722             }
723         }
724         catch (Exception e) {
725             if (_log.isWarnEnabled()) {
726                 _log.warn(e, e);
727             }
728         }
729         finally {
730             if (contextClassLoader != portalClassLoader) {
731                 currentThread.setContextClassLoader(contextClassLoader);
732             }
733 
734             if (xmlStreamReader != null) {
735                 try {
736                     xmlStreamReader.close();
737                 }
738                 catch (Exception e) {
739                 }
740             }
741         }
742 
743         if (Validator.isNull(value)) {
744             value = defaultValue;
745         }
746 
747         return value;
748     }
749 
750     private static String _sanitizeXML(String xml) {
751         if (Validator.isNull(xml) || (xml.indexOf("<root") == -1)) {
752             xml = _EMPTY_ROOT_NODE;
753         }
754 
755         return xml;
756     }
757 
758     private static void _setCachedValue(
759         String xml, String requestedLanguageId, boolean useDefault,
760         String value) {
761 
762         if (Validator.isNotNull(xml) && !xml.equals(_EMPTY_ROOT_NODE)) {
763             synchronized (_cache) {
764                 Map<Tuple, String> map = _cache.get(xml);
765 
766                 if (map == null) {
767                     map = new HashMap<Tuple, String>();
768                 }
769 
770                 Tuple subkey = new Tuple(useDefault, requestedLanguageId);
771 
772                 map.put(subkey, value);
773 
774                 _cache.put(xml, map);
775             }
776         }
777     }
778 
779     private static final String _AVAILABLE_LOCALES = "available-locales";
780 
781     private static final String _DEFAULT_LOCALE = "default-locale";
782 
783     private static final String _EMPTY_ROOT_NODE = "<root />";
784 
785     private static final String _LANGUAGE_ID = "language-id";
786 
787     private static final String _ROOT = "root";
788 
789     private static Log _log = LogFactoryUtil.getLog(LocalizationUtil.class);
790 
791     private static Map<String, Map<Tuple, String>> _cache = new ReferenceMap(
792         ReferenceMap.SOFT, ReferenceMap.HARD);
793 
794 }