blob: 445be8374fd02a74754ad81eec524a3e072ccc37 [file] [log] [blame]
/*
* Copyright 2002,2004 The Apache Software Foundation.
*
* Licensed 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 org.apache.commons.jelly.tags.fmt;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jelly.TagSupport;
import org.apache.commons.jelly.expression.Expression;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
/**
* Support for tag handlers for <bundle>, the resource bundle loading
* tag in JSTL.
*
* @author <a href="mailto:willievu@yahoo.com">Willie Vu</a>
* @version 1.1
*
* @todo decide how to implement setResponseLocale
*/
public class BundleTag extends TagSupport {
//*********************************************************************
// Private constants
private static final Locale EMPTY_LOCALE = new Locale("", "");
//*********************************************************************
// Protected state
private Expression basename; // 'basename' attribute
private Expression prefix; // 'prefix' attribute
/** evaluated basename */
private String ebasename;
/** evaluated prefix */
private String eprefix;
//*********************************************************************
// Private state
private LocalizationContext locCtxt;
//*********************************************************************
// Constructor and initialization
public BundleTag() {
}
//*********************************************************************
// Collaboration with subtags
public LocalizationContext getLocalizationContext() {
return locCtxt;
}
public String getPrefixAsString() {
return eprefix;
}
//*********************************************************************
// Tag logic
/**
* Evaluates this tag after all the tags properties have been initialized.
*
*/
public void doTag(XMLOutput output) throws JellyTagException {
Object basenameInput = null;
if (this.basename != null) {
basenameInput = this.basename.evaluate(context);
}
if (basenameInput != null) {
ebasename = basenameInput.toString();
}
Object prefixInput = null;
if (this.prefix != null) {
prefixInput = this.prefix.evaluate(context);
}
if (prefixInput != null) {
eprefix = prefixInput.toString();
}
this.locCtxt = this.getLocalizationContext(context, ebasename);
invokeBody(output);
}
//*********************************************************************
// Public utility methods
/**
* Gets the default I18N localization context.
*
* @param jc Page in which to look up the default I18N localization context
*/
public static LocalizationContext getLocalizationContext(JellyContext jc) {
LocalizationContext locCtxt = null;
Object obj = jc.getVariable(Config.FMT_LOCALIZATION_CONTEXT);
if (obj == null) {
return null;
}
if (obj instanceof LocalizationContext) {
locCtxt = (LocalizationContext) obj;
} else {
// localization context is a bundle basename
locCtxt = getLocalizationContext(jc, (String) obj);
}
return locCtxt;
}
/**
* Gets the resource bundle with the given base name, whose locale is
* determined as follows:
*
* Check if a match exists between the ordered set of preferred
* locales and the available locales, for the given base name.
* The set of preferred locales consists of a single locale
* (if the <tt>org.apache.commons.jelly.tags.fmt.locale</tt> configuration
* setting is present).
*
* <p> If no match was found in the previous step, check if a match
* exists between the fallback locale (given by the
* <tt>org.apache.commons.jelly.tags.fmt.fallbackLocale</tt> configuration
* setting) and the available locales, for the given base name.
*
* @param pageContext Page in which the resource bundle with the
* given base name is requested
* @param basename Resource bundle base name
*
* @return Localization context containing the resource bundle with the
* given base name and the locale that led to the resource bundle match,
* or the empty localization context if no resource bundle match was found
*/
public static LocalizationContext getLocalizationContext(JellyContext jc,
String basename) {
LocalizationContext locCtxt = null;
ResourceBundle bundle = null;
if ((basename == null) || basename.equals("")) {
return new LocalizationContext();
}
// Try preferred locales
Locale pref = null; {
Object tmp = jc.getVariable(Config.FMT_LOCALE);
if (tmp != null && tmp instanceof Locale) {
pref = (Locale) tmp;
}
}
if (pref != null) {
// Preferred locale is application-based
bundle = findMatch(basename, pref, jc.getClassLoader());
if (bundle != null) {
locCtxt = new LocalizationContext(bundle, pref);
}
}
if (locCtxt == null) {
// No match found with preferred locales, try using fallback locale
{
Object tmp = jc.getVariable(Config.FMT_FALLBACK_LOCALE);
if (tmp != null && tmp instanceof Locale) {
pref = (Locale) tmp;
}
}
if (pref != null) {
bundle = findMatch(basename, pref, jc.getClassLoader());
if (bundle != null) {
locCtxt = new LocalizationContext(bundle, pref);
}
}
}
if (locCtxt == null) {
// try using the root resource bundle with the given basename
try {
bundle = ResourceBundle.getBundle(basename, EMPTY_LOCALE,
jc.getClassLoader());
if (bundle != null) {
locCtxt = new LocalizationContext(bundle, null);
}
} catch (MissingResourceException mre) {
// do nothing
}
}
if (locCtxt != null) {
// set response locale
if (locCtxt.getLocale() != null) {
// TODO
// SetLocaleSupport.setResponseLocale(jc, locCtxt.getLocale());
}
} else {
// create empty localization context
locCtxt = new LocalizationContext();
}
return locCtxt;
}
/*
* Gets the resource bundle with the given base name and preferred locale.
*
* This method calls java.util.ResourceBundle.getBundle(), but ignores
* its return value unless its locale represents an exact or language match
* with the given preferred locale.
*
* @param basename the resource bundle base name
* @param pref the preferred locale
* @param cl classloader used to find resource bundle
*
* @return the requested resource bundle, or <tt>null</tt> if no resource
* bundle with the given base name exists or if there is no exact- or
* language-match between the preferred locale and the locale of
* the bundle returned by java.util.ResourceBundle.getBundle().
*/
private static ResourceBundle findMatch(String basename, Locale pref, ClassLoader cl) {
ResourceBundle match = null;
try {
ResourceBundle bundle =
ResourceBundle.getBundle(basename, pref, cl);
Locale avail = bundle.getLocale();
if (pref.equals(avail)) {
// Exact match
match = bundle;
} else {
if (pref.getLanguage().equals(avail.getLanguage())
&& ("".equals(avail.getCountry()))) {
// Language match.
// By making sure the available locale does not have a
// country and matches the preferred locale's language, we
// rule out "matches" based on the container's default
// locale. For example, if the preferred locale is
// "en-US", the container's default locale is "en-UK", and
// there is a resource bundle (with the requested base
// name) available for "en-UK", ResourceBundle.getBundle()
// will return it, but even though its language matches
// that of the preferred locale, we must ignore it,
// because matches based on the container's default locale
// are not portable across different containers with
// different default locales.
match = bundle;
}
}
} catch (MissingResourceException mre) {
}
return match;
}
/** Setter for property basename.
* @param basename New value of property basename.
*
*/
public void setBasename(Expression basename) {
this.basename = basename;
}
/** Setter for property prefix.
* @param prefix New value of property prefix.
*
*/
public void setPrefix(Expression prefix) {
this.prefix = prefix;
}
}