blob: ff0fd28b4e11e3799fb1c8a2b0c51978b187670c [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2015, 2020 CEA LIST.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dirk Fauth <dirk.fauth@googlemail.com> - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.nebula.widgets.nattable.formula;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.eclipse.nebula.widgets.nattable.coordinate.IndexCoordinate;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.formula.function.FunctionException;
import org.eclipse.nebula.widgets.nattable.formula.function.FunctionValue;
import org.eclipse.nebula.widgets.nattable.formula.function.ProductFunction;
import org.eclipse.nebula.widgets.nattable.formula.function.SumFunction;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class FormulaParserTest {
private static Locale defaultLocale;
@BeforeClass
public static void setup() {
defaultLocale = Locale.getDefault();
Locale.setDefault(Locale.ENGLISH);
}
@AfterClass
public static void tearDown() {
Locale.setDefault(defaultLocale);
}
IDataProvider dataProvider = new TwoDimensionalArrayDataProvider(new Object[10][10]);
FormulaParser parser = new FormulaParser(this.dataProvider);
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnMissingOperator() {
this.parser.parseFunction("5 3");
}
@Test
public void shouldParseNativeSumFunctionString() {
FunctionValue result = this.parser.parseFunction("5 + 3");
assertEquals(new BigDecimal(5 + 3), result.getValue());
result = this.parser.parseFunction("5+3");
assertEquals(new BigDecimal(5 + 3), result.getValue());
result = this.parser.parseFunction("5+3+15");
assertEquals(new BigDecimal(5 + 3 + 15), result.getValue());
result = this.parser.parseFunction("5+3+5+3");
assertEquals(new BigDecimal(5 + 3 + 5 + 3), result.getValue());
}
@Test
public void shouldParseNativeSumWithDecimals() {
FunctionValue result = this.parser.parseFunction("5.4 + 3.2");
assertEquals(new BigDecimal("8.6"), result.getValue());
}
@Test
public void shouldParseNativeSubtractFunctionString() {
FunctionValue result = this.parser.parseFunction("5 - 3");
assertEquals(new BigDecimal(5 - 3), result.getValue());
result = this.parser.parseFunction("15 - 3 -2");
assertEquals(new BigDecimal(15 - 3 - 2), result.getValue());
result = this.parser.parseFunction("25 - 3-15-10");
assertEquals(new BigDecimal(25 - 3 - 15 - 10), result.getValue());
result = this.parser.parseFunction("-42");
assertEquals(new BigDecimal(-42), result.getValue());
result = this.parser.parseFunction("-10-5");
assertEquals(new BigDecimal(-10 - 5), result.getValue());
}
@Test
public void shouldParseNativeSumAndSubtractionFunctionString() {
FunctionValue result = this.parser.parseFunction("- 5 + 3");
assertEquals(new BigDecimal(-5 + 3), result.getValue());
result = this.parser.parseFunction("15 - 3 +2");
assertEquals(new BigDecimal(15 - 3 + 2), result.getValue());
result = this.parser.parseFunction("-25 + 5-15");
assertEquals(new BigDecimal(-25 + 5 - 15), result.getValue());
result = this.parser.parseFunction("40+10-25-5");
assertEquals(new BigDecimal(40 + 10 - 25 - 5), result.getValue());
}
@Test
public void shouldParseNativeProductFunctionString() {
FunctionValue result = this.parser.parseFunction("5 * 3");
assertEquals(new BigDecimal(5 * 3), result.getValue());
result = this.parser.parseFunction("5 * 3 *2");
assertEquals(new BigDecimal(5 * 3 * 2), result.getValue());
result = this.parser.parseFunction("5 * 3*20 * 7");
assertEquals(new BigDecimal(5 * 3 * 20 * 7), result.getValue());
result = this.parser.parseFunction("5 * -3");
assertEquals(new BigDecimal(5 * -3), result.getValue());
result = this.parser.parseFunction("-5 * 3");
assertEquals(new BigDecimal(-5 * 3), result.getValue());
result = this.parser.parseFunction("5 * -3 * 7");
assertEquals(new BigDecimal(5 * -3 * 7), result.getValue());
}
@Test
public void shouldParseNativeDifferenceFunctionString() {
FunctionValue result = this.parser.parseFunction("15 / 3");
assertEquals(new BigDecimal(15 / 3), result.getValue());
result = this.parser.parseFunction("20 / 2 /5");
assertEquals(new BigDecimal(20 / 2 / 5), result.getValue());
result = this.parser.parseFunction("5 / 2");
assertEquals(new BigDecimal(5d / 2), result.getValue());
result = this.parser.parseFunction("100 / 4 / 5 / 2");
assertEquals(new BigDecimal(100d / 4 / 5 / 2), result.getValue());
result = this.parser.parseFunction("-15 / 3");
assertEquals(new BigDecimal(-15 / 3), result.getValue());
result = this.parser.parseFunction("-5 / 3");
assertEquals(new BigDecimal(-5).divide(new BigDecimal(3), 9, RoundingMode.HALF_UP), result.getValue());
result = this.parser.parseFunction("5 / -3");
assertEquals(new BigDecimal(5).divide(new BigDecimal(-3), 9, RoundingMode.HALF_UP), result.getValue());
}
@Test
public void shouldParseMixedNativeFunction() {
FunctionValue result = this.parser.parseFunction("5 + 3 * 2");
assertEquals(new BigDecimal(5 + 3 * 2), result.getValue());
assertEquals(new BigDecimal(11), result.getValue());
result = this.parser.parseFunction("5 * 3 + 2");
assertEquals(new BigDecimal(5 * 3 + 2), result.getValue());
assertEquals(new BigDecimal(5 * 3 + 2), result.getValue());
result = this.parser.parseFunction("15/3*6-4+3");
assertEquals(new BigDecimal(15 / 3 * 6 - 4 + 3), result.getValue());
assertEquals(new BigDecimal(29), result.getValue());
result = this.parser.parseFunction("2*5+10/4-3");
assertEquals(new BigDecimal(2 * 5 + 10d / 4 - 3), result.getValue());
assertEquals(new BigDecimal(9.5), result.getValue());
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnclosedParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
this.parser.processParenthesis("(5+3", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnopenedParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
this.parser.processParenthesis("5+3)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
}
@Test
public void shouldReplaceOneParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
String processed = this.parser.processParenthesis("(5+3)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal(5 + 3), replacements.get(0).getValue());
replacements = new HashMap<>();
processed = this.parser.processParenthesis("10 + (5+3)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("10 + {0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal(5 + 3), replacements.get(0).getValue());
replacements = new HashMap<>();
processed = this.parser.processParenthesis("(5+3) + 42", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0} + 42", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal(5 + 3), replacements.get(0).getValue());
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnclosedTwoParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
this.parser.processParenthesis("(5+3) + (7 - 2", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnopenedTwoParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
this.parser.processParenthesis("(5+3) + 7 - 3)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
}
@Test
public void shouldReplaceTwoParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
String processed = this.parser.processParenthesis("(5+3) - (5+5)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0} - {1}", processed);
assertEquals(2, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertTrue(replacements.get(1) instanceof SumFunction);
assertEquals(new BigDecimal(5 + 3), replacements.get(0).getValue());
assertEquals(new BigDecimal(5 + 5), replacements.get(1).getValue());
replacements = new HashMap<>();
processed = this.parser.processParenthesis("(5+3) + 10 + (5+5)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0} + 10 + {1}", processed);
assertEquals(2, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertTrue(replacements.get(1) instanceof SumFunction);
assertEquals(new BigDecimal(5 + 3), replacements.get(0).getValue());
assertEquals(new BigDecimal(5 + 5), replacements.get(1).getValue());
replacements = new HashMap<>();
processed = this.parser.processParenthesis("10 + (5+3) + (5+5) + 42", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("10 + {0} + {1} + 42", processed);
assertEquals(2, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertTrue(replacements.get(1) instanceof SumFunction);
assertEquals(new BigDecimal(5 + 3), replacements.get(0).getValue());
assertEquals(new BigDecimal(5 + 5), replacements.get(1).getValue());
replacements = new HashMap<>();
processed = this.parser.processParenthesis("10 + (5+3) + 42 + (5+5)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("10 + {0} + 42 + {1}", processed);
assertEquals(2, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertTrue(replacements.get(1) instanceof SumFunction);
assertEquals(new BigDecimal(5 + 3), replacements.get(0).getValue());
assertEquals(new BigDecimal(5 + 5), replacements.get(1).getValue());
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnclosedNestedParanthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
this.parser.processParenthesis("((5+3 - 3)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnopenedNestedParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
this.parser.processParenthesis("(5+3) + 7 - 3)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
}
@Test
public void shouldReplaceNestedParenthesis() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
String processed = this.parser.processParenthesis("((5+3)*2)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof ProductFunction);
assertEquals(new BigDecimal(((5 + 3) * 2)), replacements.get(0).getValue());
replacements = new HashMap<>();
processed = this.parser.processParenthesis("(2*(5+3))", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof ProductFunction);
assertEquals(new BigDecimal((2 * (5 + 3))), replacements.get(0).getValue());
replacements = new HashMap<>();
processed = this.parser.processParenthesis("5+(2*(5+3))", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("5+{0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof ProductFunction);
assertEquals(new BigDecimal((2 * (5 + 3))), replacements.get(0).getValue());
}
@Test
public void shouldMatchPlaceholderRegex() {
assertTrue("{0}".matches(FormulaParser.PLACEHOLDER_REGEX));
assertTrue("{1}".matches(FormulaParser.PLACEHOLDER_REGEX));
assertTrue("{10}".matches(FormulaParser.PLACEHOLDER_REGEX));
assertTrue("{42}".matches(FormulaParser.PLACEHOLDER_REGEX));
assertTrue("{100}".matches(FormulaParser.PLACEHOLDER_REGEX));
}
@Test
public void shouldParseComplexNativeFunctionString() {
FunctionValue result = this.parser.parseFunction("5+(2*(5+3))");
assertEquals(new BigDecimal(5 + (2 * (5 + 3))), result.getValue());
result = this.parser.parseFunction("(5 + 3) * 2");
assertEquals(new BigDecimal((5 + 3) * 2), result.getValue());
assertEquals(new BigDecimal(16), result.getValue());
result = this.parser.parseFunction("(5 + 3) * 2");
assertEquals(new BigDecimal((5 + 3) * 2), result.getValue());
assertEquals(new BigDecimal(16), result.getValue());
result = this.parser.parseFunction("30/(3+2)-2*3");
assertEquals(new BigDecimal(30 / (3 + 2) - 2 * 3), result.getValue());
assertEquals(new BigDecimal(0), result.getValue());
}
@Test
public void shouldMatchOperatorRegex() {
assertTrue("+".matches(FormulaParser.OPERATOR_REGEX));
assertTrue("-".matches(FormulaParser.OPERATOR_REGEX));
assertTrue("*".matches(FormulaParser.OPERATOR_REGEX));
assertTrue("/".matches(FormulaParser.OPERATOR_REGEX));
assertTrue("^".matches(FormulaParser.OPERATOR_REGEX));
}
@Test
public void shouldMatchDigiRegex() {
assertTrue("5".matches(FormulaParser.DIGIT_REGEX));
assertTrue("15".matches(FormulaParser.DIGIT_REGEX));
assertTrue("426".matches(FormulaParser.DIGIT_REGEX));
}
@Test
public void shouldMatchLocalizedDigitRegex() {
assertTrue("5".matches(this.parser.localizedDigitRegex));
assertTrue("15".matches(this.parser.localizedDigitRegex));
assertTrue("426".matches(this.parser.localizedDigitRegex));
assertTrue("1.5".matches(this.parser.localizedDigitRegex));
assertTrue("42.75".matches(this.parser.localizedDigitRegex));
this.parser.setDecimalFormat((DecimalFormat) DecimalFormat.getInstance(Locale.GERMAN));
assertTrue("1,5".matches(this.parser.localizedDigitRegex));
assertTrue("42,75".matches(this.parser.localizedDigitRegex));
this.parser.setDecimalFormat((DecimalFormat) DecimalFormat.getInstance());
assertTrue("1.5".matches(this.parser.localizedDigitRegex));
assertTrue("42.75".matches(this.parser.localizedDigitRegex));
}
@Test
public void shouldParseNativePowerFunction() {
FunctionValue result = this.parser.parseFunction("5 ^ 2");
assertEquals(new BigDecimal(5).pow(2), result.getValue());
result = this.parser.parseFunction("5+3^2");
assertEquals(new BigDecimal(14), result.getValue());
}
@Test
public void shouldProcessPowerFirst() {
FunctionValue result = this.parser.parseFunction("(4 * 8) ^ 2");
assertEquals(new BigDecimal(1024), result.getValue());
result = this.parser.parseFunction("4 * 8 ^ 2");
assertEquals(new BigDecimal(256), result.getValue());
}
@Test
public void shouldMatchFunctionRegex() {
assertTrue(this.parser.functionPattern.matcher("AVERAGE(5+3)").find());
assertTrue(this.parser.functionPattern.matcher("AVERAGE()").find());
assertTrue(this.parser.functionPattern.matcher("AVERAGE(5; 3)").find());
assertTrue(this.parser.functionPattern.matcher("AVERAGE(5;3)").find());
assertTrue(this.parser.functionPattern.matcher("AVERAGE(5; 3; -1)").find());
assertTrue(this.parser.functionPattern.matcher("NEGATE(5+3)").find());
assertTrue(this.parser.functionPattern.matcher("NEGATE()").find());
assertTrue(this.parser.functionPattern.matcher("NEGATE(5; 3)").find());
assertTrue(this.parser.functionPattern.matcher("NEGATE(5;3)").find());
assertTrue(this.parser.functionPattern.matcher("NEGATE(5; 3; -1)").find());
assertTrue(this.parser.functionPattern.matcher("POWER(5+3)").find());
assertTrue(this.parser.functionPattern.matcher("POWER()").find());
assertTrue(this.parser.functionPattern.matcher("POWER(5; 3)").find());
assertTrue(this.parser.functionPattern.matcher("POWER(5;3)").find());
assertTrue(this.parser.functionPattern.matcher("POWER(5; 3; -1)").find());
assertTrue(this.parser.functionPattern.matcher("PRODUCT(5+3)").find());
assertTrue(this.parser.functionPattern.matcher("PRODUCT()").find());
assertTrue(this.parser.functionPattern.matcher("PRODUCT(5; 3)").find());
assertTrue(this.parser.functionPattern.matcher("PRODUCT(5;3)").find());
assertTrue(this.parser.functionPattern.matcher("PRODUCT(5; 3; -1)").find());
assertTrue(this.parser.functionPattern.matcher("QUOTIENT(5+3)").find());
assertTrue(this.parser.functionPattern.matcher("QUOTIENT()").find());
assertTrue(this.parser.functionPattern.matcher("QUOTIENT(5; 3)").find());
assertTrue(this.parser.functionPattern.matcher("QUOTIENT(5;3)").find());
assertTrue(this.parser.functionPattern.matcher("QUOTIENT(5; 3; -1)").find());
assertTrue(this.parser.functionPattern.matcher("SQRT(5+3)").find());
assertTrue(this.parser.functionPattern.matcher("SQRT()").find());
assertTrue(this.parser.functionPattern.matcher("SQRT(5; 3)").find());
assertTrue(this.parser.functionPattern.matcher("SQRT(5;3)").find());
assertTrue(this.parser.functionPattern.matcher("SQRT(5; 3; -1)").find());
assertTrue(this.parser.functionPattern.matcher("SUM(5+3)").find());
assertTrue(this.parser.functionPattern.matcher("SUM()").find());
assertTrue(this.parser.functionPattern.matcher("SUM(5; 3)").find());
assertTrue(this.parser.functionPattern.matcher("SUM(5;3)").find());
assertTrue(this.parser.functionPattern.matcher("SUM(5; 3; -1)").find());
}
@Test
public void shouldParseFunction() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
String processed = this.parser.processFunctions("SUM(5; 3; 2)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal((5 + 3 + 2)), replacements.get(0).getValue());
replacements = new HashMap<>();
processed = this.parser.processFunctions("42 + SUM(5; 3; 2)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("42 + {0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal((5 + 3 + 2)), replacements.get(0).getValue());
replacements = new HashMap<>();
processed = this.parser.processFunctions("SUM(5; 3; 2) - 42", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0} - 42", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal((5 + 3 + 2)), replacements.get(0).getValue());
}
@Test
public void shouldParseMultipleFunction() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
String processed = this.parser.processFunctions("SUM(5; 3; 2) - PRODUCT(2; 5)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0} - {1}", processed);
assertEquals(2, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertTrue(replacements.get(1) instanceof ProductFunction);
assertEquals(new BigDecimal((5 + 3 + 2)), replacements.get(0).getValue());
assertEquals(new BigDecimal((2 * 5)), replacements.get(1).getValue());
}
@Test
public void shouldParseNestedFunctions() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
String processed = this.parser.processFunctions("SUM(PRODUCT(2; 5); QUOTIENT(10; 5))", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal((2 * 5) + (10 / 5)), replacements.get(0).getValue());
replacements = new HashMap<>();
processed = this.parser.processFunctions("SUM(PRODUCT(2; SUM(2; 2)); 5)", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
assertEquals("{0}", processed);
assertEquals(1, replacements.size());
assertTrue(replacements.get(0) instanceof SumFunction);
assertEquals(new BigDecimal((2 * (2 + 2)) + 5), replacements.get(0).getValue());
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnNotClosingFunction() {
Map<Integer, FunctionValue> replacements = new HashMap<>();
this.parser.processFunctions("SUM(5; 3; 2", replacements, new HashMap<IndexCoordinate, Set<IndexCoordinate>>(), null);
}
@Test
public void shouldProcessFunction() {
FunctionValue result = this.parser.parseFunction("SUM(5; 3; 2)");
assertEquals(new BigDecimal(10), result.getValue());
result = this.parser.parseFunction("PRODUCT(7; 6)");
assertEquals(new BigDecimal(42), result.getValue());
result = this.parser.parseFunction("AVERAGE(7; 6; 13; 14)");
assertEquals(new BigDecimal(10), result.getValue());
result = this.parser.parseFunction("QUOTIENT(15; 3)");
assertEquals(new BigDecimal(5), result.getValue());
result = this.parser.parseFunction("POWER(5; 3)");
assertEquals(new BigDecimal(125), result.getValue());
result = this.parser.parseFunction("NEGATE(42)");
assertEquals(new BigDecimal(-42), result.getValue());
result = this.parser.parseFunction("SQRT(144)");
assertEquals(BigDecimal.valueOf(12d), result.getValue());
}
@Test
public void shouldProcessMultipleFunctions() {
FunctionValue result = this.parser.parseFunction("SUM(5; 3; 2) - PRODUCT(2; 5)");
assertEquals(new BigDecimal(0), result.getValue());
}
@Test
public void shouldProcessNestedFunctions() {
FunctionValue result = this.parser.parseFunction("SUM(PRODUCT(2; 5); QUOTIENT(10; 5))");
assertEquals(new BigDecimal(12), result.getValue());
result = this.parser.parseFunction("SUM(PRODUCT(2; SUM(2; 2)); 5)");
assertEquals(new BigDecimal(13), result.getValue());
}
@Test
public void shouldMatchReferenceRegex() {
assertTrue("A7".matches(FormulaParser.REFERENCE_REGEX));
assertTrue("Z9".matches(FormulaParser.REFERENCE_REGEX));
assertTrue("AZ1".matches(FormulaParser.REFERENCE_REGEX));
assertTrue("C27".matches(FormulaParser.REFERENCE_REGEX));
assertTrue("AC420".matches(FormulaParser.REFERENCE_REGEX));
}
@Test
public void shouldNotMatchReferenceRegex() {
assertFalse("A".matches(FormulaParser.REFERENCE_REGEX));
assertFalse("9".matches(FormulaParser.REFERENCE_REGEX));
}
@Test
public void shouldMatchReferenceRangeRegex() {
assertTrue("A7:C7".matches(FormulaParser.REFERENCE_RANGE_REGEX));
assertTrue("Z9:Z10".matches(FormulaParser.REFERENCE_RANGE_REGEX));
assertTrue("AZ1:AZ12".matches(FormulaParser.REFERENCE_RANGE_REGEX));
}
@Test
public void shouldNotMatchReferenceRangeRegex() {
assertFalse("A:C7".matches(FormulaParser.REFERENCE_RANGE_REGEX));
assertFalse("Z9:Z".matches(FormulaParser.REFERENCE_RANGE_REGEX));
assertFalse("1:AZ12".matches(FormulaParser.REFERENCE_RANGE_REGEX));
assertFalse("AZ1:12".matches(FormulaParser.REFERENCE_RANGE_REGEX));
assertFalse("A1:A2:A3".matches(FormulaParser.REFERENCE_RANGE_REGEX));
}
@Test
public void shouldMatchRowRangeRegex() {
assertTrue("7:7".matches(FormulaParser.ROW_RANGE_REGEX));
assertTrue("9:10".matches(FormulaParser.ROW_RANGE_REGEX));
assertTrue("1:112".matches(FormulaParser.ROW_RANGE_REGEX));
}
@Test
public void shouldNotMatchRowRangeRegex() {
assertFalse("A1:C7".matches(FormulaParser.ROW_RANGE_REGEX));
assertFalse("9:Z".matches(FormulaParser.ROW_RANGE_REGEX));
assertFalse("1:AZ12".matches(FormulaParser.ROW_RANGE_REGEX));
assertFalse("AZ1:12".matches(FormulaParser.ROW_RANGE_REGEX));
}
@Test
public void shouldMatchColumnRangeRegex() {
assertTrue("A:C".matches(FormulaParser.COLUMN_RANGE_REGEX));
assertTrue("Z:Z".matches(FormulaParser.COLUMN_RANGE_REGEX));
assertTrue("A:AZ".matches(FormulaParser.COLUMN_RANGE_REGEX));
}
@Test
public void shouldNotMatchColumnRangeRegex() {
assertFalse("A7:C7".matches(FormulaParser.COLUMN_RANGE_REGEX));
assertFalse("9:Z".matches(FormulaParser.COLUMN_RANGE_REGEX));
assertFalse("1:AZ12".matches(FormulaParser.COLUMN_RANGE_REGEX));
assertFalse("AZ1:12".matches(FormulaParser.COLUMN_RANGE_REGEX));
}
@Test
public void shouldMatchRangeRegex() {
assertTrue("A:C".matches(FormulaParser.RANGE_REGEX));
assertTrue("1:1".matches(FormulaParser.RANGE_REGEX));
assertTrue("A1:C4".matches(FormulaParser.RANGE_REGEX));
}
@Test
public void shouldNotMatchRangeRegex() {
assertFalse("A:1".matches(FormulaParser.RANGE_REGEX));
assertFalse("1:A".matches(FormulaParser.RANGE_REGEX));
assertFalse("A:C4".matches(FormulaParser.RANGE_REGEX));
assertFalse("A1:4".matches(FormulaParser.RANGE_REGEX));
assertFalse("A1:A4:B3".matches(FormulaParser.RANGE_REGEX));
}
@Test
public void shouldEvaluateReference() {
int[] coords = this.parser.evaluateReference("A1");
assertEquals(0, coords[0]);
assertEquals(0, coords[1]);
coords = this.parser.evaluateReference("Z42");
assertEquals(25, coords[0]);
assertEquals(41, coords[1]);
coords = this.parser.evaluateReference("AZ213");
assertEquals(51, coords[0]);
assertEquals(212, coords[1]);
coords = this.parser.evaluateReference("BA1");
assertEquals(52, coords[0]);
assertEquals(0, coords[1]);
coords = this.parser.evaluateReference("ZZ1");
assertEquals(701, coords[0]);
assertEquals(0, coords[1]);
coords = this.parser.evaluateReference("AAA1");
assertEquals(702, coords[0]);
assertEquals(0, coords[1]);
}
@Test
public void shouldConvertIndexToColumnString() {
assertEquals("A", this.parser.convertIndexToColumnString(0));
assertEquals("Z", this.parser.convertIndexToColumnString(25));
assertEquals("AZ", this.parser.convertIndexToColumnString(51));
assertEquals("BA", this.parser.convertIndexToColumnString(52));
assertEquals("BJ", this.parser.convertIndexToColumnString(61));
assertEquals("AAA", this.parser.convertIndexToColumnString(702));
}
@Test
public void shouldEvaluateReferences() {
this.dataProvider.setDataValue(0, 0, 5);
this.dataProvider.setDataValue(0, 1, 5);
this.dataProvider.setDataValue(0, 2, "=SUM(A1;A2)");
this.dataProvider.setDataValue(1, 0, 3);
this.dataProvider.setDataValue(1, 1, 3);
this.dataProvider.setDataValue(1, 2, "=SUM(B1;B2)");
FunctionValue result = this.parser.parseFunction("SUM(A1;A2)");
assertEquals(new BigDecimal(10), result.getValue());
result = this.parser.parseFunction("SUM(B1;B2)");
assertEquals(new BigDecimal(6), result.getValue());
result = this.parser.parseFunction("AVERAGE(A3;B3)");
assertEquals(new BigDecimal(8), result.getValue());
}
@Test
public void shouldEvaluateRowRange() {
for (int row = 0; row < this.dataProvider.getRowCount(); row++) {
for (int column = 0; column < this.dataProvider.getColumnCount(); column++) {
this.dataProvider.setDataValue(column, row, row + 1);
}
}
FunctionValue result = this.parser.parseFunction("SUM(5:5)");
assertEquals(new BigDecimal(50), result.getValue());
result = this.parser.parseFunction("SUM(5:6)");
assertEquals(new BigDecimal(110), result.getValue());
result = this.parser.parseFunction("SUM(5:8)");
assertEquals(new BigDecimal(260), result.getValue());
result = this.parser.parseFunction("SUM(8:5)");
assertEquals(new BigDecimal(260), result.getValue());
}
@Test
public void shouldEvaluateColumnRange() {
for (int column = 0; column < this.dataProvider.getColumnCount(); column++) {
for (int row = 0; row < this.dataProvider.getRowCount(); row++) {
this.dataProvider.setDataValue(column, row, column + 1);
}
}
FunctionValue result = this.parser.parseFunction("SUM(E:E)");
assertEquals(new BigDecimal(50), result.getValue());
result = this.parser.parseFunction("SUM(E:F)");
assertEquals(new BigDecimal(110), result.getValue());
result = this.parser.parseFunction("SUM(E:H)");
assertEquals(new BigDecimal(260), result.getValue());
result = this.parser.parseFunction("SUM(H:E)");
assertEquals(new BigDecimal(260), result.getValue());
}
@Test
public void shouldEvaluateReferenceRange() {
for (int row = 0; row < this.dataProvider.getRowCount(); row++) {
for (int column = 0; column < this.dataProvider.getColumnCount(); column++) {
this.dataProvider.setDataValue(column, row, row + 1);
}
}
FunctionValue result = this.parser.parseFunction("=SUM(C3:F5)");
assertEquals(new BigDecimal(48), result.getValue());
result = this.parser.parseFunction("=SUM(F5:F8)");
assertEquals(new BigDecimal(26), result.getValue());
result = this.parser.parseFunction("=SUM(C5:F5)");
assertEquals(new BigDecimal(20), result.getValue());
result = this.parser.parseFunction("=SUM(F5:C3)");
assertEquals(new BigDecimal(48), result.getValue());
}
@Test
public void shouldEvaluateDecimalCalculation() {
this.dataProvider.setDataValue(0, 0, "3.4");
this.dataProvider.setDataValue(1, 0, "4");
FunctionValue result = this.parser.parseFunction("=PRODUCT(A1;B1)");
assertEquals(new BigDecimal("13.6"), result.getValue());
this.parser.setDecimalFormat((DecimalFormat) DecimalFormat.getInstance(Locale.GERMAN));
this.dataProvider.setDataValue(0, 0, "3,4");
this.dataProvider.setDataValue(1, 0, "4");
result = this.parser.parseFunction("=PRODUCT(A1;B1)");
assertEquals(new BigDecimal("13.6"), result.getValue());
}
@Test
public void shouldEvaluateInteger() {
assertTrue(this.parser.isIntegerValue(new BigDecimal("0")));
assertTrue(this.parser.isIntegerValue(new BigDecimal("5")));
assertTrue(this.parser.isIntegerValue(new BigDecimal("10")));
assertTrue(this.parser.isIntegerValue(new BigDecimal("240")));
assertTrue(this.parser.isIntegerValue(new BigDecimal("3.0")));
assertFalse(this.parser.isIntegerValue(new BigDecimal("2.4")));
assertFalse(this.parser.isIntegerValue(new BigDecimal("12.34")));
assertFalse(this.parser.isIntegerValue(new BigDecimal("12.01")));
}
@Test(expected = FunctionException.class)
public void shouldThrowExceptionOnUnsupportedTypes() {
this.dataProvider.setDataValue(0, 0, "3");
this.dataProvider.setDataValue(1, 0, "a");
this.parser.parseFunction("=PRODUCT(A1;B1)");
}
@Test(expected = FunctionException.class)
public void shouldThrowExceptionOnUnsupportedTypesInRange() {
this.dataProvider.setDataValue(0, 0, "3");
this.dataProvider.setDataValue(1, 0, "a");
this.parser.parseFunction("=PRODUCT(A1:B1)");
}
@Test(expected = FunctionException.class)
public void shouldThrowExceptionOnNegativeSqrt() {
this.parser.parseFunction("=SQRT(-9)");
}
@Test
public void shouldEvaluateSqrt() {
this.dataProvider.setDataValue(0, 1, "34.81");
FunctionValue result = this.parser.parseFunction("=SQRT(36)");
assertEquals(new BigDecimal("6.0"), result.getValue());
result = this.parser.parseFunction("=SQRT(A2)");
assertTrue(result.getValue().toString().startsWith("5.9"));
result = this.parser.parseFunction("=SQRT(POWER(2.5;2))");
assertEquals(new BigDecimal("2.5"), result.getValue());
}
@Test(expected = FunctionException.class)
public void shouldThrowExceptionOnWrongArgumentsMod() {
this.parser.parseFunction("=MOD(9)").getValue();
}
@Test
public void shouldEvaluateMod() {
this.dataProvider.setDataValue(0, 2, "6");
this.dataProvider.setDataValue(1, 2, "2.5");
FunctionValue result = this.parser.parseFunction("=MOD(6;4)");
assertEquals(new BigDecimal("2"), result.getValue());
result = this.parser.parseFunction("=MOD(6;3)");
assertEquals(new BigDecimal("0"), result.getValue());
result = this.parser.parseFunction("=MOD(A3;B3)");
assertEquals(new BigDecimal("1.0"), result.getValue());
}
@Test
public void shouldUpdateReferences() {
assertEquals("=PRODUCT(C1:D1)", this.parser.updateReferences("=PRODUCT(A1:B1)", 2, 0, 4, 0));
assertEquals("=PRODUCT(SUM(E4;E5):D1)", this.parser.updateReferences("=PRODUCT(SUM(C4;C5):B1)", 2, 0, 4, 0));
assertEquals("=SUM(G7;SUM(E6;E7):D3)", this.parser.updateReferences("=SUM(E5;SUM(C4;C5):B1)", 2, 0, 4, 2));
assertEquals("=PRODUCT(C3:C3)", this.parser.updateReferences("=PRODUCT(A1:A1)", 2, 0, 4, 2));
assertEquals("=PRODUCT(A1:B1)", this.parser.updateReferences("=PRODUCT(C1:D1)", 4, 0, 2, 0));
assertEquals("=PRODUCT(A1:B1)", this.parser.updateReferences("=PRODUCT(C3:D3)", 4, 2, 2, 0));
}
@Test
public void shouldEvaluateEmptyReferences() {
this.dataProvider.setDataValue(1, 1, "=4");
// both references are not set, result should be 0
assertEquals(new BigDecimal("0"), this.parser.parseFunction("=A1*B1").getValue());
assertEquals(new BigDecimal("0"), this.parser.parseFunction("=C1+D1").getValue());
// only one reference is set, result should be the value of that cell
assertEquals(new BigDecimal("4"), this.parser.parseFunction("=A2*B2").getValue());
}
@Test(expected = FunctionException.class)
public void shouldThrowExceptionOnSelfReference() {
this.dataProvider.setDataValue(0, 0, "=A1");
this.parser.parseFunction("=A1");
}
@Test(expected = FunctionException.class)
public void shouldNoticeSimpleCycleReference() {
this.dataProvider.setDataValue(0, 0, "=B1");
this.dataProvider.setDataValue(1, 0, "=A1");
this.parser.parseFunction("=A1");
}
@Test(expected = FunctionException.class)
public void shouldNoticeCycleReference() {
this.dataProvider.setDataValue(0, 0, "=B1");
this.dataProvider.setDataValue(1, 0, "=C1");
this.dataProvider.setDataValue(2, 0, "=A1");
this.parser.parseFunction("=A1");
}
@Test
public void shouldEvaluateMultiReferences() {
this.dataProvider.setDataValue(0, 0, "5");
this.dataProvider.setDataValue(1, 0, "3");
this.dataProvider.setDataValue(3, 0, "=A1+B1");
assertEquals(new BigDecimal("0.625"), this.parser.parseFunction("=A1/D1").getValue());
}
@Test(expected = FunctionException.class)
public void shouldNoticeCycleReferenceWithRange() {
this.dataProvider.setDataValue(0, 0, "5");
this.dataProvider.setDataValue(0, 1, "5");
this.dataProvider.setDataValue(0, 2, "=SUM(A1:A2)");
this.dataProvider.setDataValue(1, 0, "3");
this.dataProvider.setDataValue(1, 1, "3");
this.dataProvider.setDataValue(1, 2, "=SUM(B1:B2)");
// this should fail for circular dependencies
this.dataProvider.setDataValue(2, 2, "=SUM(A3:C3)");
this.parser.parseFunction("=C3");
}
}