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
015package org.apache.hivemind.impl;
016
017import java.io.BufferedInputStream;
018import java.io.IOException;
019import java.io.InputStream;
020import java.net.URL;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028import java.util.Properties;
029
030import org.apache.hivemind.ApplicationRuntimeException;
031import org.apache.hivemind.Resource;
032import org.apache.hivemind.internal.MessageFinder;
033import org.apache.hivemind.util.Defense;
034import org.apache.hivemind.util.IOUtils;
035import org.apache.hivemind.util.LocalizedNameGenerator;
036
037/**
038 * @author Howard M. Lewis Ship
039 * @since 1.1
040 */
041public 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}