Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2012 Udo Klimaschewski
*
* http://UdoJava.com/
* http://about.me/udo.klimaschewski
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.udojava.evalex;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
/**
*
EvalEx - Java Expression Evaluator
*
*
Introduction
EvalEx is a handy expression evaluator for Java, that
* allows to evaluate simple mathematical and boolean expressions.
* Key Features:
*
*
Uses BigDecimal for calculation and result
*
Single class implementation, very compact
*
No dependencies to external libraries
*
Precision and rounding mode can be set
*
Supports variables
*
Standard boolean and mathematical operators
*
Standard basic mathematical and boolean functions
*
Custom functions and operators can be added at runtime
*
*
*
Examples
*
*
* BigDecimal result = null;
*
* Expression expression = new Expression("1+1/3");
* result = expression.eval():
* expression.setPrecision(2);
* result = expression.eval():
*
* result = new Expression("(3.4 + -4.1)/2").eval();
*
* result = new Expression("SQRT(a^2 + b^2").with("a","2.4").and("b","9.253").eval();
*
* BigDecimal a = new BigDecimal("2.4");
* BigDecimal b = new BigDecimal("9.235");
* result = new Expression("SQRT(a^2 + b^2").with("a",a).and("b",b).eval();
*
* result = new Expression("2.4/PI").setPrecision(128).setRoundingMode(RoundingMode.UP).eval();
*
* result = new Expression("random() > 0.5").eval();
*
* result = new Expression("not(x<7 || sqrt(max(x,9)) <= 3))").with("x","22.9").eval();
*
*
*
*
Supported Operators
*
*
*
Mathematical Operators
*
*
*
Operator
*
Description
*
*
*
+
*
Additive operator
*
*
*
-
*
Subtraction operator
*
*
*
*
*
Multiplication operator
*
*
*
/
*
Division operator
*
*
*
%
*
Remainder operator (Modulo)
*
*
*
^
*
Power operator
*
*
*
*
*
*
Boolean Operators*
*
*
*
Operator
*
Description
*
*
*
=
*
Equals
*
*
*
==
*
Equals
*
*
*
!=
*
Not equals
*
*
*
<>
*
Not equals
*
*
*
<
*
Less than
*
*
*
<=
*
Less than or equal to
*
*
*
>
*
Greater than
*
*
*
>=
*
Greater than or equal to
*
*
*
&&
*
Boolean and
*
*
*
||
*
Boolean or
*
*
* *Boolean operators result always in a BigDecimal value of 1 or 0 (zero). Any
* non-zero value is treated as a _true_ value. Boolean _not_ is implemented by
* a function.
*
Supported Functions
*
*
*
Function*
*
Description
*
*
*
NOT(expression)
*
Boolean negation, 1 (means true) if the expression is not zero
*
*
*
IF(condition,value_if_true,value_if_false)
*
Returns one value if the condition evaluates to true or the other if it
* evaluates to false
*
*
*
RANDOM()
*
Produces a random number between 0 and 1
*
*
*
MIN(e1,e2, ...)
*
Returns the smallest of the given expressions
*
*
*
MAX(e1,e2, ...)
*
Returns the biggest of the given expressions
*
*
*
ABS(expression)
*
Returns the absolute (non-negative) value of the expression
*
*
*
ROUND(expression,precision)
*
Rounds a value to a certain number of digits, uses the current rounding
* mode
*
*
*
FLOOR(expression)
*
Rounds the value down to the nearest integer
*
*
*
CEILING(expression)
*
Rounds the value up to the nearest integer
*
*
*
LOG(expression)
*
Returns the natural logarithm (base e) of an expression
*
*
*
LOG10(expression)
*
Returns the common logarithm (base 10) of an expression
*
*
*
SQRT(expression)
*
Returns the square root of an expression
*
*
*
SIN(expression)
*
Returns the trigonometric sine of an angle (in degrees)
*
*
*
COS(expression)
*
Returns the trigonometric cosine of an angle (in degrees)
*
*
*
TAN(expression)
*
Returns the trigonometric tangens of an angle (in degrees)
*
*
*
ASIN(expression)
*
Returns the angle of asin (in degrees)
*
*
*
ACOS(expression)
*
Returns the angle of acos (in degrees)
*
*
*
ATAN(expression)
*
Returns the angle of atan (in degrees)
*
*
*
ATAN2(y,x)
*
Returns the angle of atan2 (in degrees)
*
*
*
SINH(expression)
*
Returns the hyperbolic sine of a value
*
*
*
COSH(expression)
*
Returns the hyperbolic cosine of a value
*
*
*
TANH(expression)
*
Returns the hyperbolic tangens of a value
*
*
*
SEC(expression)
*
Returns the secant (in degrees)
*
*
*
CSC(expression)
*
Returns the cosecant (in degrees)
*
*
*
SECH(expression)
*
Returns the hyperbolic secant (in degrees)
*
*
*
CSCH(expression)
*
Returns the hyperbolic cosecant (in degrees)
*
*
*
COT(expression)
*
Returns the trigonometric cotangens of an angle (in degrees)
*
*
*
ACOT(expression)
*
Returns the angle of acot (in degrees)
*
*
*
COTH(expression)
*
Returns the hyperbolic cotangens of a value
*
*
*
ASINH(expression)
*
Returns the angle of hyperbolic sine (in degrees)
*
*
*
ACOSH(expression)
*
Returns the angle of hyperbolic cosine (in degrees)
*
*
*
ATANH(expression)
*
Returns the angle of hyperbolic tangens of a value
*
*
RAD(expression)
*
Converts an angle measured in degrees to an approximately equivalent
* angle measured in radians
*
*
*
DEG(expression)
*
Converts an angle measured in radians to an approximately equivalent
* angle measured in degrees
*
*
* *Functions names are case insensitive.
*
Supported Constants
*
*
*
Constant
*
Description
*
*
*
e
*
The value of e, exact to 70 digits
*
*
*
PI
*
The value of PI, exact to 100 digits
*
*
*
TRUE
*
The value one
*
*
*
FALSE
*
The value zero
*
*
*
*
Add Custom Operators
*
* Custom operators can be added easily, simply create an instance of
* `Expression.Operator` and add it to the expression. Parameters are the
* operator string, its precedence and if it is left associative. The operators
* `eval()` method will be called with the BigDecimal values of the operands.
* All existing operators can also be overridden.
* For example, add an operator `x >> n`, that moves the decimal point of _x_
* _n_ digits to the right:
*
*
*
* Adding custom functions is as easy as adding custom operators. Create an
* instance of `Expression.Function`and add it to the expression. Parameters are
* the function name and the count of required parameters. The functions
* `eval()` method will be called with a list of the BigDecimal parameters. All
* existing functions can also be overridden.
* A -1 as the number of parameters denotes a variable number of arguments.
* For example, add a function `average(a,b,c)`, that will calculate the average
* value of a, b and c:
*
*
*
* The software is licensed under the MIT Open Source license (see LICENSE
* file).
*
*
The *power of* operator (^) implementation was copied from [Stack
* Overflow
* ](http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power
* -on-bigdecimal-in-java) Thanks to Gene Marin
*
The SQRT() function implementation was taken from the book [The Java
* Programmers Guide To numerical
* Computing](http://www.amazon.de/Java-Number-Cruncher
* -Programmers-Numerical/dp/0130460419) (Ronald Mak, 2002)
*
*
* @author Udo Klimaschewski (http://about.me/udo.klimaschewski)
*/
public class Expression {
/**
* Unary operators precedence: + and - as prefix
*/
public static final int OPERATOR_PRECEDENCE_UNARY = 60;
/**
* Equality operators precedence: =, ==, !=. <>
*/
public static final int OPERATOR_PRECEDENCE_EQUALITY = 7;
/**
* Comparative operators precedence: <,>,<=,>=
*/
public static final int OPERATOR_PRECEDENCE_COMPARISON = 10;
/**
* Or operator precedence: ||
*/
public static final int OPERATOR_PRECEDENCE_OR = 2;
/**
* And operator precedence: &&
*/
public static final int OPERATOR_PRECEDENCE_AND = 4;
/**
* Power operator precedence: ^
*/
public static final int OPERATOR_PRECEDENCE_POWER = 40;
/**
* Multiplicative operators precedence: *,/,%
*/
public static final int OPERATOR_PRECEDENCE_MULTIPLICATIVE = 30;
/**
* Additive operators precedence: + and -
*/
public static final int OPERATOR_PRECEDENCE_ADDITIVE = 20;
/**
* Definition of PI as a constant, can be used in expressions as variable.
*/
public static final BigDecimal PI = new BigDecimal(
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679");
/**
* Definition of e: "Euler's number" as a constant, can be used in expressions as variable.
*/
public static final BigDecimal e = new BigDecimal(
"2.71828182845904523536028747135266249775724709369995957496696762772407663");
/**
* The {@link MathContext} to use for calculations.
*/
private MathContext mc = null;
/**
* The characters (other than letters and digits) allowed as the first character in a variable.
*/
private String firstVarChars = "_";
/**
* The characters (other than letters and digits) allowed as the second or subsequent characters in a variable.
*/
private String varChars = "_";
/**
* The original infix expression.
*/
private final String originalExpression;
/**
* The current infix expression, with optional variable substitutions.
*/
private String expression = null;
/**
* The cached RPN (Reverse Polish Notation) of the expression.
*/
private List rpn = null;
/**
* All defined operators with name and implementation.
*/
private Map operators = new TreeMap(String.CASE_INSENSITIVE_ORDER);
/**
* All defined functions with name and implementation.
*/
private Map functions = new TreeMap(String.CASE_INSENSITIVE_ORDER);
/**
* All defined variables with name and value.
*/
private Map variables = new TreeMap(String.CASE_INSENSITIVE_ORDER);
/**
* What character to use for decimal separators.
*/
private static final char decimalSeparator = '.';
/**
* What character to use for minus sign (negative values).
*/
private static final char minusSign = '-';
/**
* The BigDecimal representation of the left parenthesis,
* used for parsing varying numbers of function parameters.
*/
private static final LazyNumber PARAMS_START = new LazyNumber() {
public BigDecimal eval() {
return null;
}
public String getString() {
return null;
}
};
/**
* The expression evaluators exception class.
*/
public static class ExpressionException extends RuntimeException {
private static final long serialVersionUID = 1118142866870779047L;
public ExpressionException(String message) {
super(message);
}
}
/**
* LazyNumber interface created for lazily evaluated functions
*/
public interface LazyNumber {
BigDecimal eval();
String getString();
}
public abstract class LazyFunction {
/**
* Name of this function.
*/
private String name;
/**
* Number of parameters expected for this function.
* -1 denotes a variable number of parameters.
*/
private int numParams;
/**
* Whether this function is a boolean function.
*/
protected boolean booleanFunction = false;
/**
* Creates a new function with given name and parameter count.
*
* @param name
* The name of the function.
* @param numParams
* The number of parameters for this function.
* -1 denotes a variable number of parameters.
* @param booleanFunction
* Whether this function is a boolean function.
*/
public LazyFunction(String name, int numParams, boolean booleanFunction) {
this.name = name.toUpperCase(Locale.ROOT);
this.numParams = numParams;
this.booleanFunction = booleanFunction;
}
/**
* Creates a new function with given name and parameter count.
*
* @param name
* The name of the function.
* @param numParams
* The number of parameters for this function.
* -1 denotes a variable number of parameters.
*/
public LazyFunction(String name, int numParams) {
this.name = name.toUpperCase(Locale.ROOT);
this.numParams = numParams;
}
public String getName() {
return name;
}
public int getNumParams() {
return numParams;
}
public boolean numParamsVaries() {
return numParams < 0;
}
public boolean isBooleanFunction() {
return this.booleanFunction;
}
public void setBooleanFunction(boolean booleanFunction) {
this.booleanFunction = booleanFunction;
}
public abstract LazyNumber lazyEval(List lazyParams);
}
/**
* Abstract definition of a supported expression function. A function is
* defined by a name, the number of parameters and the actual processing
* implementation.
*/
public abstract class Function extends LazyFunction {
public Function(String name, int numParams) {
super(name, numParams);
}
public Function(String name, int numParams, boolean booleanFunction) {
super(name, numParams, booleanFunction);
}
public LazyNumber lazyEval(final List lazyParams) {
return new LazyNumber() {
private List params;
public BigDecimal eval() {
return Function.this.eval(getParams());
}
public String getString() {
return String.valueOf(Function.this.eval(getParams()));
}
private List getParams() {
if (params == null) {
params = new ArrayList();
for (LazyNumber lazyParam : lazyParams) {
params.add(lazyParam.eval());
}
}
return params;
}
};
}
/**
* Implementation for this function.
*
* @param parameters
* Parameters will be passed by the expression evaluator as a
* {@link List} of {@link BigDecimal} values.
* @return The function must return a new {@link BigDecimal} value as a
* computing result.
*/
public abstract BigDecimal eval(List parameters);
}
/**
* Abstract definition of a supported operator. An operator is defined by
* its name (pattern), precedence and if it is left- or right associative.
*/
public abstract class Operator {
/**
* This operators name (pattern).
*/
private String oper;
/**
* Operators precedence.
*/
private int precedence;
/**
* Operator is left associative.
*/
private boolean leftAssoc;
/**
* Whether this operator is boolean or not.
*/
protected boolean booleanOperator = false;
public boolean isBooleanOperator() {
return booleanOperator;
}
public void setBooleanOperator(boolean booleanOperator) {
this.booleanOperator = booleanOperator;
}
/**
* Creates a new operator.
*
* @param oper
* The operator name (pattern).
* @param precedence
* The operators precedence.
* @param leftAssoc
* true if the operator is left associative,
* else false.
* @param booleanOperator
* Whether this operator is boolean.
*/
public Operator(String oper, int precedence, boolean leftAssoc, boolean booleanOperator) {
this.oper = oper;
this.precedence = precedence;
this.leftAssoc = leftAssoc;
this.booleanOperator = booleanOperator;
}
/**
* Creates a new operator.
*
* @param oper
* The operator name (pattern).
* @param precedence
* The operators precedence.
* @param leftAssoc
* true if the operator is left associative,
* else false.
*/
public Operator(String oper, int precedence, boolean leftAssoc) {
this.oper = oper;
this.precedence = precedence;
this.leftAssoc = leftAssoc;
}
public String getOper() {
return oper;
}
public int getPrecedence() {
return precedence;
}
public boolean isLeftAssoc() {
return leftAssoc;
}
/**
* Implementation for this operator.
*
* @param v1
* Operand 1.
* @param v2
* Operand 2.
* @return The result of the operation.
*/
public abstract BigDecimal eval(BigDecimal v1, BigDecimal v2);
}
public abstract class UnaryOperator extends Operator {
public UnaryOperator(String oper, int precedence, boolean leftAssoc) {
super(oper, precedence, leftAssoc);
}
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
if(v2 != null) {
throw new ExpressionException("Did not expect a second parameter for unary operator");
}
return evalUnary(v1);
}
abstract public BigDecimal evalUnary(BigDecimal v1);
}
enum TokenType {
VARIABLE, FUNCTION, LITERAL, OPERATOR, UNARY_OPERATOR, OPEN_PAREN, COMMA, CLOSE_PAREN, HEX_LITERAL, STRINGPARAM
}
class Token {
public String surface = "";
public TokenType type;
public int pos;
public void append(char c) {
surface += c;
}
public void append(String s) {
surface += s;
}
public char charAt(int pos) {
return surface.charAt(pos);
}
public int length() {
return surface.length();
}
@Override
public String toString() {
return surface;
}
}
/**
* Expression tokenizer that allows to iterate over a {@link String}
* expression token by token. Blank characters will be skipped.
*/
private class Tokenizer implements Iterator {
/**
* Actual position in expression string.
*/
private int pos = 0;
/**
* The original input expression.
*/
private String input;
/**
* The previous token or null if none.
*/
private Token previousToken;
/**
* Creates a new tokenizer for an expression.
*
* @param input
* The expression string.
*/
public Tokenizer(String input) {
this.input = input.trim();
}
@Override
public boolean hasNext() {
return (pos < input.length());
}
/**
* Peek at the next character, without advancing the iterator.
*
* @return The next character or character 0, if at end of string.
*/
private char peekNextChar() {
if (pos < (input.length() - 1)) {
return input.charAt(pos + 1);
} else {
return 0;
}
}
private boolean isHexDigit(char ch) {
return ch == 'x' || ch == 'X' || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
}
@Override
public Token next() {
Token token = new Token();
if (pos >= input.length()) {
return previousToken = null;
}
char ch = input.charAt(pos);
while (Character.isWhitespace(ch) && pos < input.length()) {
ch = input.charAt(++pos);
}
token.pos = pos;
boolean isHex = false;
if (Character.isDigit(ch) || (ch == decimalSeparator && Character.isDigit(peekNextChar()))) {
if(ch == '0' && (peekNextChar() == 'x' || peekNextChar() == 'X')) isHex = true;
while ((isHex && isHexDigit(ch)) || (Character.isDigit(ch) || ch == decimalSeparator
|| ch == 'e' || ch == 'E'
|| (ch == minusSign && token.length() > 0
&& ('e'==token.charAt(token.length()-1) || 'E'==token.charAt(token.length()-1)))
|| (ch == '+' && token.length() > 0
&& ('e'==token.charAt(token.length()-1) || 'E'==token.charAt(token.length()-1)))
) && (pos < input.length())) {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
token.type = isHex ? TokenType.HEX_LITERAL : TokenType.LITERAL;
} else if (ch == '"') {
pos++;
if (previousToken.type != TokenType.STRINGPARAM) {
ch = input.charAt(pos);
while (ch != '"') {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
token.type = TokenType.STRINGPARAM;
} else {
return next();
}
} else if (Character.isLetter(ch) || firstVarChars.indexOf(ch) >= 0) {
while ((Character.isLetter(ch) || Character.isDigit(ch)
|| varChars.indexOf(ch) >= 0 || token.length() == 0 && firstVarChars.indexOf(ch) >= 0)
&& (pos < input.length())) {
token.append(input.charAt(pos++));
ch = pos == input.length() ? 0 : input.charAt(pos);
}
//Remove optional white spaces after function or variable name
if (ch == ' ') {
while (ch == ' ' && pos < input.length()) {
ch = input.charAt(pos++);
}
pos--;
}
token.type = ch == '(' ? TokenType.FUNCTION : TokenType.VARIABLE;
} else if (ch == '(' || ch == ')' || ch == ',') {
if(ch == '(') {
token.type = TokenType.OPEN_PAREN;
} else if (ch == ')') {
token.type = TokenType.CLOSE_PAREN;
} else {
token.type = TokenType.COMMA;
}
token.append(ch);
pos++;
} else {
String greedyMatch = "";
int initialPos = pos;
ch = input.charAt(pos);
int validOperatorSeenUntil = -1;
while (!Character.isLetter(ch) && !Character.isDigit(ch)
&& firstVarChars.indexOf(ch) < 0 && !Character.isWhitespace(ch)
&& ch != '(' && ch != ')' && ch != ','
&& (pos < input.length())) {
greedyMatch += ch;
pos++;
if(operators.containsKey(greedyMatch)) {
validOperatorSeenUntil = pos;
}
ch = pos == input.length() ? 0 : input.charAt(pos);
}
if(validOperatorSeenUntil != -1) {
token.append(input.substring(initialPos, validOperatorSeenUntil));
pos = validOperatorSeenUntil;
} else {
token.append(greedyMatch);
}
if(previousToken == null || previousToken.type == TokenType.OPERATOR || previousToken.type == TokenType.OPEN_PAREN || previousToken.type == TokenType.COMMA) {
token.surface += "u";
token.type = TokenType.UNARY_OPERATOR;
} else {
token.type = TokenType.OPERATOR;
}
}
return previousToken = token;
}
@Override
public void remove() {
throw new ExpressionException("remove() not supported");
}
}
/**
* Creates a new expression instance from an expression string with a given
* default match context of {@link MathContext#DECIMAL32}.
*
* @param expression
* The expression. E.g. "2.4*sin(3)/(2-4)" or
* "sin(y)>0 & max(z, 3)>3"
*/
public Expression(String expression) {
this(expression, MathContext.DECIMAL32);
}
/**
* Creates a new expression instance from an expression string with a given
* default match context.
*
* @param expression
* The expression. E.g. "2.4*sin(3)/(2-4)" or
* "sin(y)>0 & max(z, 3)>3"
* @param defaultMathContext
* The {@link MathContext} to use by default.
*/
public Expression(String expression, MathContext defaultMathContext) {
this.mc = defaultMathContext;
this.expression = expression;
this.originalExpression = expression;
addOperator(new Operator("+", OPERATOR_PRECEDENCE_ADDITIVE, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.add(v2, mc);
}
});
addOperator(new Operator("-", OPERATOR_PRECEDENCE_ADDITIVE, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.subtract(v2, mc);
}
});
addOperator(new Operator("*", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.multiply(v2, mc);
}
});
addOperator(new Operator("/", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.divide(v2, mc);
}
});
addOperator(new Operator("%", OPERATOR_PRECEDENCE_MULTIPLICATIVE, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.remainder(v2, mc);
}
});
addOperator(new Operator("^", OPERATOR_PRECEDENCE_POWER, false) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
/*-
* Thanks to Gene Marin:
* http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java
*/
int signOf2 = v2.signum();
double dn1 = v1.doubleValue();
v2 = v2.multiply(new BigDecimal(signOf2)); // n2 is now positive
BigDecimal remainderOf2 = v2.remainder(BigDecimal.ONE);
BigDecimal n2IntPart = v2.subtract(remainderOf2);
BigDecimal intPow = v1.pow(n2IntPart.intValueExact(), mc);
BigDecimal doublePow = new BigDecimal(Math.pow(dn1,
remainderOf2.doubleValue()));
BigDecimal result = intPow.multiply(doublePow, mc);
if (signOf2 == -1) {
result = BigDecimal.ONE.divide(result, mc.getPrecision(),
RoundingMode.HALF_UP);
}
return result;
}
});
addOperator(new Operator("&&", OPERATOR_PRECEDENCE_AND, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0;
boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0;
return b1 && b2 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator("||", OPERATOR_PRECEDENCE_OR, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
boolean b1 = v1.compareTo(BigDecimal.ZERO) != 0;
boolean b2 = v2.compareTo(BigDecimal.ZERO) != 0;
return b1 || b2 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator(">", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.compareTo(v2) == 1 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator(">=", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.compareTo(v2) >= 0 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator("<", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.compareTo(v2) == -1 ? BigDecimal.ONE
: BigDecimal.ZERO;
}
});
addOperator(new Operator("<=", OPERATOR_PRECEDENCE_COMPARISON, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return v1.compareTo(v2) <= 0 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator("=", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
if (v1 == v2) {
return BigDecimal.ONE;
}
if (v1 == null || v2 == null) {
return BigDecimal.ZERO;
}
return v1.compareTo(v2) == 0 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator("==", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
return operators.get("=").eval(v1, v2);
}
});
addOperator(new Operator("!=", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
if (v1 == v2) {
return BigDecimal.ZERO;
}
if (v1 == null || v2 == null) {
return BigDecimal.ONE;
}
return v1.compareTo(v2) != 0 ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addOperator(new Operator("<>", OPERATOR_PRECEDENCE_EQUALITY, false, true) {
@Override
public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
assertNotNull(v1, v2);
return operators.get("!=").eval(v1, v2);
}
});
addOperator(new UnaryOperator("-", OPERATOR_PRECEDENCE_UNARY, false) {
@Override
public BigDecimal evalUnary(BigDecimal v1) {
return v1.multiply(new BigDecimal(-1));
}
});
addOperator(new UnaryOperator("+", OPERATOR_PRECEDENCE_UNARY, false) {
@Override
public BigDecimal evalUnary(BigDecimal v1) {
return v1.multiply(BigDecimal.ONE);
}
});
addFunction(new Function("NOT", 1, true) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
boolean zero = parameters.get(0).compareTo(BigDecimal.ZERO) == 0;
return zero ? BigDecimal.ONE : BigDecimal.ZERO;
}
});
addLazyFunction(new LazyFunction("IF", 3) {
@Override
public LazyNumber lazyEval(List lazyParams) {
BigDecimal result = lazyParams.get(0).eval();
assertNotNull(result);
boolean isTrue = result.compareTo(BigDecimal.ZERO) != 0;
return isTrue ? lazyParams.get(1) : lazyParams.get(2);
}
});
addFunction(new Function("RANDOM", 0) {
@Override
public BigDecimal eval(List parameters) {
double d = Math.random();
return new BigDecimal(d, mc);
}
});
addFunction(new Function("SIN", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.sin(Math.toRadians(parameters.get(0)
.doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("COS", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.cos(Math.toRadians(parameters.get(0)
.doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("TAN", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.tan(Math.toRadians(parameters.get(0)
.doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ASIN", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(Math.asin(parameters.get(0)
.doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ACOS", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(Math.acos(parameters.get(0)
.doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ATAN", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(Math.atan(parameters.get(0)
.doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ATAN2", 2) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0), parameters.get(1));
double d = Math.toDegrees(Math.atan2(
parameters.get(0).doubleValue(),
parameters.get(1).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("SINH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.sinh(parameters.get(0).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("COSH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.cosh(parameters.get(0).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("TANH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.tanh(parameters.get(0).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("SEC", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/** Formula: sec(x) = 1 / cos(x)*/
double one = 1;
double d = Math.cos(Math.toRadians(parameters.get(0).doubleValue()));
return new BigDecimal((one/d), mc);
}
});
addFunction(new Function("CSC", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/** Formula: csc(x) = 1 / sin(x)*/
double one = 1;
double d = Math.sin(Math.toRadians(parameters.get(0)
.doubleValue()));
return new BigDecimal((one/d), mc);
}
});
addFunction(new Function("SECH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/** Formula: sech(x) = 1 / cosh(x)*/
double one = 1;
double d = Math.cosh(parameters.get(0).doubleValue());
return new BigDecimal((one/d), mc);
}
});
addFunction(new Function("CSCH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/** Formula: csch(x) = 1 / sinh(x)*/
double one = 1;
double d = Math.sinh(parameters.get(0).doubleValue());
return new BigDecimal((one/d), mc);
}
});
addFunction(new Function("COT", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/** Formula: cot(x) = cos(x) / sin(x) = 1 / tan(x)*/
double one = 1;
double d = Math.tan(Math.toRadians(parameters.get(0).doubleValue()));
return new BigDecimal((one/d), mc);
}
});
addFunction(new Function("ACOT", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/** Formula: acot(x) = atan(1/x) */
if (parameters.get(0).doubleValue() == 0) {
throw new ExpressionException("Number must not be 0");
}
double d = Math.toDegrees(Math.atan(1/parameters.get(0).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("COTH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/** Formula: coth(x) = 1 / tanh(x) */
double one = 1;
double d = Math.tanh(parameters.get(0).doubleValue());
return new BigDecimal((one/d), mc);
}
});
addFunction(new Function("ASINH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/**Formula: asinh(x) = ln(x + sqrt(x^2 + 1)) */
double d = Math.log(parameters.get(0).doubleValue()+
(Math.sqrt(Math.pow(parameters.get(0).doubleValue(), 2) + 1)));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ACOSH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/**Formula: acosh(x) = ln(x + sqrt(x^2 - 1)) */
if (Double.compare(parameters.get(0).doubleValue(), 1) < 0) {
throw new ExpressionException("Number must be x >= 1");
}
double d = Math.log(parameters.get(0).doubleValue()+
(Math.sqrt(Math.pow(parameters.get(0).doubleValue(), 2) - 1)));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ATANH", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/**Formula: atanh(x) = 0.5*ln((1 + x)/(1 - x)) */
if (Math.abs(parameters.get(0).doubleValue()) > 1 ||
Math.abs(parameters.get(0).doubleValue()) == 1) {
throw new ExpressionException("Number must be |x| < 1");
}
double d = 0.5*Math.log((1 + parameters.get(0).doubleValue())/
(1 - parameters.get(0).doubleValue()));
return new BigDecimal(d, mc);
}
});
addFunction(new Function("RAD", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.toRadians(parameters.get(0).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("DEG", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.toDegrees(parameters.get(0).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("MAX", -1) {
@Override
public BigDecimal eval(List parameters) {
if (parameters.size() == 0) {
throw new ExpressionException("MAX requires at least one parameter");
}
BigDecimal max = null;
for (BigDecimal parameter : parameters) {
assertNotNull(parameter);
if (max == null || parameter.compareTo(max) > 0) {
max = parameter;
}
}
return max;
}
});
addFunction(new Function("MIN", -1) {
@Override
public BigDecimal eval(List parameters) {
if (parameters.size() == 0) {
throw new ExpressionException("MIN requires at least one parameter");
}
BigDecimal min = null;
for (BigDecimal parameter : parameters) {
assertNotNull(parameter);
if (min == null || parameter.compareTo(min) < 0) {
min = parameter;
}
}
return min;
}
});
addFunction(new Function("ABS", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
return parameters.get(0).abs(mc);
}
});
addFunction(new Function("LOG", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.log(parameters.get(0).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("LOG10", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
double d = Math.log10(parameters.get(0).doubleValue());
return new BigDecimal(d, mc);
}
});
addFunction(new Function("ROUND", 2) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0), parameters.get(1));
BigDecimal toRound = parameters.get(0);
int precision = parameters.get(1).intValue();
return toRound.setScale(precision, mc.getRoundingMode());
}
});
addFunction(new Function("FLOOR", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
BigDecimal toRound = parameters.get(0);
return toRound.setScale(0, RoundingMode.FLOOR);
}
});
addFunction(new Function("CEILING", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
BigDecimal toRound = parameters.get(0);
return toRound.setScale(0, RoundingMode.CEILING);
}
});
addFunction(new Function("SQRT", 1) {
@Override
public BigDecimal eval(List parameters) {
assertNotNull(parameters.get(0));
/*
* From The Java Programmers Guide To numerical Computing
* (Ronald Mak, 2003)
*/
BigDecimal x = parameters.get(0);
if (x.compareTo(BigDecimal.ZERO) == 0) {
return new BigDecimal(0);
}
if (x.signum() < 0) {
throw new ExpressionException(
"Argument to SQRT() function must not be negative");
}
BigInteger n = x.movePointRight(mc.getPrecision() << 1)
.toBigInteger();
int bits = (n.bitLength() + 1) >> 1;
BigInteger ix = n.shiftRight(bits);
BigInteger ixPrev;
do {
ixPrev = ix;
ix = ix.add(n.divide(ix)).shiftRight(1);
// Give other threads a chance to work;
Thread.yield();
} while (ix.compareTo(ixPrev) != 0);
return new BigDecimal(ix, mc.getPrecision());
}
});
variables.put("e", e);
variables.put("PI", PI);
variables.put("NULL", null);
variables.put("TRUE", BigDecimal.ONE);
variables.put("FALSE", BigDecimal.ZERO);
}
private void assertNotNull(BigDecimal v1) {
if (v1 == null) {
throw new ArithmeticException("Operand may not be null");
}
}
private void assertNotNull(BigDecimal v1, BigDecimal v2) {
if (v1 == null) {
throw new ArithmeticException("First operand may not be null");
}
if (v2 == null) {
throw new ArithmeticException("Second operand may not be null");
}
}
/**
* Is the string a number?
*
* @param st
* The string.
* @return true, if the input string is a number.
*/
private boolean isNumber(String st) {
if (st.charAt(0) == minusSign && st.length() == 1) return false;
if (st.charAt(0) == '+' && st.length() == 1) return false;
if (st.charAt(0) == decimalSeparator && (st.length() == 1 || !Character.isDigit(st.charAt(1)))) return false;
if (st.charAt(0) == 'e' || st.charAt(0) == 'E') return false;
for (char ch : st.toCharArray()) {
if (!Character.isDigit(ch) && ch != minusSign
&& ch != decimalSeparator
&& ch != 'e' && ch != 'E' && ch != '+')
return false;
}
return true;
}
/**
* Implementation of the Shunting Yard algorithm to transform an
* infix expression to a RPN expression.
*
* @param expression
* The input expression in infx.
* @return A RPN representation of the expression, with each token as a list
* member.
*/
private List shuntingYard(String expression) {
List outputQueue = new ArrayList();
Stack stack = new Stack();
Tokenizer tokenizer = new Tokenizer(expression);
Token lastFunction = null;
Token previousToken = null;
while (tokenizer.hasNext()) {
Token token = tokenizer.next();
switch(token.type) {
case STRINGPARAM:
stack.push(token);
break;
case LITERAL:
case HEX_LITERAL:
outputQueue.add(token);
break;
case VARIABLE:
outputQueue.add(token);
break;
case FUNCTION:
stack.push(token);
lastFunction = token;
break;
case COMMA:
if (previousToken != null && previousToken.type == TokenType.OPERATOR) {
throw new ExpressionException("Missing parameter(s) for operator " + previousToken +
" at character position " + previousToken.pos);
}
while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) {
outputQueue.add(stack.pop());
}
if (stack.isEmpty()) {
throw new ExpressionException("Parse error for function '"
+ lastFunction + "'");
}
break;
case OPERATOR: {
if (previousToken != null && (previousToken.type == TokenType.COMMA || previousToken.type == TokenType.OPEN_PAREN)) {
throw new ExpressionException("Missing parameter(s) for operator " + token +
" at character position " + token.pos);
}
Operator o1 = operators.get(token.surface);
if (o1 == null) {
throw new ExpressionException("Unknown operator '" + token
+ "' at position " + (token.pos + 1));
}
shuntOperators(outputQueue, stack, o1);
stack.push(token);
break;
}
case UNARY_OPERATOR: {
if (previousToken != null && previousToken.type != TokenType.OPERATOR
&& previousToken.type != TokenType.COMMA && previousToken.type != TokenType.OPEN_PAREN) {
throw new ExpressionException("Invalid position for unary operator " + token +
" at character position " + token.pos);
}
Operator o1 = operators.get(token.surface);
if (o1 == null) {
throw new ExpressionException("Unknown unary operator '"
+ token.surface.substring(0,token.surface.length() - 1)
+ "' at position " + (token.pos + 1));
}
shuntOperators(outputQueue, stack, o1);
stack.push(token);
break;
}
case OPEN_PAREN:
if (previousToken != null) {
if (previousToken.type == TokenType.LITERAL || previousToken.type == TokenType.CLOSE_PAREN
|| previousToken.type == TokenType.VARIABLE
|| previousToken.type == TokenType.HEX_LITERAL) {
// Implicit multiplication, e.g. 23(a+b) or (a+b)(a-b)
Token multiplication = new Token();
multiplication.append("*");
multiplication.type = TokenType.OPERATOR;
stack.push(multiplication);
}
// if the ( is preceded by a valid function, then it
// denotes the start of a parameter list
if (previousToken.type == TokenType.FUNCTION) {
outputQueue.add(token);
}
}
stack.push(token);
break;
case CLOSE_PAREN:
if (previousToken != null && previousToken.type == TokenType.OPERATOR) {
throw new ExpressionException("Missing parameter(s) for operator " + previousToken +
" at character position " + previousToken.pos);
}
while (!stack.isEmpty() && stack.peek().type != TokenType.OPEN_PAREN) {
outputQueue.add(stack.pop());
}
if (stack.isEmpty()) {
throw new ExpressionException("Mismatched parentheses");
}
stack.pop();
if (!stack.isEmpty() && stack.peek().type == TokenType.FUNCTION) {
outputQueue.add(stack.pop());
}
}
previousToken = token;
}
while (!stack.isEmpty()) {
Token element = stack.pop();
if (element.type == TokenType.OPEN_PAREN || element.type == TokenType.CLOSE_PAREN) {
throw new ExpressionException("Mismatched parentheses");
}
outputQueue.add(element);
}
return outputQueue;
}
private void shuntOperators(List outputQueue, Stack stack, Operator o1) {
Expression.Token nextToken = stack.isEmpty() ? null : stack.peek();
while (nextToken != null &&
(nextToken.type == Expression.TokenType.OPERATOR || nextToken.type == Expression.TokenType.UNARY_OPERATOR)
&& ((o1.isLeftAssoc()
&& o1.getPrecedence() <= operators.get(nextToken.surface).getPrecedence())
|| (o1.getPrecedence() < operators.get(nextToken.surface).getPrecedence()))) {
outputQueue.add(stack.pop());
nextToken = stack.isEmpty() ? null : stack.peek();
}
}
/**
* Evaluates the expression.
*
* @return The result of the expression. Trailing zeros are stripped.
*/
public BigDecimal eval() {
return eval(true);
}
/**
* Evaluates the expression.
*
* @param stripTrailingZeros If set to true trailing zeros in the result are stripped.
*
* @return The result of the expression.
*/
public BigDecimal eval(boolean stripTrailingZeros) {
Stack stack = new Stack();
for (final Token token : getRPN()) {
switch(token.type) {
case UNARY_OPERATOR: {
final LazyNumber value = stack.pop();
LazyNumber result = new LazyNumber() {
public BigDecimal eval() {
return operators.get(token.surface).eval(value.eval(), null);
}
@Override
public String getString() {
return String.valueOf(operators.get(token.surface).eval(value.eval(), null));
}
};
stack.push(result);
break;
}
case OPERATOR:
final LazyNumber v1 = stack.pop();
final LazyNumber v2 = stack.pop();
LazyNumber result = new LazyNumber() {
public BigDecimal eval() {
return operators.get(token.surface).eval(v2.eval(), v1.eval());
}
public String getString() {
return String.valueOf(operators.get(token.surface).eval(v2.eval(), v1.eval()));
}
};
stack.push(result);
break;
case VARIABLE:
if (!variables.containsKey(token.surface)) {
throw new ExpressionException("Unknown operator or function: " + token);
}
stack.push(new LazyNumber() {
public BigDecimal eval() {
BigDecimal value = variables.get(token.surface);
return value == null ? null : value.round(mc);
}
public String getString() {
return token.surface;
}
});
break;
case FUNCTION:
LazyFunction f = functions.get(token.surface.toUpperCase(Locale.ROOT));
ArrayList p = new ArrayList(
!f.numParamsVaries() ? f.getNumParams() : 0);
// pop parameters off the stack until we hit the start of
// this function's parameter list
while (!stack.isEmpty() && stack.peek() != PARAMS_START) {
p.add(0, stack.pop());
}
if (stack.peek() == PARAMS_START) {
stack.pop();
}
LazyNumber fResult = f.lazyEval(p);
stack.push(fResult);
break;
case OPEN_PAREN:
stack.push(PARAMS_START);
break;
case LITERAL:
stack.push(new LazyNumber() {
public BigDecimal eval() {
if (token.surface.equalsIgnoreCase("NULL")) {
return null;
}
return new BigDecimal(token.surface, mc);
}
public String getString() {
return String.valueOf(new BigDecimal(token.surface, mc));
}
});
break;
case STRINGPARAM:
stack.push(new LazyNumber() {
public BigDecimal eval() {
return null;
}
public String getString() {
return token.surface;
}
});
break;
case HEX_LITERAL:
stack.push(new LazyNumber() {
public BigDecimal eval() {
return new BigDecimal(new BigInteger(token.surface.substring(2), 16), mc);
}
public String getString() {
return new BigInteger(token.surface.substring(2), 16).toString();
}
});
break;
}
}
BigDecimal result = stack.pop().eval();
return result == null ? null : stripTrailingZeros ? result.stripTrailingZeros() : result;
}
/**
* Sets the precision for expression evaluation.
*
* @param precision
* The new precision.
*
* @return The expression, allows to chain methods.
*/
public Expression setPrecision(int precision) {
this.mc = new MathContext(precision);
return this;
}
/**
* Sets the rounding mode for expression evaluation.
*
* @param roundingMode
* The new rounding mode.
* @return The expression, allows to chain methods.
*/
public Expression setRoundingMode(RoundingMode roundingMode) {
this.mc = new MathContext(mc.getPrecision(), roundingMode);
return this;
}
/**
* Sets the characters other than letters and digits that are valid as the
* first character of a variable.
*
* @param chars
* The new set of variable characters.
* @return The expression, allows to chain methods.
*/
public Expression setFirstVariableCharacters(String chars) {
this.firstVarChars = chars;
return this;
}
/**
* Sets the characters other than letters and digits that are valid as the
* second and subsequent characters of a variable.
*
* @param chars
* The new set of variable characters.
* @return The expression, allows to chain methods.
*/
public Expression setVariableCharacters(String chars) {
this.varChars = chars;
return this;
}
/**
* Adds an operator to the list of supported operators.
*
* @param operator
* The operator to add.
* @return The previous operator with that name, or null if
* there was none.
*/
public Operator addOperator(Operator operator) {
String key = operator.getOper();
if(operator instanceof UnaryOperator) {
key += "u";
}
return operators.put(key, operator);
}
/**
* Adds a function to the list of supported functions
*
* @param function
* The function to add.
* @return The previous operator with that name, or null if
* there was none.
*/
public Function addFunction(Function function) {
return (Function) functions.put(function.getName(), function);
}
/**
* Adds a lazy function function to the list of supported functions
*
* @param function
* The function to add.
* @return The previous operator with that name, or null if
* there was none.
*/
public LazyFunction addLazyFunction(LazyFunction function) {
return functions.put(function.getName(), function);
}
/**
* Sets a variable value.
*
* @param variable
* The variable name.
* @param value
* The variable value.
* @return The expression, allows to chain methods.
*/
public Expression setVariable(String variable, BigDecimal value) {
variables.put(variable, value);
return this;
}
/**
* Sets a variable value.
*
* @param variable
* The variable to set.
* @param value
* The variable value.
* @return The expression, allows to chain methods.
*/
public Expression setVariable(String variable, String value) {
if (isNumber(value))
variables.put(variable, new BigDecimal(value));
else if (value.equalsIgnoreCase("null")) {
variables.put(variable, null);
}
else {
expression = expression.replaceAll("(?i)\\b" + variable + "\\b", "("
+ value + ")");
rpn = null;
}
return this;
}
/**
* Sets a variable value.
*
* @param variable
* The variable to set.
* @param value
* The variable value.
* @return The expression, allows to chain methods.
*/
public Expression with(String variable, BigDecimal value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable
* The variable to set.
* @param value
* The variable value.
* @return The expression, allows to chain methods.
*/
public Expression and(String variable, String value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable
* The variable to set.
* @param value
* The variable value.
* @return The expression, allows to chain methods.
*/
public Expression and(String variable, BigDecimal value) {
return setVariable(variable, value);
}
/**
* Sets a variable value.
*
* @param variable
* The variable to set.
* @param value
* The variable value.
* @return The expression, allows to chain methods.
*/
public Expression with(String variable, String value) {
return setVariable(variable, value);
}
/**
* Get an iterator for this expression, allows iterating over an expression
* token by token.
*
* @return A new iterator instance for this expression.
*/
public Iterator getExpressionTokenizer() {
final String expression = this.expression;
return new Tokenizer(expression);
}
/**
* Cached access to the RPN notation of this expression, ensures only one
* calculation of the RPN per expression instance. If no cached instance
* exists, a new one will be created and put to the cache.
*
* @return The cached RPN instance.
*/
private List getRPN() {
if (rpn == null) {
rpn = shuntingYard(this.expression);
validate(rpn);
}
return rpn;
}
/**
* Check that the expression has enough numbers and variables to fit the
* requirements of the operators and functions, also check
* for only 1 result stored at the end of the evaluation.
*/
private void validate(List rpn) {
/*-
* Thanks to Norman Ramsey:
* http://http://stackoverflow.com/questions/789847/postfix-notation-validation
*/
// each push on to this stack is a new function scope, with the value of each
// layer on the stack being the count of the number of parameters in that scope
Stack stack = new Stack();
// push the 'global' scope
stack.push(0);
for (final Token token : rpn) {
switch(token.type) {
case UNARY_OPERATOR:
if(stack.peek() < 1) {
throw new ExpressionException("Missing parameter(s) for operator " + token);
}
break;
case OPERATOR:
if (stack.peek() < 2) {
throw new ExpressionException("Missing parameter(s) for operator " + token);
}
// pop the operator's 2 parameters and add the result
stack.set(stack.size() - 1, stack.peek() - 2 + 1);
break;
case FUNCTION:
LazyFunction f = functions.get(token.surface.toUpperCase(Locale.ROOT));
if(f == null) {
throw new ExpressionException("Unknown function '" + token
+ "' at position " + (token.pos + 1));
}
int numParams = stack.pop();
if (!f.numParamsVaries() && numParams != f.getNumParams()) {
throw new ExpressionException("Function " + token + " expected " + f.getNumParams() + " parameters, got " + numParams);
}
if (stack.size() <= 0) {
throw new ExpressionException("Too many function calls, maximum scope exceeded");
}
// push the result of the function
stack.set(stack.size() - 1, stack.peek() + 1);
break;
case OPEN_PAREN:
stack.push(0);
break;
default:
stack.set(stack.size() - 1, stack.peek() + 1);
}
}
if (stack.size() > 1) {
throw new ExpressionException("Too many unhandled function parameter lists");
} else if (stack.peek() > 1) {
throw new ExpressionException("Too many numbers or variables");
} else if (stack.peek() < 1) {
throw new ExpressionException("Empty expression");
}
}
/**
* Get a string representation of the RPN (Reverse Polish Notation) for this
* expression.
*
* @return A string with the RPN representation for this expression.
*/
public String toRPN() {
StringBuilder result = new StringBuilder();
for (Token t : getRPN()) {
if (result.length() != 0)
result.append(" ");
result.append(t.toString());
}
return result.toString();
}
/**
* Exposing declared variables in the expression.
* @return All declared variables.
*/
public Set getDeclaredVariables() {
return Collections.unmodifiableSet(variables.keySet());
}
/**
* Exposing declared operators in the expression.
* @return All declared operators.
*/
public Set getDeclaredOperators() {
return Collections.unmodifiableSet(operators.keySet());
}
/**
* Exposing declared functions.
* @return All declared functions.
*/
public Set getDeclaredFunctions() {
return Collections.unmodifiableSet(functions.keySet());
}
/**
* @return The original expression string
*/
public String getExpression() {
return expression;
}
/**
* Returns a list of the variables in the expression.
*
* @return A list of the variable names in this expression.
*/
public List getUsedVariables() {
List result = new ArrayList();
Tokenizer tokenizer = new Tokenizer(expression);
while (tokenizer.hasNext()) {
Token nextToken = tokenizer.next();
String token = nextToken.toString();
if (nextToken.type != TokenType.VARIABLE
|| token.equals("PI") || token.equals("e")
|| token.equals("TRUE") || token.equals("FALSE")) {
continue;
}
result.add(token);
}
return result;
}
/**
* The original expression used to construct this expression, without
* variables substituted.
*/
public String getOriginalExpression() {
return this.originalExpression;
}
/** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Expression that = (Expression) o;
if (this.expression == null) {
return that.expression == null;
} else {
return this.expression.equals(that.expression);
}
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return this.expression == null ? 0 : this.expression.hashCode();
}
/** {@inheritDoc} */
@Override
public String toString() {
return this.expression;
}
/**
* Checks whether the expression is a boolean expression.
* An expression is considered a boolean expression, if the last operator or function is boolean.
* The IF function is handled special.
* If the third parameter is boolean, then the IF is also considered boolean, else non-boolean.
*
* @return true if the last operator/function was a boolean.
*/
public boolean isBoolean() {
List rpn = getRPN();
if (rpn.size() > 0) {
for (int i = rpn.size() - 1; i >= 0; i--) {
Token t = rpn.get(i);
/*
* The IF function is handled special.
* If the third parameter is boolean, then the IF is also considered a boolean.
* Just skip the IF function to check the second parameter.
*/
if (t.surface.equals("IF"))
continue;
if (t.type == TokenType.FUNCTION) {
return functions.get(t.surface).isBooleanFunction();
} else if (t.type == TokenType.OPERATOR) {
return operators.get(t.surface).isBooleanOperator();
}
}
}
return false;
}
}