001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.bloomfilter;
018
019import java.util.Objects;
020
021/**
022 * The interface that describes a Bloom filter that associates a count with each
023 * bit index rather than a bit.  This allows reversal of merge operations with
024 * remove operations.
025 *
026 * <p>A counting Bloom filter is expected to function identically to a standard
027 * Bloom filter that is the merge of all the Bloom filters that have been added
028 * to and not later subtracted from the counting Bloom filter. The functional
029 * state of a CountingBloomFilter at the start and end of a series of merge and
030 * subsequent remove operations of the same Bloom filters, irrespective of
031 * remove order, is expected to be the same.</p>
032 *
033 * <p>Removal of a filter that has not previously been merged results in an
034 * invalid state where the cells no longer represent a sum of merged Bloom
035 * filters. It is impossible to validate merge and remove exactly without
036 * explicitly storing all filters. Consequently such an operation may go
037 * undetected. The CountingBloomFilter maintains a state flag that is used as a
038 * warning that an operation was performed that resulted in invalid cells and
039 * thus an invalid state. For example this may occur if a cell for an index was
040 * set to negative following a remove operation.</p>
041 *
042 * <p>Implementations should document the expected state of the filter after an
043 * operation that generates invalid cells, and any potential recovery options.
044 * An implementation may support a reversal of the operation to restore the
045 * state to that prior to the operation. In the event that invalid cells are
046 * adjusted to a valid range then it should be documented if there has been
047 * irreversible information loss.</p>
048 *
049 * <p>Implementations may choose to throw an exception during an operation that
050 * generates invalid cells. Implementations should document the expected state
051 * of the filter after such an operation. For example are the cells not updated,
052 * partially updated or updated entirely before the exception is raised.</p>
053 *
054 * @see CellExtractor
055 * @since 4.5.0-M1
056 */
057public interface CountingBloomFilter extends BloomFilter<CountingBloomFilter>, CellExtractor {
058
059    // Query Operations
060
061    /**
062     * Adds the specified CellExtractor to this Bloom filter.
063     *
064     * <p>Specifically
065     * all cells for the indexes identified by the {@code other} will be incremented
066     * by their corresponding values in the {@code other}.</p>
067     *
068     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
069     *
070     * @param other The CellExtractor to add.
071     * @return {@code true} if the addition was successful and the state is valid
072     * @see #isValid()
073     * @see #subtract(CellExtractor)
074     */
075    boolean add(CellExtractor other);
076
077    /**
078     * Gets the maximum allowable value for a cell count in this Counting filter.
079     *
080     * @return The maximum allowable value for a cell count in this Counting filter.
081     */
082    int getMaxCell();
083
084    /**
085     * Determines the maximum number of times the BitMapExtractor could have been merged into this counting filter.
086     *
087     * @param bitMapExtractor The BitMapExtractor to provide the indices.
088     * @return The maximum number of times the BitMapExtractor could have been inserted.
089     */
090    default int getMaxInsert(final BitMapExtractor bitMapExtractor) {
091        if (!contains(bitMapExtractor)) {
092            return 0;
093        }
094        final long[] bitMaps = bitMapExtractor.asBitMapArray();
095        final int[] max = { Integer.MAX_VALUE };
096        processCells((x, y) -> {
097            if ((bitMaps[BitMaps.getLongIndex(x)] & BitMaps.getLongBit(x)) != 0) {
098                max[0] = max[0] <= y ? max[0] : y;
099            }
100            return true;
101        });
102        return max[0];
103    }
104
105    /**
106     * Determines the maximum number of times the Bloom filter could have been merged into this counting filter.
107     *
108     * @param bloomFilter The Bloom filter the check for.
109     * @return The maximum number of times the Bloom filter could have been inserted.
110     */
111    default int getMaxInsert(final BloomFilter<?> bloomFilter) {
112        return getMaxInsert((BitMapExtractor) bloomFilter);
113    }
114
115    /**
116     * Determines the maximum number of times the Cell Extractor could have been added.
117     *
118     * @param cellExtractor The extractor of cells.
119     * @return The maximum number of times the CellExtractor could have been inserted.
120     */
121    int getMaxInsert(CellExtractor cellExtractor);
122
123    /**
124     * Determines the maximum number of times the Hasher could have been merged into this counting filter.
125     *
126     * @param hasher The Hasher to provide the indices.
127     * @return The maximum number of times the hasher could have been inserted.
128     */
129    default int getMaxInsert(final Hasher hasher) {
130        return getMaxInsert(hasher.indices(getShape()));
131    }
132
133    /**
134     * Determines the maximum number of times the IndexExtractor could have been merged into this counting filter.
135     * <p>
136     * To determine how many times an indexExtractor could have been added create a CellExtractor from the indexExtractor and check that
137     * </p>
138     *
139     * @param indexExtractor The extractor to drive the count check.
140     * @return The maximum number of times the IndexExtractor could have been inserted.
141     * @see #getMaxInsert(CellExtractor)
142     */
143    default int getMaxInsert(final IndexExtractor indexExtractor) {
144        return getMaxInsert(CellExtractor.from(indexExtractor.uniqueIndices()));
145    }
146
147    /**
148     * Returns {@code true} if the internal state is valid.
149     *
150     * <p>This flag is a warning that an addition or
151     * subtraction of cells from this filter resulted in an invalid cell for one or more
152     * indexes. For example this may occur if a cell for an index was
153     * set to negative following a subtraction operation, or overflows the value specified by {@code getMaxCell()} following an
154     * addition operation.</p>
155     *
156     * <p>A counting Bloom filter that has an invalid state is no longer ensured to function
157     * identically to a standard Bloom filter instance that is the merge of all the Bloom filters
158     * that have been added to and not later subtracted from this counting Bloom filter.</p>
159     *
160     * <p>Note: The change to an invalid state may or may not be reversible. Implementations
161     * are expected to document their policy on recovery from an addition or removal operation
162     * that generated an invalid state.</p>
163     *
164     * @return {@code true} if the state is valid
165     */
166    boolean isValid();
167
168    /**
169     * Merges the specified BitMap extractor into this Bloom filter.
170     *
171     * <p>Specifically: all cells for the indexes identified by the {@code bitMapExtractor} will be incremented by 1.</p>
172     *
173     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
174     *
175     * @param bitMapExtractor The BitMapExtractor
176     * @return {@code true} if the removal was successful and the state is valid
177     * @see #isValid()
178     * @see #add(CellExtractor)
179     */
180    @Override
181    default boolean merge(final BitMapExtractor bitMapExtractor) {
182        return merge(IndexExtractor.fromBitMapExtractor(bitMapExtractor));
183    }
184
185    /**
186     * Merges the specified Bloom filter into this Bloom filter.
187     *
188     * <p>Specifically: all cells for the indexes identified by the {@code other} filter will be incremented by 1.</p>
189     *
190     * <p>Note: If the other filter is a counting Bloom filter the other filter's cells are ignored and it is treated as an
191     * IndexExtractor.</p>
192     *
193     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
194     *
195     * @param other The other Bloom filter
196     * @return {@code true} if the removal was successful and the state is valid
197     * @see #isValid()
198     * @see #add(CellExtractor)
199     */
200    @Override
201    default boolean merge(final BloomFilter<?> other) {
202        Objects.requireNonNull(other, "other");
203        return merge((IndexExtractor) other);
204    }
205
206    /**
207     * Merges the specified Hasher into this Bloom filter.
208     *
209     * <p>Specifically: all cells for the unique indexes identified by the {@code hasher} will be incremented by 1.</p>
210     *
211     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
212     *
213     * @param hasher The hasher
214     * @return {@code true} if the removal was successful and the state is valid
215     * @see #isValid()
216     * @see #add(CellExtractor)
217     */
218    @Override
219    default boolean merge(final Hasher hasher) {
220        Objects.requireNonNull(hasher, "hasher");
221        return merge(hasher.indices(getShape()));
222    }
223
224    /**
225     * Merges the specified index extractor into this Bloom filter.
226     *
227     * <p>Specifically: all unique cells for the indices identified by the {@code indexExtractor} will be incremented by 1.</p>
228     *
229     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
230     *
231     * <p>Notes:</p>
232     * <ul>
233     * <li>If indices that are returned multiple times should be incremented multiple times convert the IndexExtractor
234     * to a CellExtractor and add that.</li>
235     * <li>Implementations should throw {@code IllegalArgumentException} and no other exception on bad input.</li>
236     * </ul>
237     *
238     * @param indexExtractor The IndexExtractor
239     * @return {@code true} if the removal was successful and the state is valid
240     * @see #isValid()
241     * @see #add(CellExtractor)
242     */
243    @Override
244    default boolean merge(final IndexExtractor indexExtractor) {
245        Objects.requireNonNull(indexExtractor, "indexExtractor");
246        try {
247            return add(CellExtractor.from(indexExtractor.uniqueIndices()));
248        } catch (final IndexOutOfBoundsException e) {
249            throw new IllegalArgumentException(
250                    String.format("Filter only accepts values in the [0,%d) range", getShape().getNumberOfBits()), e);
251        }
252    }
253
254    /**
255     * Removes the specified BitMapExtractor from this Bloom filter.
256     *
257     * <p>Specifically all cells for the indices produced by the {@code bitMapExtractor} will be
258     * decremented by 1.</p>
259     *
260     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
261     *
262     * @param bitMapExtractor The BitMapExtractor to provide the indexes
263     * @return {@code true} if the removal was successful and the state is valid
264     * @see #isValid()
265     * @see #subtract(CellExtractor)
266     */
267    default boolean remove(final BitMapExtractor bitMapExtractor) {
268        return remove(IndexExtractor.fromBitMapExtractor(bitMapExtractor));
269    }
270
271    /**
272     * Removes the specified Bloom filter from this Bloom filter.
273     *
274     * <p>Specifically: all cells for the indexes identified by the {@code other} filter will be decremented by 1.</p>
275     *
276     * <p>Note: If the other filter is a counting Bloom filter the other filter's cells are ignored and it is treated as an
277     * IndexExtractor.</p>
278     *
279     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
280     *
281     * @param other The other Bloom filter
282     * @return {@code true} if the removal was successful and the state is valid
283     * @see #isValid()
284     * @see #subtract(CellExtractor)
285     */
286    default boolean remove(final BloomFilter<?> other) {
287        return remove((IndexExtractor) other);
288    }
289
290    /**
291     * Removes the unique values from the specified hasher from this Bloom filter.
292     *
293     * <p>Specifically all cells for the unique indices produced by the {@code hasher} will be
294     * decremented by 1.</p>
295     *
296     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
297     *
298     * @param hasher The hasher to provide the indexes
299     * @return {@code true} if the removal was successful and the state is valid
300     * @see #isValid()
301     * @see #subtract(CellExtractor)
302     */
303    default boolean remove(final Hasher hasher) {
304        Objects.requireNonNull(hasher, "hasher");
305        return remove(hasher.indices(getShape()));
306    }
307
308    /**
309     * Removes the values from the specified IndexExtractor from the Bloom filter from this Bloom filter.
310     *
311     * <p>Specifically all cells for the unique indices produced by the {@code hasher} will be
312     * decremented by 1.</p>
313     *
314     * <p>This method will return {@code true} if the filter is valid after the operation.</p>
315     *
316     * <p>Note: If indices that are returned multiple times should be decremented multiple times convert the IndexExtractor
317     * to a CellExtractor and subtract that.</p>
318     *
319     * @param indexExtractor The IndexExtractor to provide the indexes
320     * @return {@code true} if the removal was successful and the state is valid
321     * @see #isValid()
322     * @see #subtract(CellExtractor)
323     */
324    default boolean remove(final IndexExtractor indexExtractor) {
325        Objects.requireNonNull(indexExtractor, "indexExtractor");
326        try {
327            return subtract(CellExtractor.from(indexExtractor.uniqueIndices()));
328        } catch (final IndexOutOfBoundsException e) {
329            throw new IllegalArgumentException(
330                    String.format("Filter only accepts values in the [0,%d) range", getShape().getNumberOfBits()));
331        }
332    }
333
334    /**
335     * Adds the specified CellExtractor to this Bloom filter.
336     *
337     * <p>Specifically
338     * all cells for the indexes identified by the {@code other} will be decremented
339     * by their corresponding values in the {@code other}.</p>
340     *
341     * <p>This method will return true if the filter is valid after the operation.</p>
342     *
343     * @param other The CellExtractor to subtract.
344     * @return {@code true} if the subtraction was successful and the state is valid
345     * @see #isValid()
346     * @see #add(CellExtractor)
347     */
348    boolean subtract(CellExtractor other);
349
350    /**
351     * The default implementation is a no-op since the counting bloom filter returns an unique IndexExtractor by default.
352     *
353     * @return this counting Bloom filter.
354     */
355    @Override
356    default IndexExtractor uniqueIndices() {
357        return this;
358    }
359}