public final void extract(@NotNull Path outputDir) throws IOException {
    openStream();
    try {
      Deque<Path> extractedPaths = new ArrayDeque<>();

      // we'd like to keep a contact to invoke filter once per entry
      // since it was something implicit, and the introduction of
      // retry breaks the contract
      boolean proceedToNext = true;

      Entry entry = null;
      while (!proceedToNext || (entry = nextEntry()) != null) {
        if (proceedToNext && myFilter != null && !myFilter.test(entry)) {
          continue;
        }

        proceedToNext = true; // will be set to false if EH returns RETRY
        try {
          Path processedEntry = processEntry(outputDir, entry);
          if (processedEntry != null) {
            extractedPaths.push(processedEntry);
          }
        }
        catch (IOException ioException) {
          if (myIgnoreIOExceptions) {
            LOG.debug("Skipped exception because "  + ErrorHandlerChoice.SKIP_ALL + " was selected earlier", ioException);
          } else {
            switch (myErrorHandler.apply(entry, ioException)) {
              case ABORT:
                while (!extractedPaths.isEmpty()) {
                  Files.delete(extractedPaths.pop());
                }
                return;
              case BAIL_OUT:
                throw ioException;
              case RETRY:
                proceedToNext = false;
                break;
              case SKIP:
                LOG.debug("Skipped exception", ioException);
                break;
              case SKIP_ALL:
                myIgnoreIOExceptions = true;
                LOG.debug("SKIP_ALL is selected", ioException);
            }
          }
        }
      }
    }
    finally {
      closeStream();
    }
  }
public final void extract(@NotNull Path outputDir)