Browse Source

moved common formula-related code to org.apache.poi.ss.formula, eliminated dependencies on HSSF, reduced the number of eclipse warnings

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1037436 13f79535-47bb-0310-9956-ffa450edef68
tags/REL_3_8_BETA1
Yegor Kozlov 13 years ago
parent
commit
b34e1e25d6
36 changed files with 3036 additions and 32 deletions
  1. 164
    0
      src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
  2. 67
    0
      src/java/org/apache/poi/ss/formula/atp/ParityFunction.java
  3. 84
    0
      src/java/org/apache/poi/ss/formula/atp/RandBetween.java
  4. 159
    0
      src/java/org/apache/poi/ss/formula/atp/YearFrac.java
  5. 344
    0
      src/java/org/apache/poi/ss/formula/atp/YearFracCalculator.java
  6. 93
    0
      src/java/org/apache/poi/ss/formula/eval/AreaEval.java
  7. 117
    0
      src/java/org/apache/poi/ss/formula/eval/AreaEvalBase.java
  8. 35
    0
      src/java/org/apache/poi/ss/formula/eval/BlankEval.java
  9. 64
    0
      src/java/org/apache/poi/ss/formula/eval/BoolEval.java
  10. 60
    0
      src/java/org/apache/poi/ss/formula/eval/ConcatEval.java
  11. 111
    0
      src/java/org/apache/poi/ss/formula/eval/ErrorEval.java
  12. 134
    0
      src/java/org/apache/poi/ss/formula/eval/EvaluationException.java
  13. 249
    0
      src/java/org/apache/poi/ss/formula/eval/FunctionEval.java
  14. 96
    0
      src/java/org/apache/poi/ss/formula/eval/IntersectionEval.java
  15. 36
    0
      src/java/org/apache/poi/ss/formula/eval/MissingArgEval.java
  16. 46
    0
      src/java/org/apache/poi/ss/formula/eval/NameEval.java
  17. 44
    0
      src/java/org/apache/poi/ss/formula/eval/NameXEval.java
  18. 73
    0
      src/java/org/apache/poi/ss/formula/eval/NumberEval.java
  19. 30
    0
      src/java/org/apache/poi/ss/formula/eval/NumericValueEval.java
  20. 324
    0
      src/java/org/apache/poi/ss/formula/eval/OperandResolver.java
  21. 49
    0
      src/java/org/apache/poi/ss/formula/eval/PercentEval.java
  22. 75
    0
      src/java/org/apache/poi/ss/formula/eval/RangeEval.java
  23. 51
    0
      src/java/org/apache/poi/ss/formula/eval/RefEval.java
  24. 40
    0
      src/java/org/apache/poi/ss/formula/eval/RefEvalBase.java
  25. 168
    0
      src/java/org/apache/poi/ss/formula/eval/RelationalOperationEval.java
  26. 54
    0
      src/java/org/apache/poi/ss/formula/eval/StringEval.java
  27. 30
    0
      src/java/org/apache/poi/ss/formula/eval/StringValueEval.java
  28. 87
    0
      src/java/org/apache/poi/ss/formula/eval/TwoOperandNumericOperation.java
  29. 47
    0
      src/java/org/apache/poi/ss/formula/eval/UnaryMinusEval.java
  30. 51
    0
      src/java/org/apache/poi/ss/formula/eval/UnaryPlusEval.java
  31. 25
    0
      src/java/org/apache/poi/ss/formula/eval/ValueEval.java
  32. 15
    16
      src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java
  33. 0
    1
      src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java
  34. 13
    13
      src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluator.java
  35. 1
    2
      src/java/org/apache/poi/ss/usermodel/CellValue.java
  36. BIN
      test-data/spreadsheet/LookupFunctionsTestCaseData.xls

+ 164
- 0
src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java View File

@@ -0,0 +1,164 @@
/* ====================================================================
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);
}
}

+ 67
- 0
src/java/org/apache/poi/ss/formula/atp/ParityFunction.java View File

@@ -0,0 +1,67 @@
/* ====================================================================
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);
}
}

+ 84
- 0
src/java/org/apache/poi/ss/formula/atp/RandBetween.java View File

@@ -0,0 +1,84 @@
/* ====================================================================
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))));
}
}

+ 159
- 0
src/java/org/apache/poi/ss/formula/atp/YearFrac.java View File

@@ -0,0 +1,159 @@
/* ====================================================================
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);
}
}

+ 344
- 0
src/java/org/apache/poi/ss/formula/atp/YearFracCalculator.java View File

@@ -0,0 +1,344 @@
/* ====================================================================
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();
}
}
}

+ 93
- 0
src/java/org/apache/poi/ss/formula/eval/AreaEval.java View File

@@ -0,0 +1,93 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*
*/
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);
}

+ 117
- 0
src/java/org/apache/poi/ss/formula/eval/AreaEvalBase.java View File

@@ -0,0 +1,117 @@
/* ====================================================================
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;
}
}

+ 35
- 0
src/java/org/apache/poi/ss/formula/eval/BlankEval.java View File

@@ -0,0 +1,35 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt; 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
}
}

+ 64
- 0
src/java/org/apache/poi/ss/formula/eval/BoolEval.java View File

@@ -0,0 +1,64 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
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();
}
}

+ 60
- 0
src/java/org/apache/poi/ss/formula/eval/ConcatEval.java View File

@@ -0,0 +1,60 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
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() + ")");
}
}

+ 111
- 0
src/java/org/apache/poi/ss/formula/eval/ErrorEval.java View File

@@ -0,0 +1,111 @@
/*
* 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 &lt; amolweb at ya hoo dot com &gt;
*
*/
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();
}
}

