001package silicondust; 002/*- 003 * ########################################################################## 004 * TV H/W, EPGs, and Recording 005 * $Id: ChannelRangeMap.java 5999 2020-05-18 16:41:28Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/silicondust/trunk/src/main/java/silicondust/ChannelRangeMap.java $ 007 * %% 008 * Copyright (C) 2013 - 2020 Allen D. Ball 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * ########################################################################## 022 */ 023import java.io.BufferedReader; 024import java.io.InputStream; 025import java.io.InputStreamReader; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.SortedMap; 030import java.util.SortedSet; 031import java.util.TreeMap; 032import java.util.TreeSet; 033import java.util.stream.IntStream; 034import org.apache.commons.lang3.StringUtils; 035 036import static java.util.stream.Collectors.toCollection; 037import static java.util.stream.Collectors.toMap; 038 039/** 040 * From Silicondust's hdhomerun_channels.c. 041 * 042 * {@include au-bcast} 043 * {@include eu-bcast} 044 * {@include eu-cable} 045 * {@include kr-cable} 046 * {@include us-bcast} 047 * {@include us-cable} 048 * {@include us-hrc} 049 * {@include us-irc} 050 * 051 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 052 * @version $Revision: 5999 $ 053 */ 054public class ChannelRangeMap extends ArrayList<ChannelRangeMap.Range> 055 implements Cloneable { 056 private static final long serialVersionUID = 8711992377286213296L; 057 058 /** 059 * A {@link SortedMap} containing all known {@link ChannelRangeMap}s. 060 */ 061 public static final SortedMap<String,ChannelRangeMap> MAP; 062 063 static { 064 TreeMap<String,ChannelRangeMap> map = 065 new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 066 067 map.put("au-bcast", new Resource("au-bcast")); 068 map.put("eu-bcast", new Resource("eu-bcast")); 069 map.put("eu-cable", new Resource("eu-cable")); 070 map.put("kr-cable", new Resource("kr-cable")); 071 map.put("us-bcast", new Resource("us-bcast")); 072 map.put("us-cable", new Resource("us-cable")); 073 map.put("us-hrc", new Resource("us-hrc")); 074 map.put("us-irc", new Resource("us-irc")); 075 076 map.put("au-cable", new Copy("au-cable", map.get("eu-cable"))); 077 map.put("kr-bcast", new Copy("kr-bcast", map.get("us-bcast"))); 078 map.put("tw-bcast", new Copy("tw-bcast", map.get("us-bcast"))); 079 map.put("tw-cable", new Copy("tw-cable", map.get("us-cable"))); 080 081 MAP = Collections.unmodifiableSortedMap(map); 082 } 083 084 /** 085 * Static factory method to get a channel range map for name. 086 * 087 * @param name The name of the range map. 088 * 089 * @return The {@link ChannelRangeMap} for the name; {@code null} if 090 * none exists. 091 */ 092 public static ChannelRangeMap forName(String name) { 093 return MAP.containsKey(name) ? MAP.get(name).clone() : null; 094 } 095 096 /** @serial */ private final String name; 097 098 /** 099 * Sole constructor. 100 * 101 * @param name The name of the range map. 102 */ 103 protected ChannelRangeMap(String name) { 104 super(); 105 106 this.name = name; 107 } 108 109 /** 110 * Method to get the {@link ChannelRangeMap} name. 111 * 112 * @return The {@link ChannelRangeMap} name. 113 */ 114 public String name() { return name; } 115 116 /** 117 * Method to get the {@link SortedSet} of {@link Channel}s. 118 * 119 * @return The {@link SortedSet} of {@link Channel}s. 120 */ 121 public SortedSet<Channel> channelSet() { 122 SortedSet<Channel> set = new TreeSet<>(frequencyMap().values()); 123 124 return Collections.unmodifiableSortedSet(set); 125 } 126 127 /** 128 * Method to get the {@link SortedMap} of channel numbers to 129 * {@link Channel}s. 130 * 131 * @return The {@link SortedMap} of {@link Channel}s by channel 132 * numbers. 133 */ 134 public SortedMap<Integer,Channel> numberMap() { 135 SortedMap<Integer,Channel> map = 136 frequencyMap().values() 137 .stream() 138 .collect(toMap(Channel::getNumber, v -> v, 139 (t, u) -> t, TreeMap::new)); 140 141 return Collections.unmodifiableSortedMap(map); 142 } 143 144 /** 145 * Method to get the {@link SortedMap} of channel frequencies to 146 * {@link Channel}s. 147 * 148 * @return The {@link SortedMap} of {@link Channel}s by channel 149 * frequencies. 150 */ 151 public SortedMap<Integer,Channel> frequencyMap() { 152 SortedMap<Integer,Channel> map = 153 stream() 154 .flatMap(t -> t.channelSet().stream()) 155 .collect(toMap(Channel::getFrequency, v -> v, 156 (t, u) -> t, TreeMap::new)); 157 158 return Collections.unmodifiableSortedMap(map); 159 } 160 161 @Override 162 public ChannelRangeMap clone() { return (ChannelRangeMap) super.clone(); } 163 164 /** 165 * {@link Range} of {@link Channel}s (numbers and frequencies). 166 */ 167 protected class Range { 168 private final int start; 169 private final int end; 170 private final int frequency; 171 private final int step; 172 173 private Range(String start, String end, 174 String frequency, String step) { 175 try { 176 this.start = Integer.decode(start); 177 this.end = Integer.decode(end); 178 this.frequency = Integer.decode(frequency); 179 this.step = Integer.decode(step); 180 } catch (Exception exception) { 181 throw new ExceptionInInitializerError(exception); 182 } 183 } 184 185 /** 186 * Method to get the {@link Channel}s within a {@link Range}. 187 * 188 * @return The {@link SortedSet} of {@link Channel}s. 189 */ 190 public SortedSet<Channel> channelSet() { 191 SortedSet<Channel> set = 192 IntStream.rangeClosed(start, end) 193 .mapToObj(t -> new Channel(name, t, frequency(t))) 194 .collect(toCollection(TreeSet::new)); 195 196 return set; 197 } 198 199 private int frequency(int number) { 200 return frequency + ((number - start) * step); 201 } 202 203 @Override 204 public String toString() { 205 return (name 206 + ":" + start + "-" + end 207 + "[" + frequency + "/" + step + "]"); 208 } 209 } 210 211 private static class Resource extends ChannelRangeMap { 212 private static final long serialVersionUID = -3965146870869621188L; 213 214 private Resource(String name) { 215 super(name); 216 217 try (BufferedReader reader = new BufferedReaderImpl(name)) { 218 reader.lines() 219 .map(t -> t.split("#")[0].trim()) 220 .filter(StringUtils::isNotEmpty) 221 .map(String::trim) 222 .map(t -> t.split("[\\p{Space}]+", 4)) 223 .forEach(t -> add(new Range(t[0], t[1], t[2], t[3]))); 224 } catch (Exception exception) { 225 throw new ExceptionInInitializerError(exception); 226 } 227 } 228 229 private static class BufferedReaderImpl extends BufferedReader { 230 public BufferedReaderImpl(String name) { 231 this(ChannelRangeMap.class.getResourceAsStream(name)); 232 } 233 234 private BufferedReaderImpl(InputStream in) { 235 super(new InputStreamReader(in)); 236 } 237 238 @Override 239 public String toString() { return getClass().getSimpleName(); } 240 } 241 } 242 243 private static class Copy extends ChannelRangeMap { 244 private static final long serialVersionUID = 3334869309942561559L; 245 246 public Copy(String name, Collection<Range> collection) { 247 super(name); 248 249 if (collection != null) { 250 addAll(collection); 251 } 252 } 253 } 254}