blob: c6cae726e34fd9164e2b6f31896127f54d88c0e3 [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();
}
}