View Javadoc

1   package org.apache.velocity.tools.generic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import org.apache.commons.beanutils.PropertyUtils;
26  import org.apache.velocity.tools.ConversionUtils;
27  import org.apache.velocity.tools.config.DefaultKey;
28  
29  /**
30   * <p>Tool for performing math in Velocity.</p>
31   *
32   * <p>Some things should be noted here:</p>
33   * <ul>
34   * <li>This class does not have methods that take
35   * primitives.  This is simply because Velocity
36   * wraps all primitives for us automagically.</li>
37   *
38   * <li>No null pointer, number format, or divide by zero
39   * exceptions are thrown here.  This is because such exceptions
40   * thrown in template halt rendering.  It should be sufficient
41   * debugging feedback that Velocity will render the reference
42   * literally. (e.g. $math.div(1, 0) renders as '$math.div(1, 0)')</li>
43   * </ul>
44   * <p><pre>
45   * Example tools.xml config:
46   * &lt;tools&gt;
47   *   &lt;toolbox scope="application"&gt;
48   *     &lt;tool class="org.apache.velocity.tools.generic.MathTool"/&gt;
49   *   &lt;/toolbox&gt;
50   * &lt;/tools&gt;
51   * </pre></p>
52   *
53   * @author Nathan Bubna
54   * @author Leon Messerschmidt
55   * @version $Revision: 696463 $ $Date: 2008-09-17 14:39:04 -0700 (Wed, 17 Sep 2008) $
56   */
57  @DefaultKey("math")
58  public class MathTool extends FormatConfig
59  {
60      /* Old non-vararg methods (can be removed once we require Velocity 1.6) */
61  
62      public Number add(Object num1, Object num2)
63      {
64          return add(new Object[] { num1, num2 });
65      }
66  
67      public Number sub(Object num1, Object num2)
68      {
69          return sub(new Object[] { num1, num2 });
70      }
71  
72      public Number mul(Object num1, Object num2)
73      {
74          return mul(new Object[] { num1, num2 });
75      }
76  
77      public Number div(Object num1, Object num2)
78      {
79          return div(new Object[] { num1, num2 });
80      }
81  
82      public Number max(Object num1, Object num2)
83      {
84          return max(new Object[] { num1, num2 });
85      }
86  
87      public Number min(Object num1, Object num2)
88      {
89          return min(new Object[] { num1, num2 });
90      }
91  
92      /**
93       * @param nums the numbers to be added
94       * @return the sum of the numbers or
95       *         <code>null</code> if they're invalid
96       * @see #toNumber
97       */
98      public Number add(Object... nums)
99      {
100         double value = 0;
101         Number[] ns = new Number[nums.length];
102         for (Object num : nums)
103         {
104             Number n = toNumber(num);
105             if (n == null)
106             {
107                 return null;
108             }
109             value += n.doubleValue();
110         }
111         return matchType(value, ns);
112     }
113 
114 
115     /**
116      * @param nums the numbers to be subtracted
117      * @return the difference of the numbers (subtracted in order) or
118      *         <code>null</code> if they're invalid
119      * @see #toNumber
120      */
121     public Number sub(Object... nums)
122     {
123         double value = 0;
124         Number[] ns = new Number[nums.length];
125         for (int i=0; i<nums.length; i++)
126         {
127             Number n = toNumber(nums[i]);
128             if (n == null)
129             {
130                 return null;
131             }
132             if (i == 0)
133             {
134                 value = n.doubleValue();
135             }
136             else
137             {
138                 value -= n.doubleValue();
139             }
140         }
141         return matchType(value, ns);
142     }
143 
144 
145     /**
146      * @param nums the numbers to be multiplied
147      * @return the product of the numbers or
148      *         <code>null</code> if they're invalid
149      * @see #toNumber
150      */
151     public Number mul(Object... nums)
152     {
153         double value = 1;
154         Number[] ns = new Number[nums.length];
155         for (Object num : nums)
156         {
157             Number n = toNumber(num);
158             if (n == null)
159             {
160                 return null;
161             }
162             value *= n.doubleValue();
163         }
164         return matchType(value, ns);
165     }
166 
167 
168     /**
169      * @param nums the numbers to be divided
170      * @return the quotient of the numbers or
171      *         <code>null</code> if they're invalid
172      *         or if any denominator equals zero
173      * @see #toNumber
174      */
175     public Number div(Object... nums)
176     {
177         double value = 0;
178         Number[] ns = new Number[nums.length];
179         for (int i=0; i<nums.length; i++)
180         {
181             Number n = toNumber(nums[i]);
182             if (n == null)
183             {
184                 return null;
185             }
186             if (i == 0)
187             {
188                 value = n.doubleValue();
189             }
190             else
191             {
192                 double denominator = n.doubleValue();
193                 if (denominator == 0.0)
194                 {
195                     return null;
196                 }
197                 value /= denominator;
198             }
199         }
200         return matchType(value, ns);
201     }
202 
203 
204     /**
205      * @param num1 the first number
206      * @param num2 the second number
207      * @return the first number raised to the power of the
208      *         second or <code>null</code> if they're invalid
209      * @see #toNumber
210      */
211     public Number pow(Object num1, Object num2)
212     {
213         Number n1 = toNumber(num1);
214         Number n2 = toNumber(num2);
215         if (n1 == null || n2 == null)
216         {
217             return null;
218         }
219         double value = Math.pow(n1.doubleValue(), n2.doubleValue());
220         return matchType(n1, n2, value);
221     }
222 
223 
224     /**
225      * Does integer division on the int values of the specified numbers.
226      *
227      * <p>So, $math.idiv('5.1',3) will return '1',
228      *    and $math.idiv(6,'3.9') will return '2'.</p>
229      *
230      * @param num1 the first number
231      * @param num2 the second number
232      * @return the result of performing integer division
233      *         on the operands.
234      * @see #toInteger
235      */
236     public Integer idiv(Object num1, Object num2)
237     {
238         Number n1 = toNumber(num1);
239         Number n2 = toNumber(num2);
240         if (n1 == null || n2 == null || n2.intValue() == 0)
241         {
242             return null;
243         }
244         int value = n1.intValue() / n2.intValue();
245         return Integer.valueOf(value);
246     }
247 
248 
249     /**
250      * Does integer modulus on the int values of the specified numbers.
251      *
252      * <p>So, $math.mod('5.1',3) will return '2',
253      *    and $math.mod(6,'3.9') will return '0'.</p>
254      *
255      * @param num1 the first number
256      * @param num2 the second number
257      * @return the result of performing integer modulus
258      *         on the operands.
259      * @see #toInteger
260      */
261     public Integer mod(Object num1, Object num2)
262     {
263         Number n1 = toNumber(num1);
264         Number n2 = toNumber(num2);
265         if (n1 == null || n2 == null || n2.intValue() == 0)
266         {
267             return null;
268         }
269         int value = n1.intValue() % n2.intValue();
270         return Integer.valueOf(value);
271     }
272 
273 
274     /**
275      * @param nums the numbers to be searched
276      * @return the largest of the numbers or
277      *         <code>null</code> if they're invalid
278      * @see #toNumber
279      */
280     public Number max(Object... nums)
281     {
282         double value = Double.MIN_VALUE;
283         Number[] ns = new Number[nums.length];
284         for (Object num : nums)
285         {
286             Number n = toNumber(num);
287             if (n == null)
288             {
289                 return null;
290             }
291             value = Math.max(value, n.doubleValue());
292         }
293         return matchType(value, ns);
294     }
295 
296 
297     /**
298      * @param nums the numbers to be searched
299      * @return the smallest of the numbers or
300      *         <code>null</code> if they're invalid
301      * @see #toNumber
302      */
303     public Number min(Object... nums)
304     {
305         double value = Double.MAX_VALUE;
306         Number[] ns = new Number[nums.length];
307         for (Object num : nums)
308         {
309             Number n = toNumber(num);
310             if (n == null)
311             {
312                 return null;
313             }
314             value = Math.min(value, n.doubleValue());
315         }
316         return matchType(value, ns);
317     }
318 
319 
320     /**
321      * @param num the number
322      * @return the absolute value of the number or
323      *         <code>null</code> if it's invalid
324      * @see #toDouble
325      */
326     public Number abs(Object num)
327     {
328         Number n = toNumber(num);
329         if (n == null)
330         {
331             return null;
332         }
333         double value = Math.abs(n.doubleValue());
334         return matchType(n, value);
335     }
336 
337 
338     /**
339      * @param num the number
340      * @return the smallest integer that is not
341      *         less than the given number
342      */
343     public Integer ceil(Object num)
344     {
345         Number n = toNumber(num);
346         if (n == null)
347         {
348             return null;
349         }
350         return Integer.valueOf((int)Math.ceil(n.doubleValue()));
351     }
352 
353 
354     /**
355      * @param num the number
356      * @return the integer portion of the number
357      */
358     public Integer floor(Object num)
359     {
360         Number n = toNumber(num);
361         if (n == null)
362         {
363             return null;
364         }
365         return Integer.valueOf((int)Math.floor(n.doubleValue()));
366     }
367 
368 
369     /**
370      * Rounds a number to the nearest whole Integer
371      *
372      * @param num the number to round
373      * @return the number rounded to the nearest whole Integer
374      *         or <code>null</code> if it's invalid
375      * @see java.lang.Math#rint(double)
376      */
377     public Integer round(Object num)
378     {
379         Number n = toNumber(num);
380         if (n == null)
381         {
382             return null;
383         }
384         return Integer.valueOf((int)Math.rint(n.doubleValue()));
385     }
386 
387 
388     /**
389      * Rounds a number to the specified number of decimal places.
390      * This is particulary useful for simple display formatting.
391      * If you want to round an number to the nearest integer, it
392      * is better to use {@link #round}, as that will return
393      * an {@link Integer} rather than a {@link Double}.
394      *
395      * @param decimals the number of decimal places
396      * @param num the number to round
397      * @return the value rounded to the specified number of
398      *         decimal places or <code>null</code> if it's invalid
399      * @see #toNumber
400      */
401     public Double roundTo(Object decimals, Object num)
402     {
403         Number i = toNumber(decimals);
404         Number d = toNumber(num);
405         if (i == null || d == null)
406         {
407             return null;
408         }
409         //ok, go ahead and do the rounding
410         int places = i.intValue();
411         double value = d.doubleValue();
412         int delta = 10;
413         for(int j=1;j<places;j++) {
414             delta *= 10;
415         }
416         return new Double((double)Math.round(value*delta)/delta);
417     }
418 
419 
420     /**
421      * @return a pseudo-random {@link Double} greater
422      *          than or equal to 0.0 and less than 1.0
423      * @see Math#random()
424      */
425     public Double getRandom()
426     {
427         return new Double(Math.random());
428     }
429 
430 
431     /**
432      * This returns a random {@link Number} within the
433      * specified range.  The returned value will be
434      * greater than or equal to the first number
435      * and less than the second number.  If both arguments
436      * are whole numbers then the returned number will
437      * also be, otherwise a {@link Double} will
438      * be returned.
439      *
440      * @param num1 the first number
441      * @param num2 the second number
442      * @return a pseudo-random {@link Number} greater than
443      *         or equal to the first number and less than
444      *         the second
445      * @see Math#random()
446      */
447     public Number random(Object num1, Object num2)
448     {
449         Number n1 = toNumber(num1);
450         Number n2 = toNumber(num2);
451         if (n1 == null || n2 == null)
452         {
453             return null;
454         }
455 
456         double diff = n2.doubleValue() - n1.doubleValue();
457         // multiply the difference by a pseudo-random double from
458         // 0.0 to 1.0, round to the nearest int, and add the first
459         // value to the random int and return as an Integer
460         double random = (diff * Math.random()) + n1.doubleValue();
461 
462         // check if either of the args were floating points
463         String in = n1.toString() + n2.toString();
464         if (in.indexOf('.') < 0)
465         {
466             // args were whole numbers, so return the same
467             return matchType(n1, n2, Math.floor(random));
468         }
469         // one of the args was a floating point,
470         // so don't floor the result
471         return new Double(random);
472     }
473 
474 
475     // --------------- public type conversion methods ---------
476 
477     /**
478      * Converts an object with a numeric value into an Integer
479      * Valid formats are {@link Number} or a {@link String}
480      * representation of a number
481      *
482      * @param num the number to be converted
483      * @return a {@link Integer} representation of the number
484      *         or <code>null</code> if it's invalid
485      */
486     public Integer toInteger(Object num)
487     {
488         Number n = toNumber(num);
489         if (n == null)
490         {
491             return null;
492         }
493         return Integer.valueOf(n.intValue());
494     }
495 
496 
497     /**
498      * Converts an object with a numeric value into a Double
499      * Valid formats are {@link Number} or a {@link String}
500      * representation of a number
501      *
502      * @param num the number to be converted
503      * @return a {@link Double} representation of the number
504      *         or <code>null</code> if it's invalid
505      */
506     public Double toDouble(Object num)
507     {
508         Number n = toNumber(num);
509         if (n == null)
510         {
511             return null;
512         }
513         return new Double(n.doubleValue());
514     }
515 
516 
517     /**
518      * Converts an object with a numeric value into a Number
519      * Valid formats are {@link Number} or a {@link String}
520      * representation of a number.  Note that this does not
521      * handle localized number formats.  Use the {@link NumberTool}
522      * to handle such conversions.
523      *
524      * @param num the number to be converted
525      * @return a {@link Number} representation of the number
526      *         or <code>null</code> if it's invalid
527      */
528     public Number toNumber(Object num)
529     {
530         return ConversionUtils.toNumber(num, getFormat(), getLocale());
531     }
532 
533 
534     // --------------------------- protected methods ------------------
535 
536     /**
537      * @see #matchType(double,Number...)
538      */
539     protected Number matchType(Number in, double out)
540     {
541         return matchType(out, new Number[] { in });
542     }
543 
544     /**
545      * @see #matchType(double,Number...)
546      */
547     protected Number matchType(Number in1, Number in2, double out)
548     {
549         return matchType(out, new Number[] { in1, in2 });
550     }
551 
552     /**
553      * Takes the original argument(s) and returns the resulting value as
554      * an instance of the best matching type (Integer, Long, or Double).
555      * If either an argument or the result is not an integer (i.e. has no
556      * decimal when rendered) the result will be returned as a Double.
557      * If not and the result is < -2147483648 or > 2147483647, then a
558      * Long will be returned.  Otherwise, an Integer will be returned.
559      */
560     protected Number matchType(double out, Number... in)
561     {
562         //NOTE: if we just checked class types, we could miss custom
563         //      extensions of java.lang.Number, and if we only checked
564         //      the mathematical value, $math.div('3.0', 1) would render
565         //      as '3'.  To get the expected result, we check what we're
566         //      concerned about: the rendered string.
567 
568         // first check if the result is even a whole number
569         boolean isIntegral = (Math.rint(out) == out);
570         if (isIntegral)
571         {
572             for (Number n : in)
573             {
574                 if (n == null)
575                 {
576                     break;
577                 }
578                 else if (hasFloatingPoint(n.toString()))
579                 {
580                     isIntegral = false;
581                     break;
582                 }
583             }
584         }
585 
586         if (!isIntegral)
587         {
588             return new Double(out);
589         }
590         else if (out > Integer.MAX_VALUE || out < Integer.MIN_VALUE)
591         {
592             return Long.valueOf((long)out);
593         }
594         else
595         {
596             return Integer.valueOf((int)out);
597         }
598     }
599 
600     protected boolean hasFloatingPoint(String value)
601     {
602         return value.indexOf('.') >= 0;
603     }
604 
605     @Deprecated
606     protected Number parseNumber(String value)
607     {
608         // check for the floating point
609         if (!hasFloatingPoint(value))
610         {
611             // check for large numbers
612             long i = Long.valueOf(value).longValue();
613             if (i > Integer.MAX_VALUE || i < Integer.MIN_VALUE)
614             {
615                 return Long.valueOf(i);
616             }
617             else
618             {
619                 return Integer.valueOf((int)i);
620             }
621         }
622         else
623         {
624             return new Double(value);
625         }
626     }
627 
628 
629 
630     // ------------------------- Aggregation methods ------------------
631 
632     /**
633      * Get the sum of the values from a list
634      *
635      * @param collection  A collection containing Java beans
636      * @param field A Java Bean field for the objects in <i>collection</i> that
637      *              will return a number.
638      * @return The sum of the values in <i>collection</i>.
639      */
640     public Number getTotal(Collection collection, String field)
641     {
642         if (collection == null || field == null)
643         {
644             return null;
645         }
646         double result = 0;
647         // hold the first number and use it to match return type
648         Number first = null;
649         try
650         {
651             for (Iterator i = collection.iterator(); i.hasNext();)
652             {
653                 Object property = PropertyUtils.getProperty(i.next(), field);
654                 Number value = toNumber(property);
655                 // skip over nulls (i.e. treat them as 0)
656                 if (value != null)
657                 {
658                     if (first == null)
659                     {
660                         first = value;
661                     }
662                     result += value.doubleValue();
663                 }
664             }
665             return matchType(first, result);
666         }
667         catch (Exception e)
668         {
669             return null;
670         }
671     }
672 
673     /**
674      * Get the average of the values from a list
675      *
676      * @param collection  A collection containing Java beans
677      * @param field A Java Bean field for the objects in <i>collection</i> that
678      *              will return a number.
679      * @return The average of the values in <i>collection</i>.
680      */
681     public Number getAverage(Collection collection, String field)
682     {
683         Number result = getTotal(collection, field);
684         if (result == null)
685         {
686             return null;
687         }
688         double avg = result.doubleValue() / collection.size();
689         return matchType(result, avg);
690     }
691 
692     /**
693      * Get the sum of the values from a list
694      *
695      * @param array  An array containing Java beans
696      * @param field A Java Bean field for the objects in <i>array</i> that
697      *              will return a number.
698      * @return The sum of the values in <i>array</i>.
699      */
700     public Number getTotal(Object[] array, String field)
701     {
702         return getTotal(Arrays.asList(array), field);
703     }
704 
705     /**
706      * Get the sum of the values from a list
707      *
708      * @param array  A collection containing Java beans
709      * @param field A Java Bean field for the objects in <i>array</i> that
710      *      will return a number.
711      * @return The sum of the values in <i>array</i>.
712      */
713     public Number getAverage(Object[] array, String field)
714     {
715         return getAverage(Arrays.asList(array), field);
716     }
717 
718     /**
719      * Get the sum of the values
720      *
721      * @param collection  A collection containing numeric values
722      * @return The sum of the values in <i>collection</i>.
723      */
724     public Number getTotal(Collection collection)
725     {
726         if (collection == null)
727         {
728             return null;
729         }
730 
731         double result = 0;
732         // grab the first number and use it to match return type
733         Number first = null;
734         for (Iterator i = collection.iterator(); i.hasNext();)
735         {
736             Number value = toNumber(i.next());
737             if (value == null)
738             {
739                 //FIXME? or should we ignore this and keep adding?
740                 return null;
741             }
742             if (first ==  null)
743             {
744                 first = value;
745             }
746             result += value.doubleValue();
747         }
748         return matchType(first, result);
749     }
750 
751     /**
752      * Get the average of the values
753      *
754      * @param collection  A collection containing number values
755      * @return The average of the values in <i>collection</i>.
756      */
757     public Number getAverage(Collection collection)
758     {
759         Number result = getTotal(collection);
760         if (result == null)
761         {
762             return null;
763         }
764         double avg = result.doubleValue() / collection.size();
765         return matchType(result, avg);
766     }
767 
768     /**
769      * Get the sum of the values
770      *
771      * @param array  An array containing number values
772      * @return The sum of the values in <i>array</i>.
773      */
774     public Number getTotal(Object... array)
775     {
776         return getTotal(Arrays.asList(array));
777     }
778 
779     /**
780      * Get the average of the values
781      *
782      * @param array  An array containing number values
783      * @return The sum of the values in <i>array</i>.
784      */
785     public Number getAverage(Object... array)
786     {
787         return getAverage(Arrays.asList(array));
788     }
789 
790     /**
791      * Get the sum of the values
792      *
793      * @param values The list of double values to add up.
794      * @return The sum of the arrays
795      */
796     public Number getTotal(double... values)
797     {
798         if (values == null)
799         {
800             return null;
801         }
802 
803         double result = 0;
804         for (int i = 0; i < values.length; i++)
805         {
806             result += values[i];
807         }
808         return new Double(result);
809     }
810 
811     /**
812      * Get the average of the values in an array of double values
813      *
814      * @param values The list of double values
815      * @return The average of the array of values
816      */
817     public Number getAverage(double... values)
818     {
819         Number total = getTotal(values);
820         if (total == null)
821         {
822             return null;
823         }
824         return new Double(total.doubleValue() / values.length);
825     }
826 
827     /**
828      * Get the sum of the values
829      *
830      * @param values The list of long values to add up.
831      * @return The sum of the arrays
832      */
833     public Number getTotal(long... values)
834     {
835         if (values == null)
836         {
837             return null;
838         }
839 
840         long result = 0;
841         for (int i = 0; i < values.length; i++)
842         {
843             result += values[i];
844         }
845         return Long.valueOf(result);
846     }
847 
848     /**
849      * Get the average of the values in an array of long values
850      *
851      * @param values The list of long values
852      * @return The average of the array of values
853      */
854     public Number getAverage(long... values)
855     {
856         Number total = getTotal(values);
857         if (total == null)
858         {
859             return null;
860         }
861         double avg = total.doubleValue() / values.length;
862         return matchType(total, avg);
863     }
864 
865 }