| /* |
| * 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); |
| } |
| } |
| } |