1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.morph.transform.converters;
17
18 import java.math.BigDecimal;
19 import java.util.Locale;
20
21 import net.sf.morph.transform.DecoratedConverter;
22 import net.sf.morph.transform.ImpreciseTransformer;
23 import net.sf.morph.transform.TransformationException;
24 import net.sf.morph.transform.support.NumberRounder;
25 import net.sf.morph.transform.transformers.BaseTransformer;
26 import net.sf.morph.util.ClassUtils;
27 import net.sf.morph.util.NumberUtils;
28
29 /**
30 * Converts a number from one number type to another.
31 *
32 * @author Matt Sgarlata
33 * @since Dec 14, 2004
34 */
35 public class NumberConverter extends BaseTransformer implements DecoratedConverter, ImpreciseTransformer {
36
37 private static final Class[] SOURCE_AND_DESTINATION_TYPES = {
38 Number.class, byte.class, short.class, int.class, long.class,
39 float.class, double.class, null
40 };
41
42 /** Default rounding method */
43 public static final String DEFAULT_ROUNDING_METHOD = NumberRounder.ROUND_HALF_UP;
44
45 /**
46 // * The source classes for number converters.
47 // */
48
49
50
51
52
53
54
55 /**
56 // * Whether this converter should ensure that data remains consistent when
57 // * conversions are performed. The default behavior for Java allows for
58 // * rollover to occur when converting larger numbers into smaller numbers.
59 // * This causes numbers with a large absolute value to roll over to a random
60 // * value which might not even have the same sign as the original number.
61 // * (Actually usually the lower bits of the number are retained and the
62 // * higher bits are discarded, but I think most end users would tell you
63 // * that's pretty random to them!) Usually this behavior is problematic, so
64 // * an exception should be thrown. If this value is set to true, a
65 // * TransformationException is thrown when such a conversion is attempted.
66 // * By default, this value is initialized to <code>true</code> for all
67 // * transformers included in the Morph framework.
68 // */
69
70
71 private String roundingMethod;
72
73 /**
74 * Creates a number converter that is ensuring data consistency.
75 */
76 public NumberConverter() {
77 super();
78
79 setRoundingMethod(DEFAULT_ROUNDING_METHOD);
80 }
81
82 /**
83 * {@inheritDoc}
84 */
85 protected Class[] getSourceClassesImpl() throws Exception {
86 return SOURCE_AND_DESTINATION_TYPES;
87 }
88
89 /**
90 * {@inheritDoc}
91 */
92 protected Class[] getDestinationClassesImpl() throws Exception {
93 return SOURCE_AND_DESTINATION_TYPES;
94 }
95
96 /**
97 * Verify <code>number</code> is within the bounds of <code>destinationClass</code>.
98 * @param destinationClass
99 * @param number
100 * @throws Exception if validation fails
101 */
102 protected void checkNotOutOfBounds(Class destinationClass, Number number) throws Exception {
103
104 if (NumberUtils.isTooBigForType(number, destinationClass)) {
105 throw new TransformationException(destinationClass, number,
106 null, number + " is too large to be contained in a "
107 + destinationClass.getName());
108 }
109 if (NumberUtils.isTooSmallForType(number, destinationClass)) {
110 throw new TransformationException(destinationClass, number,
111 null, number + " is too small to be contained in a "
112 + destinationClass.getName());
113 }
114
115 }
116
117 /**
118 * {@inheritDoc}
119 */
120 protected boolean isImpreciseTransformationImpl(Class destinationClass, Class sourceClass) {
121 return super.isImpreciseTransformationImpl(destinationClass, sourceClass)
122 || NumberUtils.NARROWNESS_COMPARATOR.compare(destinationClass,
123 sourceClass) < 0;
124 }
125
126 /**
127 * {@inheritDoc}
128 */
129 protected Object convertImpl(Class destinationClass, Object source,
130 Locale locale) throws Exception {
131 if (destinationClass == null) {
132 return null;
133 }
134 if (destinationClass.isPrimitive() && source == null) {
135 throw new TransformationException(destinationClass, source);
136 }
137
138
139
140 if (destinationClass.isAssignableFrom(ClassUtils.getClass(source))) {
141 return source;
142 }
143 checkNotOutOfBounds(destinationClass, (Number) source);
144
145 String numberStr;
146 if (isDecimal(destinationClass)) {
147 numberStr = source.toString();
148 }
149 else {
150 BigDecimal bigDecimal = new BigDecimal(source.toString());
151 bigDecimal = bigDecimal.setScale(0,
152 NumberRounder.getBigDecimalRoundMode(getRoundingMethod()));
153 numberStr = bigDecimal.toString();
154 }
155 return NumberUtils.getNumber(destinationClass, numberStr);
156 }
157
158 /**
159 * Learn whether <code>numberType</code> is a decimal type
160 * @param numberType
161 * @return boolean
162 */
163 protected boolean isDecimal(Class numberType) {
164 return numberType == double.class || numberType == Double.class
165 || numberType == float.class || numberType == Float.class
166 || BigDecimal.class.isAssignableFrom(numberType);
167 }
168
169 /**
170 // * Gets whether this converter should ensure that data remains consistent
171 // * when conversions are performed. The default behavior for Java allows for
172 // * rollover to occur when converting larger numbers into smaller numbers.
173 // * This causes numbers with a large absolute value to roll over to a random
174 // * value which might not even have the same sign as the original number.
175 // * (Actually usually the lower bits of the number are retained and the
176 // * higher bits are discarded, but I think most end users would tell you
177 // * that's pretty random to them!) Usually this behavior is problematic, so
178 // * an exception should be thrown. If this value is set to true, a
179 // * TransformationException is thrown when such a conversion is attempted.
180 // * By default, this value is initialized to <code>true</code> for all
181 // * transformers included in the Morph framework.
182 // *
183 // * @return whether data consistency should be ensured
184 // */
185
186
187
188
189 /**
190 // * Sets whether this converter should ensure that data remains consistent
191 // * when conversions are performed. The default behavior for Java allows for
192 // * rollover to occur when converting larger numbers into smaller numbers.
193 // * This causes numbers with a large absolute value to roll over to a random
194 // * value which might not even have the same sign as the original number.
195 // * (Actually usually the lower bits of the number are retained and the
196 // * higher bits are discarded, but I think most end users would tell you
197 // * that's pretty random to them!) Usually this behavior is problematic, so
198 // * an exception should be thrown. If this value is set to true, a
199 // * TransformationException is thrown when such a conversion is attempted.
200 // * By default, this value is initialized to <code>true</code> for all
201 // * transformers included in the Morph framework.
202 // *
203 // * @param ensureDataConsistency
204 // * whether data consistency should be ensured
205 // */
206
207
208
209
210 /**
211 * Get the rounding method used by this NumberConverter.
212 * @return String
213 */
214 public String getRoundingMethod() {
215 if (roundingMethod == null) {
216 setRoundingMethod(DEFAULT_ROUNDING_METHOD);
217 }
218 return roundingMethod;
219 }
220
221 /**
222 * Set the rounding method used by this NumberConverter.
223 * @param roundingMethod
224 */
225 public void setRoundingMethod(
226 String roundingMethod) {
227 this.roundingMethod = roundingMethod;
228 }
229
230 /**
231 * {@inheritDoc}
232 */
233 protected boolean isWrappingRuntimeExceptions() {
234 return true;
235 }
236
237 }