001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Portions Copyright Apache Software Foundation.
007: *
008: * The contents of this file are subject to the terms of either the GNU
009: * General Public License Version 2 only ("GPL") or the Common Development
010: * and Distribution License("CDDL") (collectively, the "License"). You
011: * may not use this file except in compliance with the License. You can obtain
012: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
013: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
014: * language governing permissions and limitations under the License.
015: *
016: * When distributing the software, include this License Header Notice in each
017: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
018: * Sun designates this particular file as subject to the "Classpath" exception
019: * as provided by Sun in the GPL Version 2 section of the License file that
020: * accompanied this code. If applicable, add the following below the License
021: * Header, with the fields enclosed by brackets [] replaced by your own
022: * identifying information: "Portions Copyrighted [year]
023: * [name of copyright owner]"
024: *
025: * Contributor(s):
026: *
027: * If you wish your version of this file to be governed by only the CDDL or
028: * only the GPL Version 2, indicate your decision by adding "[Contributor]
029: * elects to include this software in this distribution under the [CDDL or GPL
030: * Version 2] license." If you don't indicate a single choice of license, a
031: * recipient has the option to distribute your version of this file under
032: * either the CDDL, the GPL Version 2 or to extend the choice of license to
033: * its licensees as provided above. However, if you add GPL Version 2 code
034: * and therefore, elected the GPL Version 2 license, then the option applies
035: * only if the new code is made subject to such option by the copyright
036: * holder.
037: */
038:
039: package org.apache.taglibs.standard.tag.common.fmt;
040:
041: import java.io.IOException;
042: import java.lang.reflect.Method;
043: import java.text.DecimalFormat;
044: import java.text.DecimalFormatSymbols;
045: import java.text.NumberFormat;
046: import java.util.Locale;
047:
048: import javax.servlet.jsp.JspException;
049: import javax.servlet.jsp.JspTagException;
050: import javax.servlet.jsp.PageContext;
051: import javax.servlet.jsp.tagext.BodyTagSupport;
052:
053: import org.apache.taglibs.standard.resources.Resources;
054: import org.apache.taglibs.standard.tag.common.core.Util;
055:
056: /**
057: * Support for tag handlers for <formatNumber>, the number
058: * formatting tag in JSTL 1.0.
059: *
060: * @author Jan Luehe
061: */
062:
063: public abstract class FormatNumberSupport extends BodyTagSupport {
064:
065: //*********************************************************************
066: // Private constants
067:
068: private static final Class[] GET_INSTANCE_PARAM_TYPES = new Class[] { String.class };
069: private static final String NUMBER = "number";
070: private static final String CURRENCY = "currency";
071: private static final String PERCENT = "percent";
072:
073: //*********************************************************************
074: // Protected state
075:
076: protected Object value; // 'value' attribute
077: protected boolean valueSpecified; // status
078: protected String type; // 'type' attribute
079: protected String pattern; // 'pattern' attribute
080: protected String currencyCode; // 'currencyCode' attribute
081: protected String currencySymbol; // 'currencySymbol' attribute
082: protected boolean isGroupingUsed; // 'groupingUsed' attribute
083: protected boolean groupingUsedSpecified;
084: protected int maxIntegerDigits; // 'maxIntegerDigits' attribute
085: protected boolean maxIntegerDigitsSpecified;
086: protected int minIntegerDigits; // 'minIntegerDigits' attribute
087: protected boolean minIntegerDigitsSpecified;
088: protected int maxFractionDigits; // 'maxFractionDigits' attribute
089: protected boolean maxFractionDigitsSpecified;
090: protected int minFractionDigits; // 'minFractionDigits' attribute
091: protected boolean minFractionDigitsSpecified;
092:
093: //*********************************************************************
094: // Private state
095:
096: private String var; // 'var' attribute
097: private int scope; // 'scope' attribute
098: private static Class currencyClass;
099:
100: //*********************************************************************
101: // Constructor and initialization
102:
103: static {
104: try {
105: currencyClass = Class.forName("java.util.Currency");
106: // container's runtime is J2SE 1.4 or greater
107: } catch (Exception cnfe) {
108: }
109: }
110:
111: public FormatNumberSupport() {
112: super ();
113: init();
114: }
115:
116: private void init() {
117: value = type = null;
118: valueSpecified = false;
119: pattern = var = currencyCode = currencySymbol = null;
120: groupingUsedSpecified = false;
121: maxIntegerDigitsSpecified = minIntegerDigitsSpecified = false;
122: maxFractionDigitsSpecified = minFractionDigitsSpecified = false;
123: scope = PageContext.PAGE_SCOPE;
124: }
125:
126: //*********************************************************************
127: // Tag attributes known at translation time
128:
129: public void setVar(String var) {
130: this .var = var;
131: }
132:
133: public void setScope(String scope) {
134: this .scope = Util.getScope(scope);
135: }
136:
137: //*********************************************************************
138: // Tag logic
139:
140: public int doEndTag() throws JspException {
141: String formatted = null;
142: Object input = null;
143:
144: // determine the input by...
145: if (valueSpecified) {
146: // ... reading 'value' attribute
147: input = value;
148: } else {
149: // ... retrieving and trimming our body
150: if (bodyContent != null && bodyContent.getString() != null)
151: input = bodyContent.getString().trim();
152: }
153:
154: if ((input == null) || input.equals("")) {
155: // Spec says:
156: // If value is null or empty, remove the scoped variable
157: // if it is specified (see attributes var and scope).
158: if (var != null) {
159: pageContext.removeAttribute(var, scope);
160: }
161: return EVAL_PAGE;
162: }
163:
164: /*
165: * If 'value' is a String, it is first parsed into an instance of
166: * java.lang.Number
167: */
168: if (input instanceof String) {
169: try {
170: if (((String) input).indexOf('.') != -1) {
171: input = Double.valueOf((String) input);
172: } else {
173: input = Long.valueOf((String) input);
174: }
175: } catch (NumberFormatException nfe) {
176: throw new JspException(Resources.getMessage(
177: "FORMAT_NUMBER_PARSE_ERROR", input), nfe);
178: }
179: }
180:
181: // Determine formatting locale
182: Locale loc = SetLocaleSupport.getFormattingLocale(pageContext,
183: this , false, true);
184: if (loc != null) {
185: // Create formatter
186: NumberFormat formatter = null;
187: if ((pattern != null) && !pattern.equals("")) {
188: // if 'pattern' is specified, 'type' is ignored
189: DecimalFormatSymbols symbols = new DecimalFormatSymbols(
190: loc);
191: formatter = new DecimalFormat(pattern, symbols);
192: } else {
193: formatter = createFormatter(loc);
194: }
195: if (((pattern != null) && !pattern.equals(""))
196: || CURRENCY.equalsIgnoreCase(type)) {
197: try {
198: setCurrency(formatter);
199: } catch (Exception e) {
200: throw new JspException(
201: Resources
202: .getMessage("FORMAT_NUMBER_CURRENCY_ERROR"),
203: e);
204: }
205: }
206: configureFormatter(formatter);
207: formatted = formatter.format(input);
208: } else {
209: // no formatting locale available, use toString()
210: formatted = input.toString();
211: }
212:
213: if (var != null) {
214: pageContext.setAttribute(var, formatted, scope);
215: } else {
216: try {
217: pageContext.getOut().print(formatted);
218: } catch (IOException ioe) {
219: throw new JspTagException(ioe.toString(), ioe);
220: }
221: }
222:
223: return EVAL_PAGE;
224: }
225:
226: // Releases any resources we may have (or inherit)
227: public void release() {
228: init();
229: }
230:
231: //*********************************************************************
232: // Private utility methods
233:
234: private NumberFormat createFormatter(Locale loc)
235: throws JspException {
236: NumberFormat formatter = null;
237:
238: if ((type == null) || NUMBER.equalsIgnoreCase(type)) {
239: formatter = NumberFormat.getNumberInstance(loc);
240: } else if (CURRENCY.equalsIgnoreCase(type)) {
241: formatter = NumberFormat.getCurrencyInstance(loc);
242: } else if (PERCENT.equalsIgnoreCase(type)) {
243: formatter = NumberFormat.getPercentInstance(loc);
244: } else {
245: throw new JspException(Resources.getMessage(
246: "FORMAT_NUMBER_INVALID_TYPE", type));
247: }
248:
249: return formatter;
250: }
251:
252: /*
253: * Applies the 'groupingUsed', 'maxIntegerDigits', 'minIntegerDigits',
254: * 'maxFractionDigits', and 'minFractionDigits' attributes to the given
255: * formatter.
256: */
257: private void configureFormatter(NumberFormat formatter) {
258: if (groupingUsedSpecified)
259: formatter.setGroupingUsed(isGroupingUsed);
260: if (maxIntegerDigitsSpecified)
261: formatter.setMaximumIntegerDigits(maxIntegerDigits);
262: if (minIntegerDigitsSpecified)
263: formatter.setMinimumIntegerDigits(minIntegerDigits);
264: if (maxFractionDigitsSpecified)
265: formatter.setMaximumFractionDigits(maxFractionDigits);
266: if (minFractionDigitsSpecified)
267: formatter.setMinimumFractionDigits(minFractionDigits);
268: }
269:
270: /*
271: * Override the formatting locale's default currency symbol with the
272: * specified currency code (specified via the "currencyCode" attribute) or
273: * currency symbol (specified via the "currencySymbol" attribute).
274: *
275: * If both "currencyCode" and "currencySymbol" are present,
276: * "currencyCode" takes precedence over "currencySymbol" if the
277: * java.util.Currency class is defined in the container's runtime (that
278: * is, if the container's runtime is J2SE 1.4 or greater), and
279: * "currencySymbol" takes precendence over "currencyCode" otherwise.
280: *
281: * If only "currencyCode" is given, it is used as a currency symbol if
282: * java.util.Currency is not defined.
283: *
284: * Example:
285: *
286: * JDK "currencyCode" "currencySymbol" Currency symbol being displayed
287: * -----------------------------------------------------------------------
288: * all --- --- Locale's default currency symbol
289: *
290: * <1.4 EUR --- EUR
291: * >=1.4 EUR --- Locale's currency symbol for Euro
292: *
293: * all --- \u20AC \u20AC
294: *
295: * <1.4 EUR \u20AC \u20AC
296: * >=1.4 EUR \u20AC Locale's currency symbol for Euro
297: */
298: private void setCurrency(NumberFormat formatter) throws Exception {
299: String code = null;
300: String symbol = null;
301:
302: if ((currencyCode == null) && (currencySymbol == null)) {
303: return;
304: }
305:
306: if ((currencyCode != null) && (currencySymbol != null)) {
307: if (currencyClass != null)
308: code = currencyCode;
309: else
310: symbol = currencySymbol;
311: } else if (currencyCode == null) {
312: symbol = currencySymbol;
313: } else {
314: if (currencyClass != null)
315: code = currencyCode;
316: else
317: symbol = currencyCode;
318: }
319:
320: if (code != null) {
321: Object[] methodArgs = new Object[1];
322:
323: /*
324: * java.util.Currency.getInstance()
325: */
326: Method m = currencyClass.getMethod("getInstance",
327: GET_INSTANCE_PARAM_TYPES);
328: methodArgs[0] = code;
329: Object currency = m.invoke(null, methodArgs);
330:
331: /*
332: * java.text.NumberFormat.setCurrency()
333: */
334: Class[] paramTypes = new Class[1];
335: paramTypes[0] = currencyClass;
336: Class numberFormatClass = Class
337: .forName("java.text.NumberFormat");
338: m = numberFormatClass.getMethod("setCurrency", paramTypes);
339: methodArgs[0] = currency;
340: m.invoke(formatter, methodArgs);
341: } else {
342: /*
343: * Let potential ClassCastException propagate up (will almost
344: * never happen)
345: */
346: DecimalFormat df = (DecimalFormat) formatter;
347: DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
348: dfs.setCurrencySymbol(symbol);
349: df.setDecimalFormatSymbols(dfs);
350: }
351: }
352: }
|