Annotate Filter interface with @NotNull
[rhynodge.git] / src / main / java / net / pterodactylus / rhynodge / filters / EpisodeFilter.java
index 91fe098..6aa3e1f 100644 (file)
 
 package net.pterodactylus.rhynodge.filters;
 
+import static com.google.common.base.Optional.absent;
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Arrays.asList;
 
-import java.util.LinkedHashMap;
+import java.util.Collection;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -31,6 +34,13 @@ import net.pterodactylus.rhynodge.states.FailedState;
 import net.pterodactylus.rhynodge.states.TorrentState;
 import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
 
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+
 /**
  * {@link Filter} implementation that extracts {@link Episode} information from
  * the {@link TorrentFile}s contained in a {@link TorrentState}.
@@ -39,8 +49,10 @@ import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
  */
 public class EpisodeFilter implements Filter {
 
+       private static final Logger logger = Logger.getLogger(EpisodeFilter.class);
+
        /** The pattern to parse episode information from the filename. */
-       private static Pattern episodePattern = Pattern.compile("S(\\d{2})E(\\d{2})|[^\\d](\\d{1,2})x(\\d{2})[^\\d]");
+       private static final Collection<Pattern> episodePatterns = asList(Pattern.compile("[Ss](\\d{2})[Ee](\\d{2})"), Pattern.compile("[^\\d](\\d{1,2})x(\\d{2})[^\\d]"));
 
        //
        // FILTER METHODS
@@ -49,26 +61,25 @@ public class EpisodeFilter implements Filter {
        /**
         * {@inheritDoc}
         */
+       @NotNull
        @Override
-       public State filter(State state) {
+       public State filter(@NotNull State state) {
                if (!state.success()) {
                        return FailedState.from(state);
                }
                checkState(state instanceof TorrentState, "state is not a TorrentState but a %s!", state.getClass());
 
                TorrentState torrentState = (TorrentState) state;
-               LinkedHashMap<Episode, Episode> episodes = new LinkedHashMap<Episode, Episode>();
+               final Multimap<Episode, TorrentFile> episodes = HashMultimap.create();
                for (TorrentFile torrentFile : torrentState) {
-                       Episode episode = extractEpisode(torrentFile);
-                       if (episode == null) {
+                       Optional<Episode> episode = extractEpisode(torrentFile);
+                       if (!episode.isPresent()) {
                                continue;
                        }
-                       episodes.put(episode, episode);
-                       episode = episodes.get(episode);
-                       episode.addTorrentFile(torrentFile);
+                       episodes.put(episode.get(), torrentFile);
                }
 
-               return new EpisodeState(episodes.values());
+               return new EpisodeState(from(episodes.keySet()).transform(episodeFiller(episodes)).toSet());
        }
 
        //
@@ -76,27 +87,49 @@ public class EpisodeFilter implements Filter {
        //
 
        /**
+        * Returns a function that creates an {@link Episode} that contains all {@link
+        * TorrentFile}s.
+        *
+        * @param episodeTorrents
+        *              A multimap mapping episodes to torrent files.
+        * @return The function that performs the extraction of torrent files
+        */
+       private static Function<Episode, Episode> episodeFiller(final Multimap<Episode, TorrentFile> episodeTorrents) {
+               return new Function<Episode, Episode>() {
+                       @Override
+                       public Episode apply(Episode episode) {
+                               Episode completeEpisode = new Episode(episode.season(), episode.episode());
+                               for (TorrentFile torrentFile : episodeTorrents.get(episode)) {
+                                       completeEpisode.addTorrentFile(torrentFile);
+                               }
+                               return completeEpisode;
+                       }
+               };
+       }
+
+       /**
         * Extracts episode information from the given torrent file.
         *
         * @param torrentFile
-        *            The torrent file to extract the episode information from
-        * @return The extracted episode information, or {@code null} if no episode
-        *         information could be found
+        *              The torrent file to extract the episode information from
+        * @return The extracted episode information, or {@link Optional#absent()} if
+        *         no episode information could be found
         */
-       private static Episode extractEpisode(TorrentFile torrentFile) {
-               Matcher matcher = episodePattern.matcher(torrentFile.name());
-               if (!matcher.find()) {
-                       return null;
-               }
-               String seasonString = matcher.group(1);
-               String episodeString = matcher.group(2);
-               if ((seasonString == null) && (episodeString == null)) {
-                       seasonString = matcher.group(3);
-                       episodeString = matcher.group(4);
+       private static Optional<Episode> extractEpisode(TorrentFile torrentFile) {
+               logger.debug(String.format("Extracting episode from %s...", torrentFile));
+               for (Pattern episodePattern : episodePatterns) {
+                       Matcher matcher = episodePattern.matcher(torrentFile.name());
+                       if (!matcher.find() || matcher.groupCount() < 2) {
+                               continue;
+                       }
+                       String seasonString = matcher.group(1);
+                       String episodeString = matcher.group(2);
+                       logger.debug(String.format("Parsing %s and %s as season and episode...", seasonString, episodeString));
+                       int season = Integer.valueOf(seasonString);
+                       int episode = Integer.valueOf(episodeString);
+                       return Optional.of(new Episode(season, episode));
                }
-               int season = Integer.valueOf(seasonString);
-               int episode = Integer.valueOf(episodeString);
-               return new Episode(season, episode);
+               return absent();
        }
 
 }