From: Josh Micich Date: Fri, 29 Aug 2008 05:29:56 +0000 (+0000) Subject: Removing calls to AreaEval.getValues() from count and lookup functions X-Git-Tag: REL_3_2_FINAL~124 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4897063eab19a290d5e369ca237f25618f872c12;p=poi.git Removing calls to AreaEval.getValues() from count and lookup functions git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@690112 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java index 75848eace9..182b9b618b 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java @@ -1,23 +1,20 @@ -/* -* 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 - * - */ +/* ==================================================================== + 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.hssf.record.formula.eval; /** @@ -72,13 +69,9 @@ public interface AreaEval extends ValueEval { ValueEval[] getValues(); /** - * returns the ValueEval from the values array at the specified - * row and col index. The specified indexes should be absolute indexes - * in the sheet and not relative indexes within the area. Also, - * if contains(row, col) evaluates to true, a null value will - * bre returned. - * @param row - * @param col + * @return the ValueEval from within this area at the specified row and col index. Never + * null (possibly {@link BlankEval}). The specified indexes should be absolute + * indexes in the sheet and not relative indexes within the area. */ ValueEval getValueAt(int row, int col); @@ -105,5 +98,10 @@ public interface AreaEval extends ValueEval { int getWidth(); int getHeight(); + /** + * @return the ValueEval from within this area at the specified relativeRowIndex and + * relativeColumnIndex. Never null (possibly {@link BlankEval}). The + * specified indexes should relative to the top left corner of this area. + */ ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex); } diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java index 130376c2e6..1686e75f33 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java @@ -1,19 +1,19 @@ -/* - * 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. - */ +/* ==================================================================== + 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.hssf.record.formula.eval; @@ -123,7 +123,11 @@ abstract class AreaEvalBase implements AreaEval { public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) { int index = relativeRowIndex * _nColumns + relativeColumnIndex; - return _values[index]; + ValueEval result = _values[index]; + if (result == null) { + return BlankEval.INSTANCE; + } + return result; } public int getWidth() { diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java index df671821fe..d1f28df008 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java @@ -1,30 +1,27 @@ -/* -* 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 9, 2005 - * - */ +/* ==================================================================== + 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.hssf.record.formula.eval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > This class is a * marker class. It is a special value for empty cells. */ -public class BlankEval implements ValueEval { +public final class BlankEval implements ValueEval { public static BlankEval INSTANCE = new BlankEval(); diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Count.java b/src/java/org/apache/poi/hssf/record/formula/functions/Count.java index eb55fc4a42..fd5944e858 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Count.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Count.java @@ -1,32 +1,26 @@ -/* -* 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 15, 2005 - * - */ +/* ==================================================================== + 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.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.AreaEval; -import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.NumberEval; -import org.apache.poi.hssf.record.formula.eval.RefEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate; /** * Counts the number of cells that contain numeric data within @@ -39,7 +33,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * TODO: Check this properly matches excel on edge cases * like formula cells, error cells etc */ -public class Count implements Function { +public final class Count implements Function { public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { int nArgs = args.length; @@ -56,63 +50,23 @@ public class Count implements Function { int temp = 0; for(int i=0; i @@ -144,12 +144,6 @@ public final class Countif implements Function { } } - /** - * Common interface for the matching criteria. - */ - /* package */ interface I_MatchPredicate { - boolean matches(Eval x); - } private static final class NumberMatcher implements I_MatchPredicate { @@ -360,21 +354,12 @@ public final class Countif implements Function { * @return the number of evaluated cells in the range that match the specified criteria */ private Eval countMatchingCellsInArea(Eval rangeArg, I_MatchPredicate criteriaPredicate) { - int result = 0; + + int result; if (rangeArg instanceof RefEval) { - RefEval refEval = (RefEval) rangeArg; - if(criteriaPredicate.matches(refEval.getInnerValueEval())) { - result++; - } + result = CountUtils.countMatchingCell((RefEval) rangeArg, criteriaPredicate); } else if (rangeArg instanceof AreaEval) { - - AreaEval range = (AreaEval) rangeArg; - ValueEval[] values = range.getValues(); - for (int i = 0; i < values.length; i++) { - if(criteriaPredicate.matches(values[i])) { - result++; - } - } + result = CountUtils.countMatchingCellsInArea((AreaEval) rangeArg, criteriaPredicate); } else { throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")"); } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java b/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java index d493cd5332..8604eadc37 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java @@ -42,40 +42,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; */ public final class Hlookup implements Function { - private static final class RowVector implements ValueVector { - - private final AreaEval _tableArray; - private final int _size; - private final int _rowAbsoluteIndex; - private final int _firstColumnAbsoluteIndex; - - public RowVector(AreaEval tableArray, int rowIndex) { - _rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex; - if(!tableArray.containsRow(_rowAbsoluteIndex)) { - int lastRowIx = tableArray.getLastRow() - tableArray.getFirstRow(); - throw new IllegalArgumentException("Specified row index (" + rowIndex - + ") is outside the allowed range (0.." + lastRowIx + ")"); - } - _tableArray = tableArray; - _size = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1; - if(_size < 1) { - throw new RuntimeException("bad table array size zero"); - } - _firstColumnAbsoluteIndex = tableArray.getFirstColumn(); - } - - public ValueEval getItem(int index) { - if(index>_size) { - throw new ArrayIndexOutOfBoundsException("Specified index (" + index - + ") is outside the allowed range (0.." + (_size-1) + ")"); - } - return _tableArray.getValueAt(_rowAbsoluteIndex, (short) (_firstColumnAbsoluteIndex + index)); - } - public int getSize() { - return _size; - } - } - public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { Eval arg3 = null; switch(args.length) { @@ -93,7 +59,7 @@ public final class Hlookup implements Function { ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]); boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol); - int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, new RowVector(tableArray, 0), isRangeLookup); + int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createRowVector(tableArray, 0), isRangeLookup); ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol); int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex); ValueVector resultCol = createResultColumnVector(tableArray, rowIndex); @@ -113,11 +79,9 @@ public final class Hlookup implements Function { if(colIndex < 0) { throw EvaluationException.invalidValue(); } - int nCols = tableArray.getLastColumn() - tableArray.getFirstRow() + 1; - - if(colIndex >= nCols) { + if(colIndex >= tableArray.getWidth()) { throw EvaluationException.invalidRef(); } - return new RowVector(tableArray, colIndex); + return LookupUtils.createRowVector(tableArray, colIndex); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java b/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java index be1d0d0f94..305d8fb0d7 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java @@ -40,19 +40,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; * @author Josh Micich */ public final class Lookup implements Function { - private static final class SimpleValueVector implements ValueVector { - private final ValueEval[] _values; - - public SimpleValueVector(ValueEval[] values) { - _values = values; - } - public ValueEval getItem(int index) { - return _values[index]; - } - public int getSize() { - return _values.length; - } - } public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { switch(args.length) { @@ -86,11 +73,11 @@ public final class Lookup implements Function { } private static ValueVector createVector(AreaEval ae) { - - if(!ae.isRow() && !ae.isColumn()) { - // extra complexity required to emulate the way LOOKUP can handles these abnormal cases. - throw new RuntimeException("non-vector lookup or result areas not supported yet"); + ValueVector result = LookupUtils.createVector(ae); + if (result != null) { + return result; } - return new SimpleValueVector(ae.getValues()); + // extra complexity required to emulate the way LOOKUP can handles these abnormal cases. + throw new RuntimeException("non-vector lookup or result areas not supported yet"); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java b/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java index d6a8489623..e8c083dc5a 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java @@ -34,11 +34,11 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH - * + * * @author Josh Micich */ final class LookupUtils { - + /** * Represents a single row or column within an AreaEval. */ @@ -46,14 +46,95 @@ final class LookupUtils { ValueEval getItem(int index); int getSize(); } + + + private static final class RowVector implements ValueVector { + + private final AreaEval _tableArray; + private final int _size; + private final int _rowIndex; + + public RowVector(AreaEval tableArray, int rowIndex) { + _rowIndex = rowIndex; + int _rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex; + if(!tableArray.containsRow(_rowAbsoluteIndex)) { + int lastRowIx = tableArray.getLastRow() - tableArray.getFirstRow(); + throw new IllegalArgumentException("Specified row index (" + rowIndex + + ") is outside the allowed range (0.." + lastRowIx + ")"); + } + _tableArray = tableArray; + _size = tableArray.getWidth(); + } + + public ValueEval getItem(int index) { + if(index > _size) { + throw new ArrayIndexOutOfBoundsException("Specified index (" + index + + ") is outside the allowed range (0.." + (_size-1) + ")"); + } + return _tableArray.getRelativeValue(_rowIndex, index); + } + public int getSize() { + return _size; + } + } + + private static final class ColumnVector implements ValueVector { + + private final AreaEval _tableArray; + private final int _size; + private final int _columnIndex; + + public ColumnVector(AreaEval tableArray, int columnIndex) { + _columnIndex = columnIndex; + int _columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex; + if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) { + int lastColIx = tableArray.getLastColumn() - tableArray.getFirstColumn(); + throw new IllegalArgumentException("Specified column index (" + columnIndex + + ") is outside the allowed range (0.." + lastColIx + ")"); + } + _tableArray = tableArray; + _size = _tableArray.getHeight(); + } + + public ValueEval getItem(int index) { + if(index > _size) { + throw new ArrayIndexOutOfBoundsException("Specified index (" + index + + ") is outside the allowed range (0.." + (_size-1) + ")"); + } + return _tableArray.getRelativeValue(index, _columnIndex); + } + public int getSize() { + return _size; + } + } + + public static ValueVector createRowVector(AreaEval tableArray, int relativeRowIndex) { + return new RowVector(tableArray, relativeRowIndex); + } + public static ValueVector createColumnVector(AreaEval tableArray, int relativeColumnIndex) { + return new ColumnVector(tableArray, relativeColumnIndex); + } + /** + * @return null if the supplied area is neither a single row nor a single colum + */ + public static ValueVector createVector(AreaEval ae) { + if (ae.isColumn()) { + return createColumnVector(ae, 0); + } + if (ae.isRow()) { + return createRowVector(ae, 0); + } + return null; + } + /** * Enumeration to support 4 valued comparison results.

- * Excel lookup functions have complex behaviour in the case where the lookup array has mixed + * Excel lookup functions have complex behaviour in the case where the lookup array has mixed * types, and/or is unordered. Contrary to suggestions in some Excel documentation, there * does not appear to be a universal ordering across types. The binary search algorithm used * changes behaviour when the evaluated 'mid' value has a different type to the lookup value.

- * - * A simple int might have done the same job, but there is risk in confusion with the well + * + * A simple int might have done the same job, but there is risk in confusion with the well * known Comparable.compareTo() and Comparator.compare() which both use * a ubiquitous 3 value result encoding. */ @@ -80,7 +161,7 @@ final class LookupUtils { public static final CompareResult LESS_THAN = new CompareResult(false, -1); public static final CompareResult EQUAL = new CompareResult(false, 0); public static final CompareResult GREATER_THAN = new CompareResult(false, +1); - + public static final CompareResult valueOf(int simpleCompareResult) { if(simpleCompareResult < 0) { return LESS_THAN; @@ -90,7 +171,7 @@ final class LookupUtils { } return EQUAL; } - + public boolean isTypeMismatch() { return _isTypeMismatch; } @@ -128,17 +209,17 @@ final class LookupUtils { return "??error??"; } } - + public interface LookupValueComparer { /** - * @return one of 4 instances or CompareResult: LESS_THAN, EQUAL, + * @return one of 4 instances or CompareResult: LESS_THAN, EQUAL, * GREATER_THAN or TYPE_MISMATCH */ CompareResult compareTo(ValueEval other); } - + private static abstract class LookupValueComparerBase implements LookupValueComparer { - + private final Class _targetClass; protected LookupValueComparerBase(ValueEval targetValue) { if(targetValue == null) { @@ -154,7 +235,7 @@ final class LookupUtils { return CompareResult.TYPE_MISMATCH; } if (_targetClass == StringEval.class) { - + } return compareSameType(other); } @@ -169,7 +250,7 @@ final class LookupUtils { /** used only for debug purposes */ protected abstract String getValueAsString(); } - + private static final class StringLookupComparer extends LookupValueComparerBase { private String _value; @@ -223,9 +304,9 @@ final class LookupUtils { return String.valueOf(_value); } } - + /** - * Processes the third argument to VLOOKUP, or HLOOKUP (col_index_num + * Processes the third argument to VLOOKUP, or HLOOKUP (col_index_num * or row_index_num respectively).
* Sample behaviour: * @@ -242,17 +323,17 @@ final class LookupUtils { * * *
"" #REF!
<blank> #VALUE!

- * - * * Note - out of range errors (both too high and too low) are handled by the caller. + * + * * Note - out of range errors (both too high and too low) are handled by the caller. * @return column or row index as a zero-based value - * + * */ public static int resolveRowOrColIndexArg(ValueEval veRowColIndexArg) throws EvaluationException { if(veRowColIndexArg == null) { throw new IllegalArgumentException("argument must not be null"); } if(veRowColIndexArg instanceof BlankEval) { - throw EvaluationException.invalidValue(); + throw EvaluationException.invalidValue(); } if(veRowColIndexArg instanceof StringEval) { StringEval se = (StringEval) veRowColIndexArg; @@ -260,7 +341,7 @@ final class LookupUtils { Double dVal = OperandResolver.parseDouble(strVal); if(dVal == null) { // String does not resolve to a number. Raise #VALUE! error. - throw EvaluationException.invalidRef(); + throw EvaluationException.invalidRef(); // This includes text booleans "TRUE" and "FALSE". They are not valid. } // else - numeric value parses OK @@ -268,9 +349,9 @@ final class LookupUtils { // actual BoolEval values get interpreted as FALSE->0 and TRUE->1 return OperandResolver.coerceValueToInt(veRowColIndexArg) - 1; } - - - + + + /** * The second argument (table_array) should be an area ref, but can actually be a cell ref, in * which case it is interpreted as a 1x1 area ref. Other scalar values cause #VALUE! error. @@ -279,13 +360,13 @@ final class LookupUtils { if (eval instanceof AreaEval) { return (AreaEval) eval; } - + if(eval instanceof RefEval) { RefEval refEval = (RefEval) eval; // Make this cell ref look like a 1x1 area ref. - + // It doesn't matter if eval is a 2D or 3D ref, because that detail is never asked of AreaEval. - // This code only requires the value array item. + // This code only requires the value array item. // anything would be ok for rowIx and colIx, but may as well get it right. int rowIx = refEval.getRow(); int colIx = refEval.getColumn(); @@ -295,10 +376,10 @@ final class LookupUtils { } throw EvaluationException.invalidValue(); } - + /** - * Resolves the last (optional) parameter (range_lookup) to the VLOOKUP and HLOOKUP functions. + * Resolves the last (optional) parameter (range_lookup) to the VLOOKUP and HLOOKUP functions. * @param rangeLookupArg * @param srcCellRow * @param srcCellCol @@ -318,7 +399,7 @@ final class LookupUtils { return false; } if(valEval instanceof BoolEval) { - // Happy day flow + // Happy day flow BoolEval boolEval = (BoolEval) valEval; return boolEval.getBooleanValue(); } @@ -327,7 +408,7 @@ final class LookupUtils { String stringValue = ((StringEval) valEval).getStringValue(); if(stringValue.length() < 1) { // More trickiness: - // Empty string is not the same as BlankEval. It causes #VALUE! error + // Empty string is not the same as BlankEval. It causes #VALUE! error throw EvaluationException.invalidValue(); } // TODO move parseBoolean to OperandResolver @@ -337,10 +418,10 @@ final class LookupUtils { return b.booleanValue(); } // Even more trickiness: - // Note - even if the StringEval represents a number value (for example "1"), - // Excel does not resolve it to a boolean. + // Note - even if the StringEval represents a number value (for example "1"), + // Excel does not resolve it to a boolean. throw EvaluationException.invalidValue(); - // This is in contrast to the code below,, where NumberEvals values (for + // This is in contrast to the code below,, where NumberEvals values (for // example 0.01) *do* resolve to equivalent boolean values. } if (valEval instanceof NumericValueEval) { @@ -350,7 +431,7 @@ final class LookupUtils { } throw new RuntimeException("Unexpected eval type (" + valEval.getClass().getName() + ")"); } - + public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException { LookupValueComparer lookupComparer = createLookupComparer(lookupValue); int result; @@ -364,13 +445,13 @@ final class LookupUtils { } return result; } - - + + /** * Finds first (lowest index) exact occurrence of specified value. * @param lookupValue the value to be found in column or row vector - * @param vector the values to be searched. For VLOOKUP this is the first column of the - * tableArray. For HLOOKUP this is the first row of the tableArray. + * @param vector the values to be searched. For VLOOKUP this is the first column of the + * tableArray. For HLOOKUP this is the first row of the tableArray. * @return zero based index into the vector, -1 if value cannot be found */ private static int lookupIndexOfExactValue(LookupValueComparer lookupComparer, ValueVector vector) { @@ -385,10 +466,10 @@ final class LookupUtils { return -1; } - + /** * Encapsulates some standard binary search functionality so the unusual Excel behaviour can - * be clearly distinguished. + * be clearly distinguished. */ private static final class BinarySearchIndexes { @@ -427,7 +508,7 @@ final class LookupUtils { } /** * Excel has funny behaviour when the some elements in the search vector are the wrong type. - * + * */ private static int performBinarySearch(ValueVector vector, LookupValueComparer lookupComparer) { // both low and high indexes point to values assumed too low and too high. @@ -435,7 +516,7 @@ final class LookupUtils { while(true) { int midIx = bsi.getMidIx(); - + if(midIx < 0) { return bsi.getLowIx(); } @@ -455,17 +536,17 @@ final class LookupUtils { } } /** - * Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the + * Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the * first compatible value. * @param midIx 'mid' index (value which has the wrong type) - * @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid + * @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid * index. Zero or greater signifies that an exact match for the lookup value was found */ private static int handleMidValueTypeMismatch(LookupValueComparer lookupComparer, ValueVector vector, BinarySearchIndexes bsi, int midIx) { int newMid = midIx; int highIx = bsi.getHighIx(); - + while(true) { newMid++; if(newMid == highIx) { @@ -511,9 +592,9 @@ final class LookupUtils { } public static LookupValueComparer createLookupComparer(ValueEval lookupValue) throws EvaluationException { - + if (lookupValue instanceof BlankEval) { - // blank eval can never be found in a lookup array + // blank eval can never be found in a lookup array throw new EvaluationException(ErrorEval.NA); } if (lookupValue instanceof StringEval) { diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Match.java b/src/java/org/apache/poi/hssf/record/formula/functions/Match.java index a2a12cdba8..a464ec873a 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Match.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Match.java @@ -29,18 +29,19 @@ 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.functions.LookupUtils.CompareResult; import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueComparer; +import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; /** * Implementation for the MATCH() Excel function.

- * + * * Syntax:
* MATCH(lookup_value, lookup_array, match_type)

- * - * Returns a 1-based index specifying at what position in the lookup_array the specified + * + * Returns a 1-based index specifying at what position in the lookup_array the specified * lookup_value is found.

- * + * * Specific matching behaviour can be modified with the optional match_type parameter. - * + * * * * *
ValueMatching Behaviour
1(default) find the largest value that is less than or equal to lookup_value. @@ -50,26 +51,26 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueCompa *
-1find the smallest value that is greater than or equal to lookup_value. * The lookup_array must be in descending order*.
- * + * * * Note regarding order - For the match_type cases that require the lookup_array to * be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed * behaviour in Excel is to return the lowest index value for which every item after that index * breaks the match rule.
* The (ascending) sort order expected by MATCH() is:
* numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)
- * MATCH() ignores all elements in the lookup_array with a different type to the lookup_value. + * MATCH() ignores all elements in the lookup_array with a different type to the lookup_value. * Type conversion of the lookup_array elements is never performed. - * - * + * + * * @author Josh Micich */ public final class Match implements Function { - + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { - + double match_type = 1; // default - + switch(args.length) { case 3: try { @@ -85,15 +86,15 @@ public final class Match implements Function { default: return ErrorEval.VALUE_INVALID; } - + boolean matchExact = match_type == 0; // Note - Excel does not strictly require -1 and +1 boolean findLargestLessThanOrEqual = match_type > 0; - - + + try { ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); - ValueEval[] lookupRange = evaluateLookupRange(args[1]); + ValueVector lookupRange = evaluateLookupRange(args[1]); int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual); return new NumberEval(index + 1); // +1 to convert to 1-based } catch (EvaluationException e) { @@ -101,19 +102,40 @@ public final class Match implements Function { } } - private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException { + private static final class SingleValueVector implements ValueVector { + + private final ValueEval _value; + + public SingleValueVector(ValueEval value) { + _value = value; + } + + public ValueEval getItem(int index) { + if (index != 0) { + throw new RuntimeException("Invalid index (" + + index + ") only zero is allowed"); + } + return _value; + } + + public int getSize() { + return 1; + } + } + + private static ValueVector evaluateLookupRange(Eval eval) throws EvaluationException { if (eval instanceof RefEval) { RefEval re = (RefEval) eval; - return new ValueEval[] { re.getInnerValueEval(), }; + return new SingleValueVector(re.getInnerValueEval()); } if (eval instanceof AreaEval) { - AreaEval ae = (AreaEval) eval; - if(!ae.isColumn() && !ae.isRow()) { + ValueVector result = LookupUtils.createVector((AreaEval)eval); + if (result == null) { throw new EvaluationException(ErrorEval.NA); } - return ae.getValues(); + return result; } - + // Error handling for lookup_range arg is also unusual if(eval instanceof NumericValueEval) { throw new EvaluationException(ErrorEval.NA); @@ -133,7 +155,7 @@ public final class Match implements Function { - private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) + private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); @@ -156,28 +178,29 @@ public final class Match implements Function { } throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")"); } - + /** * @return zero based index */ - private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange, + private static int findIndexOfValue(ValueEval lookupValue, ValueVector lookupRange, boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException { LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact); - + + int size = lookupRange.getSize(); if(matchExact) { - for (int i = 0; i < lookupRange.length; i++) { - if(lookupComparer.compareTo(lookupRange[i]).isEqual()) { + for (int i = 0; i < size; i++) { + if(lookupComparer.compareTo(lookupRange.getItem(i)).isEqual()) { return i; } } throw new EvaluationException(ErrorEval.NA); } - + if(findLargestLessThanOrEqual) { // Note - backward iteration - for (int i = lookupRange.length - 1; i>=0; i--) { - CompareResult cmp = lookupComparer.compareTo(lookupRange[i]); + for (int i = size - 1; i>=0; i--) { + CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i)); if(cmp.isTypeMismatch()) { continue; } @@ -187,11 +210,11 @@ public final class Match implements Function { } throw new EvaluationException(ErrorEval.NA); } - + // else - find smallest greater than or equal to // TODO - is binary search used for (match_type==+1) ? - for (int i = 0; i_size) { - throw new ArrayIndexOutOfBoundsException("Specified index (" + index - + ") is outside the allowed range (0.." + (_size-1) + ")"); - } - return _tableArray.getValueAt(_firstRowAbsoluteIndex + index, (short)_columnAbsoluteIndex); - } - public int getSize() { - return _size; - } - } - public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { Eval arg3 = null; switch(args.length) { @@ -93,7 +59,7 @@ public final class Vlookup implements Function { ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]); boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol); - int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, new ColumnVector(tableArray, 0), isRangeLookup); + int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup); ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol); int colIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex); ValueVector resultCol = createResultColumnVector(tableArray, colIndex); @@ -113,11 +79,9 @@ public final class Vlookup implements Function { if(colIndex < 0) { throw EvaluationException.invalidValue(); } - int nCols = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1; - - if(colIndex >= nCols) { + if(colIndex >= tableArray.getWidth()) { throw EvaluationException.invalidRef(); } - return new ColumnVector(tableArray, colIndex); + return LookupUtils.createColumnVector(tableArray, colIndex); } } diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls index aa08040fcd..e1193179fe 100644 Binary files a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls differ diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java index 1fd93528bd..c594f18c63 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java @@ -32,7 +32,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.Ref2DEval; 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.functions.Countif.I_MatchPredicate; +import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFRow;