001/*
002 * $Id$
003 *
004 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 *
021 */
022package org.jdesktop.swingx.text;
023
024import java.math.BigDecimal;
025import java.text.Format;
026import java.text.NumberFormat;
027import java.text.ParseException;
028
029import javax.swing.text.NumberFormatter;
030
031/**
032 * Experiment to work around Issue #1183-swingx: NumberEditorExt throws exception
033 * on getCellValue. Remaining issue: no visual error feedback if the expected 
034 * number type exceeds its range.
035 * 
036 * @author Jeanette Winzenburg
037 */
038public class StrictNumberFormatter extends NumberFormatter {
039
040    
041    private BigDecimal maxAsBig;
042    private BigDecimal minAsBig;
043
044    /**
045     * @param format
046     */
047    public StrictNumberFormatter(NumberFormat format) {
048        super(format);
049    }
050
051    /**
052     * {@inheritDoc} <p>
053     * 
054     * Overridden to automatically set the minimum/maximum to the boundaries of
055     * the Number type if it corresponds to a raw type, or null if not.
056     */
057    @Override
058    public void setValueClass(Class<?> valueClass) {
059        super.setValueClass(valueClass);
060        updateMinMax();
061    }
062
063
064    /**
065     * 
066     */
067    @SuppressWarnings({ "unchecked", "rawtypes" })
068    private void updateMinMax() {
069        Comparable min = null;
070        Comparable max = null;
071        if (getValueClass() == Integer.class) {
072            max = Integer.MAX_VALUE;
073            min = Integer.MIN_VALUE;
074        } else if (getValueClass() == Long.class) {
075            max = Long.MAX_VALUE;
076            min = Long.MIN_VALUE;
077        } else if (getValueClass() == Short.class) {
078            max = Short.MAX_VALUE;
079            min = Short.MIN_VALUE;
080        } else if (getValueClass() == Byte.class) {
081            max = Byte.MAX_VALUE;
082            min = Byte.MIN_VALUE;
083        } else if (getValueClass() == Float.class) {
084            max = Float.MAX_VALUE;
085            min = Float.MIN_VALUE;
086        } else if (getValueClass() == Double.class) {
087            // don*t understand what's happening here, naive compare with bigDecimal 
088            // fails - so don't do anything for now
089            // JW: revisit!
090        }
091        setMaximum(max);
092        setMinimum(min);
093    }
094
095
096    @SuppressWarnings({ "unchecked", "rawtypes" })
097    @Override
098    public void setMaximum(Comparable max) {
099        super.setMaximum(max);
100        this.maxAsBig = max != null ? new BigDecimal(max.toString()) : null;
101    }
102    
103    @SuppressWarnings({ "unchecked", "rawtypes" })
104    @Override
105    public void setMinimum(Comparable minimum) {
106        super.setMinimum(minimum);
107        this.minAsBig = minimum != null ? new BigDecimal(minimum.toString()) : null;
108    }
109
110    
111    /**
112     * Returns the <code>Object</code> representation of the
113     * <code>String</code> <code>text</code>, may be null.
114     *
115     * @param text <code>String</code> to convert
116     * @return <code>Object</code> representation of text
117     * @throws ParseException if there is an error in the conversion
118     */
119    @Override
120    public Object stringToValue(String text) throws ParseException {
121        Object value = getParsedValue(text, getFormat());
122        try {
123            if (!isValueInRange(value, true)) {
124                throw new ParseException("Value not within min/max range", 0);
125            }
126        } catch (ClassCastException cce) {
127            throw new ParseException("Class cast exception comparing values: "
128                                     + cce, 0);
129        }
130        return convertValueToValueClass(value, getValueClass());
131    }
132
133    /**
134     * Converts the passed in value to the passed in class. This only
135     * works if <code>valueClass</code> is one of <code>Integer</code>,
136     * <code>Long</code>, <code>Float</code>, <code>Double</code>,
137     * <code>Byte</code> or <code>Short</code> and <code>value</code>
138     * is an instanceof <code>Number</code>.
139     */
140    private Object convertValueToValueClass(Object value, Class<?> valueClass) {
141        if (valueClass != null && (value instanceof Number)) {
142            if (valueClass == Integer.class) {
143                return new Integer(((Number)value).intValue());
144            }
145            else if (valueClass == Long.class) {
146                return new Long(((Number)value).longValue());
147            }
148            else if (valueClass == Float.class) {
149                return new Float(((Number)value).floatValue());
150            }
151            else if (valueClass == Double.class) {
152                return new Double(((Number)value).doubleValue());
153            }
154            else if (valueClass == Byte.class) {
155                return new Byte(((Number)value).byteValue());
156            }
157            else if (valueClass == Short.class) {
158                return new Short(((Number)value).shortValue());
159            }
160        }
161        return value;
162    }
163
164    /**
165     * Invokes <code>parseObject</code> on <code>f</code>, returning
166     * its value.
167     */
168    private Object getParsedValue(String text, Format f) throws ParseException {
169        if (f == null) {
170            return text;
171        }
172        return f.parseObject(text);
173    }
174
175    
176    /**
177     * Returns true if <code>value</code> is between the min/max.
178     *
179     * @param wantsCCE If false, and a ClassCastException is thrown in
180     *                 comparing the values, the exception is consumed and
181     *                 false is returned.
182     */
183    private boolean isValueInRange(Object orgValue, boolean wantsCCE) {
184        if (orgValue == null) return true;
185        if ((getMinimum() == null) && getMaximum() == null) return true;
186
187        BigDecimal value = new BigDecimal(orgValue.toString());
188        Comparable<BigDecimal> min = getMinimumAsBig();
189
190        try {
191            if (min != null && min.compareTo(value) > 0) {
192                return false;
193            }
194        } catch (ClassCastException cce) {
195            if (wantsCCE) {
196                throw cce;
197            }
198            return false;
199        }
200
201        Comparable<BigDecimal> max = getMaximumAsBig();
202        try {
203            if (max != null && max.compareTo(value) < 0) {
204                return false;
205            }
206        } catch (ClassCastException cce) {
207            if (wantsCCE) {
208                throw cce;
209            }
210            return false;
211        }
212        return true;
213    }
214   
215
216    private Comparable<BigDecimal> getMinimumAsBig() {
217        return minAsBig;
218    }
219    
220    private Comparable<BigDecimal> getMaximumAsBig() {
221        return maxAsBig;
222    }
223    
224
225}