001/*
002 * Copyright 2003-2005 The Apache Software Foundation
003 * Copyright 2005 Stephen McConnell
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * 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 */
017package net.dpml.cli.validation;
018
019import java.text.NumberFormat;
020import java.text.ParsePosition;
021
022import java.util.List;
023import java.util.ListIterator;
024
025import net.dpml.cli.resource.ResourceConstants;
026import net.dpml.cli.resource.ResourceHelper;
027
028/**
029 * The <code>NumberValidator</code> validates the string argument
030 * values are numbers.  If the value is a number, the string value in
031 * the {@link java.util.List} of values is replaced with the
032 * {@link java.lang.Number} instance.
033 *
034 * A maximum and minimum value can also be specified using
035 * the {@link #setMaximum setMaximum}, and the
036 * {@link #setMinimum setMinimum} methods.
037 *
038 * The following example shows how to limit the valid values
039 * for the age attribute to integers less than 100.
040 *
041 * <pre>
042 * ...
043 * ArgumentBuilder builder = new ArgumentBuilder();
044 * NumberValidator validator = NumberValidator.getIntegerInstance();
045 * validator.setMaximum(new Integer(100));
046 *
047 * Argument age =
048 *     builder.withName("age");
049 *            .withValidator(validator);
050 * </pre>
051 *
052 * @author <a href="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
053 * @version @PROJECT-VERSION@
054 */
055public class NumberValidator implements Validator
056{
057    /** the <code>NumberFormat</code> being used. */
058    private NumberFormat m_format;
059
060    /** the lower bound for argument values. */
061    private Number m_minimum = null;
062
063    /** the upper bound for argument values */
064    private Number m_maximum = null;
065
066    /**
067     * Creates a new NumberValidator based on the specified NumberFormat
068     * @param format the format of numbers to accept
069     */
070    public NumberValidator( final NumberFormat format ) 
071    {
072        setFormat( format );
073    }
074
075    /**
076     * Returns a <code>NumberValidator</code> for a currency format
077     * for the current default locale.
078     * @return a <code>NumberValidator</code> for a currency format
079     * for the current default locale.
080     */
081    public static NumberValidator getCurrencyInstance() 
082    {
083        return new NumberValidator( NumberFormat.getCurrencyInstance() );
084    }
085
086    /**
087     * Returns a <code>NumberValidator</code> for an integer number format
088     * for the current default locale.
089     * @return a <code>NumberValidator</code> for an integer number format
090     * for the current default locale.
091     */
092    public static NumberValidator getIntegerInstance()
093    {
094        final NumberFormat format = NumberFormat.getNumberInstance();
095        format.setParseIntegerOnly( true );
096        return new NumberValidator( format );
097    }
098
099    /**
100     * Returns a <code>NumberValidator</code> for a percentage format
101     * for the current default locale.
102     * @return a <code>NumberValidator</code> for a percentage format
103     * for the current default locale.
104     */
105    public static NumberValidator getPercentInstance() 
106    {
107        return new NumberValidator( NumberFormat.getPercentInstance() );
108    }
109
110    /**
111     * Returns a <code>NumberValidator</code> for a general-purpose
112     * number format for the current default locale.
113     * @return a <code>NumberValidator</code> for a general-purpose
114     * number format for the current default locale.
115     */
116    public static NumberValidator getNumberInstance()
117    {
118        return new NumberValidator( NumberFormat.getNumberInstance() );
119    }
120
121   /**
122    * Validate the list of values against the list of permitted values.
123    * If a value is valid, replace the string in the <code>values</code>
124    * {@link java.util.List} with the {@link java.lang.Number} instance.
125    *
126    * @param values the list of values to validate 
127    * @exception InvalidArgumentException if a value is invalid
128    * @see net.dpml.cli.validation.Validator#validate(java.util.List)
129    */
130    public void validate( final List values ) throws InvalidArgumentException 
131    {
132        for( final ListIterator i = values.listIterator(); i.hasNext();) 
133        {
134            final Object next = i.next();
135            if( next instanceof Number )
136            {
137                return;
138            }
139            final String value = (String) next;
140            final ParsePosition pp = new ParsePosition( 0 );
141            final Number number = m_format.parse( value, pp );
142            if( pp.getIndex() < value.length() )
143            {
144                throw new InvalidArgumentException( value );
145            }
146            if(
147              ( ( m_minimum != null ) && ( number.doubleValue() < m_minimum.doubleValue() ) ) 
148              || ( ( m_maximum != null ) && ( number.doubleValue() > m_maximum.doubleValue() ) )
149            ) 
150            {
151                throw new InvalidArgumentException(
152                  ResourceHelper.getResourceHelper().getMessage(
153                    ResourceConstants.NUMBERVALIDATOR_NUMBER_OUTOFRANGE,
154                    new Object[]{value} ) );
155            }
156            i.set( number );
157        }
158    }
159
160    /**
161     * Return the format being used to validate argument values against.
162     *
163     * @return the format being used to validate argument values against.
164     */
165    public NumberFormat getFormat() 
166    {
167        return m_format;
168    }
169
170    /**
171     * Specify the format being used to validate argument values against.
172     *
173     * @param format the format being used to validate argument values against.
174     */
175    protected void setFormat( NumberFormat format ) 
176    {
177        m_format = format;
178    }
179
180    /**
181     * Return the maximum value allowed for an argument value.
182     *
183     * @return the maximum value allowed for an argument value.
184     */
185    public Number getMaximum() 
186    {
187        return m_maximum;
188    }
189
190    /**
191     * Specify the maximum value allowed for an argument value.
192     *
193     * @param maximum the maximum value allowed for an argument value.
194     */
195    public void setMaximum( Number maximum )
196    {
197        m_maximum = maximum;
198    }
199
200    /**
201     * Return the minimum value allowed for an argument value.
202     *
203     * @return the minimum value allowed for an argument value.
204     */
205    public Number getMinimum()
206    {
207        return m_minimum;
208    }
209
210    /**
211     * Specify the minimum value allowed for an argument value.
212     *
213     * @param minimum the minimum value allowed for an argument value.
214     */
215    public void setMinimum( Number minimum )
216    {
217        m_minimum = minimum;
218    }
219}