]> source.dussan.org Git - poi.git/commitdiff
whitespace
authorJaven O'Neal <onealj@apache.org>
Tue, 24 Nov 2015 08:15:42 +0000 (08:15 +0000)
committerJaven O'Neal <onealj@apache.org>
Tue, 24 Nov 2015 08:15:42 +0000 (08:15 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1716060 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/ss/formula/functions/Countif.java

index 9023815c363f5be8a5a9ba093a3df1e97bee81bb..dbe320d4afc7996e27523190165bfc7a619f7f94 100644 (file)
@@ -44,177 +44,177 @@ import org.apache.poi.ss.usermodel.FormulaError;
  */
 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 Fixed2ArgFunction {
                     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 Fixed2ArgFunction {
                         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;
+    }
 }