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}