You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by on...@apache.org on 2015/11/24 09:15:42 UTC

svn commit: r1716060 - /poi/trunk/src/java/org/apache/poi/ss/formula/functions/Countif.java

Author: onealj
Date: Tue Nov 24 08:15:42 2015
New Revision: 1716060

URL: http://svn.apache.org/viewvc?rev=1716060&view=rev
Log:
whitespace

Modified:
    poi/trunk/src/java/org/apache/poi/ss/formula/functions/Countif.java

Modified: poi/trunk/src/java/org/apache/poi/ss/formula/functions/Countif.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/functions/Countif.java?rev=1716060&r1=1716059&r2=1716060&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/ss/formula/functions/Countif.java (original)
+++ poi/trunk/src/java/org/apache/poi/ss/formula/functions/Countif.java Tue Nov 24 08:15:42 2015
@@ -44,177 +44,177 @@ import org.apache.poi.ss.usermodel.Formu
  */
 public final class Countif extends Fixed2ArgFunction {
 
-	private static final class CmpOp {
-		public static final int NONE = 0;
-		public static final int EQ = 1;
-		public static final int NE = 2;
-		public static final int LE = 3;
-		public static final int LT = 4;
-		public static final int GT = 5;
-		public static final int GE = 6;
-
-		public static final CmpOp OP_NONE = op("", NONE);
-		public static final CmpOp OP_EQ = op("=", EQ);
-		public static final CmpOp OP_NE = op("<>", NE);
-		public static final CmpOp OP_LE = op("<=", LE);
-		public static final CmpOp OP_LT = op("<", LT);
-		public static final CmpOp OP_GT = op(">", GT);
-		public static final CmpOp OP_GE = op(">=", GE);
-		private final String _representation;
-		private final int _code;
-
-		private static CmpOp op(String rep, int code) {
-			return new CmpOp(rep, code);
-		}
-		private CmpOp(String representation, int code) {
-			_representation = representation;
-			_code = code;
-		}
-		/**
-		 * @return number of characters used to represent this operator
-		 */
-		public int getLength() {
-			return _representation.length();
-		}
-		public int getCode() {
-			return _code;
-		}
-		public static CmpOp getOperator(String value) {
-			int len = value.length();
-			if (len < 1) {
-				return OP_NONE;
-			}
-
-			char firstChar = value.charAt(0);
-
-			switch(firstChar) {
-				case '=':
-					return OP_EQ;
-				case '>':
-					if (len > 1) {
-						switch(value.charAt(1)) {
-							case '=':
-								return OP_GE;
-						}
-					}
-					return OP_GT;
-				case '<':
-					if (len > 1) {
-						switch(value.charAt(1)) {
-							case '=':
-								return OP_LE;
-							case '>':
-								return OP_NE;
-						}
-					}
-					return OP_LT;
-			}
-			return OP_NONE;
-		}
-		public boolean evaluate(boolean cmpResult) {
-			switch (_code) {
-				case NONE:
-				case EQ:
-					return cmpResult;
-				case NE:
-					return !cmpResult;
-			}
-			throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
-					+ _representation + "'");
-		}
-		public boolean evaluate(int cmpResult) {
-			switch (_code) {
-				case NONE:
-				case EQ:
-					return cmpResult == 0;
-				case NE: return cmpResult != 0;
-				case LT: return cmpResult <  0;
-				case LE: return cmpResult <= 0;
-				case GT: return cmpResult >  0;
-				case GE: return cmpResult >= 0;
-			}
-			throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
-					+ _representation + "'");
-		}
-		public String toString() {
-			StringBuffer sb = new StringBuffer(64);
-			sb.append(getClass().getName());
-			sb.append(" [").append(_representation).append("]");
-			return sb.toString();
-		}
-		public String getRepresentation() {
-			return _representation;
-		}
-	}
-
-	private static abstract class MatcherBase implements I_MatchPredicate {
-		private final CmpOp _operator;
-
-		MatcherBase(CmpOp operator) {
-			_operator = operator;
-		}
-		protected final int getCode() {
-			return _operator.getCode();
-		}
-		protected final boolean evaluate(int cmpResult) {
-			return _operator.evaluate(cmpResult);
-		}
-		protected final boolean evaluate(boolean cmpResult) {
-			return _operator.evaluate(cmpResult);
-		}
-		@Override
-		public final String toString() {
-			StringBuffer sb = new StringBuffer(64);
-			sb.append(getClass().getName()).append(" [");
-			sb.append(_operator.getRepresentation());
-			sb.append(getValueText());
-			sb.append("]");
-			return sb.toString();
-		}
-		protected abstract String getValueText();
-	}
-
-	private static final class NumberMatcher extends MatcherBase {
-
-		private final double _value;
-
-		public NumberMatcher(double value, CmpOp operator) {
-			super(operator);
-			_value = value;
-		}
-		@Override
-		protected String getValueText() {
-			return String.valueOf(_value);
-		}
-
-		public boolean matches(ValueEval x) {
-			double testValue;
-			if(x instanceof StringEval) {
-				// if the target(x) is a string, but parses as a number
-				// it may still count as a match, only for the equality operator
-				switch (getCode()) {
-					case CmpOp.EQ:
-					case CmpOp.NONE:
-						break;
-					case CmpOp.NE:
-						// Always matches (inconsistent with above two cases).
-						// for example '<>123' matches '123', '4', 'abc', etc
-						return true;
-					default:
-						// never matches (also inconsistent with above three cases).
-						// for example '>5' does not match '6',
-						return false;
-				}
-				StringEval se = (StringEval)x;
-				Double val = OperandResolver.parseDouble(se.getStringValue());
-				if(val == null) {
-					// x is text that is not a number
-					return false;
-				}
-				return _value == val.doubleValue();
-			} else if((x instanceof NumberEval)) {
-				NumberEval ne = (NumberEval) x;
-				testValue = ne.getNumberValue();
+    private static final class CmpOp {
+        public static final int NONE = 0;
+        public static final int EQ = 1;
+        public static final int NE = 2;
+        public static final int LE = 3;
+        public static final int LT = 4;
+        public static final int GT = 5;
+        public static final int GE = 6;
+
+        public static final CmpOp OP_NONE = op("", NONE);
+        public static final CmpOp OP_EQ = op("=", EQ);
+        public static final CmpOp OP_NE = op("<>", NE);
+        public static final CmpOp OP_LE = op("<=", LE);
+        public static final CmpOp OP_LT = op("<", LT);
+        public static final CmpOp OP_GT = op(">", GT);
+        public static final CmpOp OP_GE = op(">=", GE);
+        private final String _representation;
+        private final int _code;
+
+        private static CmpOp op(String rep, int code) {
+            return new CmpOp(rep, code);
+        }
+        private CmpOp(String representation, int code) {
+            _representation = representation;
+            _code = code;
+        }
+        /**
+         * @return number of characters used to represent this operator
+         */
+        public int getLength() {
+            return _representation.length();
+        }
+        public int getCode() {
+            return _code;
+        }
+        public static CmpOp getOperator(String value) {
+            int len = value.length();
+            if (len < 1) {
+                return OP_NONE;
+            }
+
+            char firstChar = value.charAt(0);
+
+            switch(firstChar) {
+                case '=':
+                    return OP_EQ;
+                case '>':
+                    if (len > 1) {
+                        switch(value.charAt(1)) {
+                            case '=':
+                                return OP_GE;
+                        }
+                    }
+                    return OP_GT;
+                case '<':
+                    if (len > 1) {
+                        switch(value.charAt(1)) {
+                            case '=':
+                                return OP_LE;
+                            case '>':
+                                return OP_NE;
+                        }
+                    }
+                    return OP_LT;
+            }
+            return OP_NONE;
+        }
+        public boolean evaluate(boolean cmpResult) {
+            switch (_code) {
+                case NONE:
+                case EQ:
+                    return cmpResult;
+                case NE:
+                    return !cmpResult;
+            }
+            throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
+                    + _representation + "'");
+        }
+        public boolean evaluate(int cmpResult) {
+            switch (_code) {
+                case NONE:
+                case EQ:
+                    return cmpResult == 0;
+                case NE: return cmpResult != 0;
+                case LT: return cmpResult <  0;
+                case LE: return cmpResult <= 0;
+                case GT: return cmpResult >  0;
+                case GE: return cmpResult >= 0;
+            }
+            throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
+                    + _representation + "'");
+        }
+        public String toString() {
+            StringBuffer sb = new StringBuffer(64);
+            sb.append(getClass().getName());
+            sb.append(" [").append(_representation).append("]");
+            return sb.toString();
+        }
+        public String getRepresentation() {
+            return _representation;
+        }
+    }
+
+    private static abstract class MatcherBase implements I_MatchPredicate {
+        private final CmpOp _operator;
+
+        MatcherBase(CmpOp operator) {
+            _operator = operator;
+        }
+        protected final int getCode() {
+            return _operator.getCode();
+        }
+        protected final boolean evaluate(int cmpResult) {
+            return _operator.evaluate(cmpResult);
+        }
+        protected final boolean evaluate(boolean cmpResult) {
+            return _operator.evaluate(cmpResult);
+        }
+        @Override
+        public final String toString() {
+            StringBuffer sb = new StringBuffer(64);
+            sb.append(getClass().getName()).append(" [");
+            sb.append(_operator.getRepresentation());
+            sb.append(getValueText());
+            sb.append("]");
+            return sb.toString();
+        }
+        protected abstract String getValueText();
+    }
+
+    private static final class NumberMatcher extends MatcherBase {
+
+        private final double _value;
+
+        public NumberMatcher(double value, CmpOp operator) {
+            super(operator);
+            _value = value;
+        }
+        @Override
+        protected String getValueText() {
+            return String.valueOf(_value);
+        }
+
+        public boolean matches(ValueEval x) {
+            double testValue;
+            if(x instanceof StringEval) {
+                // if the target(x) is a string, but parses as a number
+                // it may still count as a match, only for the equality operator
+                switch (getCode()) {
+                    case CmpOp.EQ:
+                    case CmpOp.NONE:
+                        break;
+                    case CmpOp.NE:
+                        // Always matches (inconsistent with above two cases).
+                        // for example '<>123' matches '123', '4', 'abc', etc
+                        return true;
+                    default:
+                        // never matches (also inconsistent with above three cases).
+                        // for example '>5' does not match '6',
+                        return false;
+                }
+                StringEval se = (StringEval)x;
+                Double val = OperandResolver.parseDouble(se.getStringValue());
+                if(val == null) {
+                    // x is text that is not a number
+                    return false;
+                }
+                return _value == val.doubleValue();
+            } else if((x instanceof NumberEval)) {
+                NumberEval ne = (NumberEval) x;
+                testValue = ne.getNumberValue();
             } else if((x instanceof BlankEval)) {
                 switch (getCode()) {
                     case CmpOp.NE:
@@ -223,48 +223,48 @@ public final class Countif extends Fixed
                     default:
                         return false;
                 }
-			} else {
-				return false;
-			}
-			return evaluate(Double.compare(testValue, _value));
-		}
-	}
-	private static final class BooleanMatcher extends MatcherBase {
-
-		private final int _value;
-
-		public BooleanMatcher(boolean value, CmpOp operator) {
-			super(operator);
-			_value = boolToInt(value);
-		}
-		@Override
-		protected String getValueText() {
-			return _value == 1 ? "TRUE" : "FALSE";
-		}
-
-		private static int boolToInt(boolean value) {
-			return value ? 1 : 0;
-		}
-
-		public boolean matches(ValueEval x) {
-			int testValue;
-			if(x instanceof StringEval) {
-				if (true) { // change to false to observe more intuitive behaviour
-					// Note - Unlike with numbers, it seems that COUNTIF never matches
-					// boolean values when the target(x) is a string
-					return false;
-				}
-				@SuppressWarnings("unused")
-				StringEval se = (StringEval)x;
-				Boolean val = parseBoolean(se.getStringValue());
-				if(val == null) {
-					// x is text that is not a boolean
-					return false;
-				}
-				testValue = boolToInt(val.booleanValue());
-			} else if((x instanceof BoolEval)) {
-				BoolEval be = (BoolEval) x;
-				testValue = boolToInt(be.getBooleanValue());
+            } else {
+                return false;
+            }
+            return evaluate(Double.compare(testValue, _value));
+        }
+    }
+    private static final class BooleanMatcher extends MatcherBase {
+
+        private final int _value;
+
+        public BooleanMatcher(boolean value, CmpOp operator) {
+            super(operator);
+            _value = boolToInt(value);
+        }
+        @Override
+        protected String getValueText() {
+            return _value == 1 ? "TRUE" : "FALSE";
+        }
+
+        private static int boolToInt(boolean value) {
+            return value ? 1 : 0;
+        }
+
+        public boolean matches(ValueEval x) {
+            int testValue;
+            if(x instanceof StringEval) {
+                if (true) { // change to false to observe more intuitive behaviour
+                    // Note - Unlike with numbers, it seems that COUNTIF never matches
+                    // boolean values when the target(x) is a string
+                    return false;
+                }
+                @SuppressWarnings("unused")
+                StringEval se = (StringEval)x;
+                Boolean val = parseBoolean(se.getStringValue());
+                if(val == null) {
+                    // x is text that is not a boolean
+                    return false;
+                }
+                testValue = boolToInt(val.booleanValue());
+            } else if((x instanceof BoolEval)) {
+                BoolEval be = (BoolEval) x;
+                testValue = boolToInt(be.getBooleanValue());
             } else if((x instanceof BlankEval)) {
                 switch (getCode()) {
                     case CmpOp.NE:
@@ -282,280 +282,280 @@ public final class Countif extends Fixed
                         return false;
                 }
             } else {
-				return false;
-			}
-			return evaluate(testValue - _value);
-		}
-	}
-	public static final class ErrorMatcher extends MatcherBase {
-
-		private final int _value;
-
-		public ErrorMatcher(int errorCode, CmpOp operator) {
-			super(operator);
-			_value = errorCode;
-		}
-		@Override
-		protected String getValueText() {
-			return FormulaError.forInt(_value).getString();
-		}
-
-		public boolean matches(ValueEval x) {
-			if(x instanceof ErrorEval) {
-				int testValue = ((ErrorEval)x).getErrorCode();
-				return evaluate(testValue - _value);
-			}
-			return false;
-		}
-		
-		public int getValue() {
-		    return _value;
-		}
-	}
-	public static final class StringMatcher extends MatcherBase {
-
-		private final String _value;
-		private final Pattern _pattern;
-
-		public StringMatcher(String value, CmpOp operator) {
-			super(operator);
-			_value = value;
-			switch(operator.getCode()) {
-				case CmpOp.NONE:
-				case CmpOp.EQ:
-				case CmpOp.NE:
-					_pattern = getWildCardPattern(value);
-					break;
-				default:
-					// pattern matching is never used for < > <= =>
-					_pattern = null;
-			}
-		}
-		@Override
-		protected String getValueText() {
-			if (_pattern == null) {
-				return _value;
-			}
-			return _pattern.pattern();
-		}
-
-		public boolean matches(ValueEval x) {
-			if (x instanceof BlankEval) {
-				switch(getCode()) {
-					case CmpOp.NONE:
-					case CmpOp.EQ:
-						return _value.length() == 0;
+                return false;
+            }
+            return evaluate(testValue - _value);
+        }
+    }
+    public static final class ErrorMatcher extends MatcherBase {
+
+        private final int _value;
+
+        public ErrorMatcher(int errorCode, CmpOp operator) {
+            super(operator);
+            _value = errorCode;
+        }
+        @Override
+        protected String getValueText() {
+            return FormulaError.forInt(_value).getString();
+        }
+
+        public boolean matches(ValueEval x) {
+            if(x instanceof ErrorEval) {
+                int testValue = ((ErrorEval)x).getErrorCode();
+                return evaluate(testValue - _value);
+            }
+            return false;
+        }
+        
+        public int getValue() {
+            return _value;
+        }
+    }
+    public static final class StringMatcher extends MatcherBase {
+
+        private final String _value;
+        private final Pattern _pattern;
+
+        public StringMatcher(String value, CmpOp operator) {
+            super(operator);
+            _value = value;
+            switch(operator.getCode()) {
+                case CmpOp.NONE:
+                case CmpOp.EQ:
+                case CmpOp.NE:
+                    _pattern = getWildCardPattern(value);
+                    break;
+                default:
+                    // pattern matching is never used for < > <= =>
+                    _pattern = null;
+            }
+        }
+        @Override
+        protected String getValueText() {
+            if (_pattern == null) {
+                return _value;
+            }
+            return _pattern.pattern();
+        }
+
+        public boolean matches(ValueEval x) {
+            if (x instanceof BlankEval) {
+                switch(getCode()) {
+                    case CmpOp.NONE:
+                    case CmpOp.EQ:
+                        return _value.length() == 0;
                     case CmpOp.NE:
                         // pred '<>' matches empty string but not blank cell
                         // pred '<>ABC'  matches blank and 'not ABC'
                         return _value.length() != 0;
-				}
-				// no other criteria matches a blank cell
-				return false;
-			}
-			if(!(x instanceof StringEval)) {
-				// must always be string
-				// even if match str is wild, but contains only digits
-				// e.g. '4*7', NumberEval(4567) does not match
-				return false;
-			}
-			String testedValue = ((StringEval) x).getStringValue();
-			if (testedValue.length() < 1 && _value.length() < 1) {
-				// odd case: criteria '=' behaves differently to criteria ''
-
-				switch(getCode()) {
-					case CmpOp.NONE: return true;
-					case CmpOp.EQ:   return false;
-					case CmpOp.NE:   return true;
-				}
-				return false;
-			}
-			if (_pattern != null) {
-				return evaluate(_pattern.matcher(testedValue).matches());
-			}
+                }
+                // no other criteria matches a blank cell
+                return false;
+            }
+            if(!(x instanceof StringEval)) {
+                // must always be string
+                // even if match str is wild, but contains only digits
+                // e.g. '4*7', NumberEval(4567) does not match
+                return false;
+            }
+            String testedValue = ((StringEval) x).getStringValue();
+            if (testedValue.length() < 1 && _value.length() < 1) {
+                // odd case: criteria '=' behaves differently to criteria ''
+
+                switch(getCode()) {
+                    case CmpOp.NONE: return true;
+                    case CmpOp.EQ:   return false;
+                    case CmpOp.NE:   return true;
+                }
+                return false;
+            }
+            if (_pattern != null) {
+                return evaluate(_pattern.matcher(testedValue).matches());
+            }
             // String criteria in COUNTIF are case insensitive:
             // for example, the string "apples" and the string "APPLES" will match the same cells.
-			return evaluate(testedValue.compareToIgnoreCase(_value));
-		}
-		/**
-		 * Translates Excel countif wildcard strings into java regex strings
-		 * @return <code>null</code> if the specified value contains no special wildcard characters.
-		 */
-		public static Pattern getWildCardPattern(String value) {
-			int len = value.length();
-			StringBuffer sb = new StringBuffer(len);
-			boolean hasWildCard = false;
-			for(int i=0; i<len; i++) {
-				char ch = value.charAt(i);
-				switch(ch) {
-					case '?':  //Any single character
-						hasWildCard = true;
-						// match exactly one character
-						sb.append('.');
-						continue;
-					case '*': //Zero or more characters
-						hasWildCard = true;
-						// match one or more occurrences of any character
-						sb.append(".*");
-						continue;
-					case '~':
-						if (i+1<len) {
-							ch = value.charAt(i+1);
-							switch (ch) {
-								case '?':
-								case '*':
-									hasWildCard = true;
-									sb.append('[').append(ch).append(']');
-									i++; // Note - incrementing loop variable here
-									continue;
-							}
-						}
-						// else not '~?' or '~*'
-						sb.append('~'); // just plain '~'
-						continue;
-					case '.':
-					case '$':
-					case '^':
-					case '[':
-					case ']':
-					case '(':
-					case ')':
-						// escape literal characters that would have special meaning in regex
-						sb.append("\\").append(ch);
-						continue;
-				}
-				sb.append(ch);
-			}
-			if (hasWildCard) {
-				return Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE);
-			}
-			return null;
-		}
-	}
-
-	public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
-
-		I_MatchPredicate mp = createCriteriaPredicate(arg1, srcRowIndex, srcColumnIndex);
-		if(mp == null) {
-			// If the criteria arg is a reference to a blank cell, countif always returns zero.
-			return NumberEval.ZERO;
-		}
-		double result = countMatchingCellsInArea(arg0, mp);
-		return new NumberEval(result);
-	}
-	/**
-	 * @return the number of evaluated cells in the range that match the specified criteria
-	 */
-	private double countMatchingCellsInArea(ValueEval rangeArg, I_MatchPredicate criteriaPredicate) {
-
-		if (rangeArg instanceof RefEval) {
-			return CountUtils.countMatchingCellsInRef((RefEval) rangeArg, criteriaPredicate);
-		} else if (rangeArg instanceof ThreeDEval) {
-			return CountUtils.countMatchingCellsInArea((ThreeDEval) rangeArg, criteriaPredicate);
-		} else {
-			throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")");
-		}
-	}
-
-	/**
-	 * Creates a criteria predicate object for the supplied criteria arg
-	 * @return <code>null</code> if the arg evaluates to blank.
-	 */
-	/* package */ static I_MatchPredicate createCriteriaPredicate(ValueEval arg, int srcRowIndex, int srcColumnIndex) {
-
-		ValueEval evaluatedCriteriaArg = evaluateCriteriaArg(arg, srcRowIndex, srcColumnIndex);
-
-		if(evaluatedCriteriaArg instanceof NumberEval) {
-			return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue(), CmpOp.OP_NONE);
-		}
-		if(evaluatedCriteriaArg instanceof BoolEval) {
-			return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue(), CmpOp.OP_NONE);
-		}
-
-		if(evaluatedCriteriaArg instanceof StringEval) {
-			return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
-		}
-		if(evaluatedCriteriaArg instanceof ErrorEval) {
-			return new ErrorMatcher(((ErrorEval)evaluatedCriteriaArg).getErrorCode(), CmpOp.OP_NONE);
-		}
-		if(evaluatedCriteriaArg == BlankEval.instance) {
-			return null;
-		}
-		throw new RuntimeException("Unexpected type for criteria ("
-				+ evaluatedCriteriaArg.getClass().getName() + ")");
-	}
-
-	/**
-	 *
-	 * @return the de-referenced criteria arg (possibly {@link ErrorEval})
-	 */
-	private static ValueEval evaluateCriteriaArg(ValueEval arg, int srcRowIndex, int srcColumnIndex) {
-		try {
-			return OperandResolver.getSingleValue(arg, srcRowIndex, srcColumnIndex);
-		} catch (EvaluationException e) {
-			return e.getErrorEval();
-		}
-	}
-	/**
-	 * When the second argument is a string, many things are possible
-	 */
-	private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
-		String value = stringEval.getStringValue();
-		CmpOp operator = CmpOp.getOperator(value);
-		value = value.substring(operator.getLength());
-
-		Boolean booleanVal = parseBoolean(value);
-		if(booleanVal != null) {
-			return new BooleanMatcher(booleanVal.booleanValue(), operator);
-		}
-
-		Double doubleVal = OperandResolver.parseDouble(value);
-		if(doubleVal != null) {
-			return new NumberMatcher(doubleVal.doubleValue(), operator);
-		}
-		ErrorEval ee = parseError(value);
-		if (ee != null) {
-			return new ErrorMatcher(ee.getErrorCode(), operator);
-		}
-
-		//else - just a plain string with no interpretation.
-		return new StringMatcher(value, operator);
-	}
-	private static ErrorEval parseError(String value) {
-		if (value.length() < 4 || value.charAt(0) != '#') {
-			return null;
-		}
-		if (value.equals("#NULL!"))  return ErrorEval.NULL_INTERSECTION;
-		if (value.equals("#DIV/0!")) return ErrorEval.DIV_ZERO;
-		if (value.equals("#VALUE!")) return ErrorEval.VALUE_INVALID;
-		if (value.equals("#REF!"))   return ErrorEval.REF_INVALID;
-		if (value.equals("#NAME?"))  return ErrorEval.NAME_INVALID;
-		if (value.equals("#NUM!"))   return ErrorEval.NUM_ERROR;
-		if (value.equals("#N/A"))    return ErrorEval.NA;
-
-		return null;
-	}
-	/**
-	 * Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
-	 */
-	/* package */ static Boolean parseBoolean(String strRep) {
-		if (strRep.length() < 1) {
-			return null;
-		}
-		switch(strRep.charAt(0)) {
-			case 't':
-			case 'T':
-				if("TRUE".equalsIgnoreCase(strRep)) {
-					return Boolean.TRUE;
-				}
-				break;
-			case 'f':
-			case 'F':
-				if("FALSE".equalsIgnoreCase(strRep)) {
-					return Boolean.FALSE;
-				}
-				break;
-		}
-		return null;
-	}
+            return evaluate(testedValue.compareToIgnoreCase(_value));
+        }
+        /**
+         * Translates Excel countif wildcard strings into java regex strings
+         * @return <code>null</code> if the specified value contains no special wildcard characters.
+         */
+        public static Pattern getWildCardPattern(String value) {
+            int len = value.length();
+            StringBuffer sb = new StringBuffer(len);
+            boolean hasWildCard = false;
+            for(int i=0; i<len; i++) {
+                char ch = value.charAt(i);
+                switch(ch) {
+                    case '?':  //Any single character
+                        hasWildCard = true;
+                        // match exactly one character
+                        sb.append('.');
+                        continue;
+                    case '*': //Zero or more characters
+                        hasWildCard = true;
+                        // match one or more occurrences of any character
+                        sb.append(".*");
+                        continue;
+                    case '~':
+                        if (i+1<len) {
+                            ch = value.charAt(i+1);
+                            switch (ch) {
+                                case '?':
+                                case '*':
+                                    hasWildCard = true;
+                                    sb.append('[').append(ch).append(']');
+                                    i++; // Note - incrementing loop variable here
+                                    continue;
+                            }
+                        }
+                        // else not '~?' or '~*'
+                        sb.append('~'); // just plain '~'
+                        continue;
+                    case '.':
+                    case '$':
+                    case '^':
+                    case '[':
+                    case ']':
+                    case '(':
+                    case ')':
+                        // escape literal characters that would have special meaning in regex
+                        sb.append("\\").append(ch);
+                        continue;
+                }
+                sb.append(ch);
+            }
+            if (hasWildCard) {
+                return Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE);
+            }
+            return null;
+        }
+    }
+
+    public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
+
+        I_MatchPredicate mp = createCriteriaPredicate(arg1, srcRowIndex, srcColumnIndex);
+        if(mp == null) {
+            // If the criteria arg is a reference to a blank cell, countif always returns zero.
+            return NumberEval.ZERO;
+        }
+        double result = countMatchingCellsInArea(arg0, mp);
+        return new NumberEval(result);
+    }
+    /**
+     * @return the number of evaluated cells in the range that match the specified criteria
+     */
+    private double countMatchingCellsInArea(ValueEval rangeArg, I_MatchPredicate criteriaPredicate) {
+
+        if (rangeArg instanceof RefEval) {
+            return CountUtils.countMatchingCellsInRef((RefEval) rangeArg, criteriaPredicate);
+        } else if (rangeArg instanceof ThreeDEval) {
+            return CountUtils.countMatchingCellsInArea((ThreeDEval) rangeArg, criteriaPredicate);
+        } else {
+            throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")");
+        }
+    }
+
+    /**
+     * Creates a criteria predicate object for the supplied criteria arg
+     * @return <code>null</code> if the arg evaluates to blank.
+     */
+    /* package */ static I_MatchPredicate createCriteriaPredicate(ValueEval arg, int srcRowIndex, int srcColumnIndex) {
+
+        ValueEval evaluatedCriteriaArg = evaluateCriteriaArg(arg, srcRowIndex, srcColumnIndex);
+
+        if(evaluatedCriteriaArg instanceof NumberEval) {
+            return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue(), CmpOp.OP_NONE);
+        }
+        if(evaluatedCriteriaArg instanceof BoolEval) {
+            return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue(), CmpOp.OP_NONE);
+        }
+
+        if(evaluatedCriteriaArg instanceof StringEval) {
+            return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
+        }
+        if(evaluatedCriteriaArg instanceof ErrorEval) {
+            return new ErrorMatcher(((ErrorEval)evaluatedCriteriaArg).getErrorCode(), CmpOp.OP_NONE);
+        }
+        if(evaluatedCriteriaArg == BlankEval.instance) {
+            return null;
+        }
+        throw new RuntimeException("Unexpected type for criteria ("
+                + evaluatedCriteriaArg.getClass().getName() + ")");
+    }
+
+    /**
+     *
+     * @return the de-referenced criteria arg (possibly {@link ErrorEval})
+     */
+    private static ValueEval evaluateCriteriaArg(ValueEval arg, int srcRowIndex, int srcColumnIndex) {
+        try {
+            return OperandResolver.getSingleValue(arg, srcRowIndex, srcColumnIndex);
+        } catch (EvaluationException e) {
+            return e.getErrorEval();
+        }
+    }
+    /**
+     * When the second argument is a string, many things are possible
+     */
+    private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
+        String value = stringEval.getStringValue();
+        CmpOp operator = CmpOp.getOperator(value);
+        value = value.substring(operator.getLength());
+
+        Boolean booleanVal = parseBoolean(value);
+        if(booleanVal != null) {
+            return new BooleanMatcher(booleanVal.booleanValue(), operator);
+        }
+
+        Double doubleVal = OperandResolver.parseDouble(value);
+        if(doubleVal != null) {
+            return new NumberMatcher(doubleVal.doubleValue(), operator);
+        }
+        ErrorEval ee = parseError(value);
+        if (ee != null) {
+            return new ErrorMatcher(ee.getErrorCode(), operator);
+        }
+
+        //else - just a plain string with no interpretation.
+        return new StringMatcher(value, operator);
+    }
+    private static ErrorEval parseError(String value) {
+        if (value.length() < 4 || value.charAt(0) != '#') {
+            return null;
+        }
+        if (value.equals("#NULL!"))  return ErrorEval.NULL_INTERSECTION;
+        if (value.equals("#DIV/0!")) return ErrorEval.DIV_ZERO;
+        if (value.equals("#VALUE!")) return ErrorEval.VALUE_INVALID;
+        if (value.equals("#REF!"))   return ErrorEval.REF_INVALID;
+        if (value.equals("#NAME?"))  return ErrorEval.NAME_INVALID;
+        if (value.equals("#NUM!"))   return ErrorEval.NUM_ERROR;
+        if (value.equals("#N/A"))    return ErrorEval.NA;
+
+        return null;
+    }
+    /**
+     * Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
+     */
+    /* package */ static Boolean parseBoolean(String strRep) {
+        if (strRep.length() < 1) {
+            return null;
+        }
+        switch(strRep.charAt(0)) {
+            case 't':
+            case 'T':
+                if("TRUE".equalsIgnoreCase(strRep)) {
+                    return Boolean.TRUE;
+                }
+                break;
+            case 'f':
+            case 'F':
+                if("FALSE".equalsIgnoreCase(strRep)) {
+                    return Boolean.FALSE;
+                }
+                break;
+        }
+        return null;
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org