001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.template.instruction;
018:
019: import java.lang.reflect.Method;
020: import java.text.DecimalFormat;
021: import java.text.DecimalFormatSymbols;
022: import java.text.NumberFormat;
023: import java.util.Locale;
024: import java.util.Stack;
025:
026: import org.apache.cocoon.components.expression.ExpressionContext;
027: import org.apache.cocoon.template.environment.ErrorHolder;
028: import org.apache.cocoon.template.environment.ExecutionContext;
029: import org.apache.cocoon.template.environment.ParsingContext;
030: import org.apache.cocoon.template.expression.JXTExpression;
031: import org.apache.cocoon.template.expression.StringTemplateParser;
032: import org.apache.cocoon.template.script.event.Event;
033: import org.apache.cocoon.template.script.event.StartElement;
034: import org.apache.cocoon.xml.XMLConsumer;
035: import org.apache.commons.lang.StringUtils;
036: import org.xml.sax.Attributes;
037: import org.xml.sax.Locator;
038: import org.xml.sax.SAXException;
039: import org.xml.sax.SAXParseException;
040:
041: /**
042: * @version SVN $Id: FormatNumber.java 449189 2006-09-23 06:52:29Z crossley $
043: */
044: public class FormatNumber extends LocaleAwareInstruction {
045: private JXTExpression value;
046: private JXTExpression type;
047: private JXTExpression pattern;
048: private JXTExpression currencyCode;
049: private JXTExpression currencySymbol;
050: private JXTExpression isGroupingUsed;
051: private JXTExpression maxIntegerDigits;
052: private JXTExpression minIntegerDigits;
053: private JXTExpression maxFractionDigits;
054: private JXTExpression minFractionDigits;
055:
056: private JXTExpression var;
057:
058: private static Class currencyClass;
059: private static final String NUMBER = "number";
060: private static final String CURRENCY = "currency";
061: private static final String PERCENT = "percent";
062:
063: static {
064: try {
065: currencyClass = Class.forName("java.util.Currency");
066: // container's runtime is J2SE 1.4 or greater
067: } catch (Exception cnfe) {
068: // EMPTY
069: }
070: }
071:
072: public FormatNumber(ParsingContext parsingContext,
073: StartElement raw, Attributes attrs, Stack stack)
074: throws SAXException {
075: super (parsingContext, raw, attrs, stack);
076:
077: Locator locator = getLocation();
078:
079: StringTemplateParser expressionCompiler = parsingContext
080: .getStringTemplateParser();
081: this .value = expressionCompiler.compileExpr(attrs
082: .getValue("value"), null, locator);
083: this .type = expressionCompiler.compileExpr(attrs
084: .getValue("type"), null, locator);
085: this .pattern = expressionCompiler.compileExpr(attrs
086: .getValue("pattern"), null, locator);
087: this .currencyCode = expressionCompiler.compileExpr(attrs
088: .getValue("currencyCode"), null, locator);
089: this .currencySymbol = expressionCompiler.compileExpr(attrs
090: .getValue("currencySymbol"), null, locator);
091: this .isGroupingUsed = expressionCompiler.compileBoolean(attrs
092: .getValue("isGroupingUsed"), null, locator);
093: this .maxIntegerDigits = expressionCompiler.compileInt(attrs
094: .getValue("maxIntegerDigits"), null, locator);
095: this .minIntegerDigits = expressionCompiler.compileInt(attrs
096: .getValue("minIntegerDigits"), null, locator);
097: this .maxFractionDigits = expressionCompiler.compileInt(attrs
098: .getValue("maxFractionDigits"), null, locator);
099: this .minFractionDigits = expressionCompiler.compileInt(attrs
100: .getValue("minFractionDigits"), null, locator);
101: this .var = expressionCompiler.compileExpr(
102: attrs.getValue("var"), null, locator);
103: }
104:
105: public Event execute(final XMLConsumer consumer,
106: ExpressionContext expressionContext,
107: ExecutionContext executionContext,
108: MacroContext macroContext, Event startEvent, Event endEvent)
109: throws SAXException {
110: try {
111: String result = format(expressionContext);
112: if (result != null) {
113: char[] chars = result.toCharArray();
114: consumer.characters(chars, 0, chars.length);
115: }
116: } catch (Exception e) {
117: throw new SAXParseException(e.getMessage(), getLocation(),
118: e);
119: } catch (Error err) {
120: throw new SAXParseException(err.getMessage(),
121: getLocation(), new ErrorHolder(err));
122: }
123: return getNext();
124: }
125:
126: private String format(ExpressionContext expressionContext)
127: throws Exception {
128: // Determine formatting locale
129: String var = this .var == null ? null : this .var
130: .getStringValue(expressionContext);
131: Number input = this .value.getNumberValue(expressionContext);
132: String type = this .type == null ? null : this .type
133: .getStringValue(expressionContext);
134: String pattern = this .pattern == null ? null : this .pattern
135: .getStringValue(expressionContext);
136: String currencyCode = this .currencyCode == null ? null
137: : this .currencyCode.getStringValue(expressionContext);
138: String currencySymbol = this .currencySymbol == null ? null
139: : this .currencySymbol.getStringValue(expressionContext);
140: Boolean isGroupingUsed = this .isGroupingUsed == null ? null
141: : this .isGroupingUsed
142: .getBooleanValue(expressionContext);
143: Number maxIntegerDigits = this .maxIntegerDigits == null ? null
144: : this .maxIntegerDigits
145: .getNumberValue(expressionContext);
146: Number minIntegerDigits = this .minIntegerDigits == null ? null
147: : this .minIntegerDigits
148: .getNumberValue(expressionContext);
149: Number maxFractionDigits = this .maxFractionDigits == null ? null
150: : this .maxFractionDigits
151: .getNumberValue(expressionContext);
152: Number minFractionDigits = this .minFractionDigits == null ? null
153: : this .minFractionDigits
154: .getNumberValue(expressionContext);
155:
156: Locale loc = getLocale(expressionContext);
157: String formatted;
158: if (loc != null) {
159: // Create formatter
160: NumberFormat formatter = null;
161: if (StringUtils.isNotEmpty(pattern)) {
162: // if 'pattern' is specified, 'type' is ignored
163: DecimalFormatSymbols symbols = new DecimalFormatSymbols(
164: loc);
165: formatter = new DecimalFormat(pattern, symbols);
166: } else {
167: formatter = createFormatter(loc, type);
168: }
169: if (StringUtils.isNotEmpty(pattern)
170: || CURRENCY.equalsIgnoreCase(type)) {
171: setCurrency(formatter, currencyCode, currencySymbol);
172: }
173: configureFormatter(formatter, isGroupingUsed,
174: maxIntegerDigits, minIntegerDigits,
175: maxFractionDigits, minFractionDigits);
176: formatted = formatter.format(input);
177: } else {
178: // no formatting locale available, use toString()
179: formatted = input.toString();
180: }
181: if (var != null) {
182: expressionContext.put(var, formatted);
183: return null;
184: }
185: return formatted;
186: }
187:
188: private NumberFormat createFormatter(Locale loc, String type)
189: throws Exception {
190: NumberFormat formatter = null;
191: if ((type == null) || NUMBER.equalsIgnoreCase(type)) {
192: formatter = NumberFormat.getNumberInstance(loc);
193: } else if (CURRENCY.equalsIgnoreCase(type)) {
194: formatter = NumberFormat.getCurrencyInstance(loc);
195: } else if (PERCENT.equalsIgnoreCase(type)) {
196: formatter = NumberFormat.getPercentInstance(loc);
197: } else {
198: throw new IllegalArgumentException(
199: "Invalid type: \""
200: + type
201: + "\": should be \"number\" or \"currency\" or \"percent\"");
202: }
203: return formatter;
204: }
205:
206: /*
207: * Applies the 'groupingUsed', 'maxIntegerDigits', 'minIntegerDigits',
208: * 'maxFractionDigits', and 'minFractionDigits' attributes to the given
209: * formatter.
210: */
211: private void configureFormatter(NumberFormat formatter,
212: Boolean isGroupingUsed, Number maxIntegerDigits,
213: Number minIntegerDigits, Number maxFractionDigits,
214: Number minFractionDigits) {
215: if (isGroupingUsed != null)
216: formatter.setGroupingUsed(isGroupingUsed.booleanValue());
217: if (maxIntegerDigits != null)
218: formatter.setMaximumIntegerDigits(maxIntegerDigits
219: .intValue());
220: if (minIntegerDigits != null)
221: formatter.setMinimumIntegerDigits(minIntegerDigits
222: .intValue());
223: if (maxFractionDigits != null)
224: formatter.setMaximumFractionDigits(maxFractionDigits
225: .intValue());
226: if (minFractionDigits != null)
227: formatter.setMinimumFractionDigits(minFractionDigits
228: .intValue());
229: }
230:
231: /*
232: * Override the formatting locale's default currency symbol with the
233: * specified currency code (specified via the "currencyCode" attribute) or
234: * currency symbol (specified via the "currencySymbol" attribute).
235: *
236: * If both "currencyCode" and "currencySymbol" are present, "currencyCode"
237: * takes precedence over "currencySymbol" if the java.util.Currency class is
238: * defined in the container's runtime (that is, if the container's runtime
239: * is J2SE 1.4 or greater), and "currencySymbol" takes precendence over
240: * "currencyCode" otherwise.
241: *
242: * If only "currencyCode" is given, it is used as a currency symbol if
243: * java.util.Currency is not defined.
244: *
245: * Example:
246: *
247: * JDK "currencyCode" "currencySymbol" Currency symbol being displayed
248: * -----------------------------------------------------------------------
249: * all --- --- Locale's default currency symbol
250: *
251: * <1.4 EUR --- EUR >=1.4 EUR --- Locale's currency symbol for Euro
252: *
253: * all --- \u20AC \u20AC
254: *
255: * <1.4 EUR \u20AC \u20AC >=1.4 EUR \u20AC Locale's currency symbol for Euro
256: */
257: private void setCurrency(NumberFormat formatter,
258: String currencyCode, String currencySymbol)
259: throws Exception {
260: String code = null;
261: String symbol = null;
262:
263: if (currencyCode == null) {
264: if (currencySymbol == null) {
265: return;
266: }
267: symbol = currencySymbol;
268: } else if (currencySymbol != null) {
269: if (currencyClass != null) {
270: code = currencyCode;
271: } else {
272: symbol = currencySymbol;
273: }
274: } else if (currencyClass != null) {
275: code = currencyCode;
276: } else {
277: symbol = currencyCode;
278: }
279: if (code != null) {
280: Object[] methodArgs = new Object[1];
281:
282: /*
283: * java.util.Currency.getInstance()
284: */
285: Method m = currencyClass.getMethod("getInstance",
286: new Class[] { String.class });
287:
288: methodArgs[0] = code;
289: Object currency = m.invoke(null, methodArgs);
290:
291: /*
292: * java.text.NumberFormat.setCurrency()
293: */
294: Class[] paramTypes = new Class[1];
295: paramTypes[0] = currencyClass;
296: Class numberFormatClass = Class
297: .forName("java.text.NumberFormat");
298: m = numberFormatClass.getMethod("setCurrency", paramTypes);
299: methodArgs[0] = currency;
300: m.invoke(formatter, methodArgs);
301: } else {
302: /*
303: * Let potential ClassCastException propagate up (will almost never
304: * happen)
305: */
306: DecimalFormat df = (DecimalFormat) formatter;
307: DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
308: dfs.setCurrencySymbol(symbol);
309: df.setDecimalFormatSymbols(dfs);
310: }
311: }
312: }
|