This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git


The following commit(s) were added to refs/heads/master by this push:
     new 5479a7d76 Fix generic typing for BloomFilter.copy()
5479a7d76 is described below

commit 5479a7d765a3c5431eef56d90d9767666f39e89c
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Fri Oct 18 18:04:05 2024 -0400

    Fix generic typing for BloomFilter.copy()
    
    Avoids guaranteed exceptions. For example:
    
    SparseBloomFilter filter = new SimpleBloomFilter(Shape.fromNP(10000,
    0.01)).copy();
    
    After this commit, this type of broken code won't even compile.
---
 .../bloomfilter/ArrayCountingBloomFilter.java      |  5 ++
 .../collections4/bloomfilter/BloomFilter.java      | 27 ++++----
 .../bloomfilter/BloomFilterExtractor.java          |  4 +-
 .../bloomfilter/CountingBloomFilter.java           | 15 ++---
 .../collections4/bloomfilter/LayerManager.java     | 41 ++++++------
 .../bloomfilter/LayeredBloomFilter.java            | 22 ++++---
 .../collections4/bloomfilter/SetOperations.java    |  2 +-
 .../bloomfilter/SimpleBloomFilter.java             | 10 ++-
 .../bloomfilter/SparseBloomFilter.java             |  9 ++-
 .../bloomfilter/WrappedBloomFilter.java            | 19 +++---
 .../AbstractBloomFilterExtractorTest.java          |  4 +-
 .../BitMapExtractorFromWrappedBloomFilterTest.java | 10 +--
 ...mFilterExtractorFromLayeredBloomFilterTest.java |  8 ++-
 .../bloomfilter/DefaultBloomFilterTest.java        | 10 +--
 .../collections4/bloomfilter/LayerManagerTest.java | 54 ++++++++--------
 .../bloomfilter/LayeredBloomFilterTest.java        | 73 +++++++++++-----------
 16 files changed, 171 insertions(+), 142 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/ArrayCountingBloomFilter.java
 
b/src/main/java/org/apache/commons/collections4/bloomfilter/ArrayCountingBloomFilter.java
index 0baa3d247..5e308404e 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/ArrayCountingBloomFilter.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/ArrayCountingBloomFilter.java
@@ -160,6 +160,11 @@ public final class ArrayCountingBloomFilter implements 
CountingBloomFilter {
         return indexExtractor.processIndices(idx -> cells[idx] != 0);
     }
 