+ 134
- 0
src/java/org/apache/poi/ss/formula/eval/EvaluationException.java View File

@@ -0,0 +1,134 @@
/* ====================================================================
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;
}
}

+ 249
- 0
src/java/org/apache/poi/ss/formula/eval/FunctionEval.java View File

@@ -0,0 +1,249 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
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;
}
}

+ 96
- 0
src/java/org/apache/poi/ss/formula/eval/IntersectionEval.java View File

@@ -0,0 +1,96 @@
/* ====================================================================
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() + ")");
}
}

+ 36
- 0
src/java/org/apache/poi/ss/formula/eval/MissingArgEval.java View File

@@ -0,0 +1,36 @@
/* ====================================================================
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
}
}

+ 46
- 0
src/java/org/apache/poi/ss/formula/eval/NameEval.java View File

@@ -0,0 +1,46 @@
/* ====================================================================
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();
}
}

+ 44
- 0
src/java/org/apache/poi/ss/formula/eval/NameXEval.java View File

@@ -0,0 +1,44 @@
/* ====================================================================
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();
}
}

+ 73
- 0
src/java/org/apache/poi/ss/formula/eval/NumberEval.java View File

@@ -0,0 +1,73 @@
/*
* 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 &lt; amolweb at ya hoo dot com &gt;
*
*/
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();
}
}

+ 30
- 0
src/java/org/apache/poi/ss/formula/eval/NumericValueEval.java View File

@@ -0,0 +1,30 @@
/*
* 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 &lt; amolweb at ya hoo dot com &gt;
*
*/
public interface NumericValueEval extends ValueEval {

public abstract double getNumberValue();
}

+ 324
- 0
src/java/org/apache/poi/ss/formula/eval/OperandResolver.java View File

@@ -0,0 +1,324 @@
/* ====================================================================
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>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
* <tr><th>2</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>200</td></tr>
* <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>300</td></tr>
* <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</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>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</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/>
* &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/>
* <b>not</b>:<br/>
* &nbsp;&nbsp;<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 " -&gt; 123.0<br/>
* ".123" -&gt; 0.123<br/>
* "1E4" -&gt; 1000<br/>
* "-123" -&gt; -123.0<br/>
* These not supported yet:<br/>
* " $ 1,000.00 " -&gt; 1000.0<br/>
* "$1.25E4" -&gt; 12500.0<br/>
* "5**2" -&gt; 500<br/>
* "250%" -&gt; 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() + ")");
}
}

+ 49
- 0
src/java/org/apache/poi/ss/formula/eval/PercentEval.java View File

@@ -0,0 +1,49 @@
/* ====================================================================
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);
}
}

+ 75
- 0
src/java/org/apache/poi/ss/formula/eval/RangeEval.java View File

@@ -0,0 +1,75 @@
/* ====================================================================
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() + ")");
}
}

+ 51
- 0
src/java/org/apache/poi/ss/formula/eval/RefEval.java View File

@@ -0,0 +1,51 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*
* 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);
}

+ 40
- 0
src/java/org/apache/poi/ss/formula/eval/RefEvalBase.java View File

@@ -0,0 +1,40 @@
/* ====================================================================
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;
}
}

+ 168
- 0
src/java/org/apache/poi/ss/formula/eval/RelationalOperationEval.java View File

@@ -0,0 +1,168 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
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;
}
};
}

+ 54
- 0
src/java/org/apache/poi/ss/formula/eval/StringEval.java View File

@@ -0,0 +1,54 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
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();
}
}

+ 30
- 0
src/java/org/apache/poi/ss/formula/eval/StringValueEval.java View File

@@ -0,0 +1,30 @@
/*
* 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 &lt; amolweb at ya hoo dot com &gt;
*
*/
public interface StringValueEval extends ValueEval {

/**
* @return never <code>null</code>, possibly empty string.
*/
String getStringValue();
}

+ 87
- 0
src/java/org/apache/poi/ss/formula/eval/TwoOperandNumericOperation.java View File

@@ -0,0 +1,87 @@
/* ====================================================================
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();
}

+ 47
- 0
src/java/org/apache/poi/ss/formula/eval/UnaryMinusEval.java View File

@@ -0,0 +1,47 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
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);
}
}

+ 51
- 0
src/java/org/apache/poi/ss/formula/eval/UnaryPlusEval.java View File

@@ -0,0 +1,51 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
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);
}
}

+ 25
- 0
src/java/org/apache/poi/ss/formula/eval/ValueEval.java View File

@@ -0,0 +1,25 @@
/* ====================================================================
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 &lt; amolweb at ya hoo dot com &gt;
*/
public interface ValueEval {
// no methods
}

+ 15
- 16
src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java View File

@@ -17,13 +17,12 @@

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;
@@ -60,27 +59,27 @@ final class ForkedEvaluationCell implements EvaluationCell {
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() + ")");
@@ -105,19 +104,19 @@ final class ForkedEvaluationCell implements EvaluationCell {
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() {

+ 0
- 1
src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java View File

@@ -23,7 +23,6 @@ import java.util.Map;
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;

+ 13
- 13
src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluator.java View File

@@ -17,13 +17,12 @@

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;
@@ -31,6 +30,7 @@ import org.apache.poi.ss.formula.EvaluationCell;
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;

/**
@@ -113,17 +113,17 @@ public final class ForkedEvaluator {
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() + ")");

+ 1
- 2
src/java/org/apache/poi/ss/usermodel/CellValue.java View File

@@ -17,8 +17,7 @@

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

BIN
test-data/spreadsheet/LookupFunctionsTestCaseData.xls View File


Loading…
Cancel
Save