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