FeedEntryPicker.java

package net.sasasin.sreader.service;

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.Optional;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
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) {
    if (feed == null || feed.getEntries() == null || feed.getEntries().isEmpty()) {
      return Optional.empty();
    }
    List<SyndEntry> entries = feed.getEntries();

    return switch (selection.kind()) {
      case FIRST -> pickFirst(entries, requireLink);
      case LATEST -> pickLatest(entries, requireLink);
      case INDEX ->
          pickByIndex(entries, selection.index() != null ? selection.index() : 0, requireLink);
      case TITLE_REGEX ->
          pickByRegex(
              entries,
              selection.titleRegex() != null ? selection.titleRegex() : "",
              true,
              requireLink);
      case URL_REGEX ->
          pickByRegex(
              entries, selection.urlRegex() != null ? selection.urlRegex() : "", false, true);
    };
  }

  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> pickByRegex(
      List<SyndEntry> entries, String regex, boolean useTitle, boolean requireLink) {
    if (regex == null || regex.isBlank()) {
      return Optional.empty();
    }
    Pattern pattern;
    try {
      pattern = Pattern.compile(regex);
    } catch (PatternSyntaxException e) {
      return Optional.empty();
    }
    return entries.stream()
        .filter(e -> canPick(e, requireLink))
        .filter(
            e -> {
              String target = useTitle ? e.getTitle() : e.getLink();
              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();
  }
}