/*
* Copyright 1999-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.anodyneos.xp.tag.fmt;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import org.anodyneos.xp.XpContext;
import org.anodyneos.xp.XpException;
import org.anodyneos.xp.XpOutput;
import org.anodyneos.xp.http.HttpXpContext;
import org.anodyneos.xp.tag.Config;
import org.anodyneos.xp.tag.Util;
import org.anodyneos.xp.tagext.XpTag;
import org.anodyneos.xp.tagext.XpTagSupport;
/**
* Support for tag handlers for <setLocale>, the locale setting tag in
* JSTL 1.0.
*
* @author jvas
*/
public class SetLocaleTag extends XpTagSupport {
// *********************************************************************
// Private constants
private static final char HYPHEN = '-';
private static final char UNDERSCORE = '_';
// *********************************************************************
// Protected state
private Object value; // 'value' attribute
private String variant; // 'variant' attribute
// *********************************************************************
// Private state
private int scope; // 'scope' attribute
// *********************************************************************
// Constructor and initialization
public SetLocaleTag() {
value = variant = null;
scope = XpContext.PAGE_SCOPE;
}
// *********************************************************************
// Accessor methods
// for tag attribute
public void setValue(Object value) {
this.value = value;
}
// for tag attribute
public void setVariant(String variant) {
this.variant = variant;
}
// *********************************************************************
// Tag attributes known at translation time
public void setScope(String scope) {
this.scope = getXpContext().resolveScope(scope);
}
// *********************************************************************
// Tag logic
public void doTag(XpOutput out) throws XpException {
Locale locale = null;
if (value == null) {
locale = Locale.getDefault();
} else if (value instanceof String) {
if (((String) value).trim().equals("")) {
locale = Locale.getDefault();
} else {
locale = parseLocale((String) value, variant);
}
} else {
locale = (Locale) value;
}
Config.set(getXpContext(), Config.FMT_LOCALE, locale, scope);
// setResponseLocale(pageContext, locale);
}
// *********************************************************************
// Public utility methods
/**
* See parseLocale(String, String) for details.
*/
public static Locale parseLocale(String locale) {
return parseLocale(locale, null);
}
/**
* Parses the given locale string into its language and (optionally) country
* components, and returns the corresponding <tt>java.util.Locale</tt>
* object.
*
* If the given locale string is null or empty, the runtime's default locale
* is returned.
*
* @param locale
* the locale string to parse
* @param variant
* the variant
*
* @return <tt>java.util.Locale</tt> object corresponding to the given
* locale string, or the runtime's default locale if the locale
* string is null or empty
*
* @throws IllegalArgumentException
* if the given locale does not have a language component or has
* an empty country component
*/
public static Locale parseLocale(String locale, String variant) {
Locale ret = null;
String language = locale;
String country = null;
int index = -1;
if (((index = locale.indexOf(HYPHEN)) > -1) || ((index = locale.indexOf(UNDERSCORE)) > -1)) {
language = locale.substring(0, index);
country = locale.substring(index + 1);
}
if ((language == null) || (language.length() == 0)) {
throw new IllegalArgumentException("Missing language");
}
if (country == null) {
if (variant != null)
ret = new Locale(language, "", variant);
else
ret = new Locale(language, "");
} else if (country.length() > 0) {
if (variant != null)
ret = new Locale(language, country, variant);
else
ret = new Locale(language, country);
} else {
throw new IllegalArgumentException("Missing country");
}
return ret;
}
// *********************************************************************
// Package-scoped utility methods
/*
* Returns the formatting locale to use with the given formatting action in
* the given page.
*
* @param pc The page context containing the formatting action @param
* fromTag The formatting action @param format <tt> true </tt> if the
* formatting action is of type <formatXXX> (as opposed to <parseXXX>), and
* <tt> false </tt> otherwise (if set to <tt> true </tt> , the formatting
* locale that is returned by this method is used to set the response
* locale).
*
* @param avail the array of available locales
*
* @return the formatting locale to use
*/
static Locale getFormattingLocale(XpContext pc, XpTag fromTag, boolean format, Locale[] avail) {
LocalizationContext locCtxt = null;
// Get formatting locale from enclosing <fmt:bundle>
XpTag parent = findAncestorWithClass(fromTag, BundleTag.class);
if (parent != null) {
/*
* use locale from localization context established by parent
* <fmt:bundle> action, unless that locale is null
*/
locCtxt = ((BundleTag) parent).getLocalizationContext();
if (locCtxt.getLocale() != null) {
if (format) {
// setResponseLocale(pc, locCtxt.getLocale());
}
return locCtxt.getLocale();
}
}
// Use locale from default I18N localization context, unless it is null
if ((locCtxt = BundleTag.getLocalizationContext(pc)) != null) {
if (locCtxt.getLocale() != null) {
if (format) {
// setResponseLocale(pc, locCtxt.getLocale());
}
return locCtxt.getLocale();
}
}
/*
* Establish formatting locale by comparing the preferred locales (in
* order of preference) against the available formatting locales, and
* determining the best matching locale.
*/
Locale match = null;
Locale pref = getLocale(pc, Config.FMT_LOCALE);
if (pref != null) {
// Preferred locale is application-based
match = findFormattingMatch(pref, avail);
} else {
// Preferred locales are browser-based
match = findFormattingMatch(pc, avail);
}
if (match == null) {
// Use fallback locale.
pref = getLocale(pc, Config.FMT_FALLBACK_LOCALE);
if (pref != null) {
match = findFormattingMatch(pref, avail);
}
}
if (format && (match != null)) {
// setResponseLocale(pc, match);
}
return match;
}
/**
* Setup the available formatting locales that will be used by
* getFormattingLocale(XpContext).
*/
static Locale[] availableFormattingLocales;
static {
Locale[] dateLocales = DateFormat.getAvailableLocales();
Locale[] numberLocales = NumberFormat.getAvailableLocales();
Vector vec = new Vector(dateLocales.length);
for (int i = 0; i < dateLocales.length; i++) {
for (int j = 0; j < numberLocales.length; j++) {
if (dateLocales[i].equals(numberLocales[j])) {
vec.add(dateLocales[i]);
break;
}
}
}
availableFormattingLocales = new Locale[vec.size()];
availableFormattingLocales = (Locale[]) vec.toArray(availableFormattingLocales);
/*
* for (int i=0; i <availableFormattingLocales.length; i++) {
* System.out.println("AvailableLocale[" + i + "] " +
* availableFormattingLocales[i]); }
*/
}
/*
* Returns the formatting locale to use when <fmt:message> is used with a
* locale-less localization context.
*
* @param pc The page context containing the formatting action @return the
* formatting locale to use
*/
static Locale getFormattingLocale(XpContext pc) {
/*
* Establish formatting locale by comparing the preferred locales (in
* order of preference) against the available formatting locales, and
* determining the best matching locale.
*/
Locale match = null;
Locale pref = getLocale(pc, Config.FMT_LOCALE);
if (pref != null) {
// Preferred locale is application-based
match = findFormattingMatch(pref, availableFormattingLocales);
} else {
// Preferred locales are browser-based
match = findFormattingMatch(pc, availableFormattingLocales);
}
if (match == null) {
// Use fallback locale.
pref = getLocale(pc, Config.FMT_FALLBACK_LOCALE);
if (pref != null) {
match = findFormattingMatch(pref, availableFormattingLocales);
}
}
if (match != null) {
// setResponseLocale(pc, match);
}
return match;
}
/*
* Returns the locale specified by the named scoped attribute or context
* configuration parameter.
*
* <p> The named scoped attribute is searched in the page, request, session
* (if valid), and application scope(s) (in this order). If no such
* attribute exists in any of the scopes, the locale is taken from the named
* context configuration parameter.
*
* @param pageContext the page in which to search for the named scoped
* attribute or context configuration parameter @param name the name of the
* scoped attribute or context configuration parameter
*
* @return the locale specified by the named scoped attribute or context
* configuration parameter, or <tt> null </tt> if no scoped attribute or
* configuration parameter with the given name exists
*/
static Locale getLocale(XpContext pageContext, String name) {
Locale loc = null;
Object obj = Config.find(pageContext, name);
if (obj != null) {
if (obj instanceof Locale) {
loc = (Locale) obj;
} else {
loc = parseLocale((String) obj);
}
}
return loc;
}
// *********************************************************************
// Private utility methods
/*
* Determines the client's preferred locales from the request, and compares
* each of the locales (in order of preference) against the available
* locales in order to determine the best matching locale.
*
* @param pageContext Page containing the formatting action @param avail
* Available formatting locales
*
* @return Best matching locale, or <tt> null </tt> if no match was found
*/
private static Locale findFormattingMatch(XpContext xpc, Locale[] avail) {
if (!(xpc instanceof HttpXpContext)) {
// We don't know what the client wants, return system locale.
// TODO: should we do this?
return Locale.getDefault();
} else {
// Determine from client's browser settings.
HttpXpContext hxpc = (HttpXpContext) xpc;
Locale match = null;
for (Enumeration enum_ = Util.getRequestLocales((HttpServletRequest) hxpc.getRequest()); enum_
.hasMoreElements();) {
Locale locale = (Locale) enum_.nextElement();
match = findFormattingMatch(locale, avail);
if (match != null) {
break;
}
}
return match;
}
}
/*
* Returns the best match between the given preferred locale and the given
* available locales.
*
* The best match is given as the first available locale that exactly
* matches the given preferred locale ("exact match"). If no exact match
* exists, the best match is given to an available locale that meets the
* following criteria (in order of priority): - available locale's variant
* is empty and exact match for both language and country - available
* locale's variant and country are empty, and exact match for language.
*
* @param pref the preferred locale @param avail the available formatting
* locales
*
* @return Available locale that best matches the given preferred locale, or
* <tt> null </tt> if no match exists
*/
private static Locale findFormattingMatch(Locale pref, Locale[] avail) {
Locale match = null;
boolean langAndCountryMatch = false;
for (int i = 0; i < avail.length; i++) {
if (pref.equals(avail[i])) {
// Exact match
match = avail[i];
break;
} else if (!"".equals(pref.getVariant()) && "".equals(avail[i].getVariant())
&& pref.getLanguage().equals(avail[i].getLanguage())
&& pref.getCountry().equals(avail[i].getCountry())) {
// Language and country match; different variant
match = avail[i];
langAndCountryMatch = true;
} else if (!langAndCountryMatch && pref.getLanguage().equals(avail[i].getLanguage())
&& ("".equals(avail[i].getCountry()))) {
// Language match
if (match == null) {
match = avail[i];
}
}
}
return match;
}
}