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 *
045 * @author Bob Jacobsen Copyright (C) 2012
046 * @since 3.3.1
047 */
048@ParametersAreNonnullByDefault
049@CheckReturnValue
050@javax.annotation.concurrent.Immutable
051public class Bundle {
052
053    @CheckForNull
054    private final static String name = "jmri.NamedBeanBundle";  // NOI18N
055
056    /**
057     * Provides a translated string for a given key from the package resource
058     * bundle or parent.
059     * <p>
060     * Note that this is intentionally package-local access.
061     *
062     * @param key Bundle key to be translated
063     * @return Internationalized text
064     */
065    static String getMessage(String key) {
066        return getBundle().handleGetMessage(key);
067    }
068
069    /**
070     * Provides a translated string for a given key in a given locale from the
071     * package resource bundle or parent.
072     * <p>
073     * Note that this is intentionally package-local access.
074     *
075     * @param locale The locale to be used
076     * @param key    Bundle key to be translated
077     * @return Internationalized text
078     */
079    static String getMessage(Locale locale, String key) {
080        return getBundle().handleGetMessage(locale, key);
081    }
082
083    /**
084     * Merges user data with a translated string for a given key from the
085     * package resource bundle or parent.
086     * <p>
087     * Uses the transformation conventions of the Java MessageFormat utility.
088     * <p>
089     * Note that this is intentionally package-local access.
090     *
091     * @see java.text.MessageFormat
092     * @param key  Bundle key to be translated
093     * @param subs One or more objects to be inserted into the message
094     * @return Internationalized text
095     */
096    static String getMessage(String key, Object... subs) {
097        return getBundle().handleGetMessage(key, subs);
098    }
099
100    /**
101     * Merges user data with a translated string for a given key in a given
102     * locale from the package resource bundle or parent.
103     * <p>
104     * Uses the transformation conventions of the Java MessageFormat utility.
105     * <p>
106     * Note that this is intentionally package-local access.
107     *
108     * @see java.text.MessageFormat
109     * @param locale The locale to be used
110     * @param key    Bundle key to be translated
111     * @param subs   One or more objects to be inserted into the message
112     * @return Internationalized text
113     */
114    static String getMessage(Locale locale, String key, Object... subs) {
115        return getBundle().handleGetMessage(locale, key, subs);
116    }
117
118    /**
119     * This method handles the inheritance tree. At lower levels, it reflects
120     * upwards on failure. Once it reaches this root class, it will throw a
121     * MissingResourceException in the key can't be found via the local
122     * definition of retry().
123     *
124     * @param key Bundle key to be translated
125     * @return Internationalized text
126     * @throws MissingResourceException if message cannot be found
127     */
128    public String handleGetMessage(String key) {
129        return this.handleGetMessage(Locale.getDefault(), key);
130    }
131
132    /**
133     * This method handles the inheritance tree. At lower levels, it reflects
134     * upwards on failure. Once it reaches this root class, it will throw a
135     * MissingResourceException in the key can't be found via the local
136     * definition of retry().
137     *
138     * @param locale The locale to be used
139     * @param key    Bundle key to be translated
140     * @return Internationalized text
141     * @throws MissingResourceException if message cannot be found
142     */
143    public String handleGetMessage(Locale locale, String key) {
144        log.trace("handleGetMessage for key {}", key);
145        if (bundleName() != null) {
146            ResourceBundle rb = ResourceBundle.getBundle(bundleName(), locale);
147            if (rb.containsKey(key)) {
148                return rb.getString(key);
149            } else {
150                return retry(locale, key);
151            }
152        } else {  // case of no local bundle
153            return retry(locale, key);
154        }
155    }
156
157    /**
158     * Merges user data with a translated string for a given key from the
159     * package resource bundle or parent.
160     * <p>
161     * Uses the transformation conventions of the Java MessageFormat utility.
162     *
163     * @see java.text.MessageFormat
164     * @param key  Bundle key to be translated
165     * @param subs Array of objects to be inserted into the message
166     * @return Internationalized text
167     */
168    public String handleGetMessage(String key, Object[] subs) {
169        return this.handleGetMessage(Locale.getDefault(), key, subs);
170    }
171
172    /**
173     * Merges user data with a translated string for a given key in a given
174     * locale from the package resource bundle or parent.
175     * <p>
176     * Uses the transformation conventions of the Java MessageFormat utility.
177     *
178     * @see java.text.MessageFormat
179     * @param locale The locale to be used
180     * @param key    Bundle key to be translated
181     * @param subs   Array of objects to be inserted into the message
182     * @return Internationalized text
183     */
184    public String handleGetMessage(Locale locale, String key, Object[] subs) {
185        return MessageFormat.format(handleGetMessage(locale, key), subs);
186    }
187
188    /**
189     * Formats a message string with parameters.
190     * <p>
191     * It's used when a message is fetched from a foreign bundle.
192     * 
193     * @param message The message to be formatted
194     * @param subs    Array of objects to be inserted into the message
195     * @return The formatted message
196     */
197    public static String formatMessage(String message, Object... subs) {
198        return MessageFormat.format(message, subs);
199    }
200
201    // the following is different from the method in subclasses because
202    // this is the root of the search tree
203    protected String retry(Locale locale, String key) throws MissingResourceException {
204        throw new MissingResourceException("Resource '" + key + "' not found", this.getClass().toString(), key); // NOI18N
205    }
206
207    private final static Bundle b = new Bundle();
208
209    @CheckForNull
210    protected String bundleName() {
211        return name;
212    }
213
214    protected static jmri.Bundle getBundle() {
215        return b;
216    }
217
218    // Can get pathname of ctor class (to auto-generate BundleName) via getClass().getPackage()
219    // E.g. to cache a local bundle name via weak reference
220    //        if (rbr == null) rbr = new java.lang.ref.SoftReference<ResourceBundle>(
221    //                                   ResourceBundle.getBundle("jmri.NamedBeanBundle"));
222    //        ResourceBundle rb = rbr.get();
223    //        if (rb == null) {
224    //           log.error("Failed to load defaults because of missing bundle");
225    //           return;
226    //        }
227    
228    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Bundle.class);
229
230}