Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractKualiDecimal |
|
| 1.6666666666666667;1.667 |
1 | /* | |
2 | * Copyright 2005-2008 The Kuali Foundation | |
3 | * | |
4 | * Licensed under the Educational Community License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.opensource.org/licenses/ecl2.php | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.kuali.rice.kns.util; | |
17 | ||
18 | import java.math.BigDecimal; | |
19 | ||
20 | import org.apache.commons.lang.StringUtils; | |
21 | ||
22 | /** | |
23 | * This class is a wrapper around java.math.BigDecimal. It exposes the only the | |
24 | * needed functionality of BigDecimal and uses a standard ROUND_BEHAVIOR of | |
25 | * BigDecimal.ROUND_HALF_UP | |
26 | * | |
27 | * Members of this class are, like BigDecimal, immutable; even methods which | |
28 | * might be expected to change the value actually just return a new instance | |
29 | * with the new value. | |
30 | */ | |
31 | public abstract class AbstractKualiDecimal<T extends AbstractKualiDecimal> extends Number implements Comparable { | |
32 | ||
33 | public static final int ROUND_BEHAVIOR = BigDecimal.ROUND_HALF_UP; | |
34 | ||
35 | 0 | public static final KualiDecimal ZERO = new KualiDecimal(BigDecimal.ZERO); |
36 | ||
37 | protected BigDecimal value; | |
38 | ||
39 | 0 | public AbstractKualiDecimal() { |
40 | 0 | } |
41 | ||
42 | /** | |
43 | * This is the base constructor, used by constructors that take other types | |
44 | * | |
45 | * @param value | |
46 | * String containing numeric value - defaults to zero | |
47 | */ | |
48 | 0 | public AbstractKualiDecimal(String value, int scale) { |
49 | 0 | if (StringUtils.isBlank(value)) { |
50 | 0 | this.value = BigDecimal.ZERO.setScale(scale,ROUND_BEHAVIOR); |
51 | } else { | |
52 | 0 | this.value = new BigDecimal(value).setScale(scale, ROUND_BEHAVIOR); |
53 | } | |
54 | 0 | } |
55 | ||
56 | 0 | public AbstractKualiDecimal(int value, int scale) { |
57 | 0 | this.value = new BigDecimal(value).setScale(scale, ROUND_BEHAVIOR); |
58 | 0 | } |
59 | ||
60 | 0 | public AbstractKualiDecimal(double value, int scale) { |
61 | 0 | this.value = new BigDecimal(value).setScale(scale, ROUND_BEHAVIOR); |
62 | 0 | } |
63 | ||
64 | public AbstractKualiDecimal(BigDecimal value, int scale) { | |
65 | 0 | this(value.toPlainString(), scale); |
66 | 0 | } |
67 | ||
68 | /** | |
69 | * @param operand | |
70 | * @return true if this AbstractKualiDecimal is less than the given | |
71 | * AbstractKualiDecimal | |
72 | */ | |
73 | public boolean isLessThan(AbstractKualiDecimal operand) { | |
74 | 0 | if (operand == null) { |
75 | 0 | throw new IllegalArgumentException("invalid (null) operand"); |
76 | } | |
77 | ||
78 | 0 | return (this.compareTo(operand) == -1); |
79 | } | |
80 | ||
81 | /** | |
82 | * @param operand | |
83 | * @return true if this AbstractKualiDecimal is greater than the given | |
84 | * AbstractKualiDecimal | |
85 | */ | |
86 | public boolean isGreaterThan(AbstractKualiDecimal operand) { | |
87 | 0 | if (operand == null) { |
88 | 0 | throw new IllegalArgumentException("invalid (null) operand"); |
89 | } | |
90 | ||
91 | 0 | return (this.compareTo(operand) == 1); |
92 | } | |
93 | ||
94 | /** | |
95 | * @param operand | |
96 | * @return true if this AbstractKualiDecimal is less than or equal to the | |
97 | * given AbstractKualiDecimal | |
98 | */ | |
99 | public boolean isLessEqual(AbstractKualiDecimal operand) { | |
100 | 0 | if (operand == null) { |
101 | 0 | throw new IllegalArgumentException("invalid (null) operand"); |
102 | } | |
103 | ||
104 | 0 | return !isGreaterThan(operand); |
105 | } | |
106 | ||
107 | /** | |
108 | * @param operand | |
109 | * @return true if this AbstractKualiDecimal is greater than or equal to the | |
110 | * given AbstractKualiDecimal | |
111 | */ | |
112 | public boolean isGreaterEqual(AbstractKualiDecimal operand) { | |
113 | 0 | if (operand == null) { |
114 | 0 | throw new IllegalArgumentException("invalid (null) operand"); |
115 | } | |
116 | ||
117 | 0 | return !isLessThan(operand); |
118 | } | |
119 | ||
120 | /** | |
121 | * @return true if the given String can be used to construct a valid | |
122 | * AbstractKualiDecimal | |
123 | */ | |
124 | public static boolean isNumeric(String s) { | |
125 | 0 | boolean isValid = false; |
126 | ||
127 | 0 | if (!StringUtils.isBlank(s)) { |
128 | try { | |
129 | 0 | new BigDecimal(s); |
130 | 0 | isValid = true; |
131 | 0 | } catch (NumberFormatException e) { |
132 | 0 | } |
133 | } | |
134 | ||
135 | 0 | return isValid; |
136 | } | |
137 | ||
138 | // Number methods | |
139 | /** | |
140 | * @see java.lang.Number#doubleValue() | |
141 | */ | |
142 | @Override | |
143 | public double doubleValue() { | |
144 | 0 | return this.value.doubleValue(); |
145 | } | |
146 | ||
147 | /** | |
148 | * @see java.lang.Number#floatValue() | |
149 | */ | |
150 | @Override | |
151 | public float floatValue() { | |
152 | 0 | return this.value.floatValue(); |
153 | } | |
154 | ||
155 | /** | |
156 | * @see java.lang.Number#intValue() | |
157 | */ | |
158 | @Override | |
159 | public int intValue() { | |
160 | 0 | return this.value.intValue(); |
161 | } | |
162 | ||
163 | /** | |
164 | * @see java.lang.Number#longValue() | |
165 | */ | |
166 | @Override | |
167 | public long longValue() { | |
168 | 0 | return this.value.longValue(); |
169 | } | |
170 | ||
171 | /** | |
172 | * @return the value of this instance as a BigDecimal. | |
173 | */ | |
174 | public BigDecimal bigDecimalValue() { | |
175 | 0 | return this.value; |
176 | } | |
177 | ||
178 | // Comparable methods | |
179 | /** | |
180 | * Compares this AbstractKualiDecimal with the specified Object. If the | |
181 | * Object is a AbstractKualiDecimal, this method behaves like | |
182 | * java.lang.Comparable#compareTo(java.lang.Object). | |
183 | * | |
184 | * Otherwise, it throws a <tt>ClassCastException</tt> (as KualiDecimals | |
185 | * are comparable only to other KualiDecimals). | |
186 | * | |
187 | * @see java.lang.Comparable#compareTo(java.lang.Object) | |
188 | */ | |
189 | public int compareTo(Object o) { | |
190 | 0 | return compareTo((AbstractKualiDecimal) o); |
191 | } | |
192 | ||
193 | /** | |
194 | * Returns the result of comparing the values of this AbstractKualiDecimal | |
195 | * and the given AbstractKualiDecimal. | |
196 | * | |
197 | * @see java.lang.Comparable#compareTo(java.lang.Object) | |
198 | */ | |
199 | public int compareTo(AbstractKualiDecimal k) { | |
200 | 0 | return this.value.compareTo(k.value); |
201 | } | |
202 | ||
203 | // Object methods | |
204 | /** | |
205 | * @see java.lang.Object#equals(java.lang.Object) | |
206 | */ | |
207 | @Override | |
208 | public boolean equals(Object obj) { | |
209 | 0 | boolean equals = false; |
210 | ||
211 | 0 | if (obj instanceof AbstractKualiDecimal) { |
212 | 0 | AbstractKualiDecimal k = (AbstractKualiDecimal) obj; |
213 | ||
214 | // using AbstractKualiDecimal.compareTo instead of BigDecimal.equals | |
215 | // since | |
216 | // BigDecimal.equals only returns true if the | |
217 | // scale and precision are equal, rather than comparing the actual | |
218 | // (scaled) values | |
219 | 0 | equals = (this.compareTo(k) == 0); |
220 | } | |
221 | ||
222 | 0 | return equals; |
223 | } | |
224 | ||
225 | /** | |
226 | * | |
227 | * @see java.lang.Object#hashCode() | |
228 | */ | |
229 | @Override | |
230 | public int hashCode() { | |
231 | 0 | return this.value.hashCode(); |
232 | } | |
233 | ||
234 | /** | |
235 | * @see java.lang.Object#toString() | |
236 | */ | |
237 | @Override | |
238 | public String toString() { | |
239 | 0 | return this.value.toString(); |
240 | } | |
241 | ||
242 | /** | |
243 | * @return true if this T is less than zero | |
244 | */ | |
245 | public boolean isNegative() { | |
246 | 0 | return (this.compareTo(ZERO) == -1); |
247 | } | |
248 | ||
249 | /** | |
250 | * @return true if this T is greater than zero | |
251 | */ | |
252 | public boolean isPositive() { | |
253 | 0 | return (this.compareTo(ZERO) == 1); |
254 | } | |
255 | ||
256 | /** | |
257 | * @return true if this T is equal to zero | |
258 | */ | |
259 | public boolean isZero() { | |
260 | 0 | return (this.compareTo(ZERO) == 0); |
261 | } | |
262 | ||
263 | /** | |
264 | * @return a T with the same scale and the absolute value | |
265 | */ | |
266 | public T abs() { | |
267 | 0 | T absolute = null; |
268 | ||
269 | 0 | if (isNegative()) { |
270 | 0 | absolute = negated(); |
271 | } else { | |
272 | 0 | absolute = newInstance(this.value, this.value.scale()); |
273 | } | |
274 | ||
275 | 0 | return absolute; |
276 | } | |
277 | ||
278 | /** | |
279 | * @return true if this T is not equal to zero | |
280 | */ | |
281 | public boolean isNonZero() { | |
282 | 0 | return !this.isZero(); |
283 | } | |
284 | ||
285 | /** | |
286 | * Wraps BigDecimal's add method to accept and return T instances instead of | |
287 | * BigDecimals, so that users of the class don't have to typecast the return | |
288 | * value. | |
289 | * | |
290 | * @param addend | |
291 | * @return result of adding the given addend to this value | |
292 | * @throws IllegalArgumentException | |
293 | * if the given addend is null | |
294 | */ | |
295 | public T add(T addend) { | |
296 | 0 | if (addend == null) { |
297 | 0 | throw new IllegalArgumentException("invalid (null) addend"); |
298 | } | |
299 | ||
300 | 0 | BigDecimal sum = this.value.add(addend.value); |
301 | 0 | return newInstance(sum, sum.scale()); |
302 | } | |
303 | ||
304 | /** | |
305 | * Wraps BigDecimal's subtract method to accept and return T instances | |
306 | * instead of BigDecimals, so that users of the class don't have to typecast | |
307 | * the return value. | |
308 | * | |
309 | * @param subtrahend | |
310 | * @return result of the subtracting the given subtrahend from this value | |
311 | * @throws IllegalArgumentException | |
312 | * if the given subtrahend is null | |
313 | */ | |
314 | public T subtract(T subtrahend) { | |
315 | 0 | if (subtrahend == null) { |
316 | 0 | throw new IllegalArgumentException("invalid (null) subtrahend"); |
317 | } | |
318 | ||
319 | 0 | BigDecimal difference = this.value.subtract(subtrahend.value); |
320 | 0 | return newInstance(difference, difference.scale()); |
321 | } | |
322 | ||
323 | /** | |
324 | * Wraps BigDecimal's multiply method to accept and return T instances | |
325 | * instead of BigDecimals, so that users of the class don't have to typecast | |
326 | * the return value. | |
327 | * | |
328 | * @param multiplicand | |
329 | * @return result of multiplying this value by the given multiplier | |
330 | * @throws IllegalArgumentException | |
331 | * if the given multiplier is null | |
332 | */ | |
333 | public T multiply(T multiplier) { | |
334 | 0 | return multiply(multiplier, true); |
335 | } | |
336 | ||
337 | /** | |
338 | * Overloaded multiply method where we can specify if we need to preserve the precision of the result | |
339 | * | |
340 | * @param multiplicand | |
341 | * @param applyScale | |
342 | * @return result of multiplying this value by the given multiplier | |
343 | * @throws IllegalArgumentException | |
344 | * if the given multiplier is null | |
345 | */ | |
346 | public T multiply(T multiplier, boolean applyScale) { | |
347 | 0 | if (multiplier == null) { |
348 | 0 | throw new IllegalArgumentException("invalid (null) multiplier"); |
349 | } | |
350 | ||
351 | 0 | BigDecimal product = this.value.multiply(multiplier.value); |
352 | 0 | return newInstance(product, applyScale ? this.value.scale() : product.scale()); |
353 | } | |
354 | ||
355 | /** | |
356 | * This method calculates the mod between to T values by first casting to | |
357 | * doubles and then by performing the % operation on the two primitives. | |
358 | * | |
359 | * @param modulus | |
360 | * The other value to apply the mod to. | |
361 | * @return result of performing the mod calculation | |
362 | * @throws IllegalArgumentException | |
363 | * if the given modulus is null | |
364 | */ | |
365 | public T mod(T modulus) { | |
366 | 0 | return mod(modulus, true); |
367 | } | |
368 | ||
369 | /** | |
370 | * Overloaded mod method where we can specify if we want to preserve the result's precision | |
371 | * | |
372 | * @param modulus | |
373 | * The other value to apply the mod to. | |
374 | * @param applyScale | |
375 | * @return result of performing the mod calculation | |
376 | * @throws IllegalArgumentException | |
377 | * if the given modulus is null | |
378 | */ | |
379 | ||
380 | public T mod(T modulus, boolean applyScale) { | |
381 | 0 | if (modulus == null) { |
382 | 0 | throw new IllegalArgumentException("invalid (null) modulus"); |
383 | } | |
384 | 0 | double difference = this.value.doubleValue() % modulus.doubleValue(); |
385 | ||
386 | 0 | int scaleToApply = applyScale ? this.value.scale() : new BigDecimal(difference).scale(); |
387 | ||
388 | //return (T) newInstance(new BigDecimal(difference).setScale(scaleToApply, BigDecimal.ROUND_UNNECESSARY), scaleToApply); | |
389 | 0 | return (T) newInstance(new BigDecimal(difference), scaleToApply); |
390 | } | |
391 | ||
392 | ||
393 | /** | |
394 | * Wraps BigDecimal's divide method to enforce the default Kuali rounding | |
395 | * behavior | |
396 | * | |
397 | * @param divisor | |
398 | * @return result of dividing this value by the given divisor | |
399 | * @throws an | |
400 | * IllegalArgumentException if the given divisor is null | |
401 | */ | |
402 | public T divide(T divisor) { | |
403 | 0 | return divide(divisor, true); |
404 | } | |
405 | ||
406 | /** | |
407 | * @return a T with the same scale and a negated value (iff the value is | |
408 | * non-zero) | |
409 | */ | |
410 | public T negated() { | |
411 | 0 | return multiply(newInstance("-1")); |
412 | } | |
413 | ||
414 | public T divide(T divisor, boolean applyScale) { | |
415 | 0 | if (divisor == null) { |
416 | 0 | throw new IllegalArgumentException("invalid (null) divisor"); |
417 | } | |
418 | 0 | BigDecimal quotient = this.value.divide(divisor.value, ROUND_BEHAVIOR); |
419 | ||
420 | 0 | T result = newInstance(quotient, applyScale ? this.value.scale() : quotient.scale()); |
421 | 0 | return result; |
422 | } | |
423 | ||
424 | protected abstract T newInstance(String value); | |
425 | ||
426 | protected abstract T newInstance(double value); | |
427 | ||
428 | protected abstract T newInstance(double value, int scale); | |
429 | ||
430 | protected abstract T newInstance(BigDecimal value); | |
431 | ||
432 | protected abstract T newInstance(BigDecimal value, int scale); | |
433 | } |