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