001: //The contents of this file are subject to the Mozilla Public License Version 1.1
002: //(the "License"); you may not use this file except in compliance with the
003: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
004: //
005: //Software distributed under the License is distributed on an "AS IS" basis,
006: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
007: //for the specific language governing rights and
008: //limitations under the License.
009: //
010: //The Original Code is "The Columba Project"
011: //
012: //The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
013: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
014: //
015: //All Rights Reserved.
016:
017: package org.columba.core.resourceloader;
018:
019: import java.io.File;
020: import java.io.FileFilter;
021: import java.net.MalformedURLException;
022: import java.net.URL;
023: import java.net.URLClassLoader;
024: import java.util.Enumeration;
025: import java.util.HashSet;
026: import java.util.Hashtable;
027: import java.util.Locale;
028: import java.util.MissingResourceException;
029: import java.util.ResourceBundle;
030: import java.util.Set;
031: import java.util.StringTokenizer;
032: import java.util.logging.Logger;
033:
034: import org.columba.core.config.Config;
035: import org.columba.core.config.DefaultConfigDirectory;
036: import org.columba.core.xml.XmlElement;
037:
038: /**
039: * This is the core class to handle i18n in columba, loading, handling and
040: * returning localized strings. It should not be used directly, use
041: * MailResourceLoader or AddressbookResourceLoader (or *ResourceLoader) instead.
042: *
043: * Behaviour: When a resource is needed, getString() or getMnemonics() are
044: * called. They look for a resource with that name (in the current locale
045: * bundles). If it is not found, they look for the resource in the global
046: * resource bundle (for the current locale). If this is not found, "FIXME" is
047: * returned.
048: *
049: * Example of usage: We need to get the text for "my_cool_button" located into
050: * "org/columba/modules/mail/i18n/action/something_else_than_action" sPath:
051: * org/columba/modules/mail/i18n/action/ => The complete package path. sName:
052: * something_else_than_action => the name of the _it_IT.properties file. sID:
053: * my_cool_button => the name to be looked for inside sName file. We can call:
054: * a) MailResourceLoader.getString("action", "something_else_than_action",
055: * "my_cool_button"); b)
056: * ResourceLoader.getString("org/columba/modules/mail/i18n/action",
057: * "something_else_than_action", "my_cool_button"); They'll both work.
058: *
059: * We need to gets its mnemonic: a) MailResourceLoader.getMnemonic("action",
060: * "something_else_than_action", "my_cool_button"); b)
061: * ResourceLoader.getMnemonic("org/columba/modules/mail/i18n/action",
062: * "something_else_than_action", "my_cool_button");
063: */
064: public class GlobalResourceLoader {
065:
066: private static final Logger LOG = Logger
067: .getLogger("org.columba.core.util");
068:
069: protected static ClassLoader classLoader;
070:
071: protected static Hashtable htBundles = new Hashtable(80);
072:
073: protected static ResourceBundle globalBundle;
074:
075: private static final String GLOBAL_BUNDLE_PATH = "org.columba.core.i18n.global.global";
076:
077: static {
078: initClassLoader();
079: }
080:
081: /**
082: * Initialize in org.columba.core.main.Main to use user-definable language
083: * pack.
084: */
085: public static void loadLanguage() {
086: XmlElement locale = Config.getInstance().get("options")
087: .getElement("/options/locale");
088:
089: // no configuration available, create default config
090: if (locale == null) {
091: // create new locale xml treenode
092: locale = new XmlElement("locale");
093: locale.addAttribute("language", "en");
094: Config.getInstance().get("options").getElement("/options")
095: .addElement(locale);
096: }
097:
098: String language = locale.getAttribute("language");
099: String country = locale.getAttribute("country", "");
100: String variant = locale.getAttribute("variant", "");
101: Locale.setDefault(new Locale(language, country, variant));
102: initClassLoader();
103: }
104:
105: public static Locale[] getAvailableLocales() {
106: Set locales = new HashSet();
107: locales.add(new Locale("en", ""));
108:
109: FileFilter langpackFileFilter = new LangPackFileFilter();
110: File[] langpacks = DefaultConfigDirectory.getInstance()
111: .getCurrentPath().listFiles(langpackFileFilter);
112:
113: for (int i = 0; i < langpacks.length; i++) {
114: locales.add(extractLocaleFromFilename(langpacks[i]
115: .getName()));
116: }
117:
118: langpacks = new File(".").listFiles(langpackFileFilter);
119:
120: for (int i = 0; i < langpacks.length; i++) {
121: locales.add(extractLocaleFromFilename(langpacks[i]
122: .getName()));
123: }
124:
125: return (Locale[]) locales.toArray(new Locale[0]);
126: }
127:
128: private static Locale extractLocaleFromFilename(String name) {
129: String language = "";
130: String country = "";
131: String variant = "";
132: name = name.substring(9, name.length() - 4);
133:
134: StringTokenizer tokenizer = new StringTokenizer(name, "_");
135:
136: if (tokenizer.hasMoreElements()) {
137: language = tokenizer.nextToken();
138:
139: if (tokenizer.hasMoreElements()) {
140: country = tokenizer.nextToken();
141:
142: if (tokenizer.hasMoreElements()) {
143: variant = tokenizer.nextToken();
144: }
145: }
146: }
147:
148: return new Locale(language, country, variant);
149: }
150:
151: protected static void initClassLoader() {
152: File langpack = null;
153: try {
154: langpack = lookupLanguagePackFile(Locale.getDefault(),
155: DefaultConfigDirectory.getInstance()
156: .getCurrentPath());
157: } catch (RuntimeException ex) {
158: // this is ok
159: }
160:
161: if (langpack == null) {
162: langpack = lookupLanguagePackFile(Locale.getDefault(),
163: new File("."));
164: }
165:
166: if (langpack != null) {
167: LOG.fine("Creating new i18n class loader for "
168: + langpack.getPath());
169:
170: try {
171: classLoader = new URLClassLoader(new URL[] { langpack
172: .toURL() });
173: } catch (MalformedURLException mue) {
174: }
175: //does not occur
176: } else {
177: // using default english language, shipped with Columba
178:
179: // we can't use SystemClassLoader here, because that
180: // wouldn't work with java webstart,
181: // ResourceBundle uses its own internal classloader
182: // if no classloader is given
183: // -> set classloader = null
184: /*
185: * classLoader = ClassLoader.getSystemClassLoader();
186: */
187: classLoader = null;
188: }
189:
190: try {
191: // use ResourceBundle's internal classloader
192: if (classLoader == null) {
193: globalBundle = ResourceBundle.getBundle(
194: GLOBAL_BUNDLE_PATH, Locale.getDefault());
195: } else {
196: globalBundle = ResourceBundle.getBundle(
197: GLOBAL_BUNDLE_PATH, Locale.getDefault(),
198: classLoader);
199: }
200: } catch (MissingResourceException mre) {
201: throw new RuntimeException(
202: "Global resource bundle not found, Columba cannot start.");
203: }
204: }
205:
206: /**
207: * Checks whether there is a language pack file corresponding to the given
208: * locale in the specified directory.
209: */
210: private static File lookupLanguagePackFile(Locale locale,
211: File directory) {
212: File langpack = new File(directory, "langpack_"
213: + locale.toString() + ".jar");
214: if (!langpack.exists() || !langpack.isFile()) {
215: langpack = new File(directory, "langpack_"
216: + locale.getLanguage() + ".jar");
217: }
218: return langpack.exists() && langpack.isFile() ? langpack : null;
219: }
220:
221: protected static String generateBundlePath(String sPath,
222: String sName) {
223: return sPath + "." + sName;
224: }
225:
226: /*
227: * This method returns the translation for the given string identifier. If
228: * no translation is found, the default english item is used. Should this
229: * fail too, the sID string will be returned.
230: *
231: * Example usage call: getString("org/columba/modules/mail/i18n/", "dialog",
232: * "close") We'll look for "close" in
233: * "org/columba/modules/mail/i18n/dialog/dialog_locale_LOCALE.properties"
234: * Thus: sPath: "org/columba/modules/mail/i18n/dialog" sName: "dialog" sID:
235: * "close" The bundle name will be:
236: * "org/columba/modules/mail/i18n/dialog/dialog"
237: *
238: * Hypotetically this method should not be available to classes different
239: * from *ResourceLoader (example: MailResourceLoader,
240: * AddressbookResourceLoader); this means that *ResourceLoader classes *do
241: * know* how to call this method.
242: */
243: public static String getString(String sPath, String sName,
244: String sID) {
245: if ((sID == null) || sID.equals("")) {
246: return null;
247: }
248:
249: ResourceBundle bundle = null;
250: String sBundlePath = null;
251:
252: if ((sPath != null) && !sPath.equals("")) {
253: //Find out if we already loaded the needed ResourceBundle
254: //object in the hashtable.
255: sBundlePath = generateBundlePath(sPath, sName);
256: bundle = (ResourceBundle) htBundles.get(sBundlePath);
257: }
258:
259: if ((bundle == null) && (sBundlePath != null)) {
260: try {
261: // use ResourceBundle's internal classloader
262: if (classLoader == null) {
263: bundle = ResourceBundle.getBundle(sBundlePath,
264: Locale.getDefault());
265: } else {
266: bundle = ResourceBundle.getBundle(sBundlePath,
267: Locale.getDefault(), classLoader);
268: }
269:
270: htBundles.put(sBundlePath, bundle);
271: } catch (MissingResourceException mre) {
272: }
273: }
274:
275: if (bundle != null) {
276: try {
277: return bundle.getString(sID);
278: } catch (MissingResourceException mre) {
279: }
280: }
281:
282: try {
283: return globalBundle.getString(sID);
284: } catch (MissingResourceException mre) {
285: LOG.severe("'" + sID + "' in '" + sBundlePath
286: + "' could not be found.");
287:
288: return sID;
289: }
290: }
291:
292: public static void reload() {
293: initClassLoader();
294: LOG.fine("Reloading cached resource bundles for locale "
295: + Locale.getDefault().toString());
296:
297: try {
298: // use ResourceBundle's internal classloader
299: if (classLoader == null) {
300: globalBundle = ResourceBundle.getBundle(
301: GLOBAL_BUNDLE_PATH, Locale.getDefault());
302: } else {
303: globalBundle = ResourceBundle.getBundle(
304: GLOBAL_BUNDLE_PATH, Locale.getDefault(),
305: classLoader);
306: }
307: } catch (MissingResourceException mre) {
308: }
309: //should not occur, otherwise the static initializer should have thrown
310: // a RuntimeException
311:
312: String bundlePath;
313: ResourceBundle bundle;
314:
315: for (Enumeration entries = htBundles.keys(); entries
316: .hasMoreElements();) {
317: try {
318: bundlePath = (String) entries.nextElement();
319:
320: //retrieve new bundle
321: // use ResourceBundle's internal classloader
322: if (classLoader == null) {
323: bundle = ResourceBundle.getBundle(bundlePath,
324: Locale.getDefault());
325: } else {
326: bundle = ResourceBundle.getBundle(bundlePath,
327: Locale.getDefault(), classLoader);
328: }
329:
330: //overwrite old bundle
331: htBundles.put(bundlePath, bundle);
332: } catch (MissingResourceException mre) {
333: }
334: //should not occur, otherwise the bundlePath would not be in the
335: // hashtable
336: }
337: }
338:
339: public static class LangPackFileFilter implements FileFilter {
340: public boolean accept(File file) {
341: String name = file.getName().toLowerCase();
342:
343: return file.isFile() && name.startsWith("langpack_")
344: && name.endsWith(".jar");
345: }
346: }
347: }
|