001: package org.apache.velocity.tools.generic;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.text.DecimalFormat;
023: import java.text.DecimalFormatSymbols;
024: import java.text.NumberFormat;
025: import java.util.Locale;
026:
027: /**
028: * Tool for working with {@link Number} in Velocity templates.
029: * It is useful for accessing and
030: * formatting arbitrary {@link Number} objects. Also
031: * the tool can be used to retrieve {@link NumberFormat} instances
032: * or make conversions to and from various number types.
033: * <p><pre>
034: * Example uses:
035: * $myNumber -> 13.55
036: * $number.format('currency',$myNumber) -> $13.55
037: * $number.format('integer',$myNumber) -> 13
038: *
039: * Example toolbox.xml config (if you want to use this with VelocityView):
040: * <tool>
041: * <key>number</key>
042: * <scope>application</scope>
043: * <class>org.apache.velocity.tools.generic.NumberTool</class>
044: * </tool>
045: * </pre></p>
046: *
047: * <p>This tool is entirely threadsafe, and has no instance members.
048: * It may be used in any scope (request, session, or application).
049: * As such, the methods are highly interconnected, and overriding
050: * key methods provides an easy way to create subclasses that use
051: * a non-default format or locale.</p>
052: *
053: * @author Nathan Bubna
054: * @author <a href="mailto:mkienenb@alaska.net">Mike Kienenberger</a>
055: * @since VelocityTools 1.2
056: * @version $Id: NumberTool.java 479724 2006-11-27 18:49:37Z nbubna $
057: */
058: public class NumberTool {
059:
060: /**
061: * The default format to be used when none is specified.
062: */
063: public static final String DEFAULT_FORMAT = "default";
064:
065: private static final int STYLE_NUMBER = 0;
066: private static final int STYLE_CURRENCY = 1;
067: private static final int STYLE_PERCENT = 2;
068: //NOTE: '3' belongs to a non-public "scientific" style
069: private static final int STYLE_INTEGER = 4;
070:
071: /**
072: * Default constructor.
073: */
074: public NumberTool() {
075: // do nothing
076: }
077:
078: // ------------------------- default parameter access ----------------
079:
080: /**
081: * This implementation returns the default locale. Subclasses
082: * may override this to return alternate locales. Please note that
083: * doing so will affect all formatting methods where no locale is
084: * specified in the parameters.
085: *
086: * @return the default {@link Locale}
087: */
088: public Locale getLocale() {
089: return Locale.getDefault();
090: }
091:
092: /**
093: * Return the pattern or style to be used for formatting numbers when none
094: * is specified. This implementation gives a 'default' number format.
095: * Subclasses may override this to provide a different default format.
096: *
097: * <p>NOTE: At some point in the future it may be feasible to configure
098: * this value via the toolbox definition, but at present, it is not possible
099: * to specify custom tool configurations there. For now you should just
100: * override this in a subclass to have a different default.</p>
101: */
102: public String getFormat() {
103: return DEFAULT_FORMAT;
104: }
105:
106: // ------------------------- formatting methods ---------------------------
107:
108: /**
109: * Converts the specified object to a number and formats it according to
110: * the pattern or style returned by {@link #getFormat()}.
111: *
112: * @param obj the number object to be formatted
113: * @return the specified number formatted as a string
114: * @see #format(String format, Object obj, Locale locale)
115: */
116: public String format(Object obj) {
117: return format(getFormat(), obj);
118: }
119:
120: /**
121: * Convenience method equivalent to $number.format("currency", $foo).
122: * @since VelocityTools 1.3
123: */
124: public String currency(Object obj) {
125: return format("currency", obj);
126: }
127:
128: /**
129: * Convenience method equivalent to $number.format("integer", $foo).
130: * @since VelocityTools 1.3
131: */
132: public String integer(Object obj) {
133: return format("integer", obj);
134: }
135:
136: /**
137: * Convenience method equivalent to $number.format("number", $foo).
138: * @since VelocityTools 1.3
139: */
140: public String number(Object obj) {
141: return format("number", obj);
142: }
143:
144: /**
145: * Convenience method equivalent to $number.format("percent", $foo).
146: * @since VelocityTools 1.3
147: */
148: public String percent(Object obj) {
149: return format("percent", obj);
150: }
151:
152: /**
153: * Converts the specified object to a number and returns
154: * a formatted string representing that number in the locale
155: * returned by {@link #getLocale()}.
156: *
157: * @param format the formatting instructions
158: * @param obj the number object to be formatted
159: * @return a formatted string for this locale representing the specified
160: * number or <code>null</code> if the parameters are invalid
161: * @see #format(String format, Object obj, Locale locale)
162: */
163: public String format(String format, Object obj) {
164: return format(format, obj, getLocale());
165: }
166:
167: /**
168: * Converts the specified object to a number and returns
169: * a formatted string representing that number in the specified
170: * {@link Locale}.
171: *
172: * @param format the custom or standard pattern to be used
173: * @param obj the number object to be formatted
174: * @param locale the {@link Locale} to be used when formatting
175: * @return a formatted string representing the specified number or
176: * <code>null</code> if the parameters are invalid
177: */
178: public String format(String format, Object obj, Locale locale) {
179: Number number = toNumber(obj);
180: NumberFormat nf = getNumberFormat(format, locale);
181: if (number == null || nf == null) {
182: return null;
183: }
184: return nf.format(number);
185: }
186:
187: // -------------------------- NumberFormat creation methods --------------
188:
189: /**
190: * Returns a {@link NumberFormat} instance for the specified
191: * format and {@link Locale}. If the format specified is a standard
192: * style pattern, then a number instance
193: * will be returned with the number style set to the
194: * specified style. If it is a custom format, then a customized
195: * {@link NumberFormat} will be returned.
196: *
197: * @param format the custom or standard formatting pattern to be used
198: * @param locale the {@link Locale} to be used
199: * @return an instance of {@link NumberFormat}
200: * @see NumberFormat
201: */
202: public NumberFormat getNumberFormat(String format, Locale locale) {
203: if (format == null) {
204: return null;
205: }
206:
207: NumberFormat nf = null;
208: int style = getStyleAsInt(format);
209: if (style < 0) {
210: // we have a custom format
211: nf = new DecimalFormat(format, new DecimalFormatSymbols(
212: locale));
213: } else {
214: // we have a standard format
215: nf = getNumberFormat(style, locale);
216: }
217: return nf;
218: }
219:
220: /**
221: * Returns a {@link NumberFormat} instance for the specified
222: * number style and {@link Locale}.
223: *
224: * @param numberStyle the number style (number will be ignored if this is
225: * less than zero or the number style is not recognized)
226: * @param locale the {@link Locale} to be used
227: * @return an instance of {@link NumberFormat} or <code>null</code>
228: * if an instance cannot be constructed with the given
229: * parameters
230: */
231: protected NumberFormat getNumberFormat(int numberStyle,
232: Locale locale) {
233: try {
234: NumberFormat nf;
235: switch (numberStyle) {
236: case STYLE_NUMBER:
237: nf = NumberFormat.getNumberInstance(locale);
238: break;
239: case STYLE_CURRENCY:
240: nf = NumberFormat.getCurrencyInstance(locale);
241: break;
242: case STYLE_PERCENT:
243: nf = NumberFormat.getPercentInstance(locale);
244: break;
245: case STYLE_INTEGER:
246: nf = getIntegerInstance(locale);
247: break;
248: default:
249: // invalid style was specified, return null
250: nf = null;
251: }
252: return nf;
253: } catch (Exception suppressed) {
254: // let it go...
255: return null;
256: }
257: }
258:
259: /**
260: * Since we wish to continue supporting Java 1.3,
261: * for the present we cannot use Java 1.4's
262: * NumberFormat.getIntegerInstance(Locale) method.
263: * This method mimics that method (at least as of JDK1.4.2_01).
264: * It is private so that it can be removed later
265: * without a deprecation period.
266: */
267: private NumberFormat getIntegerInstance(Locale locale) {
268: DecimalFormat format = (DecimalFormat) NumberFormat
269: .getNumberInstance(locale);
270: format.setMaximumFractionDigits(0);
271: format.setDecimalSeparatorAlwaysShown(false);
272: format.setParseIntegerOnly(true);
273: return format;
274: }
275:
276: /**
277: * Checks a string to see if it matches one of the standard
278: * NumberFormat style patterns:
279: * NUMBER, CURRENCY, PERCENT, INTEGER, or DEFAULT.
280: * if it does it will return the integer constant for that pattern.
281: * if not, it will return -1.
282: *
283: * @see NumberFormat
284: * @param style the string to be checked
285: * @return the int identifying the style pattern
286: */
287: protected int getStyleAsInt(String style) {
288: // avoid needlessly running through all the string comparisons
289: if (style == null || style.length() < 6 || style.length() > 8) {
290: return -1;
291: }
292: if (style.equalsIgnoreCase("default")) {
293: //NOTE: java.text.NumberFormat returns "number" instances
294: // as the default (at least in Java 1.3 and 1.4).
295: return STYLE_NUMBER;
296: }
297: if (style.equalsIgnoreCase("number")) {
298: return STYLE_NUMBER;
299: }
300: if (style.equalsIgnoreCase("currency")) {
301: return STYLE_CURRENCY;
302: }
303: if (style.equalsIgnoreCase("percent")) {
304: return STYLE_PERCENT;
305: }
306: if (style.equalsIgnoreCase("integer")) {
307: return STYLE_INTEGER;
308: }
309: // ok, it's not any of the standard patterns
310: return -1;
311: }
312:
313: // ------------------------- number conversion methods ---------------
314:
315: /**
316: * Converts an object to an instance of {@link Number} using the
317: * format returned by {@link #getFormat()} and the {@link Locale}
318: * returned by {@link #getLocale()} if the object is not already
319: * an instance of Number.
320: *
321: * @param obj the number to convert
322: * @return the object as a {@link Number} or <code>null</code> if no
323: * conversion is possible
324: */
325: public Number toNumber(Object obj) {
326: return toNumber(getFormat(), obj, getLocale());
327: }
328:
329: /**
330: * Converts an object to an instance of {@link Number} using the
331: * specified format and the {@link Locale} returned by
332: * {@link #getLocale()} if the object is not already an instance
333: * of Number.
334: *
335: * @param format - the format the number is in
336: * @param obj - the number to convert
337: * @return the object as a {@link Number} or <code>null</code> if no
338: * conversion is possible
339: * @see #toNumber(String format, Object obj, Locale locale)
340: */
341: public Number toNumber(String format, Object obj) {
342: return toNumber(format, obj, getLocale());
343: }
344:
345: /**
346: * Converts an object to an instance of {@link Number} using the
347: * specified format and {@link Locale}if the object is not already
348: * an instance of Number.
349: *
350: * @param format - the format the number is in
351: * @param obj - the number to convert
352: * @param locale - the {@link Locale}
353: * @return the object as a {@link Number} or <code>null</code> if no
354: * conversion is possible
355: * @see NumberFormat#parse
356: */
357: public Number toNumber(String format, Object obj, Locale locale) {
358: if (obj == null) {
359: return null;
360: }
361: if (obj instanceof Number) {
362: return (Number) obj;
363: }
364: try {
365: NumberFormat parser = getNumberFormat(format, locale);
366: return parser.parse(String.valueOf(obj));
367: } catch (Exception e) {
368: return null;
369: }
370: }
371:
372: }
|