/* Copyright 2005-2006 Tim Fennell * * Licensed 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 net.sourceforge.stripes.validation; import java.text.NumberFormat; import java.text.ParsePosition; import java.util.Collection; import java.util.Locale; import java.util.Currency; /** * Provides the basic support for converting Strings to non-floating point numbers (i.e. shorts, * integers, and longs). * * @author Tim Fennell */ public class NumberTypeConverterSupport { private Locale locale; private NumberFormat[] formats; private String currencySymbol; /** Used by Stripes to tell the converter what locale the incoming text is in. */ public void setLocale(Locale locale) { this.locale = locale; this.formats = getNumberFormats(); // Use the appropriate currency symbol if our locale has a country, otherwise try the dollar sign! this.currencySymbol = "$"; if (locale.getCountry() != null && !"".equals(locale.getCountry())) { try { this.currencySymbol = Currency.getInstance(locale).getSymbol(locale); } catch (IllegalArgumentException exc) { // use dollar sign as default value } } } /** Returns the Locale set on the object using setLocale(). */ public Locale getLocale() { return locale; } /** * Fetches one or more NumberFormat instances that can be used to parse numbers * for the current locale. The default implementation returns two instances, one * regular NumberFormat and a currency instance of NumberFormat. * * @return one or more NumberFormats to use in parsing numbers */ protected NumberFormat[] getNumberFormats() { return new NumberFormat[] { NumberFormat.getInstance(this.locale) }; } /** * Parse the input using a NumberFormatter. If the number cannot be parsed, the error key * <em>number.invalidNumber</em> will be added to the errors. */ protected Number parse(String input, Collection<ValidationError> errors) { input = preprocess(input); ParsePosition pp = new ParsePosition(0); for (NumberFormat format : this.formats) { pp.setIndex(0); Number number = format.parse(input, pp); if (number != null && input.length() == pp.getIndex()) return number; } // If we've gotten here we could not parse the number errors.add( new ScopedLocalizableError("converter.number", "invalidNumber")); return null; } /** * Pre-processes the String to give the NumberFormats a better shot at parsing the * input. The default implementation trims the String for whitespace and then looks to * see if the number is surrounded by parentheses, e.g. (800), and if so removes the * parentheses and prepends a minus sign. Lastly it will remove the currency symbol * from the String so that we don't have to use too many NumberFormats! * * @param input the String as input by the user * @return the result of preprocessing the String */ protected String preprocess(String input) { // Step 1: trim whitespace String output = input.trim(); // Step 2: remove the currency symbol output = output.replace(currencySymbol, ""); // Step 3: trim whitespace that might precede or follow currency symbol output = output.trim(); // Step 4: replace parentheses with negation if (output.startsWith("(") && output.endsWith(")")) { output = "-" + output.substring(1, output.length() - 1); } return output; } }