public PatchFileCreator.ApplicationResult apply(ZipFile patchFile,
                                                  File rootDir,
                                                  File backupDir,
                                                  Map<String, ValidationResult.Option> options,
                                                  UpdaterUI ui) throws IOException {
    File toDir = toBaseDir(rootDir);
    List<PatchAction> actionsToApply = new ArrayList<>(myActions.size());

    try {
      for (PatchAction each : myActions) {
        ui.checkCancelled();
        if (each.shouldApply(toDir, options)) {
          actionsToApply.add(each);
        }
      }

      if (actionsToApply.isEmpty()) {
        LOG.info("nothing to apply");
        return new PatchFileCreator.ApplicationResult(false, List.of());
      }

      if (backupDir != null) {
        File _backupDir = backupDir;
        forEach(actionsToApply, UpdaterUI.message("backing.up.files"), ui, action -> action.backup(toDir, _backupDir));
      }
      else {
        @SuppressWarnings({"SSBasedInspection", "RedundantSuppression"})
        List<PatchAction> specialActions = actionsToApply.stream().filter(PatchAction::mandatoryBackup).collect(Collectors.toList());
        if (!specialActions.isEmpty()) {
          backupDir = Utils.getTempFile("partial_backup");
          if (!backupDir.mkdir()) throw new IOException("Cannot create a backup directory: " + backupDir);
          File _backupDir = backupDir;
          forEach(specialActions, UpdaterUI.message("preparing.update"), ui, action -> action.backup(toDir, _backupDir));
        }
      }
    }
    catch (OperationCancelledException e) {
      LOG.log(Level.WARNING, "cancelled", e);
      return new PatchFileCreator.ApplicationResult(false, List.of());
    }

    List<PatchAction> appliedActions = new ArrayList<>(actionsToApply.size());

    try {
      File _backupDir = backupDir;
      forEach(actionsToApply, UpdaterUI.message("applying.patch"), ui, action -> {
        if (action instanceof CreateAction && !new File(toDir, action.getPath()).getParentFile().exists()) {
          LOG.info("Create action: " + action.getPath() + " skipped. The parent directory is absent.");
        }
        else if (action instanceof UpdateAction && !new File(toDir, action.getPath()).getParentFile().exists() && !action.isCritical()) {
          LOG.info("Update action: " + action.getPath() + " skipped. The parent directory is absent.");
        }
        else {
          appliedActions.add(action);
          action.apply(patchFile, _backupDir, toDir);
        }
      });
    }
    catch (OperationCancelledException e) {
      LOG.log(Level.WARNING, "cancelled", e);
      return new PatchFileCreator.ApplicationResult(false, appliedActions);
    }
    catch (Throwable t) {
      LOG.log(Level.SEVERE, "apply failed", t);
      return new PatchFileCreator.ApplicationResult(false, appliedActions, t);
    }

    try {
      // on macOS, we need to update the bundle timestamp to reset Info.plist caches
      Files.setLastModifiedTime(toDir.toPath(), FileTime.from(Instant.now()));
    }
    catch (IOException e) {
      LOG.log(Level.WARNING, "setLastModified: " + toDir, e);
    }

    return new PatchFileCreator.ApplicationResult(true, appliedActions);
  }
public PatchFileCreator.ApplicationResult apply(ZipFile patchFile,
                                                  File rootDir,
                                                  File backupDir,
                                                  Map<String, ValidationResult.Option> options,
                                                  UpdaterUI ui)