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
026package org.eigenbase.resgen;
027
028import java.io.IOException;
029import java.io.InputStream;
030import 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 */
060public 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