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}