17
17
import android .util .Base64 ;
18
18
import androidx .annotation .NonNull ;
19
19
import androidx .annotation .VisibleForTesting ;
20
+ import com .google .protobuf .ByteString ;
20
21
import java .nio .charset .StandardCharsets ;
21
22
import java .security .MessageDigest ;
22
23
import java .security .NoSuchAlgorithmException ;
23
24
24
- public class BloomFilter {
25
+ public final class BloomFilter {
25
26
private final int bitCount ;
26
- private final byte [] bitmap ;
27
+ private final ByteString bitmap ;
27
28
private final int hashCount ;
28
29
private final MessageDigest md5HashMessageDigest ;
29
30
30
- public BloomFilter (@ NonNull byte [] bitmap , int padding , int hashCount ) {
31
- if (bitmap == null ) {
32
- throw new NullPointerException ("Bitmap cannot be null." );
33
- }
31
+ /**
32
+ * Creates a new {@link BloomFilter} with the given parameters.
33
+ *
34
+ * @param bitmap the bitmap of the bloom filter; must not be null.
35
+ * @param padding the padding, in bits, of the last byte of the bloom filter; must be between 0
36
+ * (zero) and 7, inclusive; must be 0 (zero) if {@code bitmap.length==0}.
37
+ * @param hashCount The number of hash functions to use; must be strictly greater than zero; may
38
+ * be 0 (zero) if and only if {@code bitmap.length==0}.
39
+ */
40
+ public BloomFilter (@ NonNull ByteString bitmap , int padding , int hashCount ) {
34
41
if (padding < 0 || padding >= 8 ) {
35
- throw new BloomFilterException ("Invalid padding: " + padding );
42
+ throw new IllegalArgumentException ("Invalid padding: " + padding );
36
43
}
37
44
if (hashCount < 0 ) {
38
- throw new BloomFilterException ("Invalid hash count: " + hashCount );
45
+ throw new IllegalArgumentException ("Invalid hash count: " + hashCount );
39
46
}
40
- if (bitmap .length > 0 && hashCount == 0 ) {
47
+ if (bitmap .size () > 0 && hashCount == 0 ) {
41
48
// Only empty bloom filter can have 0 hash count.
42
- throw new BloomFilterException ("Invalid hash count: " + hashCount );
49
+ throw new IllegalArgumentException ("Invalid hash count: " + hashCount );
43
50
}
44
- if (bitmap .length == 0 && padding != 0 ) {
51
+ if (bitmap .size () == 0 && padding != 0 ) {
45
52
// Empty bloom filter should have 0 padding.
46
- throw new BloomFilterException (
53
+ throw new IllegalArgumentException (
47
54
"Expected padding of 0 when bitmap length is 0, but got " + padding );
48
55
}
49
56
50
57
this .bitmap = bitmap ;
51
58
this .hashCount = hashCount ;
52
- this .bitCount = bitmap .length * 8 - padding ;
59
+ this .bitCount = bitmap .size () * 8 - padding ;
53
60
this .md5HashMessageDigest = createMd5HashMessageDigest ();
54
61
}
55
62
63
+ /**
64
+ * Creates an instance of {@link BloomFilter} with the given arguments, throwing a well-defined
65
+ * exception if the given arguments do not satisfy the requirements documented in the {@link
66
+ * BloomFilter} constructor.
67
+ */
68
+ public static BloomFilter create (@ NonNull ByteString bitmap , int padding , int hashCount )
69
+ throws BloomFilterCreateException {
70
+ if (padding < 0 || padding >= 8 ) {
71
+ throw new BloomFilterCreateException ("Invalid padding: " + padding );
72
+ }
73
+ if (hashCount < 0 ) {
74
+ throw new BloomFilterCreateException ("Invalid hash count: " + hashCount );
75
+ }
76
+ if (bitmap .size () > 0 && hashCount == 0 ) {
77
+ // Only empty bloom filter can have 0 hash count.
78
+ throw new BloomFilterCreateException ("Invalid hash count: " + hashCount );
79
+ }
80
+ if (bitmap .size () == 0 && padding != 0 ) {
81
+ // Empty bloom filter should have 0 padding.
82
+ throw new BloomFilterCreateException (
83
+ "Expected padding of 0 when bitmap length is 0, but got " + padding );
84
+ }
85
+
86
+ return new BloomFilter (bitmap , padding , hashCount );
87
+ }
88
+
89
+ /** Exception thrown by {@link #create} if the given arguments are not valid. */
90
+ public static final class BloomFilterCreateException extends Exception {
91
+ public BloomFilterCreateException (String message ) {
92
+ super (message );
93
+ }
94
+ }
95
+
56
96
@ VisibleForTesting
57
97
int getBitCount () {
58
98
return this .bitCount ;
@@ -146,7 +186,7 @@ private static long unsignedRemainder(long dividend, long divisor) {
146
186
/** Return whether the bit at the given index in the bitmap is set to 1. */
147
187
private boolean isBitSet (int index ) {
148
188
// To retrieve bit n, calculate: (bitmap[n / 8] & (0x01 << (n % 8))).
149
- byte byteAtIndex = this .bitmap [ index / 8 ] ;
189
+ byte byteAtIndex = this .bitmap . byteAt ( index / 8 ) ;
150
190
int offset = index % 8 ;
151
191
return (byteAtIndex & (0x01 << offset )) != 0 ;
152
192
}
@@ -159,7 +199,7 @@ public String toString() {
159
199
+ ", size="
160
200
+ bitCount
161
201
+ ", bitmap=\" "
162
- + Base64 .encodeToString (bitmap , Base64 .NO_WRAP )
202
+ + Base64 .encodeToString (bitmap . toByteArray () , Base64 .NO_WRAP )
163
203
+ "\" }" ;
164
204
}
165
205
}
0 commit comments