001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.gwt.client.util;
029
030import org.opencms.util.CmsStringUtil;
031
032import java.util.Date;
033import java.util.HashMap;
034import java.util.Map;
035import java.util.MissingResourceException;
036
037import com.google.gwt.i18n.client.Dictionary;
038
039/**
040 * Reads localized resource Strings from a <code>java.util.ResourceBundle</code>
041 * and provides convenience methods to access the Strings from a template.<p>
042 *
043 * @since 8.0.0
044 *
045 * @see org.opencms.i18n.CmsMessages
046 */
047public class CmsMessages {
048
049    /** The suffix of a "short" localized key name. */
050    public static final String KEY_SHORT_SUFFIX = ".short";
051
052    /** Prefix / Suffix for unknown keys. */
053    public static final String UNKNOWN_KEY_EXTENSION = "???";
054
055    /** Cached dictionaries. */
056    private static Map<String, Dictionary> m_dictionaries;
057
058    /** The name of the resource bundle. */
059    private String m_bundleName;
060
061    /** The current dictionary. */
062    private Dictionary m_dictionary;
063
064    /**
065     * Default constructor.<p>
066     *
067     * @param bundleName the localized bundle name
068     */
069    public CmsMessages(String bundleName) {
070
071        if (m_dictionaries == null) {
072            m_dictionaries = new HashMap<String, Dictionary>();
073        }
074        m_bundleName = bundleName;
075        m_dictionary = m_dictionaries.get(m_bundleName);
076        if (m_dictionary == null) {
077            m_dictionary = Dictionary.getDictionary(m_bundleName.replace('.', '_'));
078            m_dictionaries.put(m_bundleName, m_dictionary);
079        }
080    }
081
082    /**
083     * Helper method for formatting message parameters.<p>
084     *
085     * @param result the raw message containing placeholders like {0}
086     * @param args the parameters to insert into the placeholders
087     *
088     * @return the formatted message
089     */
090    public static String formatMessage(String result, Object... args) {
091
092        // key was found in the bundle - create and apply the formatter
093        for (int i = 0; i < args.length; i++) {
094            if (args[i] instanceof Date) {
095                Date date = (Date)args[i];
096                result = result.replace(getRegEx(i), CmsDateTimeUtil.getDateTime(date, CmsDateTimeUtil.Format.MEDIUM));
097                result = result.replace(
098                    getRegEx(i, "time"),
099                    CmsDateTimeUtil.getTime(date, CmsDateTimeUtil.Format.MEDIUM));
100                result = result.replace(
101                    getRegEx(i, "time", "short"),
102                    CmsDateTimeUtil.getTime(date, CmsDateTimeUtil.Format.SHORT));
103                result = result.replace(
104                    getRegEx(i, "time", "medium"),
105                    CmsDateTimeUtil.getTime(date, CmsDateTimeUtil.Format.MEDIUM));
106                result = result.replace(
107                    getRegEx(i, "time", "long"),
108                    CmsDateTimeUtil.getTime(date, CmsDateTimeUtil.Format.LONG));
109                result = result.replace(
110                    getRegEx(i, "time", "full"),
111                    CmsDateTimeUtil.getTime(date, CmsDateTimeUtil.Format.FULL));
112                result = result.replace(
113                    getRegEx(i, "date"),
114                    CmsDateTimeUtil.getDate(date, CmsDateTimeUtil.Format.MEDIUM));
115                result = result.replace(
116                    getRegEx(i, "date", "short"),
117                    CmsDateTimeUtil.getDate(date, CmsDateTimeUtil.Format.SHORT));
118                result = result.replace(
119                    getRegEx(i, "date", "medium"),
120                    CmsDateTimeUtil.getDate(date, CmsDateTimeUtil.Format.MEDIUM));
121                result = result.replace(
122                    getRegEx(i, "date", "long"),
123                    CmsDateTimeUtil.getDate(date, CmsDateTimeUtil.Format.LONG));
124                result = result.replace(
125                    getRegEx(i, "date", "full"),
126                    CmsDateTimeUtil.getDate(date, CmsDateTimeUtil.Format.FULL));
127            } else {
128                result = result.replace(getRegEx(i), String.valueOf(args[i]));
129            }
130        }
131        return result;
132    }
133
134    /**
135     * Formats an unknown key.<p>
136     *
137     * @param keyName the key to format
138     *
139     * @return the formatted unknown key
140     *
141     * @see #isUnknownKey(String)
142     */
143    public static String formatUnknownKey(String keyName) {
144
145        StringBuffer buf = new StringBuffer(64);
146        buf.append(UNKNOWN_KEY_EXTENSION);
147        buf.append(" ");
148        buf.append(keyName);
149        buf.append(" ");
150        buf.append(UNKNOWN_KEY_EXTENSION);
151        return buf.toString();
152    }
153
154    /**
155     * Returns <code>true</code> if the provided value matches the scheme
156     * <code>"??? " + keyName + " ???"</code>, that is the value appears to be an unknown key.<p>
157     *
158     * Also returns <code>true</code> if the given value is <code>null</code>.<p>
159     *
160     * @param value the value to check
161     *
162     * @return true if the value is matches the scheme for unknown keys
163     *
164     * @see #formatUnknownKey(String)
165     */
166    public static boolean isUnknownKey(String value) {
167
168        return (value == null) || (value.startsWith(UNKNOWN_KEY_EXTENSION));
169    }
170
171    /**
172     * Returns a regular expression for replacement.<p>
173     *
174     * @param position the parameter number
175     * @param options the optional options
176     *
177     * @return the regular expression for replacement
178     */
179    private static String getRegEx(int position, String... options) {
180
181        String value = "" + position;
182        for (int i = 0; i < options.length; i++) {
183            value += "," + options[i];
184        }
185        return "{" + value + "}";
186    }
187
188    /**
189     * Returns the localized message bundle wrapped in this instance.<p>
190     *
191     * Mainly for API compatibility with the core localization methods.<p>
192     *
193     * @return the localized message bundle wrapped in this instance
194     */
195    public CmsMessages getBundle() {
196
197        return this;
198    }
199
200    /**
201     * Returns the name of the resource bundle.<p>
202     *
203     * @return the name of the resource bundle
204     */
205    public String getBundleName() {
206
207        return m_bundleName;
208    }
209
210    /**
211     * Returns a formated date String from a Date value,
212     * the format being {@link CmsDateTimeUtil.Format#SHORT} and the locale
213     * based on this instance.<p>
214     *
215     * @param date the Date object to format as String
216     *
217     * @return the formatted date
218     */
219    public String getDate(Date date) {
220
221        return CmsDateTimeUtil.getDate(date, CmsDateTimeUtil.Format.SHORT);
222    }
223
224    /**
225     * Returns a formated date String from a Date value,
226     * the formatting based on the provided option and the locale
227     * based on this instance.<p>
228     *
229     * @param date the Date object to format as String
230     * @param format the format to use, see {@link CmsDateTimeUtil.Format} for possible values
231     *
232     * @return the formatted date
233     */
234    public String getDate(Date date, CmsDateTimeUtil.Format format) {
235
236        return CmsDateTimeUtil.getDate(date, format);
237    }
238
239    /**
240     * Returns a formated date String from a timestamp value,
241     * the format being {@link CmsDateTimeUtil.Format#SHORT} and the locale
242     * based on this instance.<p>
243     *
244     * @param time the time value to format as date
245     *
246     * @return the formatted date
247     */
248    public String getDate(long time) {
249
250        return CmsDateTimeUtil.getDate(new Date(time), CmsDateTimeUtil.Format.SHORT);
251    }
252
253    /**
254     * Returns a formated date and time String from a Date value,
255     * the format being {@link CmsDateTimeUtil.Format#SHORT} and the locale
256     * based on this instance.<p>
257     *
258     * @param date the Date object to format as String
259     * @return the formatted date and time
260     */
261    public String getDateTime(Date date) {
262
263        return CmsDateTimeUtil.getDateTime(date, CmsDateTimeUtil.Format.SHORT);
264    }
265
266    /**
267     * Returns a formated date and time String from a Date value,
268     * the formatting based on the provided option and the locale
269     * based on this instance.<p>
270     *
271     * @param date the Date object to format as String
272     * @param format the format to use, see {@link CmsDateTimeUtil.Format} for possible values
273     * @return the formatted date and time
274     */
275    public String getDateTime(Date date, CmsDateTimeUtil.Format format) {
276
277        return CmsDateTimeUtil.getDateTime(date, format);
278    }
279
280    /**
281     * Returns a formated date and time String from a timestamp value,
282     * the format being {@link CmsDateTimeUtil.Format#SHORT} and the locale
283     * based on this instance.<p>
284     *
285     * @param time the time value to format as date
286     * @return the formatted date and time
287     */
288    public String getDateTime(long time) {
289
290        return CmsDateTimeUtil.getDateTime(new Date(time), CmsDateTimeUtil.Format.SHORT);
291    }
292
293    /**
294     * Returns the internal dictionary.<p>
295     *
296     * @return the internal dictionary
297     */
298    public Dictionary getDictionary() {
299
300        return m_dictionary;
301    }
302
303    /**
304     * Returns the localized resource string for a given message key.<p>
305     *
306     * If the key was not found in the bundle, the return value is
307     * <code>"??? " + keyName + " ???"</code>. This will also be returned
308     * if the bundle was not properly initialized first.
309     *
310     * @param keyName the key for the desired string
311     *
312     * @return the resource string for the given key
313     */
314    public String key(String keyName) {
315
316        return key(keyName, false);
317    }
318
319    /**
320     * Returns the localized resource string for a given message key.<p>
321     *
322     * If the key was not found in the bundle, the return value
323     * depends on the setting of the allowNull parameter. If set to false,
324     * the return value is always a String in the format
325     * <code>"??? " + keyName + " ???"</code>.
326     * If set to true, null is returned if the key is not found.
327     * This will also be returned
328     * if the bundle was not properly initialized first.
329     *
330     * @param keyName the key for the desired string
331     * @param allowNull if true, 'null' is an allowed return value
332     *
333     * @return the resource string for the given key
334     */
335    public String key(String keyName, boolean allowNull) {
336
337        try {
338            if (m_dictionary != null) {
339                return m_dictionary.get(keyName);
340            }
341        } catch (@SuppressWarnings("unused") MissingResourceException e) {
342            // not found, return warning
343            if (allowNull) {
344                return null;
345            }
346        }
347        return formatUnknownKey(keyName);
348    }
349
350    /**
351     * Returns the selected localized message for the initialized resource bundle and locale.<p>
352     *
353     * If the key was found in the bundle, it will be formatted using
354     * a <code>{@link java.text.MessageFormat}</code> using the provided parameters.<p>
355     *
356     * If the key was not found in the bundle, the return value is
357     * <code>"??? " + keyName + " ???"</code>. This will also be returned
358     * if the bundle was not properly initialized first.
359     *
360     * @param key the message key
361     * @param args the message arguments
362     *
363     * @return the selected localized message for the initialized resource bundle and locale
364     */
365    public String key(String key, Object... args) {
366
367        if ((args == null) || (args.length == 0)) {
368            // no parameters available, use simple key method
369            return key(key);
370        }
371
372        String result = key(key, true);
373        if (result == null) {
374            // key was not found
375            result = formatUnknownKey(key);
376        } else {
377            result = formatMessage(result, args);
378        }
379        // return the result
380        return result;
381    }
382
383    /**
384     * Returns the localized resource string for a given message key.<p>
385     *
386     * If the key was not found in the bundle, the provided default value
387     * is returned.<p>
388     *
389     * @param keyName the key for the desired string
390     * @param defaultValue the default value in case the key does not exist in the bundle
391     *
392     * @return the resource string for the given key it it exists, or the given default if not
393     */
394    public String keyDefault(String keyName, String defaultValue) {
395
396        String result = key(keyName, true);
397        return (result == null) ? defaultValue : result;
398    }
399
400    /**
401     * Returns the localized resource string for a given message key,
402     * treating all values appended with "|" as replacement parameters.<p>
403     *
404     * If the key was found in the bundle, it will be formatted using
405     * a <code>{@link java.text.MessageFormat}</code> using the provided parameters.
406     * The parameters have to be appended to the key separated by a "|".
407     * For example, the keyName <code>error.message|First|Second</code>
408     * would use the key <code>error.message</code> with the parameters
409     * <code>First</code> and <code>Second</code>. This would be the same as calling
410     * <code>{@link CmsMessages#key(String, Object[])}</code>.<p>
411     *
412     * If no parameters are appended with "|", this is the same as calling
413     * <code>{@link CmsMessages#key(String)}</code>.<p>
414     *
415     * If the key was not found in the bundle, the return value is
416     * <code>"??? " + keyName + " ???"</code>. This will also be returned
417     * if the bundle was not properly initialized first.
418     *
419     * @param keyName the key for the desired string, optionally containing parameters appended with a "|"
420     *
421     * @return the resource string for the given key
422     *
423     * @see #key(String, Object[])
424     * @see #key(String)
425     */
426    public String keyWithParams(String keyName) {
427
428        if (keyName.indexOf('|') == -1) {
429            // no separator found, key has no parameters
430            return key(keyName, false);
431        } else {
432            // this key contains parameters
433            String[] values = CmsStringUtil.splitAsArray(keyName, "|");
434            String cutKeyName = values[0];
435            String[] params = new String[values.length - 1];
436            System.arraycopy(values, 1, params, 0, params.length);
437            return key(cutKeyName, (Object[])params);
438        }
439    }
440}