View Javadoc

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      public static final KualiDecimal ZERO = new KualiDecimal(BigDecimal.ZERO);
36      
37  	protected BigDecimal value;
38  
39  	public AbstractKualiDecimal() {
40  	}
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  	public AbstractKualiDecimal(String value, int scale) {
49  		if (StringUtils.isBlank(value)) {
50  			this.value = BigDecimal.ZERO.setScale(scale,ROUND_BEHAVIOR);
51  		} else {
52  			this.value = new BigDecimal(value).setScale(scale, ROUND_BEHAVIOR);
53  		}
54  	}
55  
56  	public AbstractKualiDecimal(int value, int scale) {
57  	    this.value = new BigDecimal(value).setScale(scale, ROUND_BEHAVIOR);
58  	}
59  
60  	public AbstractKualiDecimal(double value, int scale) {
61  		this.value = new BigDecimal(value).setScale(scale, ROUND_BEHAVIOR);
62  	}
63  
64  	public AbstractKualiDecimal(BigDecimal value, int scale) {
65  		this(value.toPlainString(), scale);
66  	}
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  		if (operand == null) {
75  			throw new IllegalArgumentException("invalid (null) operand");
76  		}
77  
78  		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  		if (operand == null) {
88  			throw new IllegalArgumentException("invalid (null) operand");
89  		}
90  
91  		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 		if (operand == null) {
101 			throw new IllegalArgumentException("invalid (null) operand");
102 		}
103 
104 		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 		if (operand == null) {
114 			throw new IllegalArgumentException("invalid (null) operand");
115 		}
116 
117 		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 		boolean isValid = false;
126 
127 		if (!StringUtils.isBlank(s)) {
128 			try {
129 				new BigDecimal(s);
130 				isValid = true;
131 			} catch (NumberFormatException e) {
132 			}
133 		}
134 
135 		return isValid;
136 	}
137 
138 	// Number methods
139 	/**
140 	 * @see java.lang.Number#doubleValue()
141 	 */
142 	@Override
143 	public double doubleValue() {
144 		return this.value.doubleValue();
145 	}
146 
147 	/**
148 	 * @see java.lang.Number#floatValue()
149 	 */
150 	@Override
151 	public float floatValue() {
152 		return this.value.floatValue();
153 	}
154 
155 	/**
156 	 * @see java.lang.Number#intValue()
157 	 */
158 	@Override
159 	public int intValue() {
160 		return this.value.intValue();
161 	}
162 
163 	/**
164 	 * @see java.lang.Number#longValue()
165 	 */
166 	@Override
167 	public long longValue() {
168 		return this.value.longValue();
169 	}
170 
171 	/**
172 	 * @return the value of this instance as a BigDecimal.
173 	 */
174 	public BigDecimal bigDecimalValue() {
175 		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 		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 		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 		boolean equals = false;
210 
211 		if (obj instanceof AbstractKualiDecimal) {
212 			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 			equals = (this.compareTo(k) == 0);
220 		}
221 
222 		return equals;
223 	}
224 
225 	/**
226 	 * 
227 	 * @see java.lang.Object#hashCode()
228 	 */
229 	@Override
230 	public int hashCode() {
231 		return this.value.hashCode();
232 	}
233 
234 	/**
235 	 * @see java.lang.Object#toString()
236 	 */
237 	@Override
238 	public String toString() {
239 		return this.value.toString();
240 	}
241 
242 	/**
243 	 * @return true if this T is less than zero
244 	 */
245 	public boolean isNegative() {
246 		return (this.compareTo(ZERO) == -1);
247 	}
248 
249 	/**
250 	 * @return true if this T is greater than zero
251 	 */
252 	public boolean isPositive() {
253 		return (this.compareTo(ZERO) == 1);
254 	}
255 
256 	/**
257 	 * @return true if this T is equal to zero
258 	 */
259 	public boolean isZero() {
260 		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 		T absolute = null;
268 
269 		if (isNegative()) {
270 			absolute = negated();
271 		} else {
272 			absolute = newInstance(this.value, this.value.scale());
273 		}
274 
275 		return absolute;
276 	}
277 
278 	/**
279 	 * @return true if this T is not equal to zero
280 	 */
281 	public boolean isNonZero() {
282 		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 		if (addend == null) {
297 			throw new IllegalArgumentException("invalid (null) addend");
298 		}
299 
300 		BigDecimal sum = this.value.add(addend.value);
301 		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 		if (subtrahend == null) {
316 			throw new IllegalArgumentException("invalid (null) subtrahend");
317 		}
318 
319 		BigDecimal difference = this.value.subtract(subtrahend.value);
320 		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 	    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         if (multiplier == null) {
348             throw new IllegalArgumentException("invalid (null) multiplier");
349         }
350 
351         BigDecimal product = this.value.multiply(multiplier.value);  
352         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 	    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         if (modulus == null) {
382             throw new IllegalArgumentException("invalid (null) modulus");
383         }
384         double difference = this.value.doubleValue() % modulus.doubleValue();
385 
386         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         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 		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 		return multiply(newInstance("-1"));
412 	}
413 
414 	public T divide(T divisor, boolean applyScale) {
415 		if (divisor == null) {
416 			throw new IllegalArgumentException("invalid (null) divisor");
417 		}
418 		BigDecimal quotient = this.value.divide(divisor.value, ROUND_BEHAVIOR);
419 
420 		T result = newInstance(quotient, applyScale ? this.value.scale() : quotient.scale());
421 		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 }