123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- /* ====================================================================
- 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;
-
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.LinkedHashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
-
- 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.RefEval;
- import org.apache.poi.ss.formula.eval.StringEval;
- import org.apache.poi.ss.formula.eval.ValueEval;
- import org.apache.poi.ss.formula.functions.AggregateFunction;
- import org.apache.poi.ss.usermodel.Cell;
- import org.apache.poi.ss.usermodel.CellType;
- import org.apache.poi.ss.usermodel.ConditionFilterData;
- import org.apache.poi.ss.usermodel.ConditionFilterType;
- import org.apache.poi.ss.usermodel.ConditionType;
- import org.apache.poi.ss.usermodel.ConditionalFormatting;
- import org.apache.poi.ss.usermodel.ConditionalFormattingRule;
- import org.apache.poi.ss.usermodel.Row;
- import org.apache.poi.ss.usermodel.Sheet;
- import org.apache.poi.ss.util.CellRangeAddress;
-
- /**
- * Abstracted and cached version of a Conditional Format rule for use with a
- * {@link ConditionalFormattingEvaluator}. This references a rule, its owning
- * {@link ConditionalFormatting}, its priority order (lower index = higher priority in Excel),
- * and the information needed to evaluate the rule for a given cell.
- * <p/>
- * Having this all combined and cached avoids repeated access calls to the
- * underlying structural objects, XSSF CT* objects and HSSF raw byte structures.
- * Those objects can be referenced from here. This object will be out of sync if
- * anything modifies the referenced structures' evaluation properties.
- * <p/>
- * The assumption is that consuming applications will read the display properties once and
- * create whatever style objects they need, caching those at the application level.
- * Thus this class only caches values needed for evaluation, not display.
- */
- public class EvaluationConditionalFormatRule implements Comparable<EvaluationConditionalFormatRule> {
-
- private final WorkbookEvaluator workbookEvaluator;
- private final Sheet sheet;
- private final ConditionalFormatting formatting;
- private final ConditionalFormattingRule rule;
-
- /* cached values */
- private final CellRangeAddress[] regions;
- /**
- * Depending on the rule type, it may want to know about certain values in the region when evaluating {@link #matches(Cell)},
- * such as top 10, unique, duplicate, average, etc. This collection stores those if needed so they are not repeatedly calculated
- */
- private final Map<CellRangeAddress, Set<ValueAndFormat>> meaningfulRegionValues = new HashMap<CellRangeAddress, Set<ValueAndFormat>>();
-
- private final int priority;
- private final int formattingIndex;
- private final int ruleIndex;
- private final String formula1;
- private final String formula2;
- private final OperatorEnum operator;
- private final ConditionType type;
-
- /**
- *
- * @param workbookEvaluator
- * @param sheet
- * @param formatting
- * @param formattingIndex for priority, zero based
- * @param rule
- * @param ruleIndex for priority, zero based, if this is an HSSF rule. Unused for XSSF rules
- * @param regions could be read from formatting, but every call creates new objects in a new array.
- * this allows calling it once per formatting instance, and re-using the array.
- */
- public EvaluationConditionalFormatRule(WorkbookEvaluator workbookEvaluator, Sheet sheet, ConditionalFormatting formatting, int formattingIndex, ConditionalFormattingRule rule, int ruleIndex, CellRangeAddress[] regions) {
- super();
- this.workbookEvaluator = workbookEvaluator;
- this.sheet = sheet;
- this.formatting = formatting;
- this.rule = rule;
- this.formattingIndex = formattingIndex;
- this.ruleIndex = ruleIndex;
-
- this.priority = rule.getPriority();
-
- this.regions = regions;
- formula1 = rule.getFormula1();
- formula2 = rule.getFormula2();
-
- operator = OperatorEnum.values()[rule.getComparisonOperation()];
- type = rule.getConditionType();
- }
-
- public Sheet getSheet() {
- return sheet;
- }
-
- /**
- * @return the formatting
- */
- public ConditionalFormatting getFormatting() {
- return formatting;
- }
-
- public int getFormattingIndex() {
- return formattingIndex;
- }
-
- /**
- * @return the rule
- */
- public ConditionalFormattingRule getRule() {
- return rule;
- }
-
- public int getRuleIndex() {
- return ruleIndex;
- }
-
- /**
- * @return the regions
- */
- public CellRangeAddress[] getRegions() {
- return regions;
- }
-
- /**
- * @return the priority
- */
- public int getPriority() {
- return priority;
- }
-
- /**
- * @return the formula1
- */
- public String getFormula1() {
- return formula1;
- }
-
- /**
- * @return the formula2
- */
- public String getFormula2() {
- return formula2;
- }
-
- /**
- * @return the operator
- */
- public OperatorEnum getOperator() {
- return operator;
- }
-
- /**
- * @return the type
- */
- public ConditionType getType() {
- return type;
- }
-
- /**
- * Defined as equal sheet name and formatting and rule indexes
- * @see java.lang.Object#equals(java.lang.Object)
- */
- public boolean equals(Object obj) {
- if (obj == null) return false;
- if (! obj.getClass().equals(this.getClass())) return false;
- final EvaluationConditionalFormatRule r = (EvaluationConditionalFormatRule) obj;
- return getSheet().getSheetName().equalsIgnoreCase(r.getSheet().getSheetName())
- && getFormattingIndex() == r.getFormattingIndex()
- && getRuleIndex() == r.getRuleIndex();
- }
-
- /**
- * Per Excel Help, XSSF rule priority is sheet-wide, not just within the owning ConditionalFormatting object.
- * This can be seen by creating 4 rules applying to two different ranges and examining the XML.
- * <p/>
- * HSSF priority is based on definition/persistence order.
- *
- * @param o
- * @return comparison based on sheet name, formatting index, and rule priority
- */
- public int compareTo(EvaluationConditionalFormatRule o) {
- int cmp = getSheet().getSheetName().compareToIgnoreCase(o.getSheet().getSheetName());
- if (cmp != 0) return cmp;
-
- final int x = getPriority();
- final int y = o.getPriority();
- // logic from Integer.compare()
- cmp = (x < y) ? -1 : ((x == y) ? 0 : 1);
- if (cmp != 0) return cmp;
-
- cmp = Integer.compare(getFormattingIndex(), o.getFormattingIndex());
- if (cmp != 0) return cmp;
- return Integer.compare(getRuleIndex(), o.getRuleIndex());
- }
-
- public int hashCode() {
- int hash = sheet.getSheetName().hashCode();
- hash = 31 * hash + formattingIndex;
- hash = 31 * hash + ruleIndex;
- return hash;
- }
-
- /**
- * @param cell
- * @return true if this rule evaluates to true for the given cell
- */
- /* package */ boolean matches(Cell cell) {
- // first check that it is in one of the regions defined for this format
- CellRangeAddress region = null;
- for (CellRangeAddress r : regions) {
- if (r.isInRange(cell)) {
- region = r;
- break;
- }
- }
-
- if (region == null) return false; // cell not in range of this rule
-
- final ConditionType ruleType = getRule().getConditionType();
-
- // these rules apply to all cells in a region. Specific condition criteria
- // may specify no special formatting for that value partition, but that's display logic
- if (ruleType.equals(ConditionType.COLOR_SCALE)
- || ruleType.equals(ConditionType.DATA_BAR)
- || ruleType.equals(ConditionType.ICON_SET)) {
- return true;
- }
-
- if (ruleType.equals(ConditionType.CELL_VALUE_IS)) {
- return checkValue(cell, region);
- }
- if (ruleType.equals(ConditionType.FORMULA)) {
- return checkFormula(cell, region);
- }
- if (ruleType.equals(ConditionType.FILTER)) {
- return checkFilter(cell, region);
- }
-
- // TODO: anything else, we don't handle yet, such as top 10
- return false;
- }
-
- /**
- * @param cell
- * @param region for adjusting relative formulas
- * @return
- */
- private boolean checkValue(Cell cell, CellRangeAddress region) {
- if (cell == null || DataValidationEvaluator.isType(cell, CellType.BLANK)
- || DataValidationEvaluator.isType(cell,CellType.ERROR)
- || (DataValidationEvaluator.isType(cell,CellType.STRING)
- && (cell.getStringCellValue() == null || cell.getStringCellValue().isEmpty())
- )
- ) return false;
-
- ValueEval eval = unwrapEval(workbookEvaluator.evaluate(rule.getFormula1(), ConditionalFormattingEvaluator.getRef(cell), region));
-
- String f2 = rule.getFormula2();
- ValueEval eval2 = null;
- if (f2 != null && f2.length() > 0) {
- eval2 = unwrapEval(workbookEvaluator.evaluate(f2, ConditionalFormattingEvaluator.getRef(cell), region));
- }
-
- // we assume the cell has been evaluated, and the current formula value stored
- if (DataValidationEvaluator.isType(cell, CellType.BOOLEAN)) {
- if (eval instanceof BoolEval && (eval2 == null || eval2 instanceof BoolEval) ) {
- return operator.isValid(cell.getBooleanCellValue(), ((BoolEval) eval).getBooleanValue(), eval2 == null ? null : ((BoolEval) eval2).getBooleanValue());
- }
- return false; // wrong types
- }
- if (DataValidationEvaluator.isType(cell, CellType.NUMERIC)) {
- if (eval instanceof NumberEval && (eval2 == null || eval2 instanceof NumberEval) ) {
- return operator.isValid(cell.getNumericCellValue(), ((NumberEval) eval).getNumberValue(), eval2 == null ? null : ((NumberEval) eval2).getNumberValue());
- }
- return false; // wrong types
- }
- if (DataValidationEvaluator.isType(cell, CellType.STRING)) {
- if (eval instanceof StringEval && (eval2 == null || eval2 instanceof StringEval) ) {
- return operator.isValid(cell.getStringCellValue(), ((StringEval) eval).getStringValue(), eval2 == null ? null : ((StringEval) eval2).getStringValue());
- }
- return false; // wrong types
- }
-
- // should not get here, but in case...
- return false;
- }
-
- private ValueEval unwrapEval(ValueEval eval) {
- ValueEval comp = eval;
-
- while (comp instanceof RefEval) {
- RefEval ref = (RefEval) comp;
- comp = ref.getInnerValueEval(ref.getFirstSheetIndex());
- }
- return comp;
- }
- /**
- * @param cell needed for offsets from region anchor
- * @param region for adjusting relative formulas
- * @return true/false using the same rules as Data Validation evaluations
- */
- private boolean checkFormula(Cell cell, CellRangeAddress region) {
- ValueEval comp = unwrapEval(workbookEvaluator.evaluate(rule.getFormula1(), ConditionalFormattingEvaluator.getRef(cell), region));
-
- // Copied for now from DataValidationEvaluator.ValidationEnum.FORMULA#isValidValue()
- if (comp instanceof BlankEval) return true;
- if (comp instanceof ErrorEval) return false;
- if (comp instanceof BoolEval) {
- return ((BoolEval) comp).getBooleanValue();
- }
- // empirically tested in Excel - 0=false, any other number = true/valid
- // see test file DataValidationEvaluations.xlsx
- if (comp instanceof NumberEval) {
- return ((NumberEval) comp).getNumberValue() != 0;
- }
- return false; // anything else is false, such as text
- }
-
- private boolean checkFilter(Cell cell, CellRangeAddress region) {
- final ConditionFilterType filterType = rule.getConditionFilterType();
- if (filterType == null) return false;
-
- // TODO: this could/should be delegated to the Enum type, but that's in the usermodel package,
- // we may not want evaluation code there. Of course, maybe the enum should go here in formula,
- // and not be returned by the SS model, but then we need the XSSF rule to expose the raw OOXML
- // type value, which isn't ideal either.
- switch (filterType) {
- case FILTER:
- return false; // we don't evaluate HSSF filters yet
- case TOP_10:
- // from testing, Excel only operates on numbers and dates (which are stored as numbers) in the range.
- // numbers stored as text are ignored, but numbers formatted as text are treated as numbers.
-
- final ValueAndFormat cv10 = getCellValue(cell);
- if (! cv10.isNumber()) return false;
-
- return getMeaningfulValues(region, false, new ValueFunction() {
- public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
- List<ValueAndFormat> values = allValues;
- final ConditionFilterData conf = rule.getFilterConfiguration();
-
- if (! conf.getBottom()) Collections.sort(values, Collections.reverseOrder());
- else Collections.sort(values);
-
- int limit = (int) conf.getRank();
- if (conf.getPercent()) limit = allValues.size() * limit / 100;
- if (allValues.size() <= limit) return new HashSet<ValueAndFormat>(allValues);
-
- return new HashSet<ValueAndFormat>(allValues.subList(0, limit));
- }
- }).contains(cv10);
- case UNIQUE_VALUES:
- // Per Excel help, "duplicate" means matching value AND format
- // https://support.office.com/en-us/article/Filter-for-unique-values-or-remove-duplicate-values-ccf664b0-81d6-449b-bbe1-8daaec1e83c2
- return getMeaningfulValues(region, true, new ValueFunction() {
- public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
- List<ValueAndFormat> values = allValues;
- Collections.sort(values);
-
- final Set<ValueAndFormat> unique = new HashSet<ValueAndFormat>();
-
- for (int i=0; i < values.size(); i++) {
- final ValueAndFormat v = values.get(i);
- // skip this if the current value matches the next one, or is the last one and matches the previous one
- if ( (i < values.size()-1 && v.equals(values.get(i+1)) ) || ( i > 0 && i == values.size()-1 && v.equals(values.get(i-1)) ) ) {
- // current value matches next value, skip both
- i++;
- continue;
- }
- unique.add(v);
- }
-
- return unique;
- }
- }).contains(getCellValue(cell));
- case DUPLICATE_VALUES:
- // Per Excel help, "duplicate" means matching value AND format
- // https://support.office.com/en-us/article/Filter-for-unique-values-or-remove-duplicate-values-ccf664b0-81d6-449b-bbe1-8daaec1e83c2
- return getMeaningfulValues(region, true, new ValueFunction() {
- public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
- List<ValueAndFormat> values = allValues;
- Collections.sort(values);
-
- final Set<ValueAndFormat> dup = new HashSet<ValueAndFormat>();
-
- for (int i=0; i < values.size(); i++) {
- final ValueAndFormat v = values.get(i);
- // skip this if the current value matches the next one, or is the last one and matches the previous one
- if ( (i < values.size()-1 && v.equals(values.get(i+1)) ) || ( i > 0 && i == values.size()-1 && v.equals(values.get(i-1)) ) ) {
- // current value matches next value, add one
- dup.add(v);
- i++;
- }
- }
- return dup;
- }
- }).contains(getCellValue(cell));
- case ABOVE_AVERAGE:
- // from testing, Excel only operates on numbers and dates (which are stored as numbers) in the range.
- // numbers stored as text are ignored, but numbers formatted as text are treated as numbers.
-
- final ConditionFilterData conf = rule.getFilterConfiguration();
-
- // actually ordered, so iteration order is predictable
- List<ValueAndFormat> values = new ArrayList<ValueAndFormat>(getMeaningfulValues(region, false, new ValueFunction() {
- public Set<ValueAndFormat> evaluate(List<ValueAndFormat> allValues) {
- List<ValueAndFormat> values = allValues;
- double total = 0;
- ValueEval[] pop = new ValueEval[values.size()];
- for (int i=0; i < values.size(); i++) {
- ValueAndFormat v = values.get(i);
- total += v.value.doubleValue();
- pop[i] = new NumberEval(v.value.doubleValue());
- }
-
- final Set<ValueAndFormat> avgSet = new LinkedHashSet<ValueAndFormat>(1);
- avgSet.add(new ValueAndFormat(new Double(values.size() == 0 ? 0 : total / values.size()), null));
-
- final double stdDev = values.size() <= 1 ? 0 : ((NumberEval) AggregateFunction.STDEV.evaluate(pop, 0, 0)).getNumberValue();
- avgSet.add(new ValueAndFormat(new Double(stdDev), null));
- return avgSet;
- }
- }));
-
- final ValueAndFormat cv = getCellValue(cell);
- Double val = cv.isNumber() ? cv.getValue() : null;
- if (val == null) return false;
-
- double avg = values.get(0).value.doubleValue();
- double stdDev = values.get(1).value.doubleValue();
-
- /*
- * use StdDev, aboveAverage, equalAverage to find:
- * comparison value
- * operator type
- */
-
- Double comp = new Double(conf.getStdDev() > 0 ? (avg + (conf.getAboveAverage() ? 1 : -1) * stdDev * conf.getStdDev()) : avg) ;
-
- OperatorEnum op = null;
- if (conf.getAboveAverage()) {
- if (conf.getEqualAverage()) op = OperatorEnum.GREATER_OR_EQUAL;
- else op = OperatorEnum.GREATER_THAN;
- } else {
- if (conf.getEqualAverage()) op = OperatorEnum.LESS_OR_EQUAL;
- else op = OperatorEnum.LESS_THAN;
- }
- return op != null && op.isValid(val, comp, null);
- case CONTAINS_TEXT:
- // implemented both by a cfRule "text" attribute and a formula. Use the formula.
- return checkFormula(cell, region);
- case NOT_CONTAINS_TEXT:
- // implemented both by a cfRule "text" attribute and a formula. Use the formula.
- return checkFormula(cell, region);
- case BEGINS_WITH:
- // implemented both by a cfRule "text" attribute and a formula. Use the formula.
- return checkFormula(cell, region);
- case ENDS_WITH:
- // implemented both by a cfRule "text" attribute and a formula. Use the formula.
- return checkFormula(cell, region);
- case CONTAINS_BLANKS:
- try {
- String v = cell.getStringCellValue();
- // see TextFunction.TRIM for implementation
- return v == null || v.trim().length() == 0;
- } catch (Exception e) {
- // not a valid string value, and not a blank cell (that's checked earlier)
- return false;
- }
- case NOT_CONTAINS_BLANKS:
- try {
- String v = cell.getStringCellValue();
- // see TextFunction.TRIM for implementation
- return v != null && v.trim().length() > 0;
- } catch (Exception e) {
- // not a valid string value, but not blank
- return true;
- }
- case CONTAINS_ERRORS:
- return cell != null && DataValidationEvaluator.isType(cell, CellType.ERROR);
- case NOT_CONTAINS_ERRORS:
- return cell == null || ! DataValidationEvaluator.isType(cell, CellType.ERROR);
- case TIME_PERIOD:
- // implemented both by a cfRule "text" attribute and a formula. Use the formula.
- return checkFormula(cell, region);
- default:
- return false;
- }
- }
-
- /**
- * from testing, Excel only operates on numbers and dates (which are stored as numbers) in the range.
- * numbers stored as text are ignored, but numbers formatted as text are treated as numbers.
- *
- * @param region
- * @return
- */
- private Set<ValueAndFormat> getMeaningfulValues(CellRangeAddress region, boolean withText, ValueFunction func) {
- Set<ValueAndFormat> values = meaningfulRegionValues.get(region);
- if (values != null) return values;
-
- List<ValueAndFormat> allValues = new ArrayList<ValueAndFormat>((region.getLastColumn() - region.getFirstColumn()+1) * (region.getLastRow() - region.getFirstRow() + 1));
-
- for (int r=region.getFirstRow(); r <= region.getLastRow(); r++) {
- final Row row = sheet.getRow(r);
- if (row == null) continue;
- for (int c = region.getFirstColumn(); c <= region.getLastColumn(); c++) {
- Cell cell = row.getCell(c);
- final ValueAndFormat cv = getCellValue(cell);
- if (cv != null && (withText || cv.isNumber()) ) allValues.add(cv);
- }
- }
-
- values = func.evaluate(allValues);
- meaningfulRegionValues.put(region, values);
-
- return values;
- }
-
- private ValueAndFormat getCellValue(Cell cell) {
- if (cell != null) {
- final CellType type = cell.getCellTypeEnum();
- if (type == CellType.NUMERIC || (type == CellType.FORMULA && cell.getCachedFormulaResultTypeEnum() == CellType.NUMERIC) ) {
- return new ValueAndFormat(new Double(cell.getNumericCellValue()), cell.getCellStyle().getDataFormatString());
- } else if (type == CellType.STRING || (type == CellType.FORMULA && cell.getCachedFormulaResultTypeEnum() == CellType.STRING) ) {
- return new ValueAndFormat(cell.getStringCellValue(), cell.getCellStyle().getDataFormatString());
- } else if (type == CellType.BOOLEAN || (type == CellType.FORMULA && cell.getCachedFormulaResultTypeEnum() == CellType.BOOLEAN) ) {
- return new ValueAndFormat(cell.getStringCellValue(), cell.getCellStyle().getDataFormatString());
- }
- }
- return null;
- }
- /**
- * instances evaluate the values for a region and return the positive matches for the function type.
- * TODO: when we get to use Java 8, this is obviously a Lambda Function.
- */
- protected interface ValueFunction {
-
- /**
- *
- * @param values
- * @return the desired values for the rules implemented by the current instance
- */
- Set<ValueAndFormat> evaluate(List<ValueAndFormat> values);
- }
-
- /**
- * Not calling it OperatorType to avoid confusion for now with other classes.
- * Definition order matches OOXML type ID indexes.
- * Note that this has NO_COMPARISON as the first item, unlike the similar
- * DataValidation operator enum. Thanks, Microsoft.
- */
- public static enum OperatorEnum {
- NO_COMPARISON {
- /** always false/invalid */
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- return false;
- }
- },
- BETWEEN {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- return cellValue.compareTo(v1) >= 0 && cellValue.compareTo(v2) <= 0;
- }
- },
- NOT_BETWEEN {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- return cellValue.compareTo(v1) < 0 || cellValue.compareTo(v2) > 0;
- }
- },
- EQUAL {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- if (cellValue instanceof String) {
- return ((String) cellValue).compareToIgnoreCase((String) v1) == 0;
- }
- return cellValue.compareTo(v1) == 0;
- }
- },
- NOT_EQUAL {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- if (cellValue instanceof String) {
- return ((String) cellValue).compareToIgnoreCase((String) v1) != 0;
- }
- return cellValue.compareTo(v1) != 0;
- }
- },
- GREATER_THAN {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- return cellValue.compareTo(v1) > 0;
- }
- },
- LESS_THAN {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- return cellValue.compareTo(v1) < 0;
- }
- },
- GREATER_OR_EQUAL {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- return cellValue.compareTo(v1) >= 0;
- }
- },
- LESS_OR_EQUAL {
- public <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2) {
- return cellValue.compareTo(v1) <= 0;
- }
- },
- ;
-
- /**
- * Evaluates comparison using operator instance rules
- * @param cellValue won't be null, assumption is previous checks handled that
- * @param v1 if null, value assumed invalid, anything passes, per Excel behavior
- * @param v2 null if not needed. If null when needed, assume anything passes, per Excel behavior
- * @return true if the comparison is valid
- */
- public abstract <C extends Comparable<C>> boolean isValid(C cellValue, C v1, C v2);
- }
-
- /**
- * Note: this class has a natural ordering that is inconsistent with equals.
- */
- protected class ValueAndFormat implements Comparable<ValueAndFormat> {
-
- private final Double value;
- private final String string;
- private final String format;
-
- public ValueAndFormat(Double value, String format) {
- this.value = value;
- this.format = format;
- string = null;
- }
-
- public ValueAndFormat(String value, String format) {
- this.value = null;
- this.format = format;
- string = value;
- }
-
- public boolean isNumber() {
- return value != null;
- }
-
- public Double getValue() {
- return value;
- }
-
- public boolean equals(Object obj) {
- ValueAndFormat o = (ValueAndFormat) obj;
- return ( value == o.value || value.equals(o.value))
- && ( format == o.format || format.equals(o.format))
- && (string == o.string || string.equals(o.string));
- }
-
- /**
- * Note: this class has a natural ordering that is inconsistent with equals.
- * @param o
- * @return value comparison
- */
- public int compareTo(ValueAndFormat o) {
- if (value == null && o.value != null) return 1;
- if (o.value == null && value != null) return -1;
- int cmp = value == null ? 0 : value.compareTo(o.value);
- if (cmp != 0) return cmp;
-
- if (string == null && o.string != null) return 1;
- if (o.string == null && string != null) return -1;
-
- return string == null ? 0 : string.compareTo(o.string);
- }
-
- public int hashCode() {
- return (string == null ? 0 : string.hashCode()) * 37 * 37 + 37 * (value == null ? 0 : value.hashCode()) + (format == null ? 0 : format.hashCode());
- }
- }
- }
|