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 *      http://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.Arrays;
020import java.util.Objects;
021import java.util.function.IntPredicate;
022import java.util.function.LongPredicate;
023
024/**
025 * A bloom filter using an array of bit maps to track enabled bits. This is a standard
026 * implementation and should work well for most Bloom filters.
027 * @since 4.5
028 */
029public final class SimpleBloomFilter implements BloomFilter {
030
031    /**
032     * The array of bit map longs that defines this Bloom filter. Will be null if the filter is empty.
033     */
034    private final long[] bitMap;
035
036    /**
037     * The Shape of this Bloom filter.
038     */
039    private final Shape shape;
040
041    /**
042     * The cardinality of this Bloom filter.
043     */
044    private int cardinality;
045
046    /**
047     * Creates an empty instance.
048     *
049     * @param shape The shape for the filter.
050     */
051    public SimpleBloomFilter(final Shape shape) {
052        Objects.requireNonNull(shape, "shape");
053        this.shape = shape;
054        this.bitMap = new long[BitMaps.numberOfBitMaps(shape.getNumberOfBits())];
055        this.cardinality = 0;
056    }
057
058    /**
059     * Copy constructor for {@code copy()} use.
060     * @param source
061     */
062    private SimpleBloomFilter(final SimpleBloomFilter source) {
063        this.shape = source.shape;
064        this.bitMap = source.bitMap.clone();
065        this.cardinality = source.cardinality;
066    }
067
068    @Override
069    public long[] asBitMapArray() {
070        return Arrays.copyOf(bitMap, bitMap.length);
071    }
072
073    @Override
074    public int cardinality() {
075        // Lazy evaluation with caching
076        int c = cardinality;
077        if (c < 0) {
078            cardinality = c = SetOperations.cardinality(this);
079        }
080        return c;
081    }
082
083    @Override
084    public int characteristics() {
085        return 0;
086    }
087
088    @Override
089    public void clear() {
090        Arrays.fill(bitMap, 0L);
091        cardinality = 0;
092    }
093
094    @Override
095    public boolean contains(final IndexExtractor indexExtractor) {
096        return indexExtractor.processIndices(idx -> BitMaps.contains(bitMap, idx));
097    }
098
099    @Override
100    public SimpleBloomFilter copy() {
101        return new SimpleBloomFilter(this);
102    }
103
104    @Override
105    public boolean processBitMaps(final LongPredicate consumer) {
106        Objects.requireNonNull(consumer, "consumer");
107        for (final long l : bitMap) {
108            if (!consumer.test(l)) {
109                return false;
110            }
111        }
112        return true;
113    }
114
115    @Override
116    public boolean processBitMapPairs(final BitMapExtractor other, final LongBiPredicate func) {
117        final CountingLongPredicate p = new CountingLongPredicate(bitMap, func);
118        return other.processBitMaps(p) && p.processRemaining();
119    }
120
121    @Override
122    public boolean processIndices(final IntPredicate consumer) {
123        Objects.requireNonNull(consumer, "consumer");
124        return IndexExtractor.fromBitMapExtractor(this).processIndices(consumer);
125    }
126
127    @Override
128    public Shape getShape() {
129        return shape;
130    }
131
132    @Override
133    public boolean isEmpty() {
134        return cardinality == 0 || processBitMaps(y -> y == 0);
135    }
136
137    @Override
138    public boolean merge(final BitMapExtractor bitMapExtractor) {
139        Objects.requireNonNull(bitMapExtractor, "bitMapExtractor");
140        try {
141            final int[] idx = new int[1];
142            bitMapExtractor.processBitMaps(value -> {
143                bitMap[idx[0]++] |= value;
144                return true;
145            });
146            // idx[0] will be limit+1 so decrement it
147            idx[0]--;
148            final int idxLimit = BitMaps.getLongIndex(shape.getNumberOfBits());
149            if (idxLimit == idx[0]) {
150                final long excess = bitMap[idxLimit] >> shape.getNumberOfBits();
151                if (excess != 0) {
152                    throw new IllegalArgumentException(
153                            String.format("BitMapExtractor set a bit higher than the limit for the shape: %s",
154                                    shape.getNumberOfBits()));
155                }
156            }
157            cardinality = -1;
158        } catch (final IndexOutOfBoundsException e) {
159            throw new IllegalArgumentException(
160                    String.format("BitMapExtractor should send at most %s maps", bitMap.length), e);
161        }
162        return true;
163    }
164
165    @Override
166    public boolean merge(final BloomFilter other) {
167        Objects.requireNonNull(other, "other");
168        if ((other.characteristics() & SPARSE) != 0) {
169            merge((IndexExtractor) other);
170        } else {
171            merge((BitMapExtractor) other);
172        }
173        return true;
174    }
175
176    @Override
177    public boolean merge(final Hasher hasher) {
178        Objects.requireNonNull(hasher, "hasher");
179        return merge(hasher.indices(shape));
180    }
181
182    @Override
183    public boolean merge(final IndexExtractor indexExtractor) {
184        Objects.requireNonNull(indexExtractor, "indexExtractor");
185        indexExtractor.processIndices(idx -> {
186            if (idx < 0 || idx >= shape.getNumberOfBits()) {
187                throw new IllegalArgumentException(String.format(
188                        "IndexExtractor should only send values in the range[0,%s)", shape.getNumberOfBits()));
189            }
190            BitMaps.set(bitMap, idx);
191            return true;
192        });
193        cardinality = -1;
194        return true;
195    }
196}