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.table;
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 * 
038 * @deprecated (pre-1.6.2) moved to text package
039 */
040@Deprecated
041class StrictNumberFormatter extends NumberFormatter {
042
043    
044    private BigDecimal maxAsBig;
045    private BigDecimal minAsBig;
046
047    /**
048     * @param format
049     */
050    public StrictNumberFormatter(NumberFormat format) {
051        super(format);
052    }
053
054    /**
055     * {@inheritDoc} <p>
056     * 
057     * Overridden to automatically set the minimum/maximum to the boundaries of
058     * the Number type if it corresponds to a raw type, or null if not.
059     */
060    @Override
061    public void setValueClass(Class<?> valueClass) {
062        super.setValueClass(valueClass);
063        updateMinMax();
064    }
065
066
067    /**
068     * 
069     */
070    @SuppressWarnings("unchecked")
071    private void updateMinMax() {
072        Comparable min = null;
073        Comparable max = null;
074        if (getValueClass() == Integer.class) {
075            max = Integer.MAX_VALUE;
076            min = Integer.MIN_VALUE;
077        } else if (getValueClass() == Long.class) {
078            max = Long.MAX_VALUE;
079            min = Long.MIN_VALUE;
080        } else if (getValueClass() == Short.class) {
081            max = Short.MAX_VALUE;
082            min = Short.MIN_VALUE;
083        } else if (getValueClass() == Byte.class) {
084            max = Byte.MAX_VALUE;
085            min = Byte.MIN_VALUE;
086        } else if (getValueClass() == Float.class) {
087            max = Float.MAX_VALUE;
088            min = Float.MIN_VALUE;
089        } else if (getValueClass() == Double.class) {
090            // don*t understand what's happening here, naive compare with bigDecimal 
091            // fails - so don't do anything for now
092            // JW: revisit!
093        }
094        setMaximum(max);
095        setMinimum(min);
096    }
097
098
099    @SuppressWarnings("unchecked")
100    @Override
101    public void setMaximum(Comparable max) {
102        super.setMaximum(max);
103        this.maxAsBig = max != null ? new BigDecimal(max.toString()) : null;
104    }
105    
106    @SuppressWarnings("unchecked")
107    @Override
108    public void setMinimum(Comparable minimum) {
109        super.setMinimum(minimum);
110        this.minAsBig = minimum != null ? new BigDecimal(minimum.toString()) : null;
111    }
112
113    
114    /**
115     * Returns the <code>Object</code> representation of the
116     * <code>String</code> <code>text</code>, may be null.
117     *
118     * @param text <code>String</code> to convert
119     * @return <code>Object</code> representation of text
120     * @throws ParseException if there is an error in the conversion
121     */
122    @Override
123    public Object stringToValue(String text) throws ParseException {
124        Object value = getParsedValue(text, getFormat());
125        try {
126            if (!isValueInRange(value, true)) {
127                throw new ParseException("Value not within min/max range", 0);
128            }
129        } catch (ClassCastException cce) {
130            throw new ParseException("Class cast exception comparing values: "
131                                     + cce, 0);
132        }
133        return convertValueToValueClass(value, getValueClass());
134    }
135
136    /**
137     * Converts the passed in value to the passed in class. This only
138     * works if <code>valueClass</code> is one of <code>Integer</code>,
139     * <code>Long</code>, <code>Float</code>, <code>Double</code>,
140     * <code>Byte</code> or <code>Short</code> and <code>value</code>
141     * is an instanceof <code>Number</code>.
142     */
143    private Object convertValueToValueClass(Object value, Class<?> valueClass) {
144        if (valueClass != null && (value instanceof Number)) {
145            if (valueClass == Integer.class) {
146                return new Integer(((Number)value).intValue());
147            }
148            else if (valueClass == Long.class) {
149                return new Long(((Number)value).longValue());
150            }
151            else if (valueClass == Float.class) {
152                return new Float(((Number)value).floatValue());
153            }
154            else if (valueClass == Double.class) {
155                return new Double(((Number)value).doubleValue());
156            }
157            else if (valueClass == Byte.class) {
158                return new Byte(((Number)value).byteValue());
159            }
160            else if (valueClass == Short.class) {
161                return new Short(((Number)value).shortValue());
162            }
163        }
164        return value;
165    }
166
167    /**
168     * Invokes <code>parseObject</code> on <code>f</code>, returning
169     * its value.
170     */
171    private Object getParsedValue(String text, Format f) throws ParseException {
172        if (f == null) {
173            return text;
174        }
175        return f.parseObject(text);
176    }
177
178    
179    /**
180     * Returns true if <code>value</code> is between the min/max.
181     *
182     * @param wantsCCE If false, and a ClassCastException is thrown in
183     *                 comparing the values, the exception is consumed and
184     *                 false is returned.
185     */
186    private boolean isValueInRange(Object orgValue, boolean wantsCCE) {
187        if (orgValue == null) return true;
188        if ((getMinimum() == null) && getMaximum() == null) return true;
189
190        BigDecimal value = new BigDecimal(orgValue.toString());
191        Comparable<BigDecimal> min = getMinimumAsBig();
192
193        try {
194            if (min != null && min.compareTo(value) > 0) {
195                return false;
196            }
197        } catch (ClassCastException cce) {
198            if (wantsCCE) {
199                throw cce;
200            }
201            return false;
202        }
203
204        Comparable<BigDecimal> max = getMaximumAsBig();
205        try {
206            if (max != null && max.compareTo(value) < 0) {
207                return false;
208            }
209        } catch (ClassCastException cce) {
210            if (wantsCCE) {
211                throw cce;
212            }
213            return false;
214        }
215        return true;
216    }
217   
218
219    private Comparable<BigDecimal> getMinimumAsBig() {
220        return minAsBig;
221    }
222    
223    private Comparable<BigDecimal> getMaximumAsBig() {
224        return maxAsBig;
225    }
226    
227
228}