package de.congrace.exp4j;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* This is Builder implementation for the exp4j API used to create a Calculable instance for the user
*
* @author frank asseg
*
*/
public class ExpressionBuilder {
/**
* Property name for unary precedence choice. You can set System.getProperty(PROPERTY_UNARY_HIGH_PRECEDENCE,"false")
* in order to change evaluation from an expression like "-3^2" from "(-3)^2" to "-(3^2)"
*/
public static final String PROPERTY_UNARY_HIGH_PRECEDENCE="exp4j.unary.precedence.high";
private final Map<String, Double> variables = new LinkedHashMap<String, Double>();
private final Map<String, CustomFunction> customFunctions;
private final Map<String, CustomOperator> builtInOperators;
private Map<String, CustomOperator> customOperators = new HashMap<String, CustomOperator>();
private final List<Character> validOperatorSymbols;
private final boolean highUnaryPrecedence;
private String expression;
/**
* Create a new ExpressionBuilder
*
* @param expression
* the expression to evaluate
*/
public ExpressionBuilder(String expression) {
this.expression = expression;
highUnaryPrecedence = System.getProperty(PROPERTY_UNARY_HIGH_PRECEDENCE) == null || !System.getProperty(PROPERTY_UNARY_HIGH_PRECEDENCE).equals("false");
customFunctions = getBuiltinFunctions();
builtInOperators = getBuiltinOperators();
validOperatorSymbols = getValidOperators();
}
private List<Character> getValidOperators() {
return Arrays.asList('!', '#', '�', '$', '&', ';', ':', '~', '<', '>', '|', '=');
}
private Map<String, CustomOperator> getBuiltinOperators() {
CustomOperator add = new CustomOperator("+") {
@Override
protected double applyOperation(double[] values) {
return values[0] + values[1];
}
};
CustomOperator sub = new CustomOperator("-") {
@Override
protected double applyOperation(double[] values) {
return values[0] - values[1];
}
};
CustomOperator div = new CustomOperator("/", 3) {
@Override
protected double applyOperation(double[] values) {
return values[0] / values[1];
}
};
CustomOperator mul = new CustomOperator("*", 3) {
@Override
protected double applyOperation(double[] values) {
return values[0] * values[1];
}
};
CustomOperator mod = new CustomOperator("%", true, 3) {
@Override
protected double applyOperation(double[] values) {
return values[0] % values[1];
}
};
CustomOperator umin = new CustomOperator("\'", false, this.highUnaryPrecedence ? 7 : 5, 1) {
@Override
protected double applyOperation(double[] values) {
return -values[0];
}
};
CustomOperator pow = new CustomOperator("^", false, 5, 2) {
@Override
protected double applyOperation(double[] values) {
return Math.pow(values[0], values[1]);
}
};
Map<String, CustomOperator> operations = new HashMap<String, CustomOperator>();
operations.put("+", add);
operations.put("-", sub);
operations.put("*", mul);
operations.put("/", div);
operations.put("\'", umin);
operations.put("^", pow);
operations.put("%", mod);
return operations;
}
private Map<String, CustomFunction> getBuiltinFunctions() {
try {
Map<String, CustomFunction> customFunctions = new HashMap<String, CustomFunction>();
// Initialize using enum
for (BultinFunction.StandardFunctions name : BultinFunction.StandardFunctions.values()) {
customFunctions.put(name.getFunctionName(), new BultinFunction(name));
}
return customFunctions;
} catch (InvalidCustomFunctionException e) {
// this should not happen...
throw new RuntimeException(e);
}
}
/**
* build a new {@link Calculable} from the expression using the supplied variables
*
* @return the {@link Calculable} which can be used to evaluate the expression
* @throws UnknownFunctionException
* when an unrecognized function name is used in the expression
* @throws UnparsableExpressionException
* if the expression could not be parsed
*/
public Calculable build() throws UnknownFunctionException, UnparsableExpressionException {
for (CustomOperator op : customOperators.values()) {
for (int i = 0; i < op.symbol.length(); i++) {
if (!validOperatorSymbols.contains(op.symbol.charAt(i))) {
throw new UnparsableExpressionException("" + op.symbol
+ " is not a valid symbol for an operator please choose from: !,#,§,$,&,;,:,~,<,>,|,=");
}
}
}
for (String varName : variables.keySet()) {
if (customFunctions.containsKey(varName)) {
throw new UnparsableExpressionException("Variable '" + varName
+ "' cannot have the same name as a function");
}
}
builtInOperators.putAll(customOperators);
return RPNConverter.toRPNExpression(expression, variables, customFunctions, builtInOperators);
}
/**
* add a custom function instance for the evaluator to recognize
*
* @param function
* the {@link CustomFunction} to add
* @return the {@link ExpressionBuilder} instance
*/
public ExpressionBuilder withCustomFunction(CustomFunction function) {
customFunctions.put(function.name, function);
return this;
}
public ExpressionBuilder withCustomFunctions(Collection<CustomFunction> functions) {
for (CustomFunction f : functions) {
withCustomFunction(f);
}
return this;
}
/**
* set the value for a variable
*
* @param variableName
* the variable name e.g. "x"
* @param value
* the value e.g. 2.32d
* @return the {@link ExpressionBuilder} instance
*/
public ExpressionBuilder withVariable(String variableName, double value) {
variables.put(variableName, value);
return this;
}
/**
* set the variables names used in the expression without setting their values
*
* @param variableNames
* vararg {@link String} of the variable names used in the expression
* @return the ExpressionBuilder instance
*/
public ExpressionBuilder withVariableNames(String... variableNames) {
for (String variable : variableNames) {
variables.put(variable, null);
}
return this;
}
/**
* set the values for variables
*
* @param variableMap
* a map of variable names to variable values
* @return the {@link ExpressionBuilder} instance
*/
public ExpressionBuilder withVariables(Map<String, Double> variableMap) {
for (Entry<String, Double> v : variableMap.entrySet()) {
variables.put(v.getKey(), v.getValue());
}
return this;
}
/**
* set a {@link CustomOperator} to be used in the expression
*
* @param operation
* the {@link CustomOperator} to be used
* @return the {@link ExpressionBuilder} instance
*/
public ExpressionBuilder withOperation(CustomOperator operation) {
customOperators.put(operation.symbol, operation);
return this;
}
/**
* set a {@link Collection} of {@link CustomOperator} to use in the expression
*
* @param operations
* the {@link Collection} of {@link CustomOperator} to use
* @return the {@link ExpressionBuilder} instance
*/
public ExpressionBuilder withOperations(Collection<CustomOperator> operations) {
for (CustomOperator op : operations) {
withOperation(op);
}
return this;
}
/**
* set the mathematical expression for parsing
*
* @param expression
* a mathematical expression
* @return the {@link ExpressionBuilder} instance
*/
public ExpressionBuilder withExpression(String expression) {
this.expression = expression;
return this;
}
}