001package net.filebot.web; 002 003import java.util.List; 004import java.util.Objects; 005import java.util.function.BiFunction; 006import java.util.function.Function; 007import java.util.stream.Stream; 008 009public class MappedEpisode extends Episode { 010 011 protected Episode original; 012 protected Episode mapping; 013 014 public MappedEpisode() { 015 // used by deserializer 016 } 017 018 public MappedEpisode(Episode original, Episode mapping) { 019 super(original); 020 this.original = unmap(original, MappedEpisode::getOriginal); 021 this.mapping = unmap(mapping, MappedEpisode::getMapping); 022 } 023 024 public Episode getOriginal() { 025 return original; 026 } 027 028 public Episode getMapping() { 029 return mapping; 030 } 031 032 private <T> T getFirst(Function<Episode, T> getter) { 033 T value = getter.apply(mapping); 034 if (value != null) { 035 return value; 036 } 037 return getter.apply(original); 038 } 039 040 @Override 041 public String getSeriesName() { 042 return getFirst(Episode::getSeriesName); 043 } 044 045 @Override 046 public Integer getEpisode() { 047 return mapping.getEpisode(); // always use mapped episode number 048 } 049 050 @Override 051 public Integer getSeason() { 052 return mapping.getSeason(); // always use mapped season number 053 } 054 055 @Override 056 public String getTitle() { 057 return mapping.getTitle(); // always use mapped episode title 058 } 059 060 @Override 061 public Integer getAbsolute() { 062 return mapping.getAbsolute(); // always use mapped absolute number 063 } 064 065 @Override 066 public Integer getSpecial() { 067 return mapping.getSpecial(); // always use mapped special number 068 } 069 070 @Override 071 public SimpleDate getAirdate() { 072 return mapping.getAirdate(); // always use mapped episode airdate 073 } 074 075 @Override 076 public Integer getId() { 077 return getFirst(Episode::getId); 078 } 079 080 @Override 081 public List<String> getSeriesNames() { 082 return getFirst(Episode::getSeriesNames); 083 } 084 085 @Override 086 public boolean equals(Object obj) { 087 return super.equals(obj); // use original episode object for episode comparison 088 } 089 090 @Override 091 public int hashCode() { 092 return super.hashCode(); // use original episode object for episode comparison 093 } 094 095 @Override 096 public MappedEpisode clone() { 097 return new MappedEpisode(original, mapping); 098 } 099 100 @Override 101 public MappedEpisode derive(Integer season, Integer episode) { 102 return new MappedEpisode(original, mapping.derive(season, episode)); 103 } 104 105 @Override 106 public MappedEpisode derive(String seriesName, Integer season, Integer episode, String title, Integer absolute, SimpleDate airdate) { 107 return new MappedEpisode(original, mapping.derive(seriesName, season, episode, title, absolute, airdate)); 108 } 109 110 public MappedEpisode reverse() { 111 return new MappedEpisode(mapping, original); 112 } 113 114 public MappedEpisode flatten() { 115 return new MappedEpisode(mapping, mapping); 116 } 117 118 @Override 119 public String toString() { 120 return mapping + " => " + original; 121 } 122 123 public static Stream<Episode> generate(Episode original, Episode[] mapping, BiFunction<Episode, Episode, Episode> wrapper) { 124 // use null as identity mapping 125 if (mapping == null) { 126 return Stream.of(original); 127 } 128 129 // deduplicate by object identity 130 for (int i = 0; i < mapping.length; i++) { 131 for (int c = i + 1; c < mapping.length; c++) { 132 if (mapping[i] == mapping[c]) { 133 mapping[c] = null; 134 } 135 } 136 } 137 138 return Stream.of(mapping).filter(Objects::nonNull).map(m -> { 139 // ignore identity mapping 140 if (original == m) { 141 return original; 142 } 143 return wrapper.apply(original, m); 144 }); 145 } 146 147 public static Episode map(Episode original, Episode mapping) { 148 // pass through already mapped episode objects (e.g. reverse() or flatten() usage) 149 if (mapping instanceof MappedEpisode) { 150 return mapping; 151 } 152 return new MappedEpisode(original, mapping); 153 } 154 155 public static Episode unmap(Episode episode, Function<MappedEpisode, Episode> original) { 156 if (episode instanceof MappedEpisode) { 157 MappedEpisode mapping = (MappedEpisode) episode; 158 episode = original.apply(mapping); 159 } 160 return episode; 161 } 162 163}