org.apache.velocity.tools.generic.MathTool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of velocity-tools-generic Show documentation
Show all versions of velocity-tools-generic Show documentation
Generic tools that can be used in any context.
PLEASE NOTE: this is a temporary fork to unblock projects migrating to Jakarta,
but I won't continue maintaining it in the future as the Velocity team doesn't
understand the value of Jakarta. I strongly suggest you plan a switch to a more
modern template engine such as Thymeleaf.
The newest version!
package org.apache.velocity.tools.generic; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import org.apache.commons.beanutils.PropertyUtils; import org.apache.velocity.tools.ConversionUtils; import org.apache.velocity.tools.Scope; import org.apache.velocity.tools.config.DefaultKey; import org.apache.velocity.tools.config.ValidScope; /** *
if they're invalid * @see #toNumber */ public Number sub(Object... nums) { double value = 0; Number[] ns = new Number[nums.length]; for (int i=0; iTool for performing math in Velocity.
* *Some things should be noted here:
**
*- This class does not have methods that take * primitives. This is simply because Velocity * wraps all primitives for us automagically.
* *- No null pointer, number format, or divide by zero * exceptions are thrown here. This is because such exceptions * thrown in template halt rendering. It should be sufficient * debugging feedback that Velocity will render the reference * literally. (e.g. $math.div(1, 0) renders as '$math.div(1, 0)')
*Example tools.xml config:
** <tools> * <toolbox scope="application"> * <tool class="org.apache.velocity.tools.generic.MathTool"/> * </toolbox> * </tools> ** * @author Nathan Bubna * @author Leon Messerschmidt * @version $Revision$ $Date$ */ @DefaultKey("math") @ValidScope(Scope.APPLICATION) public class MathTool extends FormatConfig implements Serializable { private static final long serialVersionUID = 3207828679772113341L; public Number add(Object num1, Object num2) { return add(new Object[] { num1, num2 }); } public Number sub(Object num1, Object num2) { return sub(new Object[] { num1, num2 }); } public Number mul(Object num1, Object num2) { return mul(new Object[] { num1, num2 }); } public Number div(Object num1, Object num2) { return div(new Object[] { num1, num2 }); } public Number max(Object num1, Object num2) { return max(new Object[] { num1, num2 }); } public Number min(Object num1, Object num2) { return min(new Object[] { num1, num2 }); } public Number bitwiseOr(Object num1, Object num2) { return bitwiseOr(new Object[] { num1, num2 }); } public Number bitwiseAnd(Object num1, Object num2) { return bitwiseAnd(new Object[] { num1, num2 }); } public Number bitwiseXOr(Object num1, Object num2) { return bitwiseXOr(new Object[] { num1, num2 }); } /** * @param nums the numbers to be added * @return the sum of the numbers or *null
if they're invalid * @see #toNumber */ public Number add(Object... nums) { double value = 0; Number[] ns = new Number[nums.length]; for (int i=0; inull null if they're invalid * @see #toNumber */ public Number mul(Object... nums) { double value = 1; Number[] ns = new Number[nums.length]; for (int i=0; i null if they're invalid * or if any denominator equals zero * @see #toNumber */ public Number div(Object... nums) { double value = 0; Number[] ns = new Number[nums.length]; for (int i=0; i null if they're invalid * @see #toNumber */ public Number pow(Object num1, Object num2) { Number n1 = toNumber(num1); Number n2 = toNumber(num2); if (n1 == null || n2 == null) { return null; } double value = Math.pow(n1.doubleValue(), n2.doubleValue()); return matchType(n1, n2, value); } /** * Does integer division on the int values of the specified numbers. * * So, $math.idiv('5.1',3) will return '1', * and $math.idiv(6,'3.9') will return '2'.
* * @param num1 the first number * @param num2 the second number * @return the result of performing integer division * on the operands. * @see #toInteger */ public Integer idiv(Object num1, Object num2) { Number n1 = toNumber(num1); Number n2 = toNumber(num2); if (n1 == null || n2 == null || n2.intValue() == 0) { return null; } int value = n1.intValue() / n2.intValue(); return Integer.valueOf(value); } /** * Does integer modulus on the int values of the specified numbers. * *So, $math.mod('5.1',3) will return '2', * and $math.mod(6,'3.9') will return '0'.
* * @param num1 the first number * @param num2 the second number * @return the result of performing integer modulus * on the operands. * @see #toInteger */ public Integer mod(Object num1, Object num2) { Number n1 = toNumber(num1); Number n2 = toNumber(num2); if (n1 == null || n2 == null || n2.intValue() == 0) { return null; } int value = n1.intValue() % n2.intValue(); return Integer.valueOf(value); } /** * Bitwise Or * @param nums the numbers to be Or'ed * @return the bitwise Or of the numbers or *null
if they're invalid * @see #toInteger */ public Number bitwiseOr(Object... nums) { long value = 0; Number[] ns = new Number[nums.length]; for (int i=0; inull if they're invalid * @see #toInteger */ public Number bitwiseAnd(Object... nums) { long value = -1; Number[] ns = new Number[nums.length]; for (int i=0; i null if they're invalid * @see #toInteger */ public Number bitwiseXOr(Object... nums) { long value = 0; Number[] ns = new Number[nums.length]; for (int i=0; i null if they're invalid * @see #toNumber */ public Number max(Object... nums) { double value = Double.MIN_VALUE; Number[] ns = new Number[nums.length]; for (int i=0; i null if they're invalid * @see #toNumber */ public Number min(Object... nums) { double value = Double.MAX_VALUE; Number[] ns = new Number[nums.length]; for (int i=0; i null if it's invalid * @see #toDouble */ public Number abs(Object num) { Number n = toNumber(num); if (n == null) { return null; } double value = Math.abs(n.doubleValue()); return matchType(n, value); } /** * @param num the number * @return the smallest integer that is not * less than the given number */ public Integer ceil(Object num) { Number n = toNumber(num); if (n == null) { return null; } return Integer.valueOf((int)Math.ceil(n.doubleValue())); } /** * @param num the number * @return the integer portion of the number */ public Integer floor(Object num) { Number n = toNumber(num); if (n == null) { return null; } return Integer.valueOf((int)Math.floor(n.doubleValue())); } /** * Rounds a number to the nearest whole Integer * * @param num the number to round * @return the number rounded to the nearest whole Integer * or null
if it's invalid * @see java.lang.Math#rint(double) */ public Integer round(Object num) { Number n = toNumber(num); if (n == null) { return null; } return Integer.valueOf((int)Math.rint(n.doubleValue())); } /** * Rounds a number to the specified number of decimal places. * This is particulary useful for simple display formatting. * If you want to round an number to the nearest integer, it * is better to use {@link #round}, as that will return * an {@link Integer} rather than a {@link Double}. * * @param decimals the number of decimal places * @param num the number to round * @return the value rounded to the specified number of * decimal places ornull
if it's invalid * @see #toNumber */ public Double roundTo(Object decimals, Object num) { Number i = toNumber(decimals); Number d = toNumber(num); if (i == null || d == null) { return null; } //ok, go ahead and do the rounding int places = i.intValue(); double value = d.doubleValue(); int delta = 10; for(int j=1;jnull if it's invalid * @deprecated use {@link NumberTool#toNumber(Object)}.intValue() */ @Deprecated public Integer toInteger(Object num) { Number n = toNumber(num); if (n == null) { return null; } return Integer.valueOf(n.intValue()); } /** * Converts an object with a numeric value into a Long integer * Valid formats are {@link Number} or a {@link String} * representation of a number * * @param num the number to be converted * @return a {@link Long} integer representation of the number * or null
if it's invalid * @deprecated use {@link NumberTool#toNumber(Object)}.longValue() */ @Deprecated public Long toLong(Object num) { Number n = toNumber(num); if (n == null) { return null; } return Long.valueOf(n.longValue()); } /** * Converts an object with a numeric value into a Double * Valid formats are {@link Number} or a {@link String} * representation of a number * * @param num the number to be converted * @return a {@link Double} representation of the number * ornull
if it's invalid * @deprecated use {@link NumberTool#toNumber(Object)}.doubleValue() */ @Deprecated public Double toDouble(Object num) { Number n = toNumber(num); if (n == null) { return null; } return new Double(n.doubleValue()); } /** * Converts an object with a numeric value into a Number * Valid formats are {@link Number} or a {@link String} * representation of a number. Note that this does not * handle localized number formats. Use the {@link NumberTool} * to handle such conversions. * * @param num the number to be converted * @return a {@link Number} representation of the number * ornull
if it's invalid * @deprecated use {@link NumberTool#toNumber(Object)} */ @Deprecated public Number toNumber(Object num) { return ConversionUtils.toNumber(num, getFormat(), getLocale()); } // --------------------------- protected methods ------------------ /** * @param in instance of wanted Number class * @param out input number * @return wanted Number * @see #matchType(double,Number...) */ protected Number matchType(Number in, double out) { return matchType(out, new Number[] { in }); } /** * @param in1 instance #1 of wanted Number class * @param in2 instance #1 of wanted Number class * @param out input number * @return wanted Number * @see #matchType(double,Number...) */ protected Number matchType(Number in1, Number in2, double out) { return matchType(out, new Number[] { in1, in2 }); } /** * Takes the original argument(s) and returns the resulting value as * an instance of the best matching type (Integer, Long, or Double). * If either an argument or the result is not an integer (i.e. has no * decimal when rendered) the result will be returned as a Double. * If not and the result is < -2147483648 or > 2147483647, then a * Long will be returned. Otherwise, an Integer will be returned. * @param out target number value * @param in wanted Number classes * @return wanted Number */ protected Number matchType(double out, Number... in) { //NOTE: if we just checked class types, we could miss custom // extensions of java.lang.Number, and if we only checked // the mathematical value, $math.div('3.0', 1) would render // as '3'. To get the expected result, we check what we're // concerned about: the rendered string. // first check if the result is even a whole number boolean isIntegral = (Math.rint(out) == out); if (isIntegral) { for (Number n : in) { if (n == null) { break; } else if (hasFloatingPoint(n.toString())) { isIntegral = false; break; } } } if (!isIntegral) { return new Double(out); } else if (out > Integer.MAX_VALUE || out < Integer.MIN_VALUE) { return Long.valueOf((long)out); } else { return Integer.valueOf((int)out); } } /** * @param value target value * @return wether it contains a decimal separator (non locale-aware for now - TODO) */ protected boolean hasFloatingPoint(String value) { return value.indexOf('.') >= 0; } // ------------------------- Aggregation methods ------------------ /** * Get the sum of the values from a list * * @param collection A collection containing Java beans * @param field A Java Bean field for the objects in collection that * will return a number. * @return The sum of the values in collection. */ public Number getTotal(Collection collection, String field) { if (collection == null || field == null) { return null; } double result = 0; // hold the first number and use it to match return type Number first = null; try { for (Iterator i = collection.iterator(); i.hasNext();) { Object property = PropertyUtils.getProperty(i.next(), field); Number value = toNumber(property); // skip over nulls (i.e. treat them as 0) if (value != null) { if (first == null) { first = value; } result += value.doubleValue(); } } return matchType(first, result); } catch (Exception e) { return null; } } /** * Get the average of the values from a list * * @param collection A collection containing Java beans * @param field A Java Bean field for the objects in collection that * will return a number. * @return The average of the values in collection. */ public Number getAverage(Collection collection, String field) { Number result = getTotal(collection, field); if (result == null) { return null; } double avg = result.doubleValue() / collection.size(); return matchType(result, avg); } /** * Get the sum of the values from a list * * @param array An array containing Java beans * @param field A Java Bean field for the objects in array that * will return a number. * @return The sum of the values in array. */ public Number getTotal(Object[] array, String field) { return getTotal(Arrays.asList(array), field); } /** * Get the sum of the values from a list * * @param array A collection containing Java beans * @param field A Java Bean field for the objects in array that * will return a number. * @return The sum of the values in array. */ public Number getAverage(Object[] array, String field) { return getAverage(Arrays.asList(array), field); } /** * Get the sum of the values * * @param collection A collection containing numeric values * @return The sum of the values in collection. */ public Number getTotal(Collection collection) { if (collection == null) { return null; } double result = 0; // grab the first number and use it to match return type Number first = null; for (Iterator i = collection.iterator(); i.hasNext();) { Number value = toNumber(i.next()); if (value == null) { //FIXME? or should we ignore this and keep adding? return null; } if (first == null) { first = value; } result += value.doubleValue(); } return matchType(first, result); } /** * Get the average of the values * * @param collection A collection containing number values * @return The average of the values in collection. */ public Number getAverage(Collection collection) { Number result = getTotal(collection); if (result == null) { return null; } double avg = result.doubleValue() / collection.size(); return matchType(result, avg); } /** * Get the sum of the values * * @param array An array containing number values * @return The sum of the values in array. */ public Number getTotal(Object... array) { return getTotal(Arrays.asList(array)); } /** * Get the average of the values * * @param array An array containing number values * @return The sum of the values in array. */ public Number getAverage(Object... array) { return getAverage(Arrays.asList(array)); } /** * Get the sum of the values * * @param values The list of double values to add up. * @return The sum of the arrays */ public Number getTotal(double... values) { if (values == null) { return null; } double result = 0; for (double value : values) { result += value; } return new Double(result); } /** * Get the average of the values in an array of double values * * @param values The list of double values * @return The average of the array of values */ public Number getAverage(double... values) { Number total = getTotal(values); if (total == null) { return null; } return new Double(total.doubleValue() / values.length); } /** * Get the sum of the values * * @param values The list of long values to add up. * @return The sum of the arrays */ public Number getTotal(long... values) { if (values == null) { return null; } long result = 0; for (long value : values) { result += value; } return Long.valueOf(result); } /** * Get the average of the values in an array of long values * * @param values The list of long values * @return The average of the array of values */ public Number getAverage(long... values) { Number total = getTotal(values); if (total == null) { return null; } double avg = total.doubleValue() / values.length; return matchType(total, avg); } }