FeedEntryPicker.java

package net.sasasin.sreader.service.probe;

import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import net.sasasin.sreader.domain.FeedEntrySelection;
import org.springframework.stereotype.Service;

@Service
public class FeedEntryPicker {

  public Optional<SyndEntry> pick(SyndFeed feed, FeedEntrySelection selection) {
    return pick(feed, selection, true);
  }

  public Optional<SyndEntry> pick(
      SyndFeed feed, FeedEntrySelection selection, boolean requireLink) {
    Objects.requireNonNull(selection, "selection must not be null");
    if (feed == null || feed.getEntries() == null || feed.getEntries().isEmpty()) {
      return Optional.empty();
    }
    List<SyndEntry> entries = feed.getEntries();

    return switch (selection) {
      case FeedEntrySelection.First() -> pickFirst(entries, requireLink);
      case FeedEntrySelection.Latest() -> pickLatest(entries, requireLink);
      case FeedEntrySelection.ByIndex(int index) -> pickByIndex(entries, index, requireLink);
      case FeedEntrySelection.ByTitleRegex(String regex) ->
          pickByTitleRegex(entries, regex, requireLink);
      case FeedEntrySelection.ByUrlRegex(String regex) -> pickByUrlRegex(entries, regex);
    };
  }

  private Optional<SyndEntry> pickFirst(List<SyndEntry> entries, boolean requireLink) {
    return entries.stream().filter(e -> canPick(e, requireLink)).findFirst();
  }

  private Optional<SyndEntry> pickLatest(List<SyndEntry> entries, boolean requireLink) {
    Optional<SyndEntry> dated =
        entries.stream()
            .filter(e -> canPick(e, requireLink))
            .filter(e -> effectiveDate(e) != null)
            .max(Comparator.comparing(this::effectiveDate));
    if (dated.isPresent()) {
      return dated;
    }
    return pickFirst(entries, requireLink);
  }

  private Optional<SyndEntry> pickByIndex(List<SyndEntry> entries, int index, boolean requireLink) {
    if (index < 0 || index >= entries.size()) {
      return Optional.empty();
    }
    SyndEntry e = entries.get(index);
    return canPick(e, requireLink) ? Optional.of(e) : Optional.empty();
  }

  private Optional<SyndEntry> pickByTitleRegex(
      List<SyndEntry> entries, String regex, boolean requireLink) {
    return pickByRegex(entries, regex, requireLink, SyndEntry::getTitle);
  }

  private Optional<SyndEntry> pickByUrlRegex(List<SyndEntry> entries, String regex) {
    return pickByRegex(entries, regex, true, SyndEntry::getLink);
  }

  private Optional<SyndEntry> pickByRegex(
      List<SyndEntry> entries,
      String regex,
      boolean requireLink,
      java.util.function.Function<SyndEntry, String> matchTarget) {
    Pattern pattern = Pattern.compile(regex);
    return entries.stream()
        .filter(e -> canPick(e, requireLink))
        .filter(
            e -> {
              String target = matchTarget.apply(e);
              return target != null && pattern.matcher(target).find();
            })
        .findFirst();
  }

  private boolean canPick(SyndEntry e, boolean requireLink) {
    return e != null && (!requireLink || hasLink(e));
  }

  private boolean hasLink(SyndEntry e) {
    return e != null && e.getLink() != null && !e.getLink().isBlank();
  }

  private Date effectiveDate(SyndEntry e) {
    if (e.getPublishedDate() != null) {
      return e.getPublishedDate();
    }
    return e.getUpdatedDate();
  }
}