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}