001    package hirondelle.web4j.model;
002    
003    import java.util.*;
004    import java.io.Serializable;
005    import java.io.IOException;
006    import java.io.ObjectInputStream;
007    import java.io.ObjectOutputStream;
008    import java.math.BigDecimal;
009    import java.math.RoundingMode;
010    import hirondelle.web4j.util.Util;
011    
012    /**
013     Represents a decimal amount.
014    
015    <P>Decimal amounts are typically used to represent two kinds of items :
016    <ul>
017     <li>monetary amounts 
018     <li>measurements such as temperature, distance, and so on
019     </ul>
020     
021     <P>Your applications are not obliged to use this class to represent decimal amounts. 
022     You may choose to use {@link BigDecimal} instead (perhaps along with an <tt>Id</tt>
023     to store a currency, if needed).
024     
025    <P>This class exists for these reasons :
026    <ul>
027     <li>to allow your code to read at a higher level
028     <li>to simplify calculations beyond what is available from the {@link BigDecimal} class
029     <li>to help you avoid using the floating point types <tt>double</tt> and <tt>float</tt>, 
030     which have many <a href='http://www.ibm.com/developerworks/java/library/j-jtp0114/'>pitfalls</a> 
031    </ul>
032    
033     <P><tt>Decimal</tt> objects are immutable.  
034     Many operations return new <tt>Decimal</tt> objects. 
035     
036     <h3>Currency Is Unspecified</h3>
037     This class can be used to model amounts of money. 
038    <P><em>Many will be surprised that this class does not make any reference to currency.</em> 
039     The reason for this is adding currency would render this class a poor <em>building block</em>.  
040     Building block objects such as <tt>Date</tt>, <tt>Integer</tt>, and so on, are 
041     <em>atomic</em>, in the sense of representing a <em>single</em> piece of data.   
042     They correspond to a single column in a table, or a single form control. If the currency 
043     were included in this class, then it would no longer be atomic, and it could not be 
044     treated by WEB4J as any other building block class.
045     However, allowing this class to be treated like any other building block class is 
046     highly advantageous. 
047     
048     <P>If a feature needs to explicitly distinguish between <em>multiple</em> currencies 
049     such as US Dollars and Japanese Yen, then a <tt>Decimal</tt> object 
050     will need to be paired by the caller with a <em>second</em> item representing the 
051     underlying currency (perhaps modeled as an <tt>Id</tt>). 
052     See the {@link Currency} class for more information.  
053     
054     <h3>Number of Decimal Places</h3>
055     To validate the number of decimals in your Model Objects, 
056     call the {@link Check#numDecimalsAlways(int)} or {@link Check#numDecimalsMax(int)} methods.
057     
058     <P>The {@link #init(RoundingMode, int)} method is called upon startup.
059     It takes a parameter which specifies the number of decimal places.
060     It is used only for rounding the results of <tt>times</tt> and <tt>div</tt> operations. 
061     It is <em>not</em> used to validate the number of decimals in 
062     items passed to the <tt>Decimal</tt> constructor.
063     
064     <h3>Different Numbers of Decimals</h3>
065     <P>As usual, operations can be performed on two items having a different number of decimal places. 
066     For example, these  operations are valid (using an informal, <em>ad hoc</em> notation) : 
067     <PRE>10 + 1.23 = 11.23
068    10.00 + 1.23 = 11.23
069    10 - 1.23 = 8.77
070    (10 > 1.23) => true </PRE> 
071     This corresponds to typical user expectations.
072      
073     <P>Note that {@link #equals(Object)} is unusual in that it is the only method sensitive to the exact 
074     number of decimal places, while {@link #eq(Decimal)} is not. That is,  
075     <PRE>10.equals(10.00) => false
076    10.eq(10.00) => true</PRE>
077       
078     <h3>Results With 'Extra' Decimal Places</h3>
079     <P>The <tt>times</tt> and <tt>div</tt> operations are different, since the result
080     can have a larger number of decimals than usual. For example, when dealing with US Dollars, the result  
081     <PRE>$10.00 x 0.1256 = $1.256</PRE>
082     has more than two decimals. In such cases, <em>this class will round 
083     results of multiplication and division</em>, using 
084     the setting passed to {@link #init(RoundingMode, int)}. 
085     This policy likely conforms to the expectations of most end users.
086     The {@link #times(long)} method is an exception to this rule.
087     
088     <P>The {@link #init(RoundingMode, int)} method takes two parameters.
089     One controls the rounding policy, and the other controls the number of decimals to use <em>by default</em>.
090     When the default number of decimals needs to be overridden, you must use the 
091     {@link #changeTimesDivDecimals(int)} method. 
092     
093     <h3>Terse Method Names</h3>
094     Various methods in this class have unusually terse names, such as 
095     <tt>lt</tt> for 'less than',  and <tt>gt</tt> for 'greater than', and so on. 
096     The intent of such names is to improve the legibility of mathematical 
097     expressions.
098      
099     <P>Example : 
100     <PRE> if ( amount.lt(hundred) ) {
101         cost = amount.times(price); 
102     }</PRE>
103     
104     <h3>Prefer Decimal to Double</h3>
105     The <tt>times</tt> and <tt>div</tt> methods are overloaded to take <tt>int</tt> for 
106     round numbers, and <tt>Decimal</tt> or <tt>double</tt> for numbers with a decimal.
107     
108     <P>In short, the <tt>double</tt> versions are best suited when using
109     <em>hard-coded</em> values for  factors and divisors,  while the 
110     <tt>Decimal</tt> versions are suited for the (more common) case of using
111     values coming from the database or user input.
112     
113     <P>Using <tt>Decimal</tt> is the preferred form, since there are many pitfalls associated 
114     with <tt>double</tt>. The <tt>double</tt> form has been retained since it's 
115     more convenient for the caller in some cases, and one of the goals of this class is 
116     to allow terse mathematical expressions.
117     
118     <h3>Extends Number</h3>
119     This class extends {@link Number}. An immediate benefit of this is that it allows JSTL's 
120     <tt>fmt</tt> tags to render <tt>Decimal</tt> objects in the usual way.
121    */
122    public final class Decimal extends Number implements Comparable<Decimal>, Serializable {
123      
124      /**
125       Set default values for the rounding style, and the maximum number of decimals
126       to use when calculating results of <tt>times</tt> and <tt>div</tt> operations.
127       
128       <P>This method is called by the framework upon startup.
129       The recommended rounding style is {@link RoundingMode#HALF_EVEN}, also called 
130       <em>banker's rounding</em>. That rounding style introduces the least bias.
131       
132       @param aRounding defines how all numbers are rounded by this class. This rounding 
133       style is set once, and cannot be overridden for individual <tt>Decimal</tt> objects.
134       @param aNumDecimalsForTimesDiv number of decimals for results of 
135       <tt>times</tt> and <tt>div</tt> operations. Must be 0 or more.
136       Taking the example of US Dollars, this setting would usually be '2'.
137      */
138      public static void init(RoundingMode aRounding, int aNumDecimalsForTimesDiv){
139        if ( aNumDecimalsForTimesDiv < 0 ){
140          throw new IllegalArgumentException("Number of decimals for times-div operations must be 0 or more. Value: " + Util.quote(aNumDecimalsForTimesDiv));
141        }
142        ROUNDING = aRounding;
143        TIMES_DIV_DECIMALS =  aNumDecimalsForTimesDiv;
144      }
145      
146      /** Return the rounding style passed to the <tt>init</tt> method. */
147      public static RoundingMode getRoundingStyle() { return ROUNDING; }
148      
149      /** Return the number of decimals passed to the <tt>init</tt> method. */
150      public static int getTimesDivDecimalsDefault() { return TIMES_DIV_DECIMALS; }
151     
152      /**
153       Full constructor.
154       
155       @param aAmount required, can be positive or negative.
156       Any number of decimals. The value of {@link BigDecimal#scale()} cannot 
157       be negative.
158      */
159      public Decimal(BigDecimal aAmount){
160        this(aAmount, TIMES_DIV_DECIMALS);
161      }
162      
163      /**
164       Convenience factory method. 
165       
166       <P>Instead of  : 
167       <PRE>Decimal decimal = new Decimal(new BigDecimal("100"));</PRE>
168       one may instead use  : 
169       <PRE>Decimal decimal = Decimal.from("100");</PRE>
170       which is a bit more legible. This is especially useful when you need to define 
171       specific minimum and maximum values used in validation.  
172      */
173      public static Decimal from(String aAmount){
174        return new Decimal(new BigDecimal(aAmount));
175      }
176      
177      /**
178       Override the default number of decimals retained in <tt>times</tt> and <tt>div</tt> operations.
179       
180       The <em>default</em> number of decimals retained in <tt>times</tt> and <tt>div</tt> 
181       operations is set during the call to {@link #init(RoundingMode, int)}.
182       The <tt>changeTimesDivDecimals</tt> method is called when that default is not 
183       appropriate.
184       
185       <P>This method will often be used when modeling physical measurements 
186       such as temperature, distance, and so on, where the number of decimals can 
187       vary according to context. 
188      */
189      public Decimal changeTimesDivDecimals(int aTimesDivDecimals){
190        return new Decimal(fAmount, aTimesDivDecimals);
191      }
192      
193      /** Return the amount passed to the constructor. */
194      public BigDecimal getAmount() { return fAmount; }
195      
196      /** 
197       Return the number of decimals to be retained by <tt>times</tt> and <tt>div</tt> operations.
198       
199       <P>See {@link #changeTimesDivDecimals(int)} for a way of altering this value from the default. 
200      */
201      public int getTimesDivDecimals() { return fTimesDivDecimals; }
202      
203      /** The suffix is needed to distinguish from the public field.  Declared 'early' since compiler complains.*/
204      private static final BigDecimal ZERO_BD = BigDecimal.ZERO;
205    
206      /** 
207       Zero <tt>Decimal</tt> amount.
208       
209       <P>Like {@link BigDecimal#ZERO}, this item has no explicit decimal. 
210       In most cases that will not matter, since only the {@link #equals(Object)} method is sensitive to 
211       exact decimals. All other methods, including {@link #eq(Decimal)}, are not sensitive to exact decimals.
212      */
213      public static final Decimal ZERO = new Decimal(ZERO_BD);
214      
215      /**
216       Return the number of decimals in this value.
217       
218       <P>For validating the number of decimals in user input, you are highly encouraged to 
219       use {@link Check#numDecimalsAlways(int)} or {@link Check#numDecimalsMax(int)}.
220      */
221      public int getNumDecimals(){
222        return fAmount.scale();
223      }
224      
225      /** Return <tt>true</tt> only if the amount is positive. */
226      public boolean isPlus(){
227        return fAmount.compareTo(ZERO_BD) > 0;
228      }
229      
230      /** Return <tt>true</tt> only if the amount is negative. */
231      public boolean isMinus(){
232        return fAmount.compareTo(ZERO_BD) <  0;
233      }
234      
235      /** Return <tt>true</tt> only if the amount is zero. */
236      public boolean isZero(){
237        return fAmount.compareTo(ZERO_BD) ==  0;
238      }
239      
240      /** 
241       Equals (insensitive to number of decimals).
242       
243       <P>That is, <tt>10</tt> and <tt>10.00</tt> are considered equal by this method.
244       
245       <P>Return <tt>true</tt> only if the amounts are equal.
246       This method is <em>not</em> synonymous with the <tt>equals</tt> method, 
247       since the {@link #equals(Object)} method is sensitive to the exact number of decimal places.
248      */
249      public boolean eq(Decimal aThat) {
250        return compareAmount(aThat) == 0;
251      }
252    
253      /** 
254       Greater than.
255       
256       <P>Return <tt>true</tt> only if  'this' amount is greater than
257       'that' amount. 
258      */
259      public boolean gt(Decimal aThat) { 
260        return compareAmount(aThat) > 0;  
261      }
262      
263      /** 
264       Greater than or equal to.
265       
266       <P>Return <tt>true</tt> only if 'this' amount is 
267       greater than or equal to 'that' amount. 
268      */
269      public boolean gteq(Decimal aThat) { 
270        return compareAmount(aThat) >= 0;  
271      }
272      
273      /** 
274       Less than.
275       
276       <P>Return <tt>true</tt> only if 'this' amount is less than
277       'that' amount. 
278      */
279      public boolean lt(Decimal aThat) { 
280        return compareAmount(aThat) < 0;  
281      }
282      
283      /** 
284       Less than or equal to.
285       
286       <P>Return <tt>true</tt> only if 'this' amount is less than or equal to
287       'that' amount.  
288      */
289      public boolean lteq(Decimal aThat) { 
290        return compareAmount(aThat) <= 0;  
291      }
292      
293      /** 
294       Add <tt>aThat</tt> <tt>Decimal</tt> to this <tt>Decimal</tt>.
295      */
296      public Decimal plus(Decimal aThat){
297        return new Decimal(fAmount.add(aThat.fAmount));
298      }
299    
300      /** 
301       Subtract <tt>aThat</tt> <tt>Decimal</tt> from this <tt>Decimal</tt>. 
302      */
303      public Decimal minus(Decimal aThat){
304        return new Decimal(fAmount.subtract(aThat.fAmount));
305      }
306    
307      /**
308       Sum a collection of <tt>Decimal</tt> objects.
309       You are encouraged to use database summary functions 
310       whenever possible, instead of this method. 
311       
312       @param aDecimals collection of <tt>Decimal</tt> objects.
313       If the collection is empty, then a zero value is returned.
314      */
315      public static Decimal sum(Collection<Decimal> aDecimals){
316        Decimal sum = new Decimal(ZERO_BD);
317        for(Decimal decimal : aDecimals){
318          sum = sum.plus(decimal);
319        }
320        return sum;
321      }
322      
323      /**
324       Multiply this <tt>Decimal</tt> by an integral factor.
325       
326       <P>The number of decimals in the return value is the same as 
327       the number of decimals of 'this' <tt>Decimal</tt>. For example,
328       <PRE>10 x 2 = 20
329      10.00 x 2 = 20.00</PRE>
330       This conforms to most user's expectations. 
331       This behavior is slightly different from the other <tt>times</tt> methods.
332      */
333      public Decimal times(long aFactor){  
334        BigDecimal factor = new BigDecimal(aFactor);
335        BigDecimal newAmount = fAmount.multiply(factor);
336        return new Decimal(newAmount);
337      }
338      
339      /**
340       Multiply this <tt>Decimal</tt> by an non-integral factor (having a decimal point).
341       
342       <P>The number of decimals of the result is taken from 
343       {@link #getTimesDivDecimals()}.  
344      */
345      public Decimal times(Decimal aFactor){
346        BigDecimal newAmount = fAmount.multiply(aFactor.getAmount());
347        newAmount = newAmount.setScale(fTimesDivDecimals, ROUNDING);
348        return  new Decimal(newAmount);
349      }
350      
351      /**
352       Multiply this <tt>Decimal</tt> by an non-integral factor (having a decimal point).
353       
354       <P>The number of decimals of the result is taken from 
355       {@link #getTimesDivDecimals()}.
356       
357       Consider using {@link #times(Decimal)} as the preferred alternative.   
358      */
359      public Decimal times(double aFactor){
360        return times(asDecimal(aFactor));
361      }
362      
363      /**
364       Divide this <tt>Decimal</tt> by an integral divisor.
365       
366       <P>The number of decimals of the result is taken from 
367       {@link #getTimesDivDecimals()}.  
368      */
369      public Decimal div(long aDivisor){
370        BigDecimal divisor = new BigDecimal(aDivisor);
371        BigDecimal newAmount = fAmount.divide(divisor, fTimesDivDecimals, ROUNDING);
372        return new Decimal(newAmount);
373      }
374    
375      /**
376       Divide this <tt>Decimal</tt> by an non-integral divisor.
377       
378       <P>The number of decimals of the result is taken from 
379       {@link #getTimesDivDecimals()}.  
380      */
381      public Decimal div(Decimal aDivisor){  
382        BigDecimal newAmount = fAmount.divide(aDivisor.getAmount(), fTimesDivDecimals, ROUNDING);
383        return new Decimal(newAmount);
384      }
385      
386      /**
387       Divide this <tt>Decimal</tt> by an non-integral divisor.
388       
389       <P>The number of decimals of the result is taken from 
390       {@link #getTimesDivDecimalsDefault()}.
391         
392       Consider using {@link #div(Decimal)} as the preferred alternative.   
393      */
394      public Decimal div(double aDivisor){  
395        return div(asDecimal(aDivisor));
396      }
397    
398      /** Return the absolute value of the amount. */
399      public Decimal abs(){
400        return isPlus() ? this : times(-1);
401      }
402      
403      /** Return the amount x (-1). */
404      public Decimal negate(){ 
405        return times(-1); 
406      }
407      
408      /**
409       Round to an integer.
410       
411       <P>Uses {@link #getRoundingStyle()}.
412      */
413      public Decimal round(){
414        BigDecimal amount = fAmount.setScale(0, ROUNDING);
415        return new Decimal(amount);
416      }
417    
418      /**
419       Round to 0 or more decimal places.
420       
421       <P>Uses {@link #getRoundingStyle()}.
422      */
423      public Decimal round(int aNumberOfDecimals){
424        if( aNumberOfDecimals < 0 ){
425          throw new IllegalArgumentException("Number of decimals is negative: " + Util.quote(aNumberOfDecimals));
426        }
427        BigDecimal amount = fAmount.setScale(aNumberOfDecimals, ROUNDING);
428        return new Decimal(amount);
429      }
430      
431      /**
432       Renders this <tt>Decimal</tt> in a style suitable for debugging. 
433        
434       <P>Returns the amount in the format defined by {@link BigDecimal#toPlainString()}. 
435      */
436      public String toString(){
437        return fAmount.toPlainString();
438      }
439      
440      /**
441       Equals (sensitive to number of decimals). 
442       
443       <P>That is, <tt>10</tt> and <tt>10.00</tt> are <em>not</em> 
444       considered equal by this method.
445       
446       <P>This implementation imitates {@link BigDecimal#equals(java.lang.Object)}, 
447       which is also sensitive to the number of decimals (or 'scale').
448       
449       See {@link #eq(Decimal)} as well.
450      */
451      public boolean equals(Object aThat){
452        if (this == aThat) return true;
453        if (! (aThat instanceof Decimal) ) return false;
454        Decimal that = (Decimal)aThat;
455        //the object fields are never null :
456        boolean result = (this.fAmount.equals(that.fAmount) );
457        return result;
458      }
459      
460      public int hashCode(){
461        if ( fHashCode == 0 ) {
462          fHashCode = HASH_SEED;
463          fHashCode = HASH_FACTOR * fHashCode + fAmount.hashCode(); 
464        }
465        return fHashCode;
466      }
467      
468      /**
469       Implements the {@link Comparable} interface. 
470       
471       <P>Recommended that you use the other methods such as {@link #eq(Decimal)}, {@link #lt(Decimal)}, and 
472       so on, since they have greater clarity and concision.
473      */
474      public int compareTo(Decimal aThat) {
475        final int EQUAL = 0;
476        
477        if ( this == aThat ) return EQUAL;
478    
479        //the object field is never null 
480        int comparison = this.fAmount.compareTo(aThat.fAmount);
481        if ( comparison != EQUAL ) return comparison;
482    
483        return EQUAL;
484      }
485      
486      /** 
487       Required by {@link Number}.
488          
489       <P><em>Use of floating point data is highly discouraged.</em> 
490       This method is provided only because it's required by <tt>Number</tt>. 
491      */
492      @Override public double doubleValue() {
493        return fAmount.doubleValue();
494      }
495      
496      /** 
497       Required by {@link Number}.
498          
499       <P><em>Use of floating point data is highly discouraged.</em> 
500       This method is provided only because it's required by <tt>Number</tt>. 
501      */
502      @Override public float floatValue() {
503        return fAmount.floatValue();
504      }
505    
506      /** Required by {@link Number}. */
507      @Override  public int intValue() {
508        return fAmount.intValue();
509      }
510      
511      /** Required by {@link Number}. */
512      @Override public long longValue() {
513        return fAmount.longValue();
514      }
515      
516      // PRIVATE //
517      
518      /** 
519       The decimal amount. 
520       Never null. 
521       @serial 
522      */
523      private BigDecimal fAmount;
524      
525      private final int fTimesDivDecimals;
526      
527      /**
528       The default rounding style to be used if no currency is passed to the constructor.
529       See {@link BigDecimal}. 
530      */ 
531      private static RoundingMode ROUNDING;
532      
533      /**
534       Maximum number of decimals for all monies. 
535       This provides a way of rounding results of times and div operations. 
536        For example, if a result of a multiplication or division is $10.25366,
537        it will be rounded to $10.25 implicitly by this class (if this setting is '2').
538      */
539      private static int TIMES_DIV_DECIMALS;
540      
541      /** @serial */
542      private int fHashCode;
543      private static final int HASH_SEED = 23;
544      private static final int HASH_FACTOR = 37;
545    
546      private Decimal(BigDecimal aAmount, int aTimesDivDecimals){
547        fAmount = aAmount;
548        fTimesDivDecimals = aTimesDivDecimals;
549        validateState();
550      }
551      
552      /**
553       Determines if a deserialized file is compatible with this class.
554      
555       Maintainers must change this value if and only if the new version
556       of this class is not compatible with old versions. See Sun docs
557       for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
558       /serialization/spec/version.doc.html> details. </a>
559      
560       Not necessary to include in first version of the class, but
561       included here as a reminder of its importance.
562      */
563      private static final long serialVersionUID = 7526471155622776147L;
564    
565      /**
566       Always treat de-serialization as a full-blown constructor, by
567       validating the final state of the de-serialized object.
568      */  
569      private void readObject(
570        ObjectInputStream aInputStream
571      ) throws ClassNotFoundException, IOException {
572        //always perform the default de-serialization first
573        aInputStream.defaultReadObject();
574        //defensive copy for mutable date field
575        //BigDecimal is not technically immutable, since its non-final
576        fAmount = new BigDecimal( fAmount.toPlainString() );
577        //ensure that object state has not been corrupted or tampered with maliciously
578        validateState();
579      }
580    
581      private void writeObject(ObjectOutputStream aOutputStream) throws IOException {
582        //perform the default serialization for all non-transient, non-static fields
583        aOutputStream.defaultWriteObject();
584      }  
585    
586      private void validateState(){
587        if( fAmount == null ) {
588          throw new IllegalArgumentException("Amount cannot be null");
589        }
590        if (fAmount.scale() < 0){
591          throw new IllegalArgumentException("Amount has scale that is less than zero: " + Util.quote(fAmount.scale()));
592        }
593      }
594      
595      /** Ignores scale: 0 same as 0.00 */
596      private int compareAmount(Decimal aThat){
597        return this.fAmount.compareTo(aThat.fAmount);
598      }
599      
600      private Decimal asDecimal(double aDouble){
601        //this is the recommended way of building a BigDecimal from a double.
602        BigDecimal result =  BigDecimal.valueOf(aDouble);
603        return new Decimal(result);
604      }
605    }