/// <summary>
        /// Given an IDictionary of available indexes, determine if this
        /// IIndexAwareFilter can use any of the indexes to assist in its
        /// processing, and if so, determine how effective the use of that
        /// index would be.
        /// </summary>
        /// <remarks>
        /// <p>
        /// The returned value is an effectiveness estimate of how well this
        /// filter can use the specified indexes to filter the specified
        /// keys. An operation that requires no more than a single access to
        /// the index content (i.e. Equals, NotEquals) has an effectiveness of
        /// <b>one</b>. Evaluation of a single entry is assumed to have an
        /// effectiveness that depends on the index implementation and is
        /// usually measured as a constant number of the single operations.
        /// This number is referred to as <i>evaluation cost</i>.
        /// </p>
        /// <p>
        /// If the effectiveness of a filter evaluates to a number larger
        /// than the keys.size() then a user could avoid using the index and
        /// iterate through the keys calling <tt>Evaluate</tt> rather than
        /// <tt>ApplyIndex</tt>.
        /// </p>
        /// </remarks>
        /// <param name="indexes">
        /// The available <see cref="ICacheIndex"/> objects keyed by the
        /// related IValueExtractor; read-only.
        /// </param>
        /// <param name="keys">
        /// The set of keys that will be filtered; read-only.
        /// </param>
        /// <returns>
        /// An effectiveness estimate of how well this filter can use the
        /// specified indexes to filter the specified keys.
        /// </returns>
        public int CalculateEffectiveness(IDictionary indexes, ICollection keys)
        {
            OptimizationPlan plan = m_plan;

            if (plan == OptimizationPlan.AlwaysFalse || plan == OptimizationPlan.AlwaysTrue)
            {
                return(1);
            }

            var index = (ICacheIndex)indexes[ValueExtractor];

            if (index == null)
            {
                return(CalculateIteratorEffectiveness(keys.Count));
            }

            if (plan == OptimizationPlan.ExactMatch)
            {
                return(1);
            }

            string pattern = Pattern;

            if (index.IsOrdered && pattern.IndexOf('%') != 0 && pattern.IndexOf('_') != 0)
            {
                return(Math.Max(index.IndexContents.Count / 4, 1));
            }
            return(index.IndexContents.Count);
        }
        /// <summary>
        /// Filter remaining keys using an IDictionary of available indexes.
        /// </summary>
        /// <remarks>
        /// The filter is responsible for removing all keys from the passed
        /// set of keys that the applicable indexes can prove should be
        /// filtered. If the filter does not fully evaluate the remaining
        /// keys using just the index information, it must return a filter
        /// (which may be an <see cref="IEntryFilter"/>) that can complete the
        /// task using an iterating implementation. If, on the other hand, the
        /// filter does fully evaluate the remaining keys using just the index
        /// information, then it should return <c>null</c> to indicate that no
        /// further filtering is necessary.
        /// </remarks>
        /// <param name="indexes">
        /// The available <see cref="ICacheIndex"/> objects keyed by the
        /// related IValueExtractor; read-only.
        /// </param>
        /// <param name="keys">
        /// The mutable set of keys that remain to be filtered.
        /// </param>
        /// <returns>
        /// An <see cref="IFilter"/> object that can be used to process the
        /// remaining keys, or <c>null</c> if no additional filter processing
        /// is necessary.
        /// </returns>
        public IFilter ApplyIndex(IDictionary indexes, ICollection keys)
        {
            OptimizationPlan plan = m_plan;

            switch (plan)
            {
            case OptimizationPlan.AlwaysFalse:
                CollectionUtils.Clear(keys);
                return(null);

            case OptimizationPlan.AlwaysTrue:
                return(null);
            }

            var index = (ICacheIndex)indexes[ValueExtractor];

            if (index == null)
            {
                // there is no relevant index
                return(this);
            }

            if (plan == OptimizationPlan.ExactMatch)
            {
                var setEquals = (ICollection)index.IndexContents[m_part];
                if (setEquals == null || setEquals.Count == 0)
                {
                    CollectionUtils.Clear(keys);
                }
                else
                {
                    CollectionUtils.RetainAll(keys, setEquals);
                }
                return(null);
            }

            IDictionary contents = index.IndexContents;
            ICollection matches;

            if ((plan == OptimizationPlan.StartsWithString ||
                 plan == OptimizationPlan.StartsWithChar) && index.IsOrdered)
            {
                try
                {
                    string prefix = plan == OptimizationPlan.StartsWithString ?
                                    m_part : Char.ToString(m_partChar);

                    SortedList tail = CollectionUtils.TailList(contents, prefix);
                    matches = new HashSet();
                    foreach (DictionaryEntry entry in tail)
                    {
                        var value = (string)entry.Key;
                        if (value.StartsWith(prefix))
                        {
                            CollectionUtils.AddAll(matches, (ICollection)entry.Value);
                        }
                        else
                        {
                            break;
                        }
                    }
                    CollectionUtils.RetainAll(keys, matches);
                    return(null);
                }
                catch (InvalidCastException)
                {
                    // incompatible types; go the long way
                }
            }

            matches = new HashSet();
            foreach (DictionaryEntry entry in contents)
            {
                string value = entry.Key == null ? null : entry.Key.ToString();
                if (IsMatch(value))
                {
                    CollectionUtils.AddAll(matches, (ICollection)entry.Value);
                }
            }
            CollectionUtils.RetainAll(keys, matches);

            return(null);
        }