001package jmri;
002
003import java.text.MessageFormat;
004import java.util.Locale;
005import java.util.MissingResourceException;
006import java.util.ResourceBundle;
007import javax.annotation.CheckReturnValue;
008import javax.annotation.CheckForNull;
009import javax.annotation.ParametersAreNonnullByDefault;
010
011/**
012 * Provides standard access for resource bundles in a package.
013 * <p>
014 * Convention is to provide a subclass of this same name in each package,
015 * working off the local resource bundle name, usually 'package.Bundle' stored
016 * in a Bundle.properties file.
017 * <p>
018 * This is the root of a tree of classes that are chained through class-static
019 * members so that they each do a search as a request works up the inheritance
020 * tree.
021 * <p>
022 * Only package-scope methods exposed are from the class, forcing all requests
023 * for strings to be a the package level.
024 * <p>
025 * To add this to a new package, copy exactly a subclass file such as
026 * jmri.jmrit.Bundle, and change three places:
027 * <ol>
028 * <li>The import statement at the top
029 * <li>The extends clause in the class definition statement
030 * <li>The resource pathname assigned to the name variable, which must be set to
031 * null if there are no local resources.
032 * </ol>
033 *
034 * <hr>
035 * This file is part of JMRI.
036 * <p>
037 * JMRI is free software; you can redistribute it and/or modify it under the
038 * terms of version 2 of the GNU General Public License as published by the Free
039 * Software Foundation. See the "COPYING" file for a copy of this license.
040 * <p>
041 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
042 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
043 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
044 * <p>
045 *
046 * @author Bob Jacobsen Copyright (C) 2012
047 * @since 3.3.1
048 */
049@ParametersAreNonnullByDefault
050@CheckReturnValue
051@javax.annotation.concurrent.Immutable
052public class Bundle {
053
054    @CheckForNull
055    private final static String name = "jmri.NamedBeanBundle";  // NOI18N
056
057    /**
058     * Provides a translated string for a given key from the package resource
059     * bundle or parent.
060     * <p>
061     * Note that this is intentionally package-local access.
062     *
063     * @param key Bundle key to be translated
064     * @return Internationalized text
065     */
066    static String getMessage(String key) {
067        return getBundle().handleGetMessage(key);
068    }
069
070    /**
071     * Provides a translated string for a given key in a given locale from the
072     * package resource bundle or parent.
073     * <p>
074     * Note that this is intentionally package-local access.
075     *
076     * @param locale The locale to be used
077     * @param key    Bundle key to be translated
078     * @return Internationalized text
079     */
080    static String getMessage(Locale locale, String key) {
081        return getBundle().handleGetMessage(locale, key);
082    }
083
084    /**
085     * Merges user data with a translated string for a given key from the
086     * package resource bundle or parent.
087     * <p>
088     * Uses the transformation conventions of the Java MessageFormat utility.
089     * <p>
090     * Note that this is intentionally package-local access.
091     *
092     * @see java.text.MessageFormat
093     * @param key  Bundle key to be translated
094     * @param subs One or more objects to be inserted into the message
095     * @return Internationalized text
096     */
097    static String getMessage(String key, Object... subs) {
098        return getBundle().handleGetMessage(key, subs);
099    }
100
101    /**
102     * Merges user data with a translated string for a given key in a given
103     * locale from the package resource bundle or parent.
104     * <p>
105     * Uses the transformation conventions of the Java MessageFormat utility.
106     * <p>
107     * Note that this is intentionally package-local access.
108     *
109     * @see java.text.MessageFormat
110     * @param locale The locale to be used
111     * @param key    Bundle key to be translated
112     * @param subs   One or more objects to be inserted into the message
113     * @return Internationalized text
114     */
115    static String getMessage(Locale locale, String key, Object... subs) {
116        return getBundle().handleGetMessage(locale, key, subs);
117    }
118
119    /**
120     * This method handles the inheritance tree. At lower levels, it reflects
121     * upwards on failure. Once it reaches this root class, it will throw a
122     * MissingResourceException in the key can't be found via the local
123     * definition of retry().
124     *
125     * @param key Bundle key to be translated
126     * @return Internationalized text
127     * @throws MissingResourceException if message cannot be found
128     */
129    public String handleGetMessage(String key) {
130        return this.handleGetMessage(Locale.getDefault(), key);
131    }
132
133    /**
134     * This method handles the inheritance tree. At lower levels, it reflects
135     * upwards on failure. Once it reaches this root class, it will throw a
136     * MissingResourceException in the key can't be found via the local
137     * definition of retry().
138     *
139     * @param locale The locale to be used
140     * @param key    Bundle key to be translated
141     * @return Internationalized text
142     * @throws MissingResourceException if message cannot be found
143     */
144    public String handleGetMessage(Locale locale, String key) {
145        log.trace("handleGetMessage for key {}", key);
146        if (bundleName() != null) {
147            ResourceBundle rb = ResourceBundle.getBundle(bundleName(), locale);
148            if (rb.containsKey(key)) {
149                return rb.getString(key);
150            } else {
151                return retry(locale, key);
152            }
153        } else {  // case of no local bundle
154            return retry(locale, key);
155        }
156    }
157
158    /**
159     * Merges user data with a translated string for a given key from the
160     * package resource bundle or parent.
161     * <p>
162     * Uses the transformation conventions of the Java MessageFormat utility.
163     *
164     * @see java.text.MessageFormat
165     * @param key  Bundle key to be translated
166     * @param subs Array of objects to be inserted into the message
167     * @return Internationalized text
168     */
169    public String handleGetMessage(String key, Object[] subs) {
170        return this.handleGetMessage(Locale.getDefault(), key, subs);
171    }
172
173    /**
174     * Merges user data with a translated string for a given key in a given
175     * locale from the package resource bundle or parent.
176     * <p>
177     * Uses the transformation conventions of the Java MessageFormat utility.
178     *
179     * @see java.text.MessageFormat
180     * @param locale The locale to be used
181     * @param key    Bundle key to be translated
182     * @param subs   Array of objects to be inserted into the message
183     * @return Internationalized text
184     */
185    public String handleGetMessage(Locale locale, String key, Object[] subs) {
186        return MessageFormat.format(handleGetMessage(locale, key), subs);
187    }
188
189    /**
190     * Formats a message string with parameters.
191     * <p>
192     * It's used when a message is fetched from a foreign bundle.
193     * 
194     * @param message The message to be formatted
195     * @param subs    Array of objects to be inserted into the message
196     * @return The formatted message
197     */
198    public static String formatMessage(String message, Object... subs) {
199        return MessageFormat.format(message, subs);
200    }
201
202    // the following is different from the method in subclasses because
203    // this is the root of the search tree
204    protected String retry(Locale locale, String key) throws MissingResourceException {
205        throw new MissingResourceException("Resource '" + key + "' not found", this.getClass().toString(), key); // NOI18N
206    }
207
208    private final static Bundle b = new Bundle();
209
210    @CheckForNull
211    protected String bundleName() {
212        return name;
213    }
214
215    protected static jmri.Bundle getBundle() {
216        return b;
217    }
218
219    // Can get pathname of ctor class (to auto-generate BundleName) via getClass().getPackage()
220    // E.g. to cache a local bundle name via weak reference
221    //        if (rbr == null) rbr = new java.lang.ref.SoftReference<ResourceBundle>(
222    //                                   ResourceBundle.getBundle("jmri.NamedBeanBundle"));
223    //        ResourceBundle rb = rbr.get();
224    //        if (rb == null) {
225    //           log.error("Failed to load defaults because of missing bundle");
226    //           return;
227    //        }
228    
229    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Bundle.class);
230
231}