001 /*
002 // $Id: //open/util/resgen/src/org/eigenbase/resgen/ShadowResourceBundle.java#5 $
003 // Package org.eigenbase.resgen is an i18n resource generator.
004 // Copyright (C) 2005-2005 The Eigenbase Project
005 // Copyright (C) 2005-2005 Disruptive Tech
006 // Copyright (C) 2005-2005 LucidEra, Inc.
007 // Portions Copyright (C) 2002-2005 Kana Software, Inc. and others.
008 //
009 // This library is free software; you can redistribute it and/or modify it
010 // under the terms of the GNU Lesser General Public License as published by the
011 // Free Software Foundation; either version 2 of the License, or (at your
012 // option) any later version approved by The Eigenbase Project.
013 //
014 // This library is distributed in the hope that it will be useful,
015 // but WITHOUT ANY WARRANTY; without even the implied warranty of
016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017 // GNU Lesser General Public License for more details.
018 //
019 // You should have received a copy of the GNU Lesser General Public License
020 // along with this library; if not, write to the Free Software
021 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022 //
023 // jhyde, 19 September, 2002
024 */
025
026 package org.eigenbase.resgen;
027
028 import java.io.IOException;
029 import java.io.InputStream;
030 import java.util.*;
031
032 /**
033 * <code>ShadowResourceBundle</code> is an abstract base class for
034 * {@link ResourceBundle} classes which are backed by a properties file. When
035 * the class is created, it loads a properties file with the same name as the
036 * class.
037 *
038 * <p> In the standard scheme (see {@link ResourceBundle}),
039 * if you call <code>{@link ResourceBundle#getBundle}("foo.MyResource")</code>,
040 * it first looks for a class called <code>foo.MyResource</code>, then
041 * looks for a file called <code>foo/MyResource.properties</code>. If it finds
042 * the file, it creates a {@link PropertyResourceBundle} and loads the class.
043 * The problem is if you want to load the <code>.properties</code> file
044 * into a dedicated class; <code>ShadowResourceBundle</code> helps with this
045 * case.
046 *
047 * <p> You should create a class as follows:<blockquote>
048 *
049 * <pre>package foo;
050 *class MyResource extends org.eigenbase.resgen.ShadowResourceBundle {
051 * public MyResource() throws java.io.IOException {
052 * }
053 *}</pre>
054 *
055 * </blockquote> Then when you call
056 * {@link ResourceBundle#getBundle ResourceBundle.getBundle("foo.MyResource")},
057 * it will find the class before the properties file, but still automatically
058 * load the properties file based upon the name of the class.
059 */
060 public abstract class ShadowResourceBundle extends ResourceBundle {
061 private PropertyResourceBundle bundle;
062 private static final ThreadLocal mapThreadToLocale = new ThreadLocal();
063 protected static final Object[] emptyObjectArray = new Object[0];
064
065 /**
066 * Creates a <code>ShadowResourceBundle</code>, and reads resources from
067 * a <code>.properties</code> file with the same name as the current class.
068 * For example, if the class is called <code>foo.MyResource_en_US</code>,
069 * reads from <code>foo/MyResource_en_US.properties</code>, then
070 * <code>foo/MyResource_en.properties</code>, then
071 * <code>foo/MyResource.properties</code>.
072 */
073 protected ShadowResourceBundle() throws IOException {
074 super();
075 Class clazz = getClass();
076 InputStream stream = openPropertiesFile(clazz);
077 if (stream == null) {
078 throw new IOException("could not open properties file for " + getClass());
079 }
080 MyPropertyResourceBundle previousBundle =
081 new MyPropertyResourceBundle(stream);
082 bundle = previousBundle;
083 stream.close();
084 // Now load properties files for parent locales, which we deduce from
085 // the names of our super-class, and its super-class.
086 while (true) {
087 clazz = clazz.getSuperclass();
088 if (clazz == null ||
089 clazz == ShadowResourceBundle.class ||
090 !ResourceBundle.class.isAssignableFrom(clazz)) {
091 break;
092 }
093 stream = openPropertiesFile(clazz);
094 if (stream == null) {
095 continue;
096 }
097 MyPropertyResourceBundle newBundle =
098 new MyPropertyResourceBundle(stream);
099 stream.close();
100 if (previousBundle != null) {
101 previousBundle.setParentTrojan(newBundle);
102 } else {
103 bundle = newBundle;
104 }
105 previousBundle = newBundle;
106 }
107 }
108
109 static class MyPropertyResourceBundle extends PropertyResourceBundle {
110 public MyPropertyResourceBundle(InputStream stream) throws IOException {
111 super(stream);
112 }
113
114 void setParentTrojan(ResourceBundle parent) {
115 super.setParent(parent);
116 }
117 }
118
119 /**
120 * Opens the properties file corresponding to a given class. The code is
121 * copied from {@link ResourceBundle}.
122 */
123 private static InputStream openPropertiesFile(Class clazz) {
124 final ClassLoader loader = clazz.getClassLoader();
125 final String resName = clazz.getName().replace('.', '/') + ".properties";
126 return (InputStream)java.security.AccessController.doPrivileged(
127 new java.security.PrivilegedAction() {
128 public Object run() {
129 if (loader != null) {
130 return loader.getResourceAsStream(resName);
131 } else {
132 return ClassLoader.getSystemResourceAsStream(resName);
133 }
134 }
135 }
136 );
137 }
138
139 public Enumeration getKeys() {
140 return bundle.getKeys();
141 }
142
143 protected Object handleGetObject(String key)
144 throws MissingResourceException {
145 return bundle.getObject(key);
146 }
147
148 /**
149 * Returns the instance of the <code>baseName</code> resource bundle for
150 * the current thread's locale. For example, if called with
151 * "mondrian.olap.MondrianResource", from a thread which has called {@link
152 * #setThreadLocale}({@link Locale#FRENCH}), will get an instance of
153 * "mondrian.olap.MondrianResource_FR" from the cache.
154 *
155 * <p> This method should be called from a derived class, with the proper
156 * casting:<blockquote>
157 *
158 * <pre>class MyResource extends ShadowResourceBundle {
159 * ...
160 * /**
161 * * Retrieves the instance of {@link MyResource} appropriate
162 * * to the current locale. If this thread has specified a locale
163 * * by calling {@link #setThreadLocale}, this locale is used,
164 * * otherwise the default locale is used.
165 * **/
166 * public static MyResource instance() {
167 * return (MyResource) instance(MyResource.class.getName());
168 * }
169 * ...
170 * }</pre></blockquote>
171 *
172 * @deprecated This method does not work correctly in dynamically
173 * loaded jars.
174 */
175 protected static ResourceBundle instance(String baseName) {
176 return instance(baseName, getThreadLocale());
177 }
178 /**
179 * Returns the instance of the <code>baseName</code> resource bundle
180 * for the given locale.
181 *
182 * <p> This method should be called from a derived class, with the proper
183 * casting:<blockquote>
184 *
185 * <pre>class MyResource extends ShadowResourceBundle {
186 * ...
187 *
188 * /**
189 * * Retrieves the instance of {@link MyResource} appropriate
190 * * to the given locale.
191 * **/
192 * public static MyResource instance(Locale locale) {
193 * return (MyResource) instance(MyResource.class.getName(), locale);
194 * }
195 * ...
196 * }</pre></blockquote>
197 *
198 * @deprecated This method does not work correctly in dynamically
199 * loaded jars.
200 */
201 protected static ShadowResourceBundle instance(
202 String baseName, Locale locale) {
203 if (locale == null) {
204 locale = Locale.getDefault();
205 }
206 ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale);
207 return instance(baseName, locale, bundle);
208 }
209
210 /**
211 * Returns the instance of the <code>baseName</code> resource bundle
212 * for the given locale.
213 *
214 * <p> This method should be called from a derived class, with the proper
215 * casting:<blockquote>
216 *
217 * <pre>class MyResource extends ShadowResourceBundle {
218 * ...
219 *
220 * /**
221 * * Retrieves the instance of {@link MyResource} appropriate
222 * * to the given locale.
223 * **/
224 * public static MyResource instance(Locale locale) {
225 * return (MyResource) instance(
226 * MyResource.class.getName(), locale,
227 * ResourceBundle.getBundle(MyResource.class.getName(), locale));
228 * }
229 * ...
230 * }</pre></blockquote>
231 */
232 protected static ShadowResourceBundle instance(
233 String baseName, Locale locale, ResourceBundle bundle)
234 {
235 if (bundle instanceof PropertyResourceBundle) {
236 throw new ClassCastException(
237 "ShadowResourceBundle.instance('" + baseName + "','" +
238 locale + "') found " +
239 baseName + "_" + locale + ".properties but not " +
240 baseName + "_" + locale + ".class");
241 }
242 return (ShadowResourceBundle) bundle;
243 }
244
245 /** Returns the preferred locale of the current thread, or
246 * the default locale if the current thread has not called {@link
247 * #setThreadLocale}. **/
248 protected static Locale getThreadOrDefaultLocale() {
249 Locale locale = getThreadLocale();
250 if (locale == null) {
251 return Locale.getDefault();
252 } else {
253 return locale;
254 }
255 }
256
257 /** Sets the locale for the current thread. Used by {@link
258 * #instance(String,Locale)}. **/
259 public static void setThreadLocale(Locale locale) {
260 mapThreadToLocale.set(locale);
261 }
262
263 /** Returns the preferred locale of the current thread, or null if the
264 * thread has not called {@link #setThreadLocale}. **/
265 public static Locale getThreadLocale() {
266 return (Locale) mapThreadToLocale.get();
267 }
268 }
269
270 // End ShadowResourceBundle.java