/// <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)
        {
            // NOTE: jh 2010.10.06
            // this method differs a bit from the Java version due to a lack
            // of a proper ISet interfacea and SubSet implementation
            IFilter filter = m_filter;

            if (filter is IIndexAwareFilter)
            {
                // create delta set
                var setDelta = new HashSet(keys);

                // delegate to the not-ed filter, but only use the non-partial
                // indexes, since for a partial index the fact that it contains
                // keys for entries that fit the underlying filter does not
                // mean that it contains them all. As a result, the "negating"
                // operation may produce invalid result
                IFilter filterNew = ((IIndexAwareFilter)filter).ApplyIndex(
                    indexes, setDelta);

                // see if any keys were filtered out
                //azzert(setDelta.getAdded().isEmpty());
                var setRemoved = new HashSet();
                foreach (object key in keys)
                {
                    if (!CollectionUtils.Contains(setDelta, key))
                    {
                        setRemoved.Add(key);
                    }
                }

                if (filterNew == null || setDelta.Count == 0)
                {
                    // invert the key selection by the delegated-to filter
                    if (setRemoved.Count == 0)
                    {
                        // no keys were removed; therefore the result of the
                        // "not" is to remove all keys (clear)
                        CollectionUtils.Clear(keys);
                    }
                    else if (setDelta.Count == 0)
                    {
                        // all keys were removed; therefore the result of the
                        // "not" is to retain all keys (remove none)
                    }
                    else
                    {
                        // some keys were removed; therefore the result of the
                        // "not" is to retain only those removed keys
                        CollectionUtils.RetainAll(keys, setRemoved);
                    }

                    // nothing left to do; the index fully resolved the filter
                    return(null);
                }
                if (setRemoved.Count == 0)
                {
                    // no obvious effect from the index application
                    return(filterNew == filter ? this : new NotFilter(filterNew));
                }
                // some keys have been removed; those are definitely "in";
                // the remaining keys each need to be evaluated later
                var filterKey = new KeyFilter(setRemoved);
                var filterNot = filterNew == filter ? this : new NotFilter(filterNew);
                return(new AndFilter(filterKey, filterNot));
            }
            return(this);
        }
        /// <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 override IFilter ApplyIndex(IDictionary indexes, ICollection keys)
        {
            OptimizeFilterOrder(indexes, keys);

            IFilter[] filters     = m_filters;
            int       filterCount = filters.Length;
            var       filterList  = new ArrayList(filterCount);
            var       matches     = new HashSet(keys.Count);

            // filterList is an array of filters that will have to be re-applied
            // matches    is an accumulating set of already matching keys

            for (int i = 0; i < filterCount; i++)
            {
                IFilter filter = filters[i];
                if (filter is IIndexAwareFilter)
                {
                    ICollection remain = new ArrayList(keys);
                    if (matches.Count > 0)
                    {
                        CollectionUtils.RemoveAll(remain, matches);
                    }

                    IFilter filterDefer = ApplyFilter(
                        (IIndexAwareFilter)filter, indexes, remain);

                    if (filterDefer == null)
                    {
                        // these are definitely "in"
                        CollectionUtils.AddAll(matches, remain);
                    }
                    else
                    {
                        int keyCount  = keys.Count;
                        int remaining = remain.Count;
                        if (remaining < keyCount)
                        {
                            // some keys are definitely "out" for this filter;
                            // we need to incorporate this knowledge into a deferred
                            // filter
                            if (remaining > 0)
                            {
                                var filterKey = new KeyFilter(remain);
                                filterList.Add(new AndFilter(filterDefer, filterKey));
                            }
                            //else
                            {
                                // though a filter was returned, the key set was
                                // fully reduced; this should have the same effect
                                // as a fully resolved filter without any matches
                            }
                        }
                        else
                        {
                            filterList.Add(filterDefer);
                        }
                    }
                }
                else
                {
                    filterList.Add(filter);
                }
            }
            int matchCount = matches.Count;

            filterCount = filterList.Count;
            if (filterCount == 0)
            {
                if (matchCount > 0)
                {
                    CollectionUtils.RetainAll(keys, matches);
                }
                else
                {
                    CollectionUtils.Clear(keys);
                }
                return(null);
            }
            if (filterCount == 1 && matchCount == 0)
            {
                return((IFilter)filterList[0]);
            }
            if (matchCount > 0)
            {
                // the keys that have been matched are definitely "in";
                // the remaining keys each need to be evaluated later
                var filterKey = new KeyFilter(matches);
                filterList.Insert(0, filterKey);
            }
            return(new AnyFilter((IFilter[])filterList.ToArray(typeof(IFilter))));
        }