blob: 21c2f59948ff5226ef62c28beb01f851af75d647 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package flex.messaging.util;
import java.io.InputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Locale;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import flex.messaging.log.Log;
import flex.messaging.log.Logger;
import flex.messaging.log.LogCategories;
/**
* Implementation of <code>ResourceLoader</code> that loads string resources
* from property files.
* <p>
* This class uses <code>MessageFormat</code> to perform substitutions
* within parameterized strings.
* </p>
*
* @see MessageFormat
*
*/
public class PropertyStringResourceLoader implements ResourceLoader
{
// The property file bundle that contains localized error strings for BlazeDS.
public static final String PROPERTY_BUNDLE = "flex/messaging/errors";
// The property file bundle that contains localized error strings for BlazeDS
// code specific to vendors (eg. LoginCommands for specific application serves)
public static final String VENDORS_BUNDLE = "flex/messaging/vendors";
// The property file bundle that contains localized error strings for LCDS.
public static final String LCDS_PROPERTY_BUNDLE = "flex/data/errors";
// The category to write log entries under.
private static final String LOG_CATEGORY = LogCategories.RESOURCE;
// The property bundle names to use in string lookups.
private String[] propertyBundles;
// The default FDS locale.
private Locale defaultLocale;
// The set of locales that have strings loaded.
private Set loadedLocales = new TreeSet();
// A map of all loaded strings.
private Map strings = new HashMap();
// The logger for this instance.
private Logger logger;
/**
* Constructs a <code>PropertyStringResourceLoader</code> using the default
* property bundles specified by the <code>PROPERTY_BUNDLE</code> and
* <code>LCDS_PROPERTY_BUNDLE</code> fields.
*/
public PropertyStringResourceLoader()
{
this(new String[] {PROPERTY_BUNDLE, LCDS_PROPERTY_BUNDLE});
}
/**
* Constructs a <code>PropertyStringResourceLoader</code> that will use the
* specified property bundle to use for string lookups.
*
* @param propertyBundle The property bundles to use for lookups.
*/
public PropertyStringResourceLoader(String propertyBundle)
{
this(new String[] {propertyBundle});
}
/**
* Constructs a <code>PropertyStringResourceLoader</code> that will use the
* specified property bundles to use for string lookups.
*
* @param propertyBundles The list of the property bundles to use for lookups.
*/
public PropertyStringResourceLoader(String[] propertyBundles)
{
this.propertyBundles = propertyBundles;
logger = Log.getLogger(LOG_CATEGORY);
}
// Implements flex.messaging.util.ResourceLoader.init; inherits javadoc specification.
public void init(Map properties)
{}
// Implements flex.messaging.util.ResourceLoader.getString; inherits javadoc specification.
public String getString(String key)
{
return getString(key, null, null);
}
// Implements flex.messaging.util.ResourceLoader.getString; inherits javadoc specification.
public String getString(String key, Object[] arguments)
{
return getString(key, null, arguments);
}
// Implements flex.messaging.util.ResourceLoader.getString; inherits javadoc specification.
public String getString(String key, Locale locale)
{
return getString(key, locale, null);
}
// Implements flex.messaging.util.ResourceLoader.getString; inherits javadoc specification.
public String getString(String key, Locale locale, Object[] arguments)
{
synchronized(strings)
{
if (defaultLocale == null)
{
defaultLocale = getDefaultLocale();
}
}
String value = null;
String stringKey = null;
String localeKey = (locale != null) ?
generateLocaleKey(locale) :
generateLocaleKey(defaultLocale);
String originalStringKey = generateStringKey(key, localeKey);
int trimIndex = 0;
/*
* Attempt to get a string for the target locale - fail back to less specific
* versions of the locale.
*/
while (true)
{
loadStrings(localeKey);
stringKey = generateStringKey(key, localeKey);
synchronized(strings)
{
value = (String) strings.get(stringKey);
if (value != null)
{
if (!stringKey.equals(originalStringKey))
{
strings.put(originalStringKey, value);
}
return substituteArguments(value, arguments);
}
}
trimIndex = localeKey.lastIndexOf('_');
if (trimIndex != -1)
{
localeKey = localeKey.substring(0, trimIndex);
}
else
{
break;
}
}
/*
* Attempt to get the string in our default locale if it is
* different than the requested locale.
*/
if ((locale != null) && (!locale.equals(defaultLocale)))
{
localeKey = generateLocaleKey(defaultLocale);
stringKey = generateStringKey(key, localeKey);
synchronized(strings)
{
value = (String) strings.get(stringKey);
if (value != null)
{
strings.put(originalStringKey, value);
return substituteArguments(value, arguments);
}
}
}
// As a last resort, try to get a non-locale-specific string.
loadStrings("");
stringKey = generateStringKey(key, "");
synchronized(strings)
{
value = (String) strings.get(stringKey);
if (value != null)
{
strings.put(originalStringKey, value);
return substituteArguments(value, arguments);
}
}
// No string is available. Return a formatted missing string value.
return ("???" + key + "???");
}
/**
* Sets the default locale to be used when locating resources. The
* string will be converted into a Locale.
*
* @param locale The default locale to be used.
*/
public void setDefaultLocale(String locale)
{
defaultLocale = LocaleUtils.buildLocale(locale);
}
/**
* Sets the default locale to be used when locating resources.
*
* @param locale The default locale to be used.
*/
public void setDefaultLocale(Locale locale)
{
defaultLocale = locale;
}
/**
* The default locale to be used when locating resources.
* @return Locale the default Locale object
*/
public Locale getDefaultLocale()
{
if (defaultLocale == null)
defaultLocale = Locale.getDefault();
return defaultLocale;
}
/**
* Loads localized strings for the specified locale from a property file.
*
* @param localeKey The locale to load strings for.
*/
protected synchronized void loadStrings(String localeKey)
{
if (loadedLocales.contains(localeKey))
{
return;
}
if (propertyBundles != null)
{
for (int i = 0; i < propertyBundles.length; i++)
{
String propertyBundle = propertyBundles[i];
loadProperties(localeKey, propertyBundle);
}
}
}
protected InputStream loadFile(String filename)
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream stream = loader.getResourceAsStream(filename);
// Try the properties file in our classloader too - just in case
if (stream == null)
{
stream = PropertyStringResourceLoader.class.getClassLoader().getResourceAsStream(filename);
}
return stream;
}
// Helper method for loadStrings.
protected void loadProperties(String localeKey, String propertyBundle)
{
// Build the path to the target property file.
String filename = propertyBundle;
if (localeKey.length() > 0)
{
filename += "_" + localeKey;
}
filename += ".properties";
// Load the property file.
InputStream stream = loadFile(filename);
Properties props = new Properties();
if (stream != null)
{
try
{
props.load(stream);
}
catch (IOException ioe)
{
logger.warn("There was a problem reading the string resource property file '" + filename + "' stream.", ioe);
}
catch (IllegalArgumentException iae)
{
logger.warn("The string resource property file '" + filename + "' contains a malformed Unicode escape sequence.", iae);
}
finally
{
try
{
stream.close();
}
catch (IOException ioe)
{
logger.warn("The string resource property file '" + filename + "' stream failed to close.", ioe);
}
}
}
else
{
logger.warn("The class loader could not locate the string resource property file '" + filename + "'. This may not be an issue if a property file is available for a less specific locale or the default locale.");
}
// Move strings into string cache.
if (props.size() > 0)
{
synchronized(strings)
{
Iterator iter = props.keySet().iterator();
while (iter.hasNext())
{
String key = (String) iter.next();
strings.put(generateStringKey(key, localeKey), props.getProperty(key));
}
}
}
}
/**
* Generates a locale cache key.
*
* @param locale The locale to generate a cache key for.
* @return The generated cache key.
*/
private String generateLocaleKey(Locale locale)
{
return (locale == null) ? "" : locale.toString();
}
/**
* Generates a cache key for a string resource.
*
* @param key The string to generate a cache key for.
* @param locale The locale to retrieve the string for.
* @return The generated cache key for the string resource.
*/
private String generateStringKey(String key, String locale)
{
return (key + "-" + locale);
}
/**
* Substitutes the specified arguments into a parameterized string.
*
* @param parameterized The string containing parameter tokens for substitution.
* @param arguments The arguments to substitute into the parameterized string.
* @return The resulting substituted string.
*/
private String substituteArguments(String parameterized, Object[] arguments)
{
return MessageFormat.format(parameterized, arguments).trim();
}
}