--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.NotImplementedException;
+
+/**
+ * @author Josh Micich
+ * @author Petr Udalau - systematized work of add-in libraries and user defined functions.
+ */
+public final class AnalysisToolPak implements UDFFinder {
+
+ public static final UDFFinder instance = new AnalysisToolPak();
+
+ private static final class NotImplemented implements FreeRefFunction {
+ private final String _functionName;
+
+ public NotImplemented(String functionName) {
+ _functionName = functionName;
+ }
+
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ throw new NotImplementedException(_functionName);
+ }
+ };
+
+ private final Map<String, FreeRefFunction> _functionsByName = createFunctionsMap();
+
+
+ private AnalysisToolPak() {
+ // enforce singleton
+ }
+
+ public FreeRefFunction findFunction(String name) {
+ return _functionsByName.get(name);
+ }
+
+ private Map<String, FreeRefFunction> createFunctionsMap() {
+ Map<String, FreeRefFunction> m = new HashMap<String, FreeRefFunction>(100);
+
+ r(m, "ACCRINT", null);
+ r(m, "ACCRINTM", null);
+ r(m, "AMORDEGRC", null);
+ r(m, "AMORLINC", null);
+ r(m, "BESSELI", null);
+ r(m, "BESSELJ", null);
+ r(m, "BESSELK", null);
+ r(m, "BESSELY", null);
+ r(m, "BIN2DEC", null);
+ r(m, "BIN2HEX", null);
+ r(m, "BIN2OCT", null);
+ r(m, "CO MPLEX", null);
+ r(m, "CONVERT", null);
+ r(m, "COUPDAYBS", null);
+ r(m, "COUPDAYS", null);
+ r(m, "COUPDAYSNC", null);
+ r(m, "COUPNCD", null);
+ r(m, "COUPNUM", null);
+ r(m, "COUPPCD", null);
+ r(m, "CUMIPMT", null);
+ r(m, "CUMPRINC", null);
+ r(m, "DEC2BIN", null);
+ r(m, "DEC2HEX", null);
+ r(m, "DEC2OCT", null);
+ r(m, "DELTA", null);
+ r(m, "DISC", null);
+ r(m, "DOLLARDE", null);
+ r(m, "DOLLARFR", null);
+ r(m, "DURATION", null);
+ r(m, "EDATE", null);
+ r(m, "EFFECT", null);
+ r(m, "EOMONTH", null);
+ r(m, "ERF", null);
+ r(m, "ERFC", null);
+ r(m, "FACTDOUBLE", null);
+ r(m, "FVSCHEDULE", null);
+ r(m, "GCD", null);
+ r(m, "GESTEP", null);
+ r(m, "HEX2BIN", null);
+ r(m, "HEX2DEC", null);
+ r(m, "HEX2OCT", null);
+ r(m, "IMABS", null);
+ r(m, "IMAGINARY", null);
+ r(m, "IMARGUMENT", null);
+ r(m, "IMCONJUGATE", null);
+ r(m, "IMCOS", null);
+ r(m, "IMDIV", null);
+ r(m, "IMEXP", null);
+ r(m, "IMLN", null);
+ r(m, "IMLOG10", null);
+ r(m, "IMLOG2", null);
+ r(m, "IMPOWER", null);
+ r(m, "IMPRODUCT", null);
+ r(m, "IMREAL", null);
+ r(m, "IMSIN", null);
+ r(m, "IMSQRT", null);
+ r(m, "IMSUB", null);
+ r(m, "IMSUM", null);
+ r(m, "INTRATE", null);
+ r(m, "ISEVEN", ParityFunction.IS_EVEN);
+ r(m, "ISODD", ParityFunction.IS_ODD);
+ r(m, "LCM", null);
+ r(m, "MDURATION", null);
+ r(m, "MROUND", null);
+ r(m, "MULTINOMIAL", null);
+ r(m, "NETWORKDAYS", null);
+ r(m, "NOMINAL", null);
+ r(m, "OCT2BIN", null);
+ r(m, "OCT2DEC", null);
+ r(m, "OCT2HEX", null);
+ r(m, "ODDFPRICE", null);
+ r(m, "ODDFYIELD", null);
+ r(m, "ODDLPRICE", null);
+ r(m, "ODDLYIELD", null);
+ r(m, "PRICE", null);
+ r(m, "PRICEDISC", null);
+ r(m, "PRICEMAT", null);
+ r(m, "QUOTIENT", null);
+ r(m, "RANDBETWEEN", RandBetween.instance);
+ r(m, "RECEIVED", null);
+ r(m, "SERIESSUM", null);
+ r(m, "SQRTPI", null);
+ r(m, "TBILLEQ", null);
+ r(m, "TBILLPRICE", null);
+ r(m, "TBILLYIELD", null);
+ r(m, "WEEKNUM", null);
+ r(m, "WORKDAY", null);
+ r(m, "XIRR", null);
+ r(m, "XNPV", null);
+ r(m, "YEARFRAC", YearFrac.instance);
+ r(m, "YIELD", null);
+ r(m, "YIELDDISC", null);
+ r(m, "YIELDMAT", null);
+
+ return m;
+ }
+
+ private static void r(Map<String, FreeRefFunction> m, String functionName, FreeRefFunction pFunc) {
+ FreeRefFunction func = pFunc == null ? new NotImplemented(functionName) : pFunc;
+ m.put(functionName, func);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+/**
+ * Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/>
+ *
+ * @author Josh Micich
+ */
+final class ParityFunction implements FreeRefFunction {
+
+ public static final FreeRefFunction IS_EVEN = new ParityFunction(0);
+ public static final FreeRefFunction IS_ODD = new ParityFunction(1);
+ private final int _desiredParity;
+
+ private ParityFunction(int desiredParity) {
+ _desiredParity = desiredParity;
+ }
+
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ if (args.length != 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ int val;
+ try {
+ val = evaluateArgParity(args[0], ec.getRowIndex(), ec.getColumnIndex());
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+
+ return BoolEval.valueOf(val == _desiredParity);
+ }
+
+ private static int evaluateArgParity(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+ ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short)srcCellCol);
+
+ double d = OperandResolver.coerceValueToDouble(ve);
+ if (d < 0) {
+ d = -d;
+ }
+ long v = (long) Math.floor(d);
+ return (int) (v & 0x0001);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.formula.atp;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+
+/**
+ * Implementation of Excel 'Analysis ToolPak' function RANDBETWEEN()<br/>
+ *
+ * Returns a random integer number between the numbers you specify.<p/>
+ *
+ * <b>Syntax</b><br/>
+ * <b>RANDBETWEEN</b>(<b>bottom</b>, <b>top</b>)<p/>
+ *
+ * <b>bottom</b> is the smallest integer RANDBETWEEN will return.<br/>
+ * <b>top</b> is the largest integer RANDBETWEEN will return.<br/>
+
+ * @author Brendan Nolan
+ */
+final class RandBetween implements FreeRefFunction{
+
+ public static final FreeRefFunction instance = new RandBetween();
+
+ private RandBetween() {
+ //enforces singleton
+ }
+
+ /**
+ * Evaluate for RANDBETWEEN(). Must be given two arguments. Bottom must be greater than top.
+ * Bottom is rounded up and top value is rounded down. After rounding top has to be set greater
+ * than top.
+ *
+ * @see org.apache.poi.ss.formula.functions.FreeRefFunction#evaluate(org.apache.poi.ss.formula.eval.ValueEval[], org.apache.poi.ss.formula.OperationEvaluationContext)
+ */
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+
+ double bottom, top;
+
+ if (args.length != 2) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ try {
+ bottom = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec.getColumnIndex()));
+ top = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[1], ec.getRowIndex(), ec.getColumnIndex()));
+ if(bottom > top) {
+ return ErrorEval.NUM_ERROR;
+ }
+ } catch (EvaluationException e) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ bottom = Math.ceil(bottom);
+ top = Math.floor(top);
+
+ if(bottom > top) {
+ top = bottom;
+ }
+
+ return new NumberEval((bottom + (int)(Math.random() * ((top - bottom) + 1))));
+
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.regex.Pattern;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.usermodel.DateUtil;
+/**
+ * Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/>
+ *
+ * Returns the fraction of the year spanned by two dates.<p/>
+ *
+ * <b>Syntax</b><br/>
+ * <b>YEARFRAC</b>(<b>startDate</b>, <b>endDate</b>, basis)<p/>
+ *
+ * The <b>basis</b> optionally specifies the behaviour of YEARFRAC as follows:
+ *
+ * <table border="0" cellpadding="1" cellspacing="0" summary="basis parameter description">
+ * <tr><th>Value</th><th>Days per Month</th><th>Days per Year</th></tr>
+ * <tr align='center'><td>0 (default)</td><td>30</td><td>360</td></tr>
+ * <tr align='center'><td>1</td><td>actual</td><td>actual</td></tr>
+ * <tr align='center'><td>2</td><td>actual</td><td>360</td></tr>
+ * <tr align='center'><td>3</td><td>actual</td><td>365</td></tr>
+ * <tr align='center'><td>4</td><td>30</td><td>360</td></tr>
+ * </table>
+ *
+ */
+final class YearFrac implements FreeRefFunction {
+
+ public static final FreeRefFunction instance = new YearFrac();
+
+ private YearFrac() {
+ // enforce singleton
+ }
+
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ int srcCellRow = ec.getRowIndex();
+ int srcCellCol = ec.getColumnIndex();
+ double result;
+ try {
+ int basis = 0; // default
+ switch(args.length) {
+ case 3:
+ basis = evaluateIntArg(args[2], srcCellRow, srcCellCol);
+ case 2:
+ break;
+ default:
+ return ErrorEval.VALUE_INVALID;
+ }
+ double startDateVal = evaluateDateArg(args[0], srcCellRow, srcCellCol);
+ double endDateVal = evaluateDateArg(args[1], srcCellRow, srcCellCol);
+ result = YearFracCalculator.calculate(startDateVal, endDateVal, basis);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+
+ return new NumberEval(result);
+ }
+
+ private static double evaluateDateArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+ ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
+
+ if (ve instanceof StringEval) {
+ String strVal = ((StringEval) ve).getStringValue();
+ Double dVal = OperandResolver.parseDouble(strVal);
+ if (dVal != null) {
+ return dVal.doubleValue();
+ }
+ Calendar date = parseDate(strVal);
+ return DateUtil.getExcelDate(date, false);
+ }
+ return OperandResolver.coerceValueToDouble(ve);
+ }
+
+ private static Calendar parseDate(String strVal) throws EvaluationException {
+ String[] parts = Pattern.compile("/").split(strVal);
+ if (parts.length != 3) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ String part2 = parts[2];
+ int spacePos = part2.indexOf(' ');
+ if (spacePos > 0) {
+ // drop time portion if present
+ part2 = part2.substring(0, spacePos);
+ }
+ int f0;
+ int f1;
+ int f2;
+ try {
+ f0 = Integer.parseInt(parts[0]);
+ f1 = Integer.parseInt(parts[1]);
+ f2 = Integer.parseInt(part2);
+ } catch (NumberFormatException e) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ if (f0<0 || f1<0 || f2<0 || (f0>12 && f1>12 && f2>12)) {
+ // easy to see this cannot be a valid date
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+
+ if (f0 >= 1900 && f0 < 9999) {
+ // when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
+ return makeDate(f0, f1, f2);
+ }
+ // otherwise the format seems to depend on OS settings (default date format)
+ if (false) {
+ // MM/DD/YYYY is probably a good guess, if the in the US
+ return makeDate(f2, f0, f1);
+ }
+ // TODO - find a way to choose the correct date format
+ throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
+ }
+
+ /**
+ * @param month 1-based
+ */
+ private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
+ if (month < 1 || month > 12) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ Calendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ if (day <1 || day>cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ cal.set(Calendar.DAY_OF_MONTH, day);
+ return cal;
+ }
+
+ private static int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+ ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
+ return OperandResolver.coerceValueToInt(ve);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.atp;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.usermodel.DateUtil;
+
+
+/**
+ * Internal calculation methods for Excel 'Analysis ToolPak' function YEARFRAC()<br/>
+ *
+ * Algorithm inspired by www.dwheeler.com/yearfrac
+ *
+ * @author Josh Micich
+ */
+final class YearFracCalculator {
+ /** use UTC time-zone to avoid daylight savings issues */
+ private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
+ private static final int MS_PER_HOUR = 60 * 60 * 1000;
+ private static final int MS_PER_DAY = 24 * MS_PER_HOUR;
+ private static final int DAYS_PER_NORMAL_YEAR = 365;
+ private static final int DAYS_PER_LEAP_YEAR = DAYS_PER_NORMAL_YEAR + 1;
+
+ /** the length of normal long months i.e. 31 */
+ private static final int LONG_MONTH_LEN = 31;
+ /** the length of normal short months i.e. 30 */
+ private static final int SHORT_MONTH_LEN = 30;
+ private static final int SHORT_FEB_LEN = 28;
+ private static final int LONG_FEB_LEN = SHORT_FEB_LEN + 1;
+
+ private YearFracCalculator() {
+ // no instances of this class
+ }
+
+
+ public static double calculate(double pStartDateVal, double pEndDateVal, int basis) throws EvaluationException {
+
+ if (basis < 0 || basis >= 5) {
+ // if basis is invalid the result is #NUM!
+ throw new EvaluationException(ErrorEval.NUM_ERROR);
+ }
+
+ // common logic for all bases
+
+ // truncate day values
+ int startDateVal = (int) Math.floor(pStartDateVal);
+ int endDateVal = (int) Math.floor(pEndDateVal);
+ if (startDateVal == endDateVal) {
+ // when dates are equal, result is zero
+ return 0;
+ }
+ // swap start and end if out of order
+ if (startDateVal > endDateVal) {
+ int temp = startDateVal;
+ startDateVal = endDateVal;
+ endDateVal = temp;
+ }
+
+ switch (basis) {
+ case 0: return basis0(startDateVal, endDateVal);
+ case 1: return basis1(startDateVal, endDateVal);
+ case 2: return basis2(startDateVal, endDateVal);
+ case 3: return basis3(startDateVal, endDateVal);
+ case 4: return basis4(startDateVal, endDateVal);
+ }
+ throw new IllegalStateException("cannot happen");
+ }
+
+
+ /**
+ * @param startDateVal assumed to be less than or equal to endDateVal
+ * @param endDateVal assumed to be greater than or equal to startDateVal
+ */
+ public static double basis0(int startDateVal, int endDateVal) {
+ SimpleDate startDate = createDate(startDateVal);
+ SimpleDate endDate = createDate(endDateVal);
+ int date1day = startDate.day;
+ int date2day = endDate.day;
+
+ // basis zero has funny adjustments to the day-of-month fields when at end-of-month
+ if (date1day == LONG_MONTH_LEN && date2day == LONG_MONTH_LEN) {
+ date1day = SHORT_MONTH_LEN;
+ date2day = SHORT_MONTH_LEN;
+ } else if (date1day == LONG_MONTH_LEN) {
+ date1day = SHORT_MONTH_LEN;
+ } else if (date1day == SHORT_MONTH_LEN && date2day == LONG_MONTH_LEN) {
+ date2day = SHORT_MONTH_LEN;
+ // Note: If date2day==31, it STAYS 31 if date1day < 30.
+ // Special fixes for February:
+ } else if (startDate.month == 2 && isLastDayOfMonth(startDate)) {
+ // Note - these assignments deliberately set Feb 30 date.
+ date1day = SHORT_MONTH_LEN;
+ if (endDate.month == 2 && isLastDayOfMonth(endDate)) {
+ // only adjusted when first date is last day in Feb
+ date2day = SHORT_MONTH_LEN;
+ }
+ }
+ return calculateAdjusted(startDate, endDate, date1day, date2day);
+ }
+ /**
+ * @param startDateVal assumed to be less than or equal to endDateVal
+ * @param endDateVal assumed to be greater than or equal to startDateVal
+ */
+ public static double basis1(int startDateVal, int endDateVal) {
+ SimpleDate startDate = createDate(startDateVal);
+ SimpleDate endDate = createDate(endDateVal);
+ double yearLength;
+ if (isGreaterThanOneYear(startDate, endDate)) {
+ yearLength = averageYearLength(startDate.year, endDate.year);
+ } else if (shouldCountFeb29(startDate, endDate)) {
+ yearLength = DAYS_PER_LEAP_YEAR;
+ } else {
+ yearLength = DAYS_PER_NORMAL_YEAR;
+ }
+ return dateDiff(startDate.tsMilliseconds, endDate.tsMilliseconds) / yearLength;
+ }
+
+ /**
+ * @param startDateVal assumed to be less than or equal to endDateVal
+ * @param endDateVal assumed to be greater than or equal to startDateVal
+ */
+ public static double basis2(int startDateVal, int endDateVal) {
+ return (endDateVal - startDateVal) / 360.0;
+ }
+ /**
+ * @param startDateVal assumed to be less than or equal to endDateVal
+ * @param endDateVal assumed to be greater than or equal to startDateVal
+ */
+ public static double basis3(double startDateVal, double endDateVal) {
+ return (endDateVal - startDateVal) / 365.0;
+ }
+ /**
+ * @param startDateVal assumed to be less than or equal to endDateVal
+ * @param endDateVal assumed to be greater than or equal to startDateVal
+ */
+ public static double basis4(int startDateVal, int endDateVal) {
+ SimpleDate startDate = createDate(startDateVal);
+ SimpleDate endDate = createDate(endDateVal);
+ int date1day = startDate.day;
+ int date2day = endDate.day;
+
+
+ // basis four has funny adjustments to the day-of-month fields when at end-of-month
+ if (date1day == LONG_MONTH_LEN) {
+ date1day = SHORT_MONTH_LEN;
+ }
+ if (date2day == LONG_MONTH_LEN) {
+ date2day = SHORT_MONTH_LEN;
+ }
+ // Note - no adjustments for end of Feb
+ return calculateAdjusted(startDate, endDate, date1day, date2day);
+ }
+
+
+ private static double calculateAdjusted(SimpleDate startDate, SimpleDate endDate, int date1day,
+ int date2day) {
+ double dayCount
+ = (endDate.year - startDate.year) * 360
+ + (endDate.month - startDate.month) * SHORT_MONTH_LEN
+ + (date2day - date1day) * 1;
+ return dayCount / 360;
+ }
+
+ private static boolean isLastDayOfMonth(SimpleDate date) {
+ if (date.day < SHORT_FEB_LEN) {
+ return false;
+ }
+ return date.day == getLastDayOfMonth(date);
+ }
+
+ private static int getLastDayOfMonth(SimpleDate date) {
+ switch (date.month) {
+ case 1:
+ case 3:
+ case 5:
+ case 7:
+ case 8:
+ case 10:
+ case 12:
+ return LONG_MONTH_LEN;
+ case 4:
+ case 6:
+ case 9:
+ case 11:
+ return SHORT_MONTH_LEN;
+ }
+ if (isLeapYear(date.year)) {
+ return LONG_FEB_LEN;
+ }
+ return SHORT_FEB_LEN;
+ }
+
+ /**
+ * Assumes dates are no more than 1 year apart.
+ * @return <code>true</code> if dates both within a leap year, or span a period including Feb 29
+ */
+ private static boolean shouldCountFeb29(SimpleDate start, SimpleDate end) {
+ boolean startIsLeapYear = isLeapYear(start.year);
+ if (startIsLeapYear && start.year == end.year) {
+ // note - dates may not actually span Feb-29, but it gets counted anyway in this case
+ return true;
+ }
+
+ boolean endIsLeapYear = isLeapYear(end.year);
+ if (!startIsLeapYear && !endIsLeapYear) {
+ return false;
+ }
+ if (startIsLeapYear) {
+ switch (start.month) {
+ case SimpleDate.JANUARY:
+ case SimpleDate.FEBRUARY:
+ return true;
+ }
+ return false;
+ }
+ if (endIsLeapYear) {
+ switch (end.month) {
+ case SimpleDate.JANUARY:
+ return false;
+ case SimpleDate.FEBRUARY:
+ break;
+ default:
+ return true;
+ }
+ return end.day == LONG_FEB_LEN;
+ }
+ return false;
+ }
+
+ /**
+ * @return the whole number of days between the two time-stamps. Both time-stamps are
+ * assumed to represent 12:00 midnight on the respective day.
+ */
+ private static int dateDiff(long startDateMS, long endDateMS) {
+ long msDiff = endDateMS - startDateMS;
+
+ // some extra checks to make sure we don't hide some other bug with the rounding
+ int remainderHours = (int) ((msDiff % MS_PER_DAY) / MS_PER_HOUR);
+ switch (remainderHours) {
+ case 0: // normal case
+ break;
+ case 1: // transition from normal time to daylight savings adjusted
+ case 23: // transition from daylight savings adjusted to normal time
+ // Unexpected since we are using UTC_TIME_ZONE
+ default:
+ throw new RuntimeException("Unexpected date diff between " + startDateMS + " and " + endDateMS);
+
+ }
+ return (int) (0.5 + ((double)msDiff / MS_PER_DAY));
+ }
+
+ private static double averageYearLength(int startYear, int endYear) {
+ int dayCount = 0;
+ for (int i=startYear; i<=endYear; i++) {
+ dayCount += DAYS_PER_NORMAL_YEAR;
+ if (isLeapYear(i)) {
+ dayCount++;
+ }
+ }
+ double numberOfYears = endYear-startYear+1;
+ return dayCount / numberOfYears;
+ }
+
+ private static boolean isLeapYear(int i) {
+ // leap years are always divisible by 4
+ if (i % 4 != 0) {
+ return false;
+ }
+ // each 4th century is a leap year
+ if (i % 400 == 0) {
+ return true;
+ }
+ // all other centuries are *not* leap years
+ if (i % 100 == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isGreaterThanOneYear(SimpleDate start, SimpleDate end) {
+ if (start.year == end.year) {
+ return false;
+ }
+ if (start.year + 1 != end.year) {
+ return true;
+ }
+
+ if (start.month > end.month) {
+ return false;
+ }
+ if (start.month < end.month) {
+ return true;
+ }
+
+ return start.day < end.day;
+ }
+
+ private static SimpleDate createDate(int dayCount) {
+ GregorianCalendar calendar = new GregorianCalendar(UTC_TIME_ZONE);
+ DateUtil.setCalendar(calendar, dayCount, 0, false);
+ return new SimpleDate(calendar);
+ }
+
+ private static final class SimpleDate {
+
+ public static final int JANUARY = 1;
+ public static final int FEBRUARY = 2;
+
+ public final int year;
+ /** 1-based month */
+ public final int month;
+ /** day of month */
+ public final int day;
+ /** milliseconds since 1970 */
+ public long tsMilliseconds;
+
+ public SimpleDate(Calendar cal) {
+ year = cal.get(Calendar.YEAR);
+ month = cal.get(Calendar.MONTH) + 1;
+ day = cal.get(Calendar.DAY_OF_MONTH);
+ tsMilliseconds = cal.getTimeInMillis();
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.TwoDEval;
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ *
+ */
+public interface AreaEval extends TwoDEval {
+
+ /**
+ * returns the 0-based index of the first row in
+ * this area.
+ */
+ int getFirstRow();
+
+ /**
+ * returns the 0-based index of the last row in
+ * this area.
+ */
+ int getLastRow();
+
+ /**
+ * returns the 0-based index of the first col in
+ * this area.
+ */
+ int getFirstColumn();
+
+ /**
+ * returns the 0-based index of the last col in
+ * this area.
+ */
+ int getLastColumn();
+
+ /**
+ * @return the ValueEval from within this area at the specified row and col index. Never
+ * <code>null</code> (possibly {@link BlankEval}). The specified indexes should be absolute
+ * indexes in the sheet and not relative indexes within the area.
+ */
+ ValueEval getAbsoluteValue(int row, int col);
+
+ /**
+ * returns true if the cell at row and col specified
+ * as absolute indexes in the sheet is contained in
+ * this area.
+ * @param row
+ * @param col
+ */
+ boolean contains(int row, int col);
+
+ /**
+ * returns true if the specified col is in range
+ * @param col
+ */
+ boolean containsColumn(int col);
+
+ /**
+ * returns true if the specified row is in range
+ * @param row
+ */
+ boolean containsRow(int row);
+
+ int getWidth();
+ int getHeight();
+ /**
+ * @return the ValueEval from within this area at the specified relativeRowIndex and
+ * relativeColumnIndex. Never <code>null</code> (possibly {@link BlankEval}). The
+ * specified indexes should relative to the top left corner of this area.
+ */
+ ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex);
+
+ /**
+ * Creates an {@link AreaEval} offset by a relative amount from from the upper left cell
+ * of this area
+ */
+ AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx);
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.AreaI;
+
+/**
+ * @author Josh Micich
+ */
+public abstract class AreaEvalBase implements AreaEval {
+
+ private final int _firstColumn;
+ private final int _firstRow;
+ private final int _lastColumn;
+ private final int _lastRow;
+ private final int _nColumns;
+ private final int _nRows;
+
+ protected AreaEvalBase(int firstRow, int firstColumn, int lastRow, int lastColumn) {
+ _firstColumn = firstColumn;
+ _firstRow = firstRow;
+ _lastColumn = lastColumn;
+ _lastRow = lastRow;
+
+ _nColumns = _lastColumn - _firstColumn + 1;
+ _nRows = _lastRow - _firstRow + 1;
+ }
+
+ protected AreaEvalBase(AreaI ptg) {
+ _firstRow = ptg.getFirstRow();
+ _firstColumn = ptg.getFirstColumn();
+ _lastRow = ptg.getLastRow();
+ _lastColumn = ptg.getLastColumn();
+
+ _nColumns = _lastColumn - _firstColumn + 1;
+ _nRows = _lastRow - _firstRow + 1;
+ }
+
+ public final int getFirstColumn() {
+ return _firstColumn;
+ }
+
+ public final int getFirstRow() {
+ return _firstRow;
+ }
+
+ public final int getLastColumn() {
+ return _lastColumn;
+ }
+
+ public final int getLastRow() {
+ return _lastRow;
+ }
+ public final ValueEval getAbsoluteValue(int row, int col) {
+ int rowOffsetIx = row - _firstRow;
+ int colOffsetIx = col - _firstColumn;
+
+ if(rowOffsetIx < 0 || rowOffsetIx >= _nRows) {
+ throw new IllegalArgumentException("Specified row index (" + row
+ + ") is outside the allowed range (" + _firstRow + ".." + _lastRow + ")");
+ }
+ if(colOffsetIx < 0 || colOffsetIx >= _nColumns) {
+ throw new IllegalArgumentException("Specified column index (" + col
+ + ") is outside the allowed range (" + _firstColumn + ".." + col + ")");
+ }
+ return getRelativeValue(rowOffsetIx, colOffsetIx);
+ }
+
+ public final boolean contains(int row, int col) {
+ return _firstRow <= row && _lastRow >= row
+ && _firstColumn <= col && _lastColumn >= col;
+ }
+
+ public final boolean containsRow(int row) {
+ return _firstRow <= row && _lastRow >= row;
+ }
+
+ public final boolean containsColumn(int col) {
+ return _firstColumn <= col && _lastColumn >= col;
+ }
+
+ public final boolean isColumn() {
+ return _firstColumn == _lastColumn;
+ }
+
+ public final boolean isRow() {
+ return _firstRow == _lastRow;
+ }
+ public int getHeight() {
+ return _lastRow-_firstRow+1;
+ }
+
+ public final ValueEval getValue(int row, int col) {
+ return getRelativeValue(row, col);
+ }
+
+ public abstract ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex);
+
+ public int getWidth() {
+ return _lastColumn-_firstColumn+1;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com > This class is a
+ * marker class. It is a special value for empty cells.
+ */
+public final class BlankEval implements ValueEval {
+
+ public static final BlankEval instance = new BlankEval();
+ /**
+ * @deprecated (Nov 2009) use {@link #instance}
+ */
+ public static final BlankEval INSTANCE = instance;
+
+ private BlankEval() {
+ // enforce singleton
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public final class BoolEval implements NumericValueEval, StringValueEval {
+
+ private boolean _value;
+
+ public static final BoolEval FALSE = new BoolEval(false);
+
+ public static final BoolEval TRUE = new BoolEval(true);
+
+ /**
+ * Convenience method for the following:<br/>
+ * <code>(b ? BoolEval.TRUE : BoolEval.FALSE)</code>
+ *
+ * @return the <tt>BoolEval</tt> instance representing <tt>b</tt>.
+ */
+ public static final BoolEval valueOf(boolean b) {
+ return b ? TRUE : FALSE;
+ }
+
+ private BoolEval(boolean value) {
+ _value = value;
+ }
+
+ public boolean getBooleanValue() {
+ return _value;
+ }
+
+ public double getNumberValue() {
+ return _value ? 1 : 0;
+ }
+
+ public String getStringValue() {
+ return _value ? "TRUE" : "FALSE";
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getStringValue());
+ sb.append("]");
+ return sb.toString();
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public final class ConcatEval extends Fixed2ArgFunction {
+
+ public static final Function instance = new ConcatEval();
+
+ private ConcatEval() {
+ // enforce singleton
+ }
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+ ValueEval ve0;
+ ValueEval ve1;
+ try {
+ ve0 = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+ ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(getText(ve0));
+ sb.append(getText(ve1));
+ return new StringEval(sb.toString());
+ }
+
+ private Object getText(ValueEval ve) {
+ if (ve instanceof StringValueEval) {
+ StringValueEval sve = (StringValueEval) ve;
+ return sve.getStringValue();
+ }
+ if (ve == BlankEval.instance) {
+ return "";
+ }
+ throw new IllegalAccessError("Unexpected value type ("
+ + ve.getClass().getName() + ")");
+ }
+}
--- /dev/null
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.usermodel.ErrorConstants;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ *
+ */
+public final class ErrorEval implements ValueEval {
+
+ // convenient access to namespace
+ private static final ErrorConstants EC = null;
+
+ /** <b>#NULL!</b> - Intersection of two cell ranges is empty */
+ public static final ErrorEval NULL_INTERSECTION = new ErrorEval(EC.ERROR_NULL);
+ /** <b>#DIV/0!</b> - Division by zero */
+ public static final ErrorEval DIV_ZERO = new ErrorEval(EC.ERROR_DIV_0);
+ /** <b>#VALUE!</b> - Wrong type of operand */
+ public static final ErrorEval VALUE_INVALID = new ErrorEval(EC.ERROR_VALUE);
+ /** <b>#REF!</b> - Illegal or deleted cell reference */
+ public static final ErrorEval REF_INVALID = new ErrorEval(EC.ERROR_REF);
+ /** <b>#NAME?</b> - Wrong function or range name */
+ public static final ErrorEval NAME_INVALID = new ErrorEval(EC.ERROR_NAME);
+ /** <b>#NUM!</b> - Value range overflow */
+ public static final ErrorEval NUM_ERROR = new ErrorEval(EC.ERROR_NUM);
+ /** <b>#N/A</b> - Argument or function not available */
+ public static final ErrorEval NA = new ErrorEval(EC.ERROR_NA);
+
+
+ // POI internal error codes
+ private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4;
+ private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2;
+
+ // Note - Excel does not seem to represent this condition with an error code
+ public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(CIRCULAR_REF_ERROR_CODE);
+
+
+ /**
+ * Translates an Excel internal error code into the corresponding POI ErrorEval instance
+ * @param errorCode
+ */
+ public static ErrorEval valueOf(int errorCode) {
+ switch(errorCode) {
+ case ErrorConstants.ERROR_NULL: return NULL_INTERSECTION;
+ case ErrorConstants.ERROR_DIV_0: return DIV_ZERO;
+ case ErrorConstants.ERROR_VALUE: return VALUE_INVALID;
+ case ErrorConstants.ERROR_REF: return REF_INVALID;
+ case ErrorConstants.ERROR_NAME: return NAME_INVALID;
+ case ErrorConstants.ERROR_NUM: return NUM_ERROR;
+ case ErrorConstants.ERROR_NA: return NA;
+ // non-std errors (conditions modeled as errors by POI)
+ case CIRCULAR_REF_ERROR_CODE: return CIRCULAR_REF_ERROR;
+ }
+ throw new RuntimeException("Unexpected error code (" + errorCode + ")");
+ }
+
+ /**
+ * Converts error codes to text. Handles non-standard error codes OK.
+ * For debug/test purposes (and for formatting error messages).
+ * @return the String representation of the specified Excel error code.
+ */
+ public static String getText(int errorCode) {
+ if(ErrorConstants.isValidCode(errorCode)) {
+ return ErrorConstants.getText(errorCode);
+ }
+ // It is desirable to make these (arbitrary) strings look clearly different from any other
+ // value expression that might appear in a formula. In addition these error strings should
+ // look unlike the standard Excel errors. Hence tilde ('~') was used.
+ switch(errorCode) {
+ case CIRCULAR_REF_ERROR_CODE: return "~CIRCULAR~REF~";
+ case FUNCTION_NOT_IMPLEMENTED_CODE: return "~FUNCTION~NOT~IMPLEMENTED~";
+ }
+ return "~non~std~err(" + errorCode + ")~";
+ }
+
+ private int _errorCode;
+ /**
+ * @param errorCode an 8-bit value
+ */
+ private ErrorEval(int errorCode) {
+ _errorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return _errorCode;
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getText(_errorCode));
+ sb.append("]");
+ return sb.toString();
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * This class is used to simplify error handling logic <i>within</i> operator and function
+ * implementations. Note - <tt>OperationEval.evaluate()</tt> and <tt>Function.evaluate()</tt>
+ * method signatures do not throw this exception so it cannot propagate outside.<p/>
+ *
+ * Here is an example coded without <tt>EvaluationException</tt>, to show how it can help:
+ * <pre>
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ * // ...
+ * Eval arg0 = args[0];
+ * if(arg0 instanceof ErrorEval) {
+ * return arg0;
+ * }
+ * if(!(arg0 instanceof AreaEval)) {
+ * return ErrorEval.VALUE_INVALID;
+ * }
+ * double temp = 0;
+ * AreaEval area = (AreaEval)arg0;
+ * ValueEval[] values = area.getValues();
+ * for (int i = 0; i < values.length; i++) {
+ * ValueEval ve = values[i];
+ * if(ve instanceof ErrorEval) {
+ * return ve;
+ * }
+ * if(!(ve instanceof NumericValueEval)) {
+ * return ErrorEval.VALUE_INVALID;
+ * }
+ * temp += ((NumericValueEval)ve).getNumberValue();
+ * }
+ * // ...
+ * }
+ * </pre>
+ * In this example, if any error is encountered while processing the arguments, an error is
+ * returned immediately. This code is difficult to refactor due to all the points where errors
+ * are returned.<br/>
+ * Using <tt>EvaluationException</tt> allows the error returning code to be consolidated to one
+ * place.<p/>
+ * <pre>
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ * try {
+ * // ...
+ * AreaEval area = getAreaArg(args[0]);
+ * double temp = sumValues(area.getValues());
+ * // ...
+ * } catch (EvaluationException e) {
+ * return e.getErrorEval();
+ * }
+ *}
+ *
+ *private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
+ * if (arg0 instanceof ErrorEval) {
+ * throw new EvaluationException((ErrorEval) arg0);
+ * }
+ * if (arg0 instanceof AreaEval) {
+ * return (AreaEval) arg0;
+ * }
+ * throw EvaluationException.invalidValue();
+ *}
+ *
+ *private double sumValues(ValueEval[] values) throws EvaluationException {
+ * double temp = 0;
+ * for (int i = 0; i < values.length; i++) {
+ * ValueEval ve = values[i];
+ * if (ve instanceof ErrorEval) {
+ * throw new EvaluationException((ErrorEval) ve);
+ * }
+ * if (!(ve instanceof NumericValueEval)) {
+ * throw EvaluationException.invalidValue();
+ * }
+ * temp += ((NumericValueEval) ve).getNumberValue();
+ * }
+ * return temp;
+ *}
+ * </pre>
+ * It is not mandatory to use EvaluationException, doing so might give the following advantages:<br/>
+ * - Methods can more easily be extracted, allowing for re-use.<br/>
+ * - Type management (typecasting etc) is simpler because error conditions have been separated from
+ * intermediate calculation values.<br/>
+ * - Fewer local variables are required. Local variables can have stronger types.<br/>
+ * - It is easier to mimic common Excel error handling behaviour (exit upon encountering first
+ * error), because exceptions conveniently propagate up the call stack regardless of execution
+ * points or the number of levels of nested calls.<p/>
+ *
+ * <b>Note</b> - Only standard evaluation errors are represented by <tt>EvaluationException</tt> (
+ * i.e. conditions expected to be encountered when evaluating arbitrary Excel formulas). Conditions
+ * that could never occur in an Excel spreadsheet should result in runtime exceptions. Care should
+ * be taken to not translate any POI internal error into an Excel evaluation error code.
+ *
+ * @author Josh Micich
+ */
+public final class EvaluationException extends Exception {
+ private final ErrorEval _errorEval;
+
+ public EvaluationException(ErrorEval errorEval) {
+ _errorEval = errorEval;
+ }
+ // some convenience factory methods
+
+ /** <b>#VALUE!</b> - Wrong type of operand */
+ public static EvaluationException invalidValue() {
+ return new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ /** <b>#REF!</b> - Illegal or deleted cell reference */
+ public static EvaluationException invalidRef() {
+ return new EvaluationException(ErrorEval.REF_INVALID);
+ }
+ /** <b>#NUM!</b> - Value range overflow */
+ public static EvaluationException numberError() {
+ return new EvaluationException(ErrorEval.NUM_ERROR);
+ }
+
+ public ErrorEval getErrorEval() {
+ return _errorEval;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.function.FunctionMetadata;
+import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
+import org.apache.poi.ss.formula.functions.*;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public final class FunctionEval {
+ /**
+ * Some function IDs that require special treatment
+ */
+ private static final class FunctionID {
+ /** 1 */
+ public static final int IF = FunctionMetadataRegistry.FUNCTION_INDEX_IF;
+ /** 4 */
+ public static final int SUM = FunctionMetadataRegistry.FUNCTION_INDEX_SUM;
+ /** 78 */
+ public static final int OFFSET = 78;
+ /** 100 */
+ public static final int CHOOSE = FunctionMetadataRegistry.FUNCTION_INDEX_CHOOSE;
+ /** 148 */
+ public static final int INDIRECT = FunctionMetadataRegistry.FUNCTION_INDEX_INDIRECT;
+ /** 255 */
+ public static final int EXTERNAL_FUNC = FunctionMetadataRegistry.FUNCTION_INDEX_EXTERNAL;
+ }
+ // convenient access to namespace
+ private static final FunctionID ID = null;
+
+ /**
+ * Array elements corresponding to unimplemented functions are <code>null</code>
+ */
+ protected static final Function[] functions = produceFunctions();
+
+ private static Function[] produceFunctions() {
+ Function[] retval = new Function[368];
+
+ retval[0] = new Count();
+ retval[ID.IF] = new IfFunc();
+ retval[2] = LogicalFunction.ISNA;
+ retval[3] = LogicalFunction.ISERROR;
+ retval[ID.SUM] = AggregateFunction.SUM;
+ retval[5] = AggregateFunction.AVERAGE;
+ retval[6] = AggregateFunction.MIN;
+ retval[7] = AggregateFunction.MAX;
+ retval[8] = new RowFunc(); // ROW
+ retval[9] = new Column();
+ retval[10] = new Na();
+ retval[11] = new Npv();
+ retval[12] = AggregateFunction.STDEV;
+ retval[13] = NumericFunction.DOLLAR;
+
+ retval[15] = NumericFunction.SIN;
+ retval[16] = NumericFunction.COS;
+ retval[17] = NumericFunction.TAN;
+ retval[18] = NumericFunction.ATAN;
+ retval[19] = NumericFunction.PI;
+ retval[20] = NumericFunction.SQRT;
+ retval[21] = NumericFunction.EXP;
+ retval[22] = NumericFunction.LN;
+ retval[23] = NumericFunction.LOG10;
+ retval[24] = NumericFunction.ABS;
+ retval[25] = NumericFunction.INT;
+ retval[26] = NumericFunction.SIGN;
+ retval[27] = NumericFunction.ROUND;
+ retval[28] = new Lookup();
+ retval[29] = new Index();
+
+ retval[31] = TextFunction.MID;
+ retval[32] = TextFunction.LEN;
+ retval[33] = new Value();
+ retval[34] = BooleanFunction.TRUE;
+ retval[35] = BooleanFunction.FALSE;
+ retval[36] = BooleanFunction.AND;
+ retval[37] = BooleanFunction.OR;
+ retval[38] = BooleanFunction.NOT;
+ retval[39] = NumericFunction.MOD;
+ retval[48] = TextFunction.TEXT;
+
+ retval[56] = FinanceFunction.PV;
+ retval[57] = FinanceFunction.FV;
+ retval[58] = FinanceFunction.NPER;
+ retval[59] = FinanceFunction.PMT;
+
+ retval[63] = NumericFunction.RAND;
+ retval[64] = new Match();
+ retval[65] = DateFunc.instance;
+ retval[66] = new TimeFunc();
+ retval[67] = CalendarFieldFunction.DAY;
+ retval[68] = CalendarFieldFunction.MONTH;
+ retval[69] = CalendarFieldFunction.YEAR;
+
+ retval[74] = new Now();
+
+ retval[76] = new Rows();
+ retval[77] = new Columns();
+ retval[82] = TextFunction.SEARCH;
+ retval[ID.OFFSET] = new Offset();
+ retval[82] = TextFunction.SEARCH;
+
+ retval[97] = NumericFunction.ATAN2;
+ retval[98] = NumericFunction.ASIN;
+ retval[99] = NumericFunction.ACOS;
+ retval[ID.CHOOSE] = new Choose();
+ retval[101] = new Hlookup();
+ retval[102] = new Vlookup();
+
+ retval[105] = LogicalFunction.ISREF;
+
+ retval[109] = NumericFunction.LOG;
+
+ retval[112] = TextFunction.LOWER;
+ retval[113] = TextFunction.UPPER;
+
+ retval[115] = TextFunction.LEFT;
+ retval[116] = TextFunction.RIGHT;
+ retval[117] = TextFunction.EXACT;
+ retval[118] = TextFunction.TRIM;
+ retval[119] = new Replace();
+ retval[120] = new Substitute();
+
+ retval[124] = TextFunction.FIND;
+
+ retval[127] = LogicalFunction.ISTEXT;
+ retval[128] = LogicalFunction.ISNUMBER;
+ retval[129] = LogicalFunction.ISBLANK;
+ retval[130] = new T();
+
+ retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature
+
+ retval[169] = new Counta();
+
+ retval[183] = AggregateFunction.PRODUCT;
+ retval[184] = NumericFunction.FACT;
+
+ retval[190] = LogicalFunction.ISNONTEXT;
+ retval[197] = NumericFunction.TRUNC;
+ retval[198] = LogicalFunction.ISLOGICAL;
+
+ retval[212] = NumericFunction.ROUNDUP;
+ retval[213] = NumericFunction.ROUNDDOWN;
+
+ retval[220] = new Days360();
+ retval[221] = new Today();
+
+ retval[227] = AggregateFunction.MEDIAN;
+ retval[228] = new Sumproduct();
+ retval[229] = NumericFunction.SINH;
+ retval[230] = NumericFunction.COSH;
+ retval[231] = NumericFunction.TANH;
+ retval[232] = NumericFunction.ASINH;
+ retval[233] = NumericFunction.ACOSH;
+ retval[234] = NumericFunction.ATANH;
+
+ retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction
+
+ retval[261] = new Errortype();
+
+ retval[269] = AggregateFunction.AVEDEV;
+
+ retval[276] = NumericFunction.COMBIN;
+
+ retval[279] = new Even();
+
+ retval[285] = NumericFunction.FLOOR;
+
+ retval[288] = NumericFunction.CEILING;
+
+ retval[298] = new Odd();
+
+ retval[300] = NumericFunction.POISSON;
+
+ retval[303] = new Sumxmy2();
+ retval[304] = new Sumx2my2();
+ retval[305] = new Sumx2py2();
+
+ retval[318] = AggregateFunction.DEVSQ;
+
+ retval[321] = AggregateFunction.SUMSQ;
+
+ retval[325] = AggregateFunction.LARGE;
+ retval[326] = AggregateFunction.SMALL;
+
+ retval[330] = new Mode();
+
+ retval[336] = TextFunction.CONCATENATE;
+ retval[337] = NumericFunction.POWER;
+
+ retval[342] = NumericFunction.RADIANS;
+ retval[343] = NumericFunction.DEGREES;
+
+ retval[344] = new Subtotal();
+ retval[345] = new Sumif();
+ retval[346] = new Countif();
+ retval[347] = new Countblank();
+
+ retval[359] = new Hyperlink();
+
+ retval[362] = MinaMaxa.MAXA;
+ retval[363] = MinaMaxa.MINA;
+
+ for (int i = 0; i < retval.length; i++) {
+ Function f = retval[i];
+ if (f == null) {
+ FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByIndex(i);
+ if (fm == null) {
+ continue;
+ }
+ retval[i] = new NotImplementedFunction(fm.getName());
+ }
+ }
+ return retval;
+ }
+ /**
+ * @return <code>null</code> if the specified functionIndex is for INDIRECT() or any external (add-in) function.
+ */
+ public static Function getBasicFunction(int functionIndex) {
+ // check for 'free ref' functions first
+ switch (functionIndex) {
+ case FunctionID.INDIRECT:
+ case FunctionID.EXTERNAL_FUNC:
+ return null;
+ }
+ // else - must be plain function
+ Function result = functions[functionIndex];
+ if (result == null) {
+ throw new NotImplementedException("FuncIx=" + functionIndex);
+ }
+ return result;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Josh Micich
+ */
+public final class IntersectionEval extends Fixed2ArgFunction {
+
+ public static final Function instance = new IntersectionEval();
+
+ private IntersectionEval() {
+ // enforces singleton
+ }
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+
+ try {
+ AreaEval reA = evaluateRef(arg0);
+ AreaEval reB = evaluateRef(arg1);
+ AreaEval result = resolveRange(reA, reB);
+ if (result == null) {
+ return ErrorEval.NULL_INTERSECTION;
+ }
+ return result;
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+ /**
+ * @return simple rectangular {@link AreaEval} which represents the intersection of areas
+ * <tt>aeA</tt> and <tt>aeB</tt>. If the two areas do not intersect, the result is <code>null</code>.
+ */
+ private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
+
+ int aeAfr = aeA.getFirstRow();
+ int aeAfc = aeA.getFirstColumn();
+ int aeBlc = aeB.getLastColumn();
+ if (aeAfc > aeBlc) {
+ return null;
+ }
+ int aeBfc = aeB.getFirstColumn();
+ if (aeBfc > aeA.getLastColumn()) {
+ return null;
+ }
+ int aeBlr = aeB.getLastRow();
+ if (aeAfr > aeBlr) {
+ return null;
+ }
+ int aeBfr = aeB.getFirstRow();
+ int aeAlr = aeA.getLastRow();
+ if (aeBfr > aeAlr) {
+ return null;
+ }
+
+
+ int top = Math.max(aeAfr, aeBfr);
+ int bottom = Math.min(aeAlr, aeBlr);
+ int left = Math.max(aeAfc, aeBfc);
+ int right = Math.min(aeA.getLastColumn(), aeBlc);
+
+ return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
+ }
+
+ private static AreaEval evaluateRef(ValueEval arg) throws EvaluationException {
+ if (arg instanceof AreaEval) {
+ return (AreaEval) arg;
+ }
+ if (arg instanceof RefEval) {
+ return ((RefEval) arg).offset(0, 0, 0, 0);
+ }
+ if (arg instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval)arg);
+ }
+ throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * Represents the (intermediate) evaluated result of a missing function argument. In most cases
+ * this can be translated into {@link BlankEval} but there are some notable exceptions. Functions
+ * COUNT and COUNTA <em>do</em> count their missing args. Note - the differences between
+ * {@link MissingArgEval} and {@link BlankEval} have not been investigated fully, so the POI
+ * evaluator may need to be updated to account for these as they are found.
+ *
+ * @author Josh Micich
+ */
+public final class MissingArgEval implements ValueEval {
+
+ public static final MissingArgEval instance = new MissingArgEval();
+
+ private MissingArgEval() {
+ // enforce singleton
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Josh Micich
+ */
+public final class NameEval implements ValueEval {
+
+ private final String _functionName;
+
+ /**
+ * Creates a NameEval representing a function name
+ */
+ public NameEval(String functionName) {
+ _functionName = functionName;
+ }
+
+
+ public String getFunctionName() {
+ return _functionName;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_functionName);
+ sb.append("]");
+ return sb.toString();
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.NameXPtg;
+
+/**
+ * @author Josh Micich
+ */
+public final class NameXEval implements ValueEval {
+
+ private final NameXPtg _ptg;
+
+ public NameXEval(NameXPtg ptg) {
+ _ptg = ptg;
+ }
+
+ public NameXPtg getPtg() {
+ return _ptg;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_ptg.getSheetRefIndex()).append(", ").append(_ptg.getNameIndex());
+ sb.append("]");
+ return sb.toString();
+ }
+}
--- /dev/null
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+/*
+ * Created on May 8, 2005
+ *
+ */
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.IntPtg;
+import org.apache.poi.hssf.record.formula.NumberPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.ss.util.NumberToTextConverter;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ *
+ */
+public final class NumberEval implements NumericValueEval, StringValueEval {
+
+ public static final NumberEval ZERO = new NumberEval(0);
+
+ private final double _value;
+ private String _stringValue;
+
+ public NumberEval(Ptg ptg) {
+ if (ptg == null) {
+ throw new IllegalArgumentException("ptg must not be null");
+ }
+ if (ptg instanceof IntPtg) {
+ _value = ((IntPtg) ptg).getValue();
+ } else if (ptg instanceof NumberPtg) {
+ _value = ((NumberPtg) ptg).getValue();
+ } else {
+ throw new IllegalArgumentException("bad argument type (" + ptg.getClass().getName() + ")");
+ }
+ }
+
+ public NumberEval(double value) {
+ _value = value;
+ }
+
+ public double getNumberValue() {
+ return _value;
+ }
+
+ public String getStringValue() {
+ if (_stringValue == null) {
+ _stringValue = NumberToTextConverter.toText(_value);
+ }
+ return _stringValue;
+ }
+ public final String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getStringValue());
+ sb.append("]");
+ return sb.toString();
+ }
+}
--- /dev/null
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+/*
+ * Created on May 8, 2005
+ *
+ */
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ *
+ */
+public interface NumericValueEval extends ValueEval {
+
+ public abstract double getNumberValue();
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import java.util.regex.Pattern;
+
+/**
+ * Provides functionality for evaluating arguments to functions and operators.
+ *
+ * @author Josh Micich
+ * @author Brendan Nolan
+ */
+public final class OperandResolver {
+
+ // Based on regular expression defined in JavaDoc at {@link java.lang.Double#valueOf}
+ // modified to remove support for NaN, Infinity, Hexadecimal support and floating type suffixes
+ private static final String Digits = "(\\p{Digit}+)";
+ private static final String Exp = "[eE][+-]?"+Digits;
+ private static final String fpRegex =
+ ("[\\x00-\\x20]*" +
+ "[+-]?(" +
+ "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+
+ "(\\.("+Digits+")("+Exp+")?))))"+
+ "[\\x00-\\x20]*");
+
+
+ private OperandResolver() {
+ // no instances of this class
+ }
+
+ /**
+ * Retrieves a single value from a variety of different argument types according to standard
+ * Excel rules. Does not perform any type conversion.
+ * @param arg the evaluated argument as passed to the function or operator.
+ * @param srcCellRow used when arg is a single column AreaRef
+ * @param srcCellCol used when arg is a single row AreaRef
+ * @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>.
+ * Never <code>null</code> or <tt>ErrorEval</tt>.
+ * @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into
+ * an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding
+ * EvaluationException is thrown.
+ */
+ public static ValueEval getSingleValue(ValueEval arg, int srcCellRow, int srcCellCol)
+ throws EvaluationException {
+ ValueEval result;
+ if (arg instanceof RefEval) {
+ result = ((RefEval) arg).getInnerValueEval();
+ } else if (arg instanceof AreaEval) {
+ result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
+ } else {
+ result = arg;
+ }
+ if (result instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval) result);
+ }
+ return result;
+ }
+
+ /**
+ * Implements (some perhaps not well known) Excel functionality to select a single cell from an
+ * area depending on the coordinates of the calling cell. Here is an example demonstrating
+ * both selection from a single row area and a single column area in the same formula.
+ *
+ * <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
+ * <tr><th> </th><th> A </th><th> B </th><th> C </th><th> D </th></tr>
+ * <tr><th>1</th><td>15</td><td>20</td><td>25</td><td> </td></tr>
+ * <tr><th>2</th><td> </td><td> </td><td> </td><td>200</td></tr>
+ * <tr><th>3</th><td> </td><td> </td><td> </td><td>300</td></tr>
+ * <tr><th>3</th><td> </td><td> </td><td> </td><td>400</td></tr>
+ * </table>
+ *
+ * If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
+ * will look like this:
+ *
+ * <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
+ * <tr><th> </th><th> A </th><th> B </th><th> C </th><th> D </th></tr>
+ * <tr><th>1</th><td>15</td><td>20</td><td>25</td><td> </td></tr>
+ * <tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr>
+ * <tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr>
+ * <tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr>
+ * </table>
+ *
+ * Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does
+ * not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula
+ * as written, but in the 4 cells A2:B3, the row and column selection works ok.<p/>
+ *
+ * The same concept is extended to references across sheets, such that even multi-row,
+ * multi-column areas can be useful.<p/>
+ *
+ * Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and
+ * hence this method <b>can</b> throw a 'circular reference' EvaluationException. Note that
+ * this method does not attempt to detect cycles. Every cell in the specified Area <tt>ae</tt>
+ * has already been evaluated prior to this method call. Any cell (or cell<b>s</b>) part of
+ * <tt>ae</tt> that would incur a cyclic reference error if selected by this method, will
+ * already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> upon entry to this method. It
+ * is assumed logic exists elsewhere to produce this behaviour.
+ *
+ * @return whatever the selected cell's evaluated value is. Never <code>null</code>. Never
+ * <tt>ErrorEval</tt>.
+ * @throws EvaluationException if there is a problem with indexing into the area, or if the
+ * evaluated cell has an error.
+ */
+ public static ValueEval chooseSingleElementFromArea(AreaEval ae,
+ int srcCellRow, int srcCellCol) throws EvaluationException {
+ ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol);
+ if (result instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval) result);
+ }
+ return result;
+ }
+
+ /**
+ * @return possibly <tt>ErrorEval</tt>, and <code>null</code>
+ */
+ private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae,
+ int srcCellRow, int srcCellCol) throws EvaluationException {
+
+ if(false) {
+ // this is too simplistic
+ if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
+ throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
+ }
+ /*
+ Circular references are not dealt with directly here, but it is worth noting some issues.
+
+ ANY one of the return statements in this method could return a cell that is identical
+ to the one immediately being evaluated. The evaluating cell is identified by srcCellRow,
+ srcCellRow AND sheet. The sheet is not available in any nearby calling method, so that's
+ one reason why circular references are not easy to detect here. (The sheet of the returned
+ cell can be obtained from ae if it is an Area3DEval.)
+
+ Another reason there's little value in attempting to detect circular references here is
+ that only direct circular references could be detected. If the cycle involved two or more
+ cells this method could not detect it.
+
+ Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector
+ (and FormulaEvaluator).
+ */
+ }
+
+ if (ae.isColumn()) {
+ if(ae.isRow()) {
+ return ae.getRelativeValue(0, 0);
+ }
+ if(!ae.containsRow(srcCellRow)) {
+ throw EvaluationException.invalidValue();
+ }
+ return ae.getAbsoluteValue(srcCellRow, ae.getFirstColumn());
+ }
+ if(!ae.isRow()) {
+ // multi-column, multi-row area
+ if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
+ return ae.getAbsoluteValue(ae.getFirstRow(), ae.getFirstColumn());
+ }
+ throw EvaluationException.invalidValue();
+ }
+ if(!ae.containsColumn(srcCellCol)) {
+ throw EvaluationException.invalidValue();
+ }
+ return ae.getAbsoluteValue(ae.getFirstRow(), srcCellCol);
+ }
+
+ /**
+ * Applies some conversion rules if the supplied value is not already an integer.<br/>
+ * Value is first coerced to a <tt>double</tt> ( See <tt>coerceValueToDouble()</tt> ).
+ * Note - <tt>BlankEval</tt> is converted to <code>0</code>.<p/>
+ *
+ * Excel typically converts doubles to integers by truncating toward negative infinity.<br/>
+ * The equivalent java code is:<br/>
+ * <code>return (int)Math.floor(d);</code><br/>
+ * <b>not</b>:<br/>
+ * <code>return (int)d; // wrong - rounds toward zero</code>
+ *
+ */
+ public static int coerceValueToInt(ValueEval ev) throws EvaluationException {
+ if (ev == BlankEval.instance) {
+ return 0;
+ }
+ double d = coerceValueToDouble(ev);
+ // Note - the standard java type conversion from double to int truncates toward zero.
+ // but Math.floor() truncates toward negative infinity
+ return (int)Math.floor(d);
+ }
+
+ /**
+ * Applies some conversion rules if the supplied value is not already a number.
+ * Note - <tt>BlankEval</tt> is converted to {@link NumberEval#ZERO}.
+ * @param ev must be a {@link NumberEval}, {@link StringEval}, {@link BoolEval} or
+ * {@link BlankEval}
+ * @return actual, parsed or interpreted double value (respectively).
+ * @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed
+ * as a double (See <tt>parseDouble()</tt> for allowable formats).
+ * @throws RuntimeException if the supplied parameter is not {@link NumberEval},
+ * {@link StringEval}, {@link BoolEval} or {@link BlankEval}
+ */
+ public static double coerceValueToDouble(ValueEval ev) throws EvaluationException {
+
+ if (ev == BlankEval.instance) {
+ return 0.0;
+ }
+ if (ev instanceof NumericValueEval) {
+ // this also handles booleans
+ return ((NumericValueEval)ev).getNumberValue();
+ }
+ if (ev instanceof StringEval) {
+ Double dd = parseDouble(((StringEval) ev).getStringValue());
+ if (dd == null) {
+ throw EvaluationException.invalidValue();
+ }
+ return dd.doubleValue();
+ }
+ throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")");
+ }
+
+ /**
+ * Converts a string to a double using standard rules that Excel would use.<br/>
+ * Tolerates leading and trailing spaces, <p/>
+ *
+ * Doesn't support currency prefixes, commas, percentage signs or arithmetic operations strings.
+ *
+ * Some examples:<br/>
+ * " 123 " -> 123.0<br/>
+ * ".123" -> 0.123<br/>
+ * "1E4" -> 1000<br/>
+ * "-123" -> -123.0<br/>
+ * These not supported yet:<br/>
+ * " $ 1,000.00 " -> 1000.0<br/>
+ * "$1.25E4" -> 12500.0<br/>
+ * "5**2" -> 500<br/>
+ * "250%" -> 2.5<br/>
+ *
+ * @return <code>null</code> if the specified text cannot be parsed as a number
+ */
+ public static Double parseDouble(String pText) {
+
+ if (Pattern.matches(fpRegex, pText))
+ try {
+ return Double.parseDouble(pText);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ else {
+ return null;
+ }
+
+ }
+
+ /**
+ * @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
+ * @return the converted string value. never <code>null</code>
+ */
+ public static String coerceValueToString(ValueEval ve) {
+ if (ve instanceof StringValueEval) {
+ StringValueEval sve = (StringValueEval) ve;
+ return sve.getStringValue();
+ }
+ if (ve == BlankEval.instance) {
+ return "";
+ }
+ throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")");
+ }
+
+ /**
+ * @return <code>null</code> to represent blank values
+ * @throws EvaluationException if ve is an ErrorEval, or if a string value cannot be converted
+ */
+ public static Boolean coerceValueToBoolean(ValueEval ve, boolean stringsAreBlanks) throws EvaluationException {
+
+ if (ve == null || ve == BlankEval.instance) {
+ // TODO - remove 've == null' condition once AreaEval is fixed
+ return null;
+ }
+ if (ve instanceof BoolEval) {
+ return Boolean.valueOf(((BoolEval) ve).getBooleanValue());
+ }
+
+ if (ve == BlankEval.instance) {
+ return null;
+ }
+
+ if (ve instanceof StringEval) {
+ if (stringsAreBlanks) {
+ return null;
+ }
+ String str = ((StringEval) ve).getStringValue();
+ if (str.equalsIgnoreCase("true")) {
+ return Boolean.TRUE;
+ }
+ if (str.equalsIgnoreCase("false")) {
+ return Boolean.FALSE;
+ }
+ // else - string cannot be converted to boolean
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+
+ if (ve instanceof NumericValueEval) {
+ NumericValueEval ne = (NumericValueEval) ve;
+ double d = ne.getNumberValue();
+ if (Double.isNaN(d)) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ return Boolean.valueOf(d != 0);
+ }
+ if (ve instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval) ve);
+ }
+ throw new RuntimeException("Unexpected eval (" + ve.getClass().getName() + ")");
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+
+/**
+ * Implementation of Excel formula token '%'. <p/>
+ * @author Josh Micich
+ */
+public final class PercentEval extends Fixed1ArgFunction {
+
+ public static final Function instance = new PercentEval();
+
+ private PercentEval() {
+ // enforce singleton
+ }
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+ double d;
+ try {
+ ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+ d = OperandResolver.coerceValueToDouble(ve);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ if (d == 0.0) { // this '==' matches +0.0 and -0.0
+ return NumberEval.ZERO;
+ }
+ return new NumberEval(d / 100);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+
+/**
+ *
+ * @author Josh Micich
+ */
+public final class RangeEval extends Fixed2ArgFunction {
+
+ public static final Function instance = new RangeEval();
+
+ private RangeEval() {
+ // enforces singleton
+ }
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+
+ try {
+ AreaEval reA = evaluateRef(arg0);
+ AreaEval reB = evaluateRef(arg1);
+ return resolveRange(reA, reB);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+ /**
+ * @return simple rectangular {@link AreaEval} which fully encloses both areas
+ * <tt>aeA</tt> and <tt>aeB</tt>
+ */
+ private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
+ int aeAfr = aeA.getFirstRow();
+ int aeAfc = aeA.getFirstColumn();
+
+ int top = Math.min(aeAfr, aeB.getFirstRow());
+ int bottom = Math.max(aeA.getLastRow(), aeB.getLastRow());
+ int left = Math.min(aeAfc, aeB.getFirstColumn());
+ int right = Math.max(aeA.getLastColumn(), aeB.getLastColumn());
+
+ return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
+ }
+
+ private static AreaEval evaluateRef(ValueEval arg) throws EvaluationException {
+ if (arg instanceof AreaEval) {
+ return (AreaEval) arg;
+ }
+ if (arg instanceof RefEval) {
+ return ((RefEval) arg).offset(0, 0, 0, 0);
+ }
+ if (arg instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval)arg);
+ }
+ throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S Deshmukh < amolweb at ya hoo dot com >
+ *
+ * RefEval is the super interface for Ref2D and Ref3DEval. Basically a RefEval
+ * impl should contain reference to the original ReferencePtg or Ref3DPtg as
+ * well as the final "value" resulting from the evaluation of the cell
+ * reference. Thus if the Cell has type CELL_TYPE_NUMERIC, the contained
+ * value object should be of type NumberEval; if cell type is CELL_TYPE_STRING,
+ * contained value object should be of type StringEval
+ */
+public interface RefEval extends ValueEval {
+
+ /**
+ * @return the evaluated value of the cell referred to by this RefEval.
+ */
+ ValueEval getInnerValueEval();
+
+ /**
+ * returns the zero based column index.
+ */
+ int getColumn();
+
+ /**
+ * returns the zero based row index.
+ */
+ int getRow();
+
+ /**
+ * Creates an {@link AreaEval} offset by a relative amount from this RefEval
+ */
+ AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx);
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * Common base class for implementors of {@link RefEval}
+ *
+ * @author Josh Micich
+ */
+public abstract class RefEvalBase implements RefEval {
+
+ private final int _rowIndex;
+ private final int _columnIndex;
+
+ protected RefEvalBase(int rowIndex, int columnIndex) {
+ _rowIndex = rowIndex;
+ _columnIndex = columnIndex;
+ }
+ public final int getRow() {
+ return _rowIndex;
+ }
+ public final int getColumn() {
+ return _columnIndex;
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+import org.apache.poi.ss.util.NumberComparer;
+
+/**
+ * Base class for all comparison operator evaluators
+ *
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public abstract class RelationalOperationEval extends Fixed2ArgFunction {
+
+ /**
+ * Converts a standard compare result (-1, 0, 1) to <code>true</code> or <code>false</code>
+ * according to subclass' comparison type.
+ */
+ protected abstract boolean convertComparisonResult(int cmpResult);
+
+ /**
+ * This is a description of how the relational operators apply in MS Excel.
+ * Use this as a guideline when testing/implementing the evaluate methods
+ * for the relational operators Evals.
+ *
+ * <pre>
+ * Bool.TRUE > any number.
+ * Bool > any string. ALWAYS
+ * Bool.TRUE > Bool.FALSE
+ * Bool.FALSE == Blank
+ *
+ * Strings are never converted to numbers or booleans
+ * String > any number. ALWAYS
+ * Non-empty String > Blank
+ * Empty String == Blank
+ * String are sorted dictionary wise
+ *
+ * Blank > Negative numbers
+ * Blank == 0
+ * Blank < Positive numbers
+ * </pre>
+ */
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+
+ ValueEval vA;
+ ValueEval vB;
+ try {
+ vA = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+ vB = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ int cmpResult = doCompare(vA, vB);
+ boolean result = convertComparisonResult(cmpResult);
+ return BoolEval.valueOf(result);
+ }
+
+ private static int doCompare(ValueEval va, ValueEval vb) {
+ // special cases when one operand is blank
+ if (va == BlankEval.instance) {
+ return compareBlank(vb);
+ }
+ if (vb == BlankEval.instance) {
+ return -compareBlank(va);
+ }
+
+ if (va instanceof BoolEval) {
+ if (vb instanceof BoolEval) {
+ BoolEval bA = (BoolEval) va;
+ BoolEval bB = (BoolEval) vb;
+ if (bA.getBooleanValue() == bB.getBooleanValue()) {
+ return 0;
+ }
+ return bA.getBooleanValue() ? 1 : -1;
+ }
+ return 1;
+ }
+ if (vb instanceof BoolEval) {
+ return -1;
+ }
+ if (va instanceof StringEval) {
+ if (vb instanceof StringEval) {
+ StringEval sA = (StringEval) va;
+ StringEval sB = (StringEval) vb;
+ return sA.getStringValue().compareToIgnoreCase(sB.getStringValue());
+ }
+ return 1;
+ }
+ if (vb instanceof StringEval) {
+ return -1;
+ }
+ if (va instanceof NumberEval) {
+ if (vb instanceof NumberEval) {
+ NumberEval nA = (NumberEval) va;
+ NumberEval nB = (NumberEval) vb;
+ return NumberComparer.compare(nA.getNumberValue(), nB.getNumberValue());
+ }
+ }
+ throw new IllegalArgumentException("Bad operand types (" + va.getClass().getName() + "), ("
+ + vb.getClass().getName() + ")");
+ }
+
+ private static int compareBlank(ValueEval v) {
+ if (v == BlankEval.instance) {
+ return 0;
+ }
+ if (v instanceof BoolEval) {
+ BoolEval boolEval = (BoolEval) v;
+ return boolEval.getBooleanValue() ? -1 : 0;
+ }
+ if (v instanceof NumberEval) {
+ NumberEval ne = (NumberEval) v;
+ return NumberComparer.compare(0.0, ne.getNumberValue());
+ }
+ if (v instanceof StringEval) {
+ StringEval se = (StringEval) v;
+ return se.getStringValue().length() < 1 ? 0 : -1;
+ }
+ throw new IllegalArgumentException("bad value class (" + v.getClass().getName() + ")");
+ }
+
+ public static final Function EqualEval = new RelationalOperationEval() {
+ protected boolean convertComparisonResult(int cmpResult) {
+ return cmpResult == 0;
+ }
+ };
+ public static final Function GreaterEqualEval = new RelationalOperationEval() {
+ protected boolean convertComparisonResult(int cmpResult) {
+ return cmpResult >= 0;
+ }
+ };
+ public static final Function GreaterThanEval = new RelationalOperationEval() {
+ protected boolean convertComparisonResult(int cmpResult) {
+ return cmpResult > 0;
+ }
+ };
+ public static final Function LessEqualEval = new RelationalOperationEval() {
+ protected boolean convertComparisonResult(int cmpResult) {
+ return cmpResult <= 0;
+ }
+ };
+ public static final Function LessThanEval = new RelationalOperationEval() {
+ protected boolean convertComparisonResult(int cmpResult) {
+ return cmpResult < 0;
+ }
+ };
+ public static final Function NotEqualEval = new RelationalOperationEval() {
+ protected boolean convertComparisonResult(int cmpResult) {
+ return cmpResult != 0;
+ }
+ };
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.StringPtg;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public final class StringEval implements StringValueEval {
+
+ public static final StringEval EMPTY_INSTANCE = new StringEval("");
+
+ private final String _value;
+
+ public StringEval(Ptg ptg) {
+ this(((StringPtg) ptg).getValue());
+ }
+
+ public StringEval(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("value must not be null");
+ }
+ _value = value;
+ }
+
+ public String getStringValue() {
+ return _value;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_value);
+ sb.append("]");
+ return sb.toString();
+ }
+}
--- /dev/null
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ *
+ */
+public interface StringValueEval extends ValueEval {
+
+ /**
+ * @return never <code>null</code>, possibly empty string.
+ */
+ String getStringValue();
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Josh Micich
+ */
+public abstract class TwoOperandNumericOperation extends Fixed2ArgFunction {
+
+ protected final double singleOperandEvaluate(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
+ ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
+ return OperandResolver.coerceValueToDouble(ve);
+ }
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+ double result;
+ try {
+ double d0 = singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex);
+ double d1 = singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex);
+ result = evaluate(d0, d1);
+ if (result == 0.0) { // this '==' matches +0.0 and -0.0
+ // Excel converts -0.0 to +0.0 for '*', '/', '%', '+' and '^'
+ if (!(this instanceof SubtractEvalClass)) {
+ return NumberEval.ZERO;
+ }
+ }
+ if (Double.isNaN(result) || Double.isInfinite(result)) {
+ return ErrorEval.NUM_ERROR;
+ }
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ return new NumberEval(result);
+ }
+
+ protected abstract double evaluate(double d0, double d1) throws EvaluationException;
+
+ public static final Function AddEval = new TwoOperandNumericOperation() {
+ protected double evaluate(double d0, double d1) {
+ return d0+d1;
+ }
+ };
+ public static final Function DivideEval = new TwoOperandNumericOperation() {
+ protected double evaluate(double d0, double d1) throws EvaluationException {
+ if (d1 == 0.0) {
+ throw new EvaluationException(ErrorEval.DIV_ZERO);
+ }
+ return d0/d1;
+ }
+ };
+ public static final Function MultiplyEval = new TwoOperandNumericOperation() {
+ protected double evaluate(double d0, double d1) {
+ return d0*d1;
+ }
+ };
+ public static final Function PowerEval = new TwoOperandNumericOperation() {
+ protected double evaluate(double d0, double d1) {
+ return Math.pow(d0, d1);
+ }
+ };
+ private static final class SubtractEvalClass extends TwoOperandNumericOperation {
+ public SubtractEvalClass() {
+ //
+ }
+ protected double evaluate(double d0, double d1) {
+ return d0-d1;
+ }
+ }
+ public static final Function SubtractEval = new SubtractEvalClass();
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public final class UnaryMinusEval extends Fixed1ArgFunction {
+
+ public static final Function instance = new UnaryMinusEval();
+
+ private UnaryMinusEval() {
+ // enforce singleton
+ }
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+ double d;
+ try {
+ ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+ d = OperandResolver.coerceValueToDouble(ve);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ if (d == 0.0) { // this '==' matches +0.0 and -0.0
+ return NumberEval.ZERO;
+ }
+ return new NumberEval(-d);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
+import org.apache.poi.ss.formula.functions.Function;
+
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public final class UnaryPlusEval extends Fixed1ArgFunction {
+
+ public static final Function instance = new UnaryPlusEval();
+
+ private UnaryPlusEval() {
+ // enforce singleton
+ }
+
+ public ValueEval evaluate(int srcCellRow, int srcCellCol, ValueEval arg0) {
+ double d;
+ try {
+ ValueEval ve = OperandResolver.getSingleValue(arg0, srcCellRow, srcCellCol);
+ if(ve instanceof StringEval) {
+ // Note - asymmetric with UnaryMinus
+ // -"hello" evaluates to #VALUE!
+ // but +"hello" evaluates to "hello"
+ return ve;
+ }
+ d = OperandResolver.coerceValueToDouble(ve);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ return new NumberEval(+d);
+ }
+}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.eval;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public interface ValueEval {
+ // no methods
+}
package org.apache.poi.ss.formula.eval.forked;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
-import org.apache.poi.hssf.record.formula.eval.BoolEval;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.EvaluationCell;
import org.apache.poi.ss.formula.EvaluationSheet;
import org.apache.poi.ss.usermodel.Cell;
Class<? extends ValueEval> cls = value.getClass();
if (cls == NumberEval.class) {
- _cellType = HSSFCell.CELL_TYPE_NUMERIC;
+ _cellType = Cell.CELL_TYPE_NUMERIC;
_numberValue = ((NumberEval)value).getNumberValue();
return;
}
if (cls == StringEval.class) {
- _cellType = HSSFCell.CELL_TYPE_STRING;
+ _cellType = Cell.CELL_TYPE_STRING;
_stringValue = ((StringEval)value).getStringValue();
return;
}
if (cls == BoolEval.class) {
- _cellType = HSSFCell.CELL_TYPE_BOOLEAN;
+ _cellType = Cell.CELL_TYPE_BOOLEAN;
_booleanValue = ((BoolEval)value).getBooleanValue();
return;
}
if (cls == ErrorEval.class) {
- _cellType = HSSFCell.CELL_TYPE_ERROR;
+ _cellType = Cell.CELL_TYPE_ERROR;
_errorValue = ((ErrorEval)value).getErrorCode();
return;
}
if (cls == BlankEval.class) {
- _cellType = HSSFCell.CELL_TYPE_BLANK;
+ _cellType = Cell.CELL_TYPE_BLANK;
return;
}
throw new IllegalArgumentException("Unexpected value class (" + cls.getName() + ")");
return _cellType;
}
public boolean getBooleanCellValue() {
- checkCellType(HSSFCell.CELL_TYPE_BOOLEAN);
+ checkCellType(Cell.CELL_TYPE_BOOLEAN);
return _booleanValue;
}
public int getErrorCellValue() {
- checkCellType(HSSFCell.CELL_TYPE_ERROR);
+ checkCellType(Cell.CELL_TYPE_ERROR);
return _errorValue;
}
public double getNumericCellValue() {
- checkCellType(HSSFCell.CELL_TYPE_NUMERIC);
+ checkCellType(Cell.CELL_TYPE_NUMERIC);
return _numberValue;
}
public String getStringCellValue() {
- checkCellType(HSSFCell.CELL_TYPE_STRING);
+ checkCellType(Cell.CELL_TYPE_STRING);
return _stringValue;
}
public EvaluationSheet getSheet() {
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.EvaluationCell;
import org.apache.poi.ss.formula.EvaluationName;
import org.apache.poi.ss.formula.EvaluationSheet;
package org.apache.poi.ss.formula.eval.forked;
-import org.apache.poi.hssf.record.formula.eval.BoolEval;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.record.formula.udf.UDFFinder;
-import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.ss.formula.IStabilityClassifier;
import org.apache.poi.ss.formula.WorkbookEvaluator;
+import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
/**
EvaluationCell cell = _sewb.getEvaluationCell(sheetName, rowIndex, columnIndex);
switch (cell.getCellType()) {
- case HSSFCell.CELL_TYPE_BOOLEAN:
+ case Cell.CELL_TYPE_BOOLEAN:
return BoolEval.valueOf(cell.getBooleanCellValue());
- case HSSFCell.CELL_TYPE_ERROR:
+ case Cell.CELL_TYPE_ERROR:
return ErrorEval.valueOf(cell.getErrorCellValue());
- case HSSFCell.CELL_TYPE_FORMULA:
+ case Cell.CELL_TYPE_FORMULA:
return _evaluator.evaluate(cell);
- case HSSFCell.CELL_TYPE_NUMERIC:
+ case Cell.CELL_TYPE_NUMERIC:
return new NumberEval(cell.getNumericCellValue());
- case HSSFCell.CELL_TYPE_STRING:
+ case Cell.CELL_TYPE_STRING:
return new StringEval(cell.getStringCellValue());
- case HSSFCell.CELL_TYPE_BLANK:
+ case Cell.CELL_TYPE_BLANK:
return null;
}
throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
package org.apache.poi.ss.usermodel;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.formula.eval.ErrorEval;
/**
* Mimics the 'data view' of a cell. This allows formula evaluator