1717import android .util .Base64 ;
1818import androidx .annotation .NonNull ;
1919import androidx .annotation .VisibleForTesting ;
20+ import com .google .protobuf .ByteString ;
2021import java .nio .charset .StandardCharsets ;
2122import java .security .MessageDigest ;
2223import java .security .NoSuchAlgorithmException ;
2324
24- public class BloomFilter {
25+ public final class BloomFilter {
2526 private final int bitCount ;
26- private final byte [] bitmap ;
27+ private final ByteString bitmap ;
2728 private final int hashCount ;
2829 private final MessageDigest md5HashMessageDigest ;
2930
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 ) {
3441 if (padding < 0 || padding >= 8 ) {
35- throw new BloomFilterException ("Invalid padding: " + padding );
42+ throw new IllegalArgumentException ("Invalid padding: " + padding );
3643 }
3744 if (hashCount < 0 ) {
38- throw new BloomFilterException ("Invalid hash count: " + hashCount );
45+ throw new IllegalArgumentException ("Invalid hash count: " + hashCount );
3946 }
40- if (bitmap .length > 0 && hashCount == 0 ) {
47+ if (bitmap .size () > 0 && hashCount == 0 ) {
4148 // 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 );
4350 }
44- if (bitmap .length == 0 && padding != 0 ) {
51+ if (bitmap .size () == 0 && padding != 0 ) {
4552 // Empty bloom filter should have 0 padding.
46- throw new BloomFilterException (
53+ throw new IllegalArgumentException (
4754 "Expected padding of 0 when bitmap length is 0, but got " + padding );
4855 }
4956
5057 this .bitmap = bitmap ;
5158 this .hashCount = hashCount ;
52- this .bitCount = bitmap .length * 8 - padding ;
59+ this .bitCount = bitmap .size () * 8 - padding ;
5360 this .md5HashMessageDigest = createMd5HashMessageDigest ();
5461 }
5562
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+
5696 @ VisibleForTesting
5797 int getBitCount () {
5898 return this .bitCount ;
@@ -146,7 +186,7 @@ private static long unsignedRemainder(long dividend, long divisor) {
146186 /** Return whether the bit at the given index in the bitmap is set to 1. */
147187 private boolean isBitSet (int index ) {
148188 // 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 ) ;
150190 int offset = index % 8 ;
151191 return (byteAtIndex & (0x01 << offset )) != 0 ;
152192 }
@@ -159,7 +199,7 @@ public String toString() {
159199 + ", size="
160200 + bitCount
161201 + ", bitmap=\" "
162- + Base64 .encodeToString (bitmap , Base64 .NO_WRAP )
202+ + Base64 .encodeToString (bitmap . toByteArray () , Base64 .NO_WRAP )
163203 + "\" }" ;
164204 }
165205}
0 commit comments