+    /**
+     * Creates a new instance of this {@link ArrayCountingBloomFilter} with 
the same properties as the current one.
+     *
+     * @return a copy of this BloomFilter.
+     */
     @Override
     public ArrayCountingBloomFilter copy() {
         return new ArrayCountingBloomFilter(this);
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilter.java 
b/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilter.java
index 0a6e1b38e..6f850c025 100644
--- a/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilter.java
+++ b/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilter.java
@@ -23,11 +23,13 @@ import java.util.Objects;
  * <p>
  * <em>See implementation notes for {@link BitMapExtractor} and {@link 
IndexExtractor}.</em>
  * </p>
+ *
+ * @param <T> The BloomFilter type.
  * @see BitMapExtractor
  * @see IndexExtractor
  * @since 4.5.0
  */
-public interface BloomFilter extends IndexExtractor, BitMapExtractor {
+public interface BloomFilter<T extends BloomFilter<T>> extends IndexExtractor, 
BitMapExtractor {
 
     /**
      * The sparse characteristic used to determine the best method for 
matching.
@@ -84,7 +86,7 @@ public interface BloomFilter extends IndexExtractor, 
BitMapExtractor {
      * @param other the other Bloom filter
      * @return true if all enabled bits in the other filter are enabled in 
this filter.
      */
-    default boolean contains(final BloomFilter other) {
+    default boolean contains(final BloomFilter<?> other) {
         Objects.requireNonNull(other, "other");
         return (characteristics() & SPARSE) != 0 ? contains((IndexExtractor) 
other) : contains((BitMapExtractor) other);
     }
@@ -117,12 +119,11 @@ public interface BloomFilter extends IndexExtractor, 
BitMapExtractor {
     boolean contains(IndexExtractor indexExtractor);
 
     /**
-     * Creates a new instance of the BloomFilter with the same properties as 
the current one.
+     * Creates a new instance of this {@link BloomFilter} with the same 
properties as the current one.
      *
-     * @param <T> Type of BloomFilter.
-     * @return a copy of this BloomFilter
+     * @return a copy of this {@link BloomFilter}.
      */
-    <T extends BloomFilter> T copy();
+    T copy();
 
     // update operations
 
@@ -142,7 +143,7 @@ public interface BloomFilter extends IndexExtractor, 
BitMapExtractor {
      * @see #estimateN()
      * @see Shape
      */
-    default int estimateIntersection(final BloomFilter other) {
+    default int estimateIntersection(final BloomFilter<?> other) {
         Objects.requireNonNull(other, "other");
         final double eThis = getShape().estimateN(cardinality());
         final double eOther = getShape().estimateN(other.cardinality());
@@ -157,7 +158,7 @@ public interface BloomFilter extends IndexExtractor, 
BitMapExtractor {
         } else if (Double.isInfinite(eOther)) {
             estimate = Math.round(eThis);
         } else {
-            final BloomFilter union = this.copy();
+            final T union = this.copy();
             union.merge(other);
             final double eUnion = getShape().estimateN(union.cardinality());
             if (Double.isInfinite(eUnion)) {
@@ -220,11 +221,11 @@ public interface BloomFilter extends IndexExtractor, 
BitMapExtractor {
      * @see #estimateN()
      * @see Shape
      */
-    default int estimateUnion(final BloomFilter other) {
+    default int estimateUnion(final BloomFilter<?> other) {
         Objects.requireNonNull(other, "other");
-        final BloomFilter cpy = this.copy();
-        cpy.merge(other);
-        return cpy.estimateN();
+        final T copy = this.copy();
+        copy.merge(other);
+        return copy.estimateN();
     }
 
     /**
@@ -290,7 +291,7 @@ public interface BloomFilter extends IndexExtractor, 
BitMapExtractor {
      * @param other The bloom filter to merge into this one.
      * @return true if the merge was successful
      */
-    default boolean merge(final BloomFilter other) {
+    default boolean merge(final BloomFilter<?> other) {
         return (characteristics() & SPARSE) != 0 ? merge((IndexExtractor) 
other) : merge((BitMapExtractor) other);
     }
 
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractor.java
 
b/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractor.java
index 004c2e38f..3fcc0026f 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractor.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractor.java
@@ -41,12 +41,14 @@ public interface BloomFilterExtractor {
      * </ul>
      * <p><em>All modifications to the Bloom filters are reflected in the 
original filters</em></p>
      *
+     * @param <T> The BloomFilter type.
      * @param filters The filters to be returned by the extractor.
      * @return THe BloomFilterExtractor containing the filters.
      */
-    static BloomFilterExtractor fromBloomFilterArray(final BloomFilter... 
filters) {
+    static <T extends BloomFilter<T>> BloomFilterExtractor 
fromBloomFilterArray(final BloomFilter<?>... filters) {
         Objects.requireNonNull(filters, "filters");
         return new BloomFilterExtractor() {
+
             /**
              * This implementation returns a copy the original array, the 
contained Bloom filters
              * are references to the originals, any modifications to them are 
reflected in the original
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/CountingBloomFilter.java
 
b/src/main/java/org/apache/commons/collections4/bloomfilter/CountingBloomFilter.java
index 00e8a6231..60f597fbc 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/CountingBloomFilter.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/CountingBloomFilter.java
@@ -54,7 +54,7 @@ import java.util.Objects;
  * @see CellExtractor
  * @since 4.5.0
  */
-public interface CountingBloomFilter extends BloomFilter, CellExtractor {
+public interface CountingBloomFilter extends BloomFilter<CountingBloomFilter>, 
CellExtractor {
 
     // Query Operations
 
@@ -74,13 +74,6 @@ public interface CountingBloomFilter extends BloomFilter, 
CellExtractor {
      */
     boolean add(CellExtractor other);
 
-    /**
-     * Creates a new instance of the CountingBloomFilter with the same 
properties as the current one.
-     * @return a copy of this CountingBloomFilter
-     */
-    @Override
-    CountingBloomFilter copy();
-
     /**
      * Returns the maximum allowable value for a cell count in this Counting 
filter.
      * @return the maximum allowable value for a cell count in this Counting 
filter.
@@ -114,7 +107,7 @@ public interface CountingBloomFilter extends BloomFilter, 
CellExtractor {
      * @param bloomFilter the Bloom filter the check for.
      * @return the maximum number of times the Bloom filter could have been 
inserted.
      */
-    default int getMaxInsert(final BloomFilter bloomFilter) {
+    default int getMaxInsert(final BloomFilter<?> bloomFilter) {
         return getMaxInsert((BitMapExtractor) bloomFilter);
     }
 
@@ -204,7 +197,7 @@ public interface CountingBloomFilter extends BloomFilter, 
CellExtractor {
      * @see #add(CellExtractor)
      */
     @Override
-    default boolean merge(final BloomFilter other) {
+    default boolean merge(final BloomFilter<?> other) {
         Objects.requireNonNull(other, "other");
         return merge((IndexExtractor) other);
     }
@@ -288,7 +281,7 @@ public interface CountingBloomFilter extends BloomFilter, 
CellExtractor {
      * @see #isValid()
      * @see #subtract(CellExtractor)
      */
-    default boolean remove(final BloomFilter other) {
+    default boolean remove(final BloomFilter<?> other) {
         return remove((IndexExtractor) other);
     }
 
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/LayerManager.java 
b/src/main/java/org/apache/commons/collections4/bloomfilter/LayerManager.java
index 110a514d1..2afb352b3 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/LayerManager.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/LayerManager.java
@@ -53,14 +53,14 @@ import java.util.function.Supplier;
  * @param <T> the {@link BloomFilter} type.
  * @since 4.5.0
  */
-public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor {
+public class LayerManager<T extends BloomFilter<T>> implements 
BloomFilterExtractor {
 
     /**
      * Builds new instances of {@link LayerManager}.
      *
      * @param <T> the {@link BloomFilter} type.
      */
-    public static class Builder<T extends BloomFilter> implements 
Supplier<LayerManager<T>> {
+    public static class Builder<T extends BloomFilter<T>> implements 
Supplier<LayerManager<T>> {
 
         private Predicate<LayerManager<T>> extendCheck;
         private Supplier<T> supplier;
@@ -131,7 +131,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
          * @param <T> Type of BloomFilter.
          * @return A Consumer suitable for the LayerManager {@code cleanup} 
parameter.
          */
-        public static <T extends BloomFilter> Consumer<Deque<T>> noCleanup() {
+        public static <T extends BloomFilter<T>> Consumer<Deque<T>> 
noCleanup() {
             return x -> {
                 // empty
             };
@@ -147,7 +147,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
          * @return A Consumer suitable for the LayerManager {@code cleanup} 
parameter.
          * @throws IllegalArgumentException if {@code maxSize <= 0}.
          */
-        public static <T extends BloomFilter> Consumer<Deque<T>> 
onMaxSize(final int maxSize) {
+        public static <T extends BloomFilter<T>> Consumer<Deque<T>> 
onMaxSize(final int maxSize) {
             if (maxSize <= 0) {
                 throw new IllegalArgumentException("'maxSize' must be greater 
than 0");
             }
@@ -165,7 +165,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
          * @param <T> Type of BloomFilter.
          * @return A Consumer suitable for the LayerManager {@code cleanup} 
parameter.
          */
-        public static <T extends BloomFilter> Consumer<Deque<T>> 
removeEmptyTarget() {
+        public static <T extends BloomFilter<T>> Consumer<Deque<T>> 
removeEmptyTarget() {
             return x -> {
                 if (!x.isEmpty() && x.getLast().isEmpty()) {
                     x.removeLast();
@@ -180,7 +180,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
          * @param test Predicate.
          * @return A Consumer suitable for the LayerManager {@code cleanup} 
parameter.
          */
-        public static <T extends BloomFilter> Consumer<Deque<T>> 
removeIf(final Predicate<? super T> test) {
+        public static <T extends BloomFilter<T>> Consumer<Deque<T>> 
removeIf(final Predicate<? super T> test) {
             return x -> x.removeIf(test);
         }
 
@@ -202,7 +202,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
          * @return A Predicate suitable for the LayerManager {@code 
extendCheck} parameter.
          * @throws IllegalArgumentException if {@code breakAt <= 0}
          */
-        public static <T extends BloomFilter> Predicate<LayerManager<T>> 
advanceOnCount(final int breakAt) {
+        public static <T extends BloomFilter<T>> Predicate<LayerManager<T>> 
advanceOnCount(final int breakAt) {
             if (breakAt <= 0) {
                 throw new IllegalArgumentException("'breakAt' must be greater 
than 0");
             }
@@ -226,7 +226,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
          * @param <T> Type of BloomFilter.
          * @return A Predicate suitable for the LayerManager {@code 
extendCheck} parameter.
          */
-        public static <T extends BloomFilter> Predicate<LayerManager<T>> 
advanceOnPopulated() {
+        public static <T extends BloomFilter<T>> Predicate<LayerManager<T>> 
advanceOnPopulated() {
             return lm -> !lm.last().isEmpty();
         }
 
@@ -242,12 +242,12 @@ public class LayerManager<T extends BloomFilter> 
implements BloomFilterExtractor
          * @return A Predicate suitable for the LayerManager {@code 
extendCheck} parameter.
          * @throws IllegalArgumentException if {@code maxN <= 0}
          */
-        public static <T extends BloomFilter> Predicate<LayerManager<T>> 
advanceOnSaturation(final double maxN) {
+        public static <T extends BloomFilter<T>> Predicate<LayerManager<T>> 
advanceOnSaturation(final double maxN) {
             if (maxN <= 0) {
                 throw new IllegalArgumentException("'maxN' must be greater 
than 0");
             }
             return manager -> {
-                final BloomFilter bf = manager.last();
+                final T bf = manager.last();
                 return maxN <= bf.getShape().estimateN(bf.cardinality());
             };
         }
@@ -259,13 +259,14 @@ public class LayerManager<T extends BloomFilter> 
implements BloomFilterExtractor
          * @param <T> Type of BloomFilter.
          * @return A Predicate suitable for the LayerManager {@code 
extendCheck} parameter.
          */
-        public static <T extends BloomFilter> Predicate<LayerManager<T>> 
neverAdvance() {
+        public static <T extends BloomFilter<T>> Predicate<LayerManager<T>> 
neverAdvance() {
             return x -> false;
         }
 
         private ExtendCheck() {
         }
     }
+
     /**
      * Creates a new Builder with defaults of {@code 
ExtendCheck.neverAdvance()} and
      * {@code Cleanup.noCleanup()}.
@@ -275,7 +276,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
      * @see ExtendCheck#neverAdvance()
      * @see Cleanup#noCleanup()
      */
-    public static <T extends BloomFilter> Builder<T> builder() {
+    public static <T extends BloomFilter<T>> Builder<T> builder() {
         return new Builder<>();
     }
 
@@ -337,13 +338,15 @@ public class LayerManager<T extends BloomFilter> 
implements BloomFilterExtractor
     }
 
     /**
-     * Creates a deep copy of this LayerManager.
-     * <p><em>Filters in the copy are deep copies, not references, so changes 
in the copy
-     * are NOT reflected in the original.</em></p>
-     * <p>The {@code filterSupplier}, {@code extendCheck}, and the {@code 
filterCleanup} are shared between
-     * the copy and this instance.</p>
+     * Creates a deep copy of this {@link LayerManager}.
+     * <p>
+     * <em>Filters in the copy are deep copies, not references, so changes in 
the copy are NOT reflected in the original.</em>
+     * </p>
+     * <p>
+     * The {@code filterSupplier}, {@code extendCheck}, and the {@code 
filterCleanup} are shared between the copy and this instance.
+     * </p>
      *
-     * @return a copy of this layer Manager.
+     * @return a copy of this {@link LayerManager}.
      */
     public LayerManager<T> copy() {
         final LayerManager<T> newMgr = new LayerManager<>(filterSupplier, 
extendCheck, filterCleanup, false);
@@ -438,7 +441,7 @@ public class LayerManager<T extends BloomFilter> implements 
BloomFilterExtractor
      */
     @Override
     public boolean processBloomFilters(final Predicate<BloomFilter> 
bloomFilterPredicate) {
-        for (final BloomFilter bf : filters) {
+        for (final T bf : filters) {
             if (!bloomFilterPredicate.test(bf)) {
                 return false;
             }
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilter.java
 
b/src/main/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilter.java
index 9f5e3e4a4..207e0fe73 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilter.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilter.java
@@ -59,10 +59,11 @@ import java.util.function.Predicate;
  * removes them. It also checks it a new layer should be added, and if so adds
  * it and sets the {@code target} before the operation.</li>
  * </ul>
+ *
  * @param <T> The type of Bloom Filter that is used for the layers.
  * @since 4.5.0
  */
-public class LayeredBloomFilter<T extends BloomFilter> implements BloomFilter, 
BloomFilterExtractor {
+public class LayeredBloomFilter<T extends BloomFilter<T>> implements 
BloomFilter<LayeredBloomFilter<T>>, BloomFilterExtractor {
     /**
      * A class used to locate matching filters across all the layers.
      */
@@ -70,9 +71,9 @@ public class LayeredBloomFilter<T extends BloomFilter> 
implements BloomFilter, B
         int[] result = new int[layerManager.getDepth()];
         int bfIdx;
         int resultIdx;
-        BloomFilter bf;
+        BloomFilter<?> bf;
 
-        Finder(final BloomFilter bf) {
+        Finder(final BloomFilter<?> bf) {
             this.bf = bf;
         }
 
@@ -180,6 +181,11 @@ public class LayeredBloomFilter<T extends BloomFilter> 
implements BloomFilter, B
         return contains(createFilter(indexExtractor));
     }
 
+    /**
+     * Creates a new instance of this {@link LayeredBloomFilter} with the same 
properties as the current one.
+     *
+     * @return a copy of this {@link LayeredBloomFilter}.
+     */
     @Override
     public LayeredBloomFilter<T> copy() {
         return new LayeredBloomFilter<>(shape, layerManager.copy());
@@ -191,7 +197,7 @@ public class LayeredBloomFilter<T extends BloomFilter> 
implements BloomFilter, B
      * @param bitMapExtractor the BitMapExtractor to create the filter from.
      * @return the BloomFilter.
      */
-    private BloomFilter createFilter(final BitMapExtractor bitMapExtractor) {
+    private SimpleBloomFilter createFilter(final BitMapExtractor 
bitMapExtractor) {
         final SimpleBloomFilter bf = new SimpleBloomFilter(shape);
         bf.merge(bitMapExtractor);
         return bf;
@@ -203,7 +209,7 @@ public class LayeredBloomFilter<T extends BloomFilter> 
implements BloomFilter, B
      * @param hasher the hasher to create the filter from.
      * @return the BloomFilter.
      */
-    private BloomFilter createFilter(final Hasher hasher) {
+    private SimpleBloomFilter createFilter(final Hasher hasher) {
         final SimpleBloomFilter bf = new SimpleBloomFilter(shape);
         bf.merge(hasher);
         return bf;
@@ -215,7 +221,7 @@ public class LayeredBloomFilter<T extends BloomFilter> 
implements BloomFilter, B
      * @param indexExtractor the IndexExtractor to create the filter from.
      * @return the BloomFilter.
      */
-    private BloomFilter createFilter(final IndexExtractor indexExtractor) {
+    private SimpleBloomFilter createFilter(final IndexExtractor 
indexExtractor) {
         final SimpleBloomFilter bf = new SimpleBloomFilter(shape);
         bf.merge(indexExtractor);
         return bf;
@@ -289,8 +295,8 @@ public class LayeredBloomFilter<T extends BloomFilter> 
implements BloomFilter, B
      * @return the merged bloom filter.
      */
     @Override
-    public BloomFilter flatten() {
-        final BloomFilter bf = new SimpleBloomFilter(shape);
+    public SimpleBloomFilter flatten() {
+        final SimpleBloomFilter bf = new SimpleBloomFilter(shape);
         processBloomFilters(bf::merge);
         return bf;
     }
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/SetOperations.java 
b/src/main/java/org/apache/commons/collections4/bloomfilter/SetOperations.java
index 85040ff45..5226b35b1 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/SetOperations.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/SetOperations.java
@@ -112,7 +112,7 @@ public final class SetOperations {
      * @param second the second Bloom filter.
      * @return the Cosine similarity.
      */
-    public static double cosineSimilarity(final BloomFilter first, final 
BloomFilter second) {
+    public static double cosineSimilarity(final BloomFilter<?> first, final 
BloomFilter<?> second) {
         final int numerator = andCardinality(first, second);
         // Given that the cardinality is an int then the product as a double 
will not
         // overflow, we can use one sqrt:
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/SimpleBloomFilter.java
 
b/src/main/java/org/apache/commons/collections4/bloomfilter/SimpleBloomFilter.java
index 1b646d4a5..f98fceeb8 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/SimpleBloomFilter.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/SimpleBloomFilter.java
@@ -24,9 +24,10 @@ import java.util.function.LongPredicate;
 /**
  * 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.
+ *
  * @since 4.5.0
  */
-public final class SimpleBloomFilter implements BloomFilter {
+public final class SimpleBloomFilter implements BloomFilter<SimpleBloomFilter> 
{
 
     /**
      * The array of bit map longs that defines this Bloom filter. Will be null 
if the filter is empty.
@@ -96,6 +97,11 @@ public final class SimpleBloomFilter implements BloomFilter {
         return indexExtractor.processIndices(idx -> BitMaps.contains(bitMap, 
idx));
     }
 
+    /**
+     * Creates a new instance of this {@link SimpleBloomFilter} with the same 
properties as the current one.
+     *
+     * @return a copy of this {@link SimpleBloomFilter}.
+     */
     @Override
     public SimpleBloomFilter copy() {
         return new SimpleBloomFilter(this);
@@ -140,7 +146,7 @@ public final class SimpleBloomFilter implements BloomFilter 
{
     }
 
     @Override
-    public boolean merge(final BloomFilter other) {
+    public boolean merge(final BloomFilter<?> other) {
         Objects.requireNonNull(other, "other");
         if ((other.characteristics() & SPARSE) != 0) {
             merge((IndexExtractor) other);
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/SparseBloomFilter.java
 
b/src/main/java/org/apache/commons/collections4/bloomfilter/SparseBloomFilter.java
index e53a5a2ff..b7f15ddfa 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/SparseBloomFilter.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/SparseBloomFilter.java
@@ -26,7 +26,7 @@ import java.util.function.LongPredicate;
  * implementation and should work well for most low cardinality Bloom filters.
  * @since 4.5.0
  */
-public final class SparseBloomFilter implements BloomFilter {
+public final class SparseBloomFilter implements BloomFilter<SparseBloomFilter> 
{
 
     /**
      * The bitSet that defines this BloomFilter.
@@ -98,6 +98,11 @@ public final class SparseBloomFilter implements BloomFilter {
         return indexExtractor.processIndices(indices::contains);
     }
 
+    /**
+     * Creates a new instance of this {@link SparseBloomFilter} with the same 
properties as the current one.
+     *
+     * @return a copy of this {@link SparseBloomFilter}.
+     */
     @Override
     public SparseBloomFilter copy() {
         return new SparseBloomFilter(this);
@@ -120,7 +125,7 @@ public final class SparseBloomFilter implements BloomFilter 
{
     }
 
     @Override
-    public boolean merge(final BloomFilter other) {
+    public boolean merge(final BloomFilter<?> other) {
         Objects.requireNonNull(other, "other");
         final IndexExtractor indexExtractor = (other.characteristics() & 
SPARSE) != 0 ? (IndexExtractor) other : 
IndexExtractor.fromBitMapExtractor(other);
         merge(indexExtractor);
diff --git 
a/src/main/java/org/apache/commons/collections4/bloomfilter/WrappedBloomFilter.java
 
b/src/main/java/org/apache/commons/collections4/bloomfilter/WrappedBloomFilter.java
index a03ab0e85..9b3d9b629 100644
--- 
a/src/main/java/org/apache/commons/collections4/bloomfilter/WrappedBloomFilter.java
+++ 
b/src/main/java/org/apache/commons/collections4/bloomfilter/WrappedBloomFilter.java
@@ -22,17 +22,20 @@ import java.util.function.LongPredicate;
 /**
  * An abstract class to assist in implementing Bloom filter decorators.
  *
+ * @param <T> The WrappedBloomFilter type.
+ * @param <W> The <em>wrapped</em> BloomFilter type.
  * @since 4.5.0
  */
-public abstract class WrappedBloomFilter implements BloomFilter {
-    private final BloomFilter wrapped;
+public abstract class WrappedBloomFilter<T extends WrappedBloomFilter<T, W>, W 
extends BloomFilter<W>> implements BloomFilter<T> {
+
+    private final W wrapped;
 
     /**
      * Wraps a Bloom filter.  The wrapped filter is maintained as a reference
      * not a copy.  Changes in one will be reflected in the other.
      * @param wrapped The Bloom filter.
      */
-    public WrappedBloomFilter(final BloomFilter wrapped) {
+    public WrappedBloomFilter(final W wrapped) {
         this.wrapped = wrapped;
     }
 
@@ -67,7 +70,7 @@ public abstract class WrappedBloomFilter implements 
BloomFilter {
     }
 
     @Override
-    public boolean contains(final BloomFilter other) {
+    public boolean contains(final BloomFilter<?> other) {
         return wrapped.contains(other);
     }
 
@@ -82,7 +85,7 @@ public abstract class WrappedBloomFilter implements 
BloomFilter {
     }
 
     @Override
-    public int estimateIntersection(final BloomFilter other) {
+    public int estimateIntersection(final BloomFilter<?> other) {
         return wrapped.estimateIntersection(other);
     }
 
@@ -92,7 +95,7 @@ public abstract class WrappedBloomFilter implements 
BloomFilter {
     }
 
     @Override
-    public int estimateUnion(final BloomFilter other) {
+    public int estimateUnion(final BloomFilter<?> other) {
         return wrapped.estimateUnion(other);
     }
 
@@ -106,7 +109,7 @@ public abstract class WrappedBloomFilter implements 
BloomFilter {
      *
      * @return the wrapped BloomFilter.
      */
-    protected BloomFilter getWrapped() {
+    protected W getWrapped() {
         return wrapped;
     }
 
@@ -121,7 +124,7 @@ public abstract class WrappedBloomFilter implements 
BloomFilter {
     }
 
     @Override
-    public boolean merge(final BloomFilter other) {
+    public boolean merge(final BloomFilter<?> other) {
         return wrapped.merge(other);
     }
 
diff --git 
a/src/test/java/org/apache/commons/collections4/bloomfilter/AbstractBloomFilterExtractorTest.java
 
b/src/test/java/org/apache/commons/collections4/bloomfilter/AbstractBloomFilterExtractorTest.java
index 32c7097c2..4f9112e73 100644
--- 
a/src/test/java/org/apache/commons/collections4/bloomfilter/AbstractBloomFilterExtractorTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bloomfilter/AbstractBloomFilterExtractorTest.java
@@ -29,8 +29,8 @@ import org.junit.jupiter.api.Test;
 public abstract class AbstractBloomFilterExtractorTest {
     private final Shape shape = Shape.fromKM(17, 72);
 
-    BloomFilter one = new SimpleBloomFilter(shape);
-    BloomFilter two = new SimpleBloomFilter(shape);
+    SimpleBloomFilter one = new SimpleBloomFilter(shape);
+    SimpleBloomFilter two = new SimpleBloomFilter(shape);
     int[] nullCount = { 0, 0 };
     int[] equalityCount = { 0 };
     BiPredicate<BloomFilter, BloomFilter> counter = (x, y) -> {
diff --git 
a/src/test/java/org/apache/commons/collections4/bloomfilter/BitMapExtractorFromWrappedBloomFilterTest.java
 
b/src/test/java/org/apache/commons/collections4/bloomfilter/BitMapExtractorFromWrappedBloomFilterTest.java
index 4040efb95..0c04e784b 100644
--- 
a/src/test/java/org/apache/commons/collections4/bloomfilter/BitMapExtractorFromWrappedBloomFilterTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bloomfilter/BitMapExtractorFromWrappedBloomFilterTest.java
@@ -24,8 +24,8 @@ public class BitMapExtractorFromWrappedBloomFilterTest 
extends AbstractBitMapExt
     protected BitMapExtractor createEmptyExtractor() {
         return new WrappedBloomFilter(new 
DefaultBloomFilterTest.SparseDefaultBloomFilter(shape)) {
             @Override
-            public BloomFilter copy() {
-                final BloomFilter result = new 
DefaultBloomFilterTest.SparseDefaultBloomFilter(shape);
+            public DefaultBloomFilterTest.SparseDefaultBloomFilter copy() {
+                final DefaultBloomFilterTest.SparseDefaultBloomFilter result = 
new DefaultBloomFilterTest.SparseDefaultBloomFilter(shape);
                 result.merge(getWrapped());
                 return result;
             }
@@ -35,10 +35,10 @@ public class BitMapExtractorFromWrappedBloomFilterTest 
extends AbstractBitMapExt
     @Override
     protected BitMapExtractor createExtractor() {
         final Hasher hasher = new IncrementingHasher(0, 1);
-        final BloomFilter bf = new WrappedBloomFilter(new 
DefaultBloomFilterTest.SparseDefaultBloomFilter(shape)) {
+        final WrappedBloomFilter bf = new WrappedBloomFilter(new 
DefaultBloomFilterTest.SparseDefaultBloomFilter(shape)) {
             @Override
-            public BloomFilter copy() {
-                final BloomFilter result = new 
DefaultBloomFilterTest.SparseDefaultBloomFilter(shape);
+            public DefaultBloomFilterTest.SparseDefaultBloomFilter copy() {
+                final DefaultBloomFilterTest.SparseDefaultBloomFilter result = 
new DefaultBloomFilterTest.SparseDefaultBloomFilter(shape);
                 result.merge(getWrapped());
                 return result;
             }
diff --git 
a/src/test/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractorFromLayeredBloomFilterTest.java
 
b/src/test/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractorFromLayeredBloomFilterTest.java
index 644dc253a..4d81a49c1 100644
--- 
a/src/test/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractorFromLayeredBloomFilterTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bloomfilter/BloomFilterExtractorFromLayeredBloomFilterTest.java
@@ -24,13 +24,15 @@ public class BloomFilterExtractorFromLayeredBloomFilterTest 
extends AbstractBloo
     @Override
     protected BloomFilterExtractor createUnderTest(final BloomFilter... 
filters) {
         final Builder<SimpleBloomFilter> builder = 
LayerManager.<SimpleBloomFilter>builder();
-        if (!ArrayUtils.isEmpty(filters)) {
+        final BloomFilter bloomFilter0 = ArrayUtils.get(filters, 0);
+        final Shape shape0 = bloomFilter0 != null ? bloomFilter0.getShape() : 
null;
+        if (shape0 != null) {
             // Avoid an NPE in test code and let the domain classes decide 
what to do when there is no supplier set.
-            builder.setSupplier(() -> new 
SimpleBloomFilter(filters[0].getShape()));
+            builder.setSupplier(() -> new SimpleBloomFilter(shape0));
         }
         final LayerManager<SimpleBloomFilter> layerManager = 
builder.setExtendCheck(LayerManager.ExtendCheck.advanceOnPopulated())
                 .setCleanup(LayerManager.Cleanup.noCleanup()).get();
-        final LayeredBloomFilter underTest = new 
LayeredBloomFilter(filters[0].getShape(), layerManager);
+        final LayeredBloomFilter underTest = new LayeredBloomFilter(shape0, 
layerManager);
         for (final BloomFilter bf : filters) {
             underTest.merge(bf);
         }
diff --git 
a/src/test/java/org/apache/commons/collections4/bloomfilter/DefaultBloomFilterTest.java
 
b/src/test/java/org/apache/commons/collections4/bloomfilter/DefaultBloomFilterTest.java
index 081bc0fbc..0ce88a0ea 100644
--- 
a/src/test/java/org/apache/commons/collections4/bloomfilter/DefaultBloomFilterTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bloomfilter/DefaultBloomFilterTest.java
@@ -30,7 +30,9 @@ import org.junit.jupiter.api.Test;
  * Tests for the {@link BloomFilter}.
  */
 public class DefaultBloomFilterTest extends 
AbstractBloomFilterTest<DefaultBloomFilterTest.AbstractDefaultBloomFilter> {
-    abstract static class AbstractDefaultBloomFilter implements BloomFilter {
+
+    abstract static class AbstractDefaultBloomFilter<T extends 
AbstractDefaultBloomFilter<T>> implements BloomFilter<T> {
+
         private final Shape shape;
         protected TreeSet<Integer> indices;
 
@@ -146,7 +148,7 @@ public class DefaultBloomFilterTest extends 
AbstractBloomFilterTest<DefaultBloom
     /**
      * A default implementation of a Sparse bloom filter.
      */
-    public static class SparseDefaultBloomFilter extends 
AbstractDefaultBloomFilter {
+    public static class SparseDefaultBloomFilter extends 
AbstractDefaultBloomFilter<SparseDefaultBloomFilter> {
 
         public SparseDefaultBloomFilter(final Shape shape) {
             super(shape);
@@ -158,8 +160,8 @@ public class DefaultBloomFilterTest extends 
AbstractBloomFilterTest<DefaultBloom
         }
 
         @Override
-        public AbstractDefaultBloomFilter copy() {
-            final AbstractDefaultBloomFilter result = new 
SparseDefaultBloomFilter(getShape());
+        public SparseDefaultBloomFilter copy() {
+            final SparseDefaultBloomFilter result = new 
SparseDefaultBloomFilter(getShape());
             result.indices.addAll(indices);
             return result;
         }
diff --git 
a/src/test/java/org/apache/commons/collections4/bloomfilter/LayerManagerTest.java
 
b/src/test/java/org/apache/commons/collections4/bloomfilter/LayerManagerTest.java
index 9aa60ae1c..9ba06459a 100644
--- 
a/src/test/java/org/apache/commons/collections4/bloomfilter/LayerManagerTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bloomfilter/LayerManagerTest.java
@@ -45,8 +45,8 @@ public class LayerManagerTest {
     @ParameterizedTest
     @ValueSource(ints = {4, 10, 2, 1})
     public void testAdvanceOnCount(final int breakAt) {
-        final Predicate<LayerManager<BloomFilter>> underTest = 
LayerManager.ExtendCheck.advanceOnCount(breakAt);
-        final LayerManager<BloomFilter> layerManager = testingBuilder().get();
+        final Predicate<LayerManager<SimpleBloomFilter>> underTest = 
LayerManager.ExtendCheck.advanceOnCount(breakAt);
+        final LayerManager<SimpleBloomFilter> layerManager = 
testingBuilder().get();
         for (int i = 0; i < breakAt - 1; i++) {
             assertFalse(underTest.test(layerManager), "at " + i);
             layerManager.getTarget().merge(TestingHashers.FROM1);
@@ -62,8 +62,8 @@ public class LayerManagerTest {
 
     @Test
     public void testAdvanceOnPopulated() {
-        final Predicate<LayerManager<BloomFilter>> underTest = 
LayerManager.ExtendCheck.advanceOnPopulated();
-        final LayerManager<BloomFilter> layerManager = testingBuilder().get();
+        final Predicate<LayerManager<SimpleBloomFilter>> underTest = 
LayerManager.ExtendCheck.advanceOnPopulated();
+        final LayerManager<SimpleBloomFilter> layerManager = 
testingBuilder().get();
         assertFalse(underTest.test(layerManager));
         layerManager.getTarget().merge(TestingHashers.FROM1);
         assertTrue(underTest.test(layerManager));
@@ -73,8 +73,8 @@ public class LayerManagerTest {
     public void testAdvanceOnSaturation() {
         final double maxN = shape.estimateMaxN();
         int hashStart = 0;
-        final Predicate<LayerManager<BloomFilter>> underTest = 
LayerManager.ExtendCheck.advanceOnSaturation(maxN);
-        final LayerManager<BloomFilter> layerManager = testingBuilder().get();
+        final Predicate<LayerManager<SimpleBloomFilter>> underTest = 
LayerManager.ExtendCheck.advanceOnSaturation(maxN);
+        final LayerManager<SimpleBloomFilter> layerManager = 
testingBuilder().get();
         while 
(layerManager.getTarget().getShape().estimateN(layerManager.getTarget().cardinality())
 < maxN) {
             assertFalse(underTest.test(layerManager));
             layerManager.getTarget().merge(new IncrementingHasher(hashStart, 
shape.getNumberOfHashFunctions()));
@@ -87,7 +87,7 @@ public class LayerManagerTest {
 
     @Test
     public void testBuilder() {
-        final LayerManager.Builder<BloomFilter> underTest = 
LayerManager.builder();
+        final LayerManager.Builder<SimpleBloomFilter> underTest = 
LayerManager.builder();
         NullPointerException npe = assertThrows(NullPointerException.class, 
underTest::get);
         assertTrue(npe.getMessage().contains("filterSupplier"));
         underTest.setSupplier(() -> null).setCleanup(null);
@@ -105,7 +105,7 @@ public class LayerManagerTest {
 
     @Test
     public void testClear() {
-        final LayerManager<BloomFilter> underTest = 
LayerManager.builder().setSupplier(() -> new SimpleBloomFilter(shape)).get();
+        final LayerManager<SimpleBloomFilter> underTest = 
LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> new 
SimpleBloomFilter(shape)).get();
         underTest.getTarget().merge(TestingHashers.randomHasher());
         underTest.next();
         underTest.getTarget().merge(TestingHashers.randomHasher());
@@ -119,7 +119,7 @@ public class LayerManagerTest {
 
     @Test
     public void testCopy() {
-        final LayerManager<BloomFilter> underTest = 
LayerManager.builder().setSupplier(() -> new SimpleBloomFilter(shape)).get();
+        final LayerManager<SimpleBloomFilter> underTest = 
LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> new 
SimpleBloomFilter(shape)).get();
         underTest.getTarget().merge(TestingHashers.randomHasher());
         underTest.next();
         underTest.getTarget().merge(TestingHashers.randomHasher());
@@ -127,7 +127,7 @@ public class LayerManagerTest {
         underTest.getTarget().merge(TestingHashers.randomHasher());
         assertEquals(3, underTest.getDepth());
 
-        final LayerManager<BloomFilter> copy = underTest.copy();
+        final LayerManager<SimpleBloomFilter> copy = underTest.copy();
         assertNotSame(underTest, copy);
         // object equals not implemented
         assertNotEquals(underTest, copy);
@@ -139,12 +139,12 @@ public class LayerManagerTest {
 
     @Test
     public void testForEachBloomFilter() {
-        final LayerManager<BloomFilter> underTest = 
LayerManager.builder().setSupplier(() -> new SimpleBloomFilter(shape))
+        final LayerManager<SimpleBloomFilter> underTest = 
LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> new 
SimpleBloomFilter(shape))
                 
.setExtendCheck(LayerManager.ExtendCheck.advanceOnPopulated()).get();
 
-        final List<BloomFilter> lst = new ArrayList<>();
+        final List<SimpleBloomFilter> lst = new ArrayList<>();
         for (int i = 0; i < 10; i++) {
-            final BloomFilter bf = new SimpleBloomFilter(shape);
+            final SimpleBloomFilter bf = new SimpleBloomFilter(shape);
             bf.merge(TestingHashers.randomHasher());
             lst.add(bf);
             underTest.getTarget().merge(bf);
@@ -161,21 +161,21 @@ public class LayerManagerTest {
     @Test
     public void testGet() {
         final SimpleBloomFilter f = new SimpleBloomFilter(shape);
-        final LayerManager<BloomFilter> underTest = 
LayerManager.builder().setSupplier(() -> f).get();
+        final LayerManager<SimpleBloomFilter> underTest = 
LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> f).get();
         assertEquals(1, underTest.getDepth());
         assertSame(f, underTest.get(0));
         assertThrows(NoSuchElementException.class, () -> underTest.get(-1));
         assertThrows(NoSuchElementException.class, () -> underTest.get(1));
     }
 
-    private LayerManager.Builder<BloomFilter> testingBuilder() {
-        return LayerManager.builder().setSupplier(() -> new 
SimpleBloomFilter(shape));
+    private LayerManager.Builder<SimpleBloomFilter> testingBuilder() {
+        return LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> new 
SimpleBloomFilter(shape));
     }
 
     @Test
     public void testNeverAdvance() {
-        final Predicate<LayerManager<BloomFilter>> underTest = 
LayerManager.ExtendCheck.neverAdvance();
-        final LayerManager<BloomFilter> layerManager = testingBuilder().get();
+        final Predicate<LayerManager<SimpleBloomFilter>> underTest = 
LayerManager.ExtendCheck.neverAdvance();
+        final LayerManager<SimpleBloomFilter> layerManager = 
testingBuilder().get();
         assertFalse(underTest.test(layerManager));
         for (int i = 0; i < 10; i++) {
             layerManager.getTarget().merge(TestingHashers.randomHasher());
@@ -185,7 +185,7 @@ public class LayerManagerTest {
 
     @Test
     public void testNextAndGetDepth() {
-        final LayerManager<BloomFilter> underTest = 
LayerManager.builder().setSupplier(() -> new SimpleBloomFilter(shape)).get();
+        final LayerManager<SimpleBloomFilter> underTest = 
LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> new 
SimpleBloomFilter(shape)).get();
         assertEquals(1, underTest.getDepth());
         underTest.getTarget().merge(TestingHashers.randomHasher());
         assertEquals(1, underTest.getDepth());
@@ -195,8 +195,8 @@ public class LayerManagerTest {
 
     @Test
     public void testNoCleanup() {
-        final Consumer<Deque<BloomFilter>> underTest = 
LayerManager.Cleanup.noCleanup();
-        final Deque<BloomFilter> list = new LinkedList<>();
+        final Consumer<Deque<SimpleBloomFilter>> underTest = 
LayerManager.Cleanup.noCleanup();
+        final Deque<SimpleBloomFilter> list = new LinkedList<>();
         for (int i = 0; i < 20; i++) {
             assertEquals(i, list.size());
             list.add(new SimpleBloomFilter(shape));
@@ -207,8 +207,8 @@ public class LayerManagerTest {
     @ParameterizedTest
     @ValueSource(ints = {5, 100, 2, 1})
     public void testOnMaxSize(final int maxSize) {
-        final Consumer<Deque<BloomFilter>> underTest = 
LayerManager.Cleanup.onMaxSize(maxSize);
-        final LinkedList<BloomFilter> list = new LinkedList<>();
+        final Consumer<Deque<SimpleBloomFilter>> underTest = 
LayerManager.Cleanup.onMaxSize(maxSize);
+        final LinkedList<SimpleBloomFilter> list = new LinkedList<>();
         for (int i = 0; i < maxSize; i++) {
             assertEquals(i, list.size());
             list.add(new SimpleBloomFilter(shape));
@@ -231,11 +231,11 @@ public class LayerManagerTest {
 
     @Test
     public void testRemoveEmptyTarget() {
-        final Consumer<Deque<BloomFilter>> underTest = 
LayerManager.Cleanup.removeEmptyTarget();
-        final LinkedList<BloomFilter> list = new LinkedList<>();
+        final Consumer<Deque<SimpleBloomFilter>> underTest = 
LayerManager.Cleanup.removeEmptyTarget();
+        final LinkedList<SimpleBloomFilter> list = new LinkedList<>();
 
         // removes an empty filter
-        final BloomFilter bf = new SimpleBloomFilter(shape);
+        final SimpleBloomFilter bf = new SimpleBloomFilter(shape);
         list.add(bf);
         assertEquals(bf, list.get(0));
         underTest.accept(list);
@@ -273,7 +273,7 @@ public class LayerManagerTest {
         final boolean[] extendCheckCalled = { false };
         final boolean[] cleanupCalled = { false };
         final int[] supplierCount = { 0 };
-        final LayerManager<BloomFilter> underTest = 
LayerManager.builder().setSupplier(() -> {
+        final LayerManager<SimpleBloomFilter> underTest = 
LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> {
             supplierCount[0]++;
             return new SimpleBloomFilter(shape);
         }).setExtendCheck(lm -> {
diff --git 
a/src/test/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilterTest.java
 
b/src/test/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilterTest.java
index 270e139d5..c84bf88a0 100644
--- 
a/src/test/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilterTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bloomfilter/LayeredBloomFilterTest.java
@@ -40,7 +40,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
     /**
      * A Predicate that advances after a quantum of time.
      */
-    static class AdvanceOnTimeQuanta implements 
Predicate<LayerManager<TimestampedBloomFilter>> {
+    static class AdvanceOnTimeQuanta<T extends BloomFilter<T>> implements 
Predicate<LayerManager<TimestampedBloomFilter<T>>> {
         Duration quanta;
 
         AdvanceOnTimeQuanta(final Duration quanta) {
@@ -48,7 +48,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
         }
 
         @Override
-        public boolean test(final LayerManager<TimestampedBloomFilter> 
layerManager) {
+        public boolean test(final LayerManager<TimestampedBloomFilter<T>> 
layerManager) {
             // can not use getTarget() as it causes recursion.
             return 
layerManager.last().getTimestamp().plus(quanta).isBefore(Instant.now());
         }
@@ -80,9 +80,11 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
         }
     }
 
-    static class NumberedBloomFilter extends WrappedBloomFilter {
+    static class NumberedBloomFilter extends 
WrappedBloomFilter<NumberedBloomFilter, SimpleBloomFilter> {
+
         int value;
         int sequence;
+
         NumberedBloomFilter(final Shape shape, final int value, final int 
sequence) {
             super(new SimpleBloomFilter(shape));
             this.value = value;
@@ -90,7 +92,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
         }
 
         @Override
-        public BloomFilter copy() {
+        public NumberedBloomFilter copy() {
             return new NumberedBloomFilter(getShape(), value, sequence);
         }
     }
@@ -98,22 +100,22 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
     /**
      * A Bloom filter implementation that tracks the creation time.
      */
-    public static class TimestampedBloomFilter extends WrappedBloomFilter {
+    public static class TimestampedBloomFilter<T extends BloomFilter<T>> 
extends WrappedBloomFilter<TimestampedBloomFilter<T>, T> {
 
         private final Instant timestamp;
 
-        TimestampedBloomFilter(final BloomFilter bf) {
+        TimestampedBloomFilter(final T bf) {
             this(bf, Instant.now());
         }
 
-        TimestampedBloomFilter(final BloomFilter bf, final Instant timestamp) {
+        TimestampedBloomFilter(final T bf, final Instant timestamp) {
             super(bf);
             this.timestamp = timestamp;
         }
 
         @Override
-        public TimestampedBloomFilter copy() {
-            return new TimestampedBloomFilter(getWrapped().copy(), timestamp);
+        public TimestampedBloomFilter<T> copy() {
+            return new TimestampedBloomFilter<>(getWrapped().copy(), 
timestamp);
         }
 
         public Instant getTimestamp() {
@@ -135,11 +137,11 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
      *                 will span at most this much time.
      * @return LayeredBloomFilter with the above properties.
      */
-    static LayeredBloomFilter<TimestampedBloomFilter> 
createTimedLayeredFilter(final Shape shape, final Duration duration, final 
Duration quanta) {
-        final LayerManager.Builder<TimestampedBloomFilter> builder = 
LayerManager.builder();
-        final Consumer<Deque<TimestampedBloomFilter>> cleanup = 
Cleanup.removeEmptyTarget().andThen(new CleanByTime(duration));
-        final LayerManager<TimestampedBloomFilter> layerManager = builder
-                .setSupplier(() -> new TimestampedBloomFilter(new 
SimpleBloomFilter(shape)))
+    static LayeredBloomFilter<TimestampedBloomFilter<SimpleBloomFilter>> 
createTimedLayeredFilter(final Shape shape, final Duration duration, final 
Duration quanta) {
+        final LayerManager.Builder<TimestampedBloomFilter<SimpleBloomFilter>> 
builder = LayerManager.builder();
+        final Consumer<Deque<TimestampedBloomFilter<SimpleBloomFilter>>> 
cleanup = Cleanup.removeEmptyTarget().andThen(new CleanByTime(duration));
+        final LayerManager<TimestampedBloomFilter<SimpleBloomFilter>> 
layerManager = builder
+                .setSupplier(() -> new TimestampedBloomFilter<>(new 
SimpleBloomFilter(shape)))
                 .setCleanup(cleanup)
                 .setExtendCheck(new AdvanceOnTimeQuanta(quanta)
                         
.or(LayerManager.ExtendCheck.advanceOnSaturation(shape.estimateMaxN())))
@@ -156,7 +158,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
      * @param maxDepth The maximum depth of layers.
      * @return An empty layered Bloom filter of the specified shape and depth.
      */
-    public static LayeredBloomFilter<BloomFilter> fixed(final Shape shape, 
final int maxDepth) {
+    public static LayeredBloomFilter<SimpleBloomFilter> fixed(final Shape 
shape, final int maxDepth) {
         return fixed(shape, maxDepth, () -> new SimpleBloomFilter(shape));
     }
 
@@ -170,7 +172,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
      * @param supplier A supplier of the Bloom filters to create layers with.
      * @return An empty layered Bloom filter of the specified shape and depth.
      */
-    public static <T extends BloomFilter> LayeredBloomFilter<T> fixed(final 
Shape shape, final int maxDepth, final Supplier<T> supplier) {
+    public static <T extends BloomFilter<T>> LayeredBloomFilter<T> fixed(final 
Shape shape, final int maxDepth, final Supplier<T> supplier) {
         final LayerManager.Builder<T> builder = LayerManager.builder();
         builder.setExtendCheck(LayerManager.ExtendCheck.advanceOnPopulated())
                 
.setCleanup(LayerManager.Cleanup.onMaxSize(maxDepth)).setSupplier(supplier);
@@ -188,7 +190,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
     // *** end of instrumentation ***
 
     @Override
-    protected LayeredBloomFilter<BloomFilter> createEmptyFilter(final Shape 
shape) {
+    protected LayeredBloomFilter<SimpleBloomFilter> createEmptyFilter(final 
Shape shape) {
         return LayeredBloomFilterTest.fixed(shape, 10);
     }
 
@@ -208,8 +210,8 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
         return makeFilter(IndexExtractor.fromIndexArray(values));
     }
 
-    private LayeredBloomFilter<BloomFilter> setupFindTest() {
-        final LayeredBloomFilter<BloomFilter> filter = 
LayeredBloomFilterTest.fixed(getTestShape(), 10);
+    private LayeredBloomFilter<SimpleBloomFilter> setupFindTest() {
+        final LayeredBloomFilter<SimpleBloomFilter> filter = 
LayeredBloomFilterTest.fixed(getTestShape(), 10);
         filter.merge(TestingHashers.FROM1);
         filter.merge(TestingHashers.FROM11);
         filter.merge(new IncrementingHasher(11, 2));
@@ -220,7 +222,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
     @Override
     @Test
     public void testCardinalityAndIsEmpty() {
-        final LayerManager<BloomFilter> layerManager = 
LayerManager.builder().setExtendCheck(ExtendCheck.neverAdvance())
+        final LayerManager<SimpleBloomFilter> layerManager = 
LayerManager.<SimpleBloomFilter>builder().setExtendCheck(ExtendCheck.neverAdvance())
                 .setSupplier(() -> new 
SimpleBloomFilter(getTestShape())).get();
         testCardinalityAndIsEmpty(new LayeredBloomFilter<>(getTestShape(), 
layerManager));
     }
@@ -230,11 +232,11 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
     @Test
     public void testCleanup() {
         final int[] sequence = {1};
-        final LayerManager layerManager = LayerManager.builder()
+        final LayerManager<NumberedBloomFilter> layerManager = 
LayerManager.<NumberedBloomFilter>builder()
                 .setSupplier(() -> new NumberedBloomFilter(getTestShape(), 3, 
sequence[0]++))
                 .setExtendCheck(ExtendCheck.neverAdvance())
-                .setCleanup(ll -> ll.removeIf(f -> (((NumberedBloomFilter) 
f).value-- == 0))).get();
-        final LayeredBloomFilter underTest = new 
LayeredBloomFilter(getTestShape(), layerManager);
+                .setCleanup(ll -> ll.removeIf(f -> (f.value-- == 0))).get();
+        final LayeredBloomFilter<NumberedBloomFilter> underTest = new 
LayeredBloomFilter<>(getTestShape(), layerManager);
         assertEquals(1, underTest.getDepth());
         underTest.merge(TestingHashers.randomHasher());
         underTest.cleanup(); // first count == 2
@@ -243,19 +245,19 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
         assertEquals(2, underTest.getDepth());
         underTest.merge(TestingHashers.randomHasher());
         underTest.cleanup(); // first count == 0
-        NumberedBloomFilter f = (NumberedBloomFilter) underTest.get(0);
+        NumberedBloomFilter f = underTest.get(0);
         assertEquals(1, f.sequence);
 
         assertEquals(2, underTest.getDepth());
         underTest.cleanup(); // should be removed ; second is now 1st with 
value 1
         assertEquals(1, underTest.getDepth());
-        f = (NumberedBloomFilter) underTest.get(0);
+        f = underTest.get(0);
         assertEquals(2, f.sequence);
 
         underTest.cleanup(); // first count == 0
         underTest.cleanup(); // should be removed.  But there is always at 
least one
         assertEquals(1, underTest.getDepth());
-        f = (NumberedBloomFilter) underTest.get(0);
+        f = underTest.get(0);
         assertEquals(3, f.sequence);  // it is a new one.
     }
     /**
@@ -282,7 +284,8 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
 
         // create a filter that removes filters that are 4 seconds old
         // and quantises time to 1 second intervals.
-        final LayeredBloomFilter<TimestampedBloomFilter> underTest = 
createTimedLayeredFilter(shape, Duration.ofMillis(600), Duration.ofMillis(150));
+        final LayeredBloomFilter<TimestampedBloomFilter<SimpleBloomFilter>> 
underTest = createTimedLayeredFilter(shape, Duration.ofMillis(600),
+                Duration.ofMillis(150));
 
         for (int i = 0; i < 10; i++) {
             underTest.merge(TestingHashers.randomHasher());
@@ -314,7 +317,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
 
     @Test
     public void testFindBitMapExtractor() {
-        final LayeredBloomFilter<BloomFilter> filter = setupFindTest();
+        final LayeredBloomFilter<SimpleBloomFilter> filter = setupFindTest();
 
         IndexExtractor indexExtractor = 
TestingHashers.FROM1.indices(getTestShape());
         BitMapExtractor bitMapExtractor = 
BitMapExtractor.fromIndexExtractor(indexExtractor, 
getTestShape().getNumberOfBits());
@@ -332,7 +335,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
 
     @Test
     public void testFindBloomFilter() {
-        final LayeredBloomFilter<BloomFilter> filter = setupFindTest();
+        final LayeredBloomFilter<SimpleBloomFilter> filter = setupFindTest();
         int[] expected = {0, 3};
         int[] result = filter.find(TestingHashers.FROM1);
         assertArrayEquals(expected, result);
@@ -344,7 +347,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
     @Test
     public void testFindIndexExtractor() {
         IndexExtractor indexExtractor = 
TestingHashers.FROM1.indices(getTestShape());
-        final LayeredBloomFilter<BloomFilter> filter = setupFindTest();
+        final LayeredBloomFilter<SimpleBloomFilter> filter = setupFindTest();
 
         int[] expected = {0, 3};
         int[] result = filter.find(indexExtractor);
@@ -360,7 +363,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
     public final void testGetLayer() {
         final BloomFilter bf = new SimpleBloomFilter(getTestShape());
         bf.merge(TestingHashers.FROM11);
-        final LayeredBloomFilter<BloomFilter> filter = 
LayeredBloomFilterTest.fixed(getTestShape(), 10);
+        final LayeredBloomFilter<SimpleBloomFilter> filter = 
LayeredBloomFilterTest.fixed(getTestShape(), 10);
         filter.merge(TestingHashers.FROM1);
         filter.merge(TestingHashers.FROM11);
         filter.merge(new IncrementingHasher(11, 2));
@@ -370,7 +373,7 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
 
     @Test
     public void testMultipleFilters() {
-        final LayeredBloomFilter<BloomFilter> filter = 
LayeredBloomFilterTest.fixed(getTestShape(), 10);
+        final LayeredBloomFilter<SimpleBloomFilter> filter = 
LayeredBloomFilterTest.fixed(getTestShape(), 10);
         filter.merge(TestingHashers.FROM1);
         filter.merge(TestingHashers.FROM11);
         assertEquals(2, filter.getDepth());
@@ -384,10 +387,8 @@ public class LayeredBloomFilterTest extends 
AbstractBloomFilterTest<LayeredBloom
 
     @Test
     public final void testNext() {
-        final LayerManager<BloomFilter> layerManager = 
LayerManager.builder().setSupplier(() -> new SimpleBloomFilter(getTestShape()))
-                .get();
-
-        final LayeredBloomFilter<BloomFilter> filter = new 
LayeredBloomFilter<>(getTestShape(), layerManager);
+        final LayerManager<SimpleBloomFilter> layerManager = 
LayerManager.<SimpleBloomFilter>builder().setSupplier(() -> new 
SimpleBloomFilter(getTestShape())).get();
+        final LayeredBloomFilter<SimpleBloomFilter> filter = new 
LayeredBloomFilter<>(getTestShape(), layerManager);
         filter.merge(TestingHashers.FROM1);
         filter.merge(TestingHashers.FROM11);
         assertEquals(1, filter.getDepth());

Reply via email to