001    // Copyright 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.hivemind.impl;
016    
017    import java.io.BufferedInputStream;
018    import java.io.IOException;
019    import java.io.InputStream;
020    import java.net.URL;
021    import java.util.ArrayList;
022    import java.util.Collections;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Locale;
027    import java.util.Map;
028    import java.util.Properties;
029    
030    import org.apache.hivemind.ApplicationRuntimeException;
031    import org.apache.hivemind.Resource;
032    import org.apache.hivemind.internal.MessageFinder;
033    import org.apache.hivemind.util.Defense;
034    import org.apache.hivemind.util.IOUtils;
035    import org.apache.hivemind.util.LocalizedNameGenerator;
036    
037    /**
038     * @author Howard M. Lewis Ship
039     * @since 1.1
040     */
041    public class MessageFinderImpl implements MessageFinder
042    {
043        private static final String EXTENSION = ".properties";
044    
045        private static class Localization
046        {
047            private Locale _locale;
048    
049            private Resource _resource;
050    
051            Localization(Locale locale, Resource resource)
052            {
053                _locale = locale;
054                _resource = resource;
055            }
056    
057            public Locale getLocale()
058            {
059                return _locale;
060            }
061    
062            public Resource getResource()
063            {
064                return _resource;
065            }
066    
067        }
068    
069        private Resource _baseResource;
070    
071        private String _baseName;
072    
073        private Map _propertiesMap = new HashMap();
074    
075        private Properties _emptyProperties = new Properties();
076    
077        public MessageFinderImpl(Resource baseResource)
078        {
079            Defense.notNull(baseResource, "baseResource");
080    
081            _baseResource = baseResource;
082    
083            // Strip off the extension to form the base name
084            // when building new (localized) resources.
085    
086            String name = _baseResource.getName();
087            int dotx = name.lastIndexOf('.');
088    
089            _baseName = name.substring(0, dotx);
090        }
091    
092        public String getMessage(String key, Locale locale)
093        {
094            return findProperties(locale).getProperty(key);
095        }
096    
097        private synchronized Properties findProperties(Locale locale)
098        {
099            Properties result = (Properties) _propertiesMap.get(locale);
100    
101            // If doesn't exist, build it (which will update the
102            // propertiesMap as a side effect.
103    
104            if (result == null)
105                result = buildProperties(locale);
106    
107            return result;
108        }
109    
110        private Properties buildProperties(Locale locale)
111        {
112            Properties result = _emptyProperties;
113    
114            List localizations = findLocalizations(locale);
115    
116            Iterator i = localizations.iterator();
117            while (i.hasNext())
118            {
119                Localization l = (Localization) i.next();
120    
121                result = readProperties(l.getLocale(), l.getResource(), result);
122            }
123    
124            return result;
125        }
126    
127        /**
128         * Returns the properties, reading them if necessary. Properties may have been previously read
129         * for this locale, in which case the cached value is returned. Also, if the resource doesn't
130         * exist, then the parent is returned as is. Updates the propertiesMap cache.
131         */
132    
133        private Properties readProperties(Locale locale, Resource propertiesResource, Properties parent)
134        {
135            Properties result = (Properties) _propertiesMap.get(locale);
136    
137            if (result != null)
138                return result;
139    
140            URL url = propertiesResource.getResourceURL();
141    
142            if (url == null)
143                result = parent;
144            else
145                result = readPropertiesFile(url, parent);
146    
147            _propertiesMap.put(locale, result);
148    
149            return result;
150        }
151    
152        private Properties readPropertiesFile(URL url, Properties parent)
153        {
154            InputStream stream = null;
155    
156            Properties result = new Properties(parent);
157    
158            try
159            {
160                stream = new BufferedInputStream(url.openStream());
161    
162                result.load(stream);
163    
164                stream.close();
165    
166                stream = null;
167            }
168            catch (IOException ex)
169            {
170                throw new ApplicationRuntimeException(ImplMessages.unableToReadMessages(url), ex);
171    
172            }
173            finally
174            {
175                IOUtils.close(stream);
176            }
177    
178            return result;
179        }
180    
181        /**
182         * Returns a List of Localizations, in order from most generic (i.e., hivemodule.properties) to
183         * most specific (i.e., hivemodule_en_US_yokel.properties).
184         */
185    
186        private List findLocalizations(Locale locale)
187        {
188            List result = new ArrayList();
189    
190            LocalizedNameGenerator g = new LocalizedNameGenerator(_baseName, locale, EXTENSION);
191    
192            while (g.more())
193            {
194                String name = g.next();
195    
196                Localization l = new Localization(g.getCurrentLocale(), _baseResource
197                        .getRelativeResource(name));
198    
199                result.add(l);
200            }
201    
202            Collections.reverse(result);
203    
204            return result;
205        }
206    }