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         *    /&#42;&#42;
161         *      &#42; Retrieves the instance of {&#64;link MyResource} appropriate
162         *      &#42; to the current locale. If this thread has specified a locale
163         *      &#42; by calling {&#64;link #setThreadLocale}, this locale is used,
164         *      &#42; otherwise the default locale is used.
165         *      &#42;&#42;/
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         *    /&#42;&#42;
189         *      &#42; Retrieves the instance of {&#64;link MyResource} appropriate
190         *      &#42; to the given locale.
191         *      &#42;&#42;/
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         *    /&#42;&#42;
221         *      &#42; Retrieves the instance of {&#64;link MyResource} appropriate
222         *      &#42; to the given locale.
223         *      &#42;&#42;/
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