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