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 * <tools>
47 * <toolbox scope="application">
48 * <tool class="org.apache.velocity.tools.generic.MathTool"/>
49 * </toolbox>
50 * </tools>
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 }