View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.bloomfilter;
18  
19  import java.util.Arrays;
20  import java.util.Objects;
21  import java.util.function.IntPredicate;
22  import java.util.function.LongPredicate;
23  
24  /**
25   * 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.
26   *
27   * @since 4.5.0-M1
28   */
29  public final class SimpleBloomFilter implements BloomFilter<SimpleBloomFilter> {
30  
31      /**
32       * The array of bit map longs that defines this Bloom filter. Will be null if the filter is empty.
33       */
34      private final long[] bitMap;
35  
36      /**
37       * The Shape of this Bloom filter.
38       */
39      private final Shape shape;
40  
41      /**
42       * The cardinality of this Bloom filter.
43       */
44      private int cardinality;
45  
46      /**
47       * Creates an empty instance.
48       *
49       * @param shape The shape for the filter.
50       */
51      public SimpleBloomFilter(final Shape shape) {
52          Objects.requireNonNull(shape, "shape");
53          this.shape = shape;
54          this.bitMap = BitMaps.newBitMap(shape);
55          this.cardinality = 0;
56      }
57  
58      /**
59       * Copy constructor for {@code copy()} use.
60       *
61       * @param source
62       */
63      private SimpleBloomFilter(final SimpleBloomFilter source) {
64          this.shape = source.shape;
65          this.bitMap = source.bitMap.clone();
66          this.cardinality = source.cardinality;
67      }
68  
69      @Override
70      public long[] asBitMapArray() {
71          return Arrays.copyOf(bitMap, bitMap.length);
72      }
73  
74      @Override
75      public int cardinality() {
76          // Lazy evaluation with caching
77          int c = cardinality;
78          if (c < 0) {
79              cardinality = c = SetOperations.cardinality(this);
80          }
81          return c;
82      }
83  
84      @Override
85      public int characteristics() {
86          return 0;
87      }
88  
89      @Override
90      public void clear() {
91          Arrays.fill(bitMap, 0L);
92          cardinality = 0;
93      }
94  
95      @Override
96      public boolean contains(final IndexExtractor indexExtractor) {
97          return indexExtractor.processIndices(idx -> BitMaps.contains(bitMap, idx));
98      }
99  
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 }