code: public <ReturnType> CompletableFuture<ReturnType> addWithFilter(Predicate<KeyType> keyFilter, Supplier<CompletableFuture<? extends ReturnType>> toRun) {
          CompletableFuture<ReturnType> result = new CompletableFuture<>();
          ArrayList<CompletableFuture<?>> existingTasks = new ArrayList<>();
          synchronized (this.queue) {
              Exceptions.checkNotClosed(this.closed, this);
              // Collect all currently executing tasks for the given keys.
              for (val e : this.queue.entrySet()) {
                  if (keyFilter.test(e.getKey())) {
                      existingTasks.add(e.getValue());
                  }
              }
  
              executeAfterIfNeeded(existingTasks, toRun, result);
  
              // Record the action.
              this.filterQueue.put(keyFilter, result);
          }
  
          executeNowIfNeeded(existingTasks, toRun, result);
  
          // Cleanup: if this was the last task in the queue, then clean up the queue.
          result.whenComplete((r, ex) -> cleanupFilter(keyFilter));
          return result;
      }
docs: Queues up a new task to execute, subject to the dependency keys that match the given filter.
  
  This task will not begin execution until all previous tasks whose keys match the given filter have finished.
  In addition, no subsequent task for any key that matches the given filter will begin executing until this task has
  finished executing.
  
  @param keyFilter    A Predicate defining the filter that determines which keys this task will depend on.
  @param toRun        A Supplier that will be invoked when it is this task's turn to run. It will return a
  CompletableFuture that will complete when this task completes.
  @param <ReturnType> Return type.
  @return A CompletableFuture that will complete with the result from the CompletableFuture returned by toRun,
  when toRun completes executing.