blob: 1bc1a1a64536c31a5ef815f12b300c009a814e14 [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 org.apache.cocoon.template.instruction;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Stack;
import org.apache.cocoon.components.expression.ExpressionContext;
import org.apache.cocoon.template.environment.ErrorHolder;
import org.apache.cocoon.template.environment.ExecutionContext;
import org.apache.cocoon.template.environment.ParsingContext;
import org.apache.cocoon.template.expression.JXTExpression;
import org.apache.cocoon.template.expression.StringTemplateParser;
import org.apache.cocoon.template.script.event.Event;
import org.apache.cocoon.template.script.event.StartElement;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.commons.lang.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* @version SVN $Id$
*/
public class FormatNumber extends LocaleAwareInstruction {
private JXTExpression value;
private JXTExpression type;
private JXTExpression pattern;
private JXTExpression currencyCode;
private JXTExpression currencySymbol;
private JXTExpression isGroupingUsed;
private JXTExpression maxIntegerDigits;
private JXTExpression minIntegerDigits;
private JXTExpression maxFractionDigits;
private JXTExpression minFractionDigits;
private JXTExpression var;
private static Class currencyClass;
private static final String NUMBER = "number";
private static final String CURRENCY = "currency";
private static final String PERCENT = "percent";
static {
try {
currencyClass = Class.forName("java.util.Currency");
// container's runtime is J2SE 1.4 or greater
} catch (Exception cnfe) {
// EMPTY
}
}
public FormatNumber(ParsingContext parsingContext, StartElement raw, Attributes attrs, Stack stack)
throws SAXException {
super(parsingContext, raw, attrs, stack);
Locator locator = getLocation();
StringTemplateParser expressionCompiler = parsingContext.getStringTemplateParser();
this.value = expressionCompiler.compileExpr(attrs.getValue("value"), null, locator);
this.type = expressionCompiler.compileExpr(attrs.getValue("type"), null, locator);
this.pattern = expressionCompiler.compileExpr(attrs.getValue("pattern"), null, locator);
this.currencyCode =
expressionCompiler.compileExpr(attrs.getValue("currencyCode"), null, locator);
this.currencySymbol =
expressionCompiler.compileExpr(attrs.getValue("currencySymbol"), null, locator);
this.isGroupingUsed =
expressionCompiler.compileBoolean(attrs.getValue("isGroupingUsed"), null, locator);
this.maxIntegerDigits =
expressionCompiler.compileInt(attrs.getValue("maxIntegerDigits"), null, locator);
this.minIntegerDigits =
expressionCompiler.compileInt(attrs.getValue("minIntegerDigits"), null, locator);
this.maxFractionDigits =
expressionCompiler.compileInt(attrs.getValue("maxFractionDigits"), null, locator);
this.minFractionDigits =
expressionCompiler.compileInt(attrs.getValue("minFractionDigits"), null, locator);
this.var = expressionCompiler.compileExpr(attrs.getValue("var"), null, locator);
}
public Event execute(final XMLConsumer consumer,
ExpressionContext expressionContext, ExecutionContext executionContext,
MacroContext macroContext, Event startEvent, Event endEvent)
throws SAXException {
try {
String result = format(expressionContext);
if (result != null) {
char[] chars = result.toCharArray();
consumer.characters(chars, 0, chars.length);
}
} catch (Exception e) {
throw new SAXParseException(e.getMessage(), getLocation(), e);
} catch (Error err) {
throw new SAXParseException(err.getMessage(), getLocation(),
new ErrorHolder(err));
}
return getNext();
}
private String format(ExpressionContext expressionContext) throws Exception {
// Determine formatting locale
String var = this.var == null ? null : this.var.getStringValue(expressionContext);
Number input = this.value.getNumberValue(expressionContext);
String type = this.type == null ? null : this.type.getStringValue(expressionContext);
String pattern = this.pattern == null ? null : this.pattern.getStringValue(expressionContext);
String currencyCode = this.currencyCode == null ? null : this.currencyCode.getStringValue(expressionContext);
String currencySymbol = this.currencySymbol == null ? null : this.currencySymbol.getStringValue(expressionContext);
Boolean isGroupingUsed = this.isGroupingUsed == null ? null : this.isGroupingUsed.getBooleanValue(expressionContext);
Number maxIntegerDigits = this.maxIntegerDigits == null ? null : this.maxIntegerDigits.getNumberValue(expressionContext);
Number minIntegerDigits = this.minIntegerDigits == null ? null : this.minIntegerDigits.getNumberValue(expressionContext);
Number maxFractionDigits = this.maxFractionDigits == null ? null : this.maxFractionDigits.getNumberValue(expressionContext);
Number minFractionDigits = this.minFractionDigits == null ? null : this.minFractionDigits.getNumberValue(expressionContext);
Locale loc = getLocale(expressionContext);
String formatted;
if (loc != null) {
// Create formatter
NumberFormat formatter = null;
if (StringUtils.isNotEmpty(pattern)) {
// if 'pattern' is specified, 'type' is ignored
DecimalFormatSymbols symbols = new DecimalFormatSymbols(loc);
formatter = new DecimalFormat(pattern, symbols);
} else {
formatter = createFormatter(loc, type);
}
if (StringUtils.isNotEmpty(pattern)
|| CURRENCY.equalsIgnoreCase(type)) {
setCurrency(formatter, currencyCode, currencySymbol);
}
configureFormatter(formatter, isGroupingUsed, maxIntegerDigits,
minIntegerDigits, maxFractionDigits, minFractionDigits);
formatted = formatter.format(input);
} else {
// no formatting locale available, use toString()
formatted = input.toString();
}
if (var != null) {
expressionContext.put(var, formatted);
return null;
}
return formatted;
}
private NumberFormat createFormatter(Locale loc, String type)
throws Exception {
NumberFormat formatter = null;
if ((type == null) || NUMBER.equalsIgnoreCase(type)) {
formatter = NumberFormat.getNumberInstance(loc);
} else if (CURRENCY.equalsIgnoreCase(type)) {
formatter = NumberFormat.getCurrencyInstance(loc);
} else if (PERCENT.equalsIgnoreCase(type)) {
formatter = NumberFormat.getPercentInstance(loc);
} else {
throw new IllegalArgumentException("Invalid type: \"" + type
+ "\": should be \"number\" or \"currency\" or \"percent\"");
}
return formatter;
}
/*
* Applies the 'groupingUsed', 'maxIntegerDigits', 'minIntegerDigits',
* 'maxFractionDigits', and 'minFractionDigits' attributes to the given
* formatter.
*/
private void configureFormatter(NumberFormat formatter,
Boolean isGroupingUsed, Number maxIntegerDigits,
Number minIntegerDigits, Number maxFractionDigits,
Number minFractionDigits) {
if (isGroupingUsed != null)
formatter.setGroupingUsed(isGroupingUsed.booleanValue());
if (maxIntegerDigits != null)
formatter.setMaximumIntegerDigits(maxIntegerDigits.intValue());
if (minIntegerDigits != null)
formatter.setMinimumIntegerDigits(minIntegerDigits.intValue());
if (maxFractionDigits != null)
formatter.setMaximumFractionDigits(maxFractionDigits.intValue());
if (minFractionDigits != null)
formatter.setMinimumFractionDigits(minFractionDigits.intValue());
}
/*
* Override the formatting locale's default currency symbol with the
* specified currency code (specified via the "currencyCode" attribute) or
* currency symbol (specified via the "currencySymbol" attribute).
*
* If both "currencyCode" and "currencySymbol" are present, "currencyCode"
* takes precedence over "currencySymbol" if the java.util.Currency class is
* defined in the container's runtime (that is, if the container's runtime
* is J2SE 1.4 or greater), and "currencySymbol" takes precendence over
* "currencyCode" otherwise.
*
* If only "currencyCode" is given, it is used as a currency symbol if
* java.util.Currency is not defined.
*
* Example:
*
* JDK "currencyCode" "currencySymbol" Currency symbol being displayed
* -----------------------------------------------------------------------
* all --- --- Locale's default currency symbol
*
* <1.4 EUR --- EUR >=1.4 EUR --- Locale's currency symbol for Euro
*
* all --- \u20AC \u20AC
*
* <1.4 EUR \u20AC \u20AC >=1.4 EUR \u20AC Locale's currency symbol for Euro
*/
private void setCurrency(NumberFormat formatter, String currencyCode,
String currencySymbol) throws Exception {
String code = null;
String symbol = null;
if (currencyCode == null) {
if (currencySymbol == null) {
return;
}
symbol = currencySymbol;
} else if (currencySymbol != null) {
if (currencyClass != null) {
code = currencyCode;
} else {
symbol = currencySymbol;
}
} else if (currencyClass != null) {
code = currencyCode;
} else {
symbol = currencyCode;
}
if (code != null) {
Object[] methodArgs = new Object[1];
/*
* java.util.Currency.getInstance()
*/
Method m = currencyClass.getMethod("getInstance",
new Class[] { String.class });
methodArgs[0] = code;
Object currency = m.invoke(null, methodArgs);
/*
* java.text.NumberFormat.setCurrency()
*/
Class[] paramTypes = new Class[1];
paramTypes[0] = currencyClass;
Class numberFormatClass = Class.forName("java.text.NumberFormat");
m = numberFormatClass.getMethod("setCurrency", paramTypes);
methodArgs[0] = currency;
m.invoke(formatter, methodArgs);
} else {
/*
* Let potential ClassCastException propagate up (will almost never
* happen)
*/
DecimalFormat df = (DecimalFormat) formatter;
DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
dfs.setCurrencySymbol(symbol);
df.setDecimalFormatSymbols(dfs);
}
}
}