KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > util > LocalizedString


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.util;
11
12 import java.util.*;
13 import org.mmbase.util.logging.*;
14 import org.mmbase.util.xml.DocumentReader;
15 import org.w3c.dom.*;
16
17 /**
18  *<p>
19  * A String which is localized. There are two mechanisms to find and provide translations: They can
20  * explicitely be set with {@link #set} (e.g. during parsing an XML), or a resource-bundle can be
21  * associated with {@link #setBundle}, which will be used to find translations based on the key of
22  * this object.
23  *</p>
24  *<p>
25  * The 'set' mechanism can also be driven by {@link #fillFromXml}, which provides a sensible way to fill the LocalizedString with
26  * setting from a sub element of XMLs.
27  *</p>
28  *<p>
29  * The idea is that objects of this type can be used in stead of normal String objects, for error
30  * messages, descriptions and other texts which need localization (e.g. because they are exposed to
31  * end-users).
32  *</p>
33  *
34  * @author Michiel Meeuwissen
35  * @version $Id: LocalizedString.java,v 1.25 2006/07/08 14:31:53 michiel Exp $
36  * @since MMBase-1.8
37  */

38 public class LocalizedString implements java.io.Serializable JavaDoc, Cloneable JavaDoc {
39
40     private static final Logger log = Logging.getLoggerInstance(LocalizedString.class);
41
42     private static final long serialVersionUID = 1L;
43
44     private static Locale defaultLocale = null; // means 'system default' and 'unset'.
45

46     /**
47      * Sets a default locale for this JVM or web-app. When not using it, the locale is the system
48      * default. Several web-apps do run in one JVM however and it is very imaginable that you want a
49      * different default for the Locale.
50      *
51      * So, this function can be called only once. Calling it the second time will not do
52      * anything. It returns the already set default locale then, which should probably prompt you to log an error
53      * or throw an exception or so. Otherwise it returns <code>null</code> indicating that the
54      * default locale is now what you just set.
55      */

56     public static Locale setDefault(Locale locale) {
57         if (defaultLocale != null) return defaultLocale;
58         defaultLocale = locale;
59         return null;
60     }
61     /**
62      * Returns the default locale if set, or otherwise the system default ({@link java.util.Locale#getDefault}).
63      */

64     public static Locale getDefault() {
65         return defaultLocale != null ? defaultLocale : Locale.getDefault();
66     }
67
68     /**
69      * Converts a collection of localized strings to a collection of normal strings.
70      * @param col Collection of LocalizedString objects
71      * @param locale Locale to be used for the call to {@link #get(Locale)} which obviously is needed
72      */

73     public static Collection toStrings(Collection col, Locale locale) {
74         if (col == null || col.size() == 0) return col;
75         Collection res = new ArrayList();
76         Iterator i = col.iterator();
77         while (i.hasNext()) {
78             LocalizedString s = (LocalizedString) i.next();
79             res.add(s.get(locale));
80         }
81         return res;
82     }
83
84     private String JavaDoc key;
85
86     private Map values = null;
87     private String JavaDoc bundle = null;
88
89     // just for the contract of Serializable
90
protected LocalizedString() {
91     }
92
93     /**
94      * @param k The key of this String, if k == <code>null</code> then the first set will define it.
95      */

96     public LocalizedString(String JavaDoc k) {
97         key = k;
98     }
99
100     /**
101      * Gets the key to use as a default and/or for obtaining a value from the bundle
102      */

103     public String JavaDoc getKey() {
104         return key;
105     }
106
107     /**
108      * Sets the key to use as a default and/or for obtaining a value from the bundle
109      */

110     public void setKey(String JavaDoc key) {
111         this.key = key;
112     }
113
114     /**
115      * Gets the value for a certain locale. If no match is found, it falls back to the key.
116      */

117     public String JavaDoc get(Locale locale) {
118         if (locale == null) {
119             locale = defaultLocale == null ? Locale.getDefault() : defaultLocale;
120         }
121         if (values != null) {
122             String JavaDoc result = (String JavaDoc) values.get(locale);
123
124             if (result != null) return result;
125
126             String JavaDoc variant = locale.getVariant();
127             String JavaDoc country = locale.getCountry();
128             String JavaDoc language = locale.getLanguage();
129
130             if (! "".equals(variant)) {
131                 result = (String JavaDoc) values.get(new Locale(language, country));
132                 if (result != null) return result;
133             }
134
135             if (! "".equals(country)) {
136                 result = (String JavaDoc) values.get(new Locale(language));
137                 if (result != null) return result;
138             }
139
140             // Some LocalizedString instances may have a default value stored with the key 'null'
141
// instead of the locale from MMBase. This is the case for values stored while the
142
// MMBase module was not yet active.
143
// This code 'fixes' that reference.
144
// It's not nice, but as a proper fix likely requires a total rewrite of Module.java and
145
// MMBase.java, this will have to do for the moment.
146
if (locale.equals(defaultLocale)) {
147                 result = (String JavaDoc) values.get(null);
148                 if (result != null) {
149                     values.put(locale, result);
150                     return result;
151                 }
152             }
153         }
154
155         if (bundle != null) {
156             try {
157                 return ResourceBundle.getBundle(bundle, locale).getString(key);
158             } catch (MissingResourceException mre) {
159                 // fall back to key.
160
if (log.isDebugEnabled()) {
161                     log.debug("Cannot get resource from bundle: " + bundle + ", key: " + key);
162                 }
163             }
164         }
165
166         return key;
167     }
168
169     /**
170      * Sets the value for a certain locale. If the value for a more general locale is still unset,
171      * it will also set that (so, it sets also nl when setting nl_BE if nl still is unset).
172      */

173     public void set(final String JavaDoc value, Locale locale) {
174         if (key == null) key = value;
175
176         if (values == null) {
177             values = new HashMap();
178         }
179
180         if (locale == null) {
181             locale = defaultLocale;
182         }
183
184         values.put(locale, value);
185
186         if (locale != null) {
187             String JavaDoc variant = locale.getVariant();
188             String JavaDoc country = locale.getCountry();
189             String JavaDoc language = locale.getLanguage();
190             if (! "".equals(variant)) {
191                 Locale loc = new Locale(language, country);
192                 if (values.get(loc) == null) {
193                     values.put(loc, value);
194                 }
195             }
196             if (! "".equals(country)) {
197                 Locale loc = new Locale(language);
198                 if (values.get(loc) == null) {
199                     values.put(loc, value);
200                 }
201             }
202         }
203     }
204
205     /**
206      * Returns a Map representation of the localisation setting represented by this
207      * LocalizedString. It is an unmodifiable mapping: Locale -> localized value.
208      */

209     public Map asMap() {
210         if (values == null) return Collections.EMPTY_MAP;
211         return Collections.unmodifiableMap(values);
212     }
213
214     /**
215      * A resource-bundle with given name can be associated to this LocalizedString. If no
216      * translations were explicitely added, it can be used to look up the translation in the bundle,
217      * using the key.
218      */

219
220     public void setBundle(String JavaDoc b) {
221         bundle = b;
222     }
223
224     /**
225      * {@inheritDoc}
226      *
227      * For LocalizedString this returns the String for the default Locale (see {@link #getDefault}).
228      */

229     public String JavaDoc toString() {
230         return get(null);
231     }
232
233
234     /**
235      * This utility takes care of reading the xml:lang attribute from an element
236      * @param element a DOM element
237      * @return A {@link java.util.Locale} object, or <code>null</code> if the element did not have,
238      * or had an empty, xml:lang attribute
239      */

240     public static Locale getLocale(Element element) {
241         return getLocale(element.getAttribute("xml:lang"));
242     }
243
244
245     /**
246      * @since MMBase-1.8.1
247      */

248     public static Locale getLocale(String JavaDoc xmlLang) {
249         Locale loc = null;
250         if (xmlLang != null && (! xmlLang.equals(""))) {
251
252             String JavaDoc[] split = xmlLang.split("-");
253             if (split.length == 1) {
254                 loc = new Locale(split[0]);
255             } else if (split.length == 2) {
256                 loc = new Locale(split[0], split[1]);
257             } else {
258                 loc = new Locale(split[0], split[1], split[2]);
259             }
260         }
261         return loc;
262     }
263
264     /**
265      * This utility determines the value of an xml:lang attribute. So, given a {@link java.util.Locale}
266      * it produces a String.
267      * @param locale A java locale
268      * @return A string that can be used as the value for an XML xml:lang attribute.
269      * @since MMBase-1.8.1
270      */

271     public static String JavaDoc getXmlLang(Locale locale) {
272         if (locale == null) return null;
273         StringBuffer JavaDoc lang = new StringBuffer JavaDoc(locale.getLanguage());
274         String JavaDoc country = locale.getCountry();
275         if (country.length() > 0) {
276             lang.append("-").append(country);
277             String JavaDoc variant = locale.getVariant();
278             if (variant != null && variant.length() > 0) {
279                 lang.append("-").append(variant);
280             }
281         }
282         return lang.toString();
283     }
284
285     /**
286      * This utility takes care of setting the xml:lang attribute on an element.
287      * @param element Element on which the xml:lang attribute is going to be set
288      * @param locale Java's Locale object
289      * @since MMBase-1.8.1
290      */

291     public static void setXmlLang(Element element, Locale locale) {
292         String JavaDoc xmlLang = getXmlLang(locale);
293         if (xmlLang != null) {
294             element.setAttribute("xml:lang", xmlLang);
295         }
296     }
297
298     /**
299      * Given a certain tagname, and a DOM parent element, it configures this LocalizedString, using
300      * subtags with this tagname with 'xml:lang' attributes. This boils down to repeative calls to {@link #set(String, Locale)}.
301      */

302
303     public void fillFromXml(final String JavaDoc tagName, final Element element) {
304         NodeList childNodes = element.getChildNodes();
305         for (int k = 0; k < childNodes.getLength(); k++) {
306             if (childNodes.item(k) instanceof Element) {
307                 Element childElement = (Element) childNodes.item(k);
308                 if (tagName.equals(childElement.getLocalName())) {
309                     Locale locale = getLocale(childElement);
310                     String JavaDoc description = DocumentReader.getNodeTextValue(childElement);
311                     set(description, locale);
312                 }
313             }
314         }
315     }
316
317     /**
318      * Writes this LocalizedString object back to an XML, i.e. it searches for and creates
319      * sub-elements (identified by xml:lang attributes) of a certain given parent element, and sets
320      * the node-text-value of those elements corresponding to the locale.
321      * @param tagName Tag-name of the to be used sub-elements
322      * @param ns Namespace of the to be created sub-elements, or <code>null</code>
323      * @param element The parent element which must contain the localized elements.
324      * @param path A comma separated list of names of tags which must skipped, before appending
325      * childs. See {@link org.mmbase.util.xml.DocumentReader#appendChild(Element, Element, String)}.
326      *
327      * @since MMBase-1.8.1
328      */

329     public void toXml(final String JavaDoc tagName, final String JavaDoc ns, final Element element, final String JavaDoc path) {
330         if (values != null) { // if no explicit values, nothing can be done
331

332             // what if there are corresponding elements already:
333
org.w3c.dom.NodeList JavaDoc nl = element.getElementsByTagName(tagName);
334             Iterator i = values.entrySet().iterator();
335             while (i.hasNext()) {
336                 Map.Entry entry = (Map.Entry) i.next();
337                 Locale loc = (Locale) entry.getKey();
338                 String JavaDoc value = (String JavaDoc) entry.getValue();
339                 String JavaDoc xmlLang = getXmlLang(loc);
340                 // look if such an element is available
341
Element child = null;
342                 for (int j = 0; j < nl.getLength(); j++) {
343                     Element cand = (Element) nl.item(j);
344                     if (cand.getAttribute("xml:lang").equals(xmlLang)) {
345                         child = cand;
346                         break;
347                     }
348                 }
349                 if (child == null) {
350                     if (ns != null) {
351                         child = element.getOwnerDocument().createElementNS(ns, tagName);
352                     } else {
353                         child = element.getOwnerDocument().createElement(tagName);
354                     }
355                     DocumentReader.appendChild(element, child, path);
356                     setXmlLang(child, loc);
357                 }
358                 DocumentReader.setNodeTextValue(child, value);
359             }
360         }
361     }
362
363     public Object JavaDoc clone() {
364         try {
365             LocalizedString clone = (LocalizedString)super.clone();
366             if (values != null) {
367                 clone.values = (Map)((HashMap)values).clone();
368             }
369             return clone;
370         } catch (CloneNotSupportedException JavaDoc cnse) {
371             // should not happen
372
log.error("Cannot clone this LocalizedString");
373             throw new RuntimeException JavaDoc("Cannot clone this LocalizedString", cnse);
374         }
375     }
376
377     public boolean equals(Object JavaDoc o) {
378         if (o instanceof LocalizedString) {
379             LocalizedString os = (LocalizedString) o;
380             return
381                 key.equals(os.key) &&
382                 (values == null ? os.values == null : values.equals(os.values)) &&
383                 (bundle == null ? os.bundle == null : bundle.equals(os.bundle))
384                 ;
385         } else {
386             return false;
387         }
388     }
389
390     public int hashCode() {
391         int result = 0;
392         result = HashCodeUtil.hashCode(result, key);
393         result = HashCodeUtil.hashCode(result, values);
394         result = HashCodeUtil.hashCode(result, bundle);
395         return result;
396     }
397
398 }
399
Popular Tags