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            if (dotx < 1) {
089                _baseName = name;
090            } else {
091                _baseName = name.substring(0, dotx);
092            }
093        }
094    
095        public String getMessage(String key, Locale locale)
096        {
097            return findProperties(locale).getProperty(key);
098        }
099    
100        private synchronized Properties findProperties(Locale locale)
101        {
102            Properties result = (Properties) _propertiesMap.get(locale);
103    
104            // If doesn't exist, build it (which will update the
105            // propertiesMap as a side effect.
106    
107            if (result == null)
108                result = buildProperties(locale);
109    
110            return result;
111        }
112    
113        private Properties buildProperties(Locale locale)
114        {
115            Properties result = _emptyProperties;
116    
117            List localizations = findLocalizations(locale);
118    
119            Iterator i = localizations.iterator();
120            while (i.hasNext())
121            {
122                Localization l = (Localization) i.next();
123    
124                result = readProperties(l.getLocale(), l.getResource(), result);
125            }
126    
127            return result;
128        }
129    
130        /**
131         * Returns the properties, reading them if necessary. Properties may have been previously read
132         * for this locale, in which case the cached value is returned. Also, if the resource doesn't
133         * exist, then the parent is returned as is. Updates the propertiesMap cache.
134         */
135    
136        private Properties readProperties(Locale locale, Resource propertiesResource, Properties parent)
137        {
138            Properties result = (Properties) _propertiesMap.get(locale);
139    
140            if (result != null)
141                return result;
142    
143            URL url = propertiesResource.getResourceURL();
144    
145            if (url == null)
146                result = parent;
147            else
148                result = readPropertiesFile(url, parent);
149    
150            _propertiesMap.put(locale, result);
151    
152            return result;
153        }
154    
155        private Properties readPropertiesFile(URL url, Properties parent)
156        {
157            InputStream stream = null;
158    
159            Properties result = new Properties(parent);
160    
161            try
162            {
163                stream = new BufferedInputStream(url.openStream());
164    
165                result.load(stream);
166    
167                stream.close();
168    
169                stream = null;
170            }
171            catch (IOException ex)
172            {
173                throw new ApplicationRuntimeException(ImplMessages.unableToReadMessages(url), ex);
174    
175            }
176            finally
177            {
178                IOUtils.close(stream);
179            }
180    
181            return result;
182        }
183    
184        /**
185         * Returns a List of Localizations, in order from most generic (i.e., hivemodule.properties) to
186         * most specific (i.e., hivemodule_en_US_yokel.properties).
187         */
188    
189        private List findLocalizations(Locale locale)
190        {
191            List result = new ArrayList();
192    
193            LocalizedNameGenerator g = new LocalizedNameGenerator(_baseName, locale, EXTENSION);
194    
195            while (g.more())
196            {
197                String name = g.next();
198    
199                Localization l = new Localization(g.getCurrentLocale(), _baseResource
200                        .getRelativeResource(name));
201    
202                result.add(l);
203            }
204    
205            Collections.reverse(result);
206    
207            return result;
208        }
209    }