]> git.basschouten.com Git - openhab-addons.git/blob
26ad3e5a098ca6f4189ffbddf8b18b9e650fbdd8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.deutschebahn.internal;
14
15 import java.text.ParseException;
16 import java.text.SimpleDateFormat;
17 import java.time.ZoneId;
18 import java.time.ZonedDateTime;
19 import java.util.Arrays;
20 import java.util.Date;
21 import java.util.List;
22 import java.util.function.BiConsumer;
23 import java.util.function.Function;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.deutschebahn.internal.timetable.dto.Event;
30 import org.openhab.binding.deutschebahn.internal.timetable.dto.EventStatus;
31 import org.openhab.binding.deutschebahn.internal.timetable.dto.Message;
32 import org.openhab.core.library.types.DateTimeType;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.types.State;
37
38 /**
39  * Selector for the Attribute of an {@link Event}.
40  *
41  * chapter "1.2.11 Event" in Technical Interface Description for external Developers
42  *
43  * @see https://developer.deutschebahn.com/store/apis/info?name=Timetables&version=v1&provider=DBOpenData&#tab1
44  *
45  * @author Sönke Küper - initial contribution
46  *
47  * @param <VALUE_TYPE> type of value in Bean.
48  * @param <STATE_TYPE> type of state.
49  */
50 @NonNullByDefault
51 public final class EventAttribute<VALUE_TYPE, STATE_TYPE extends State>
52         extends AbstractDtoAttributeSelector<Event, @Nullable VALUE_TYPE, STATE_TYPE> {
53
54     /**
55      * Planned Path.
56      */
57     public static final EventAttribute<String, StringType> PPTH = new EventAttribute<>("planned-path", Event::getPpth,
58             Event::setPpth, StringType::new, StringType.class);
59
60     /**
61      * Changed Path.
62      */
63     public static final EventAttribute<String, StringType> CPTH = new EventAttribute<>("changed-path", Event::getCpth,
64             Event::setCpth, StringType::new, StringType.class);
65     /**
66      * Planned platform.
67      */
68     public static final EventAttribute<String, StringType> PP = new EventAttribute<>("planned-platform", Event::getPp,
69             Event::setPp, StringType::new, StringType.class);
70     /**
71      * Changed platform.
72      */
73     public static final EventAttribute<String, StringType> CP = new EventAttribute<>("changed-platform", Event::getCp,
74             Event::setCp, StringType::new, StringType.class);
75     /**
76      * Planned time.
77      */
78     public static final EventAttribute<Date, DateTimeType> PT = new EventAttribute<>("planned-time",
79             getDate(Event::getPt), setDate(Event::setPt), EventAttribute::createDateTimeType, DateTimeType.class);
80     /**
81      * Changed time.
82      */
83     public static final EventAttribute<Date, DateTimeType> CT = new EventAttribute<>("changed-time",
84             getDate(Event::getCt), setDate(Event::setCt), EventAttribute::createDateTimeType, DateTimeType.class);
85     /**
86      * Planned status.
87      */
88     public static final EventAttribute<EventStatus, StringType> PS = new EventAttribute<>("planned-status",
89             Event::getPs, Event::setPs, EventAttribute::fromEventStatus, StringType.class);
90     /**
91      * Changed status.
92      */
93     public static final EventAttribute<EventStatus, StringType> CS = new EventAttribute<>("changed-status",
94             Event::getCs, Event::setCs, EventAttribute::fromEventStatus, StringType.class);
95     /**
96      * Hidden.
97      */
98     public static final EventAttribute<Integer, OnOffType> HI = new EventAttribute<>("hidden", Event::getHi,
99             Event::setHi, EventAttribute::parseHidden, OnOffType.class);
100     /**
101      * Cancellation time.
102      */
103     public static final EventAttribute<Date, DateTimeType> CLT = new EventAttribute<>("cancellation-time",
104             getDate(Event::getClt), setDate(Event::setClt), EventAttribute::createDateTimeType, DateTimeType.class);
105     /**
106      * Wing.
107      */
108     public static final EventAttribute<String, StringType> WINGS = new EventAttribute<>("wings", Event::getWings,
109             Event::setWings, StringType::new, StringType.class);
110     /**
111      * Transition.
112      */
113     public static final EventAttribute<String, StringType> TRA = new EventAttribute<>("transition", Event::getTra,
114             Event::setTra, StringType::new, StringType.class);
115     /**
116      * Planned distant endpoint.
117      */
118     public static final EventAttribute<String, StringType> PDE = new EventAttribute<>("planned-distant-endpoint",
119             Event::getPde, Event::setPde, StringType::new, StringType.class);
120     /**
121      * Changed distant endpoint.
122      */
123     public static final EventAttribute<String, StringType> CDE = new EventAttribute<>("changed-distant-endpoint",
124             Event::getCde, Event::setCde, StringType::new, StringType.class);
125     /**
126      * Distant change.
127      */
128     public static final EventAttribute<Integer, DecimalType> DC = new EventAttribute<>("distant-change", Event::getDc,
129             Event::setDc, DecimalType::new, DecimalType.class);
130     /**
131      * Line.
132      */
133     public static final EventAttribute<String, StringType> L = new EventAttribute<>("line", Event::getL, Event::setL,
134             StringType::new, StringType.class);
135
136     /**
137      * Messages.
138      */
139     public static final EventAttribute<List<Message>, StringType> MESSAGES = new EventAttribute<>("messages",
140             EventAttribute.getMessages(), EventAttribute::setMessages, EventAttribute::mapMessages, StringType.class);
141
142     /**
143      * Planned Start station.
144      */
145     public static final EventAttribute<String, StringType> PLANNED_START_STATION = new EventAttribute<>(
146             "planned-start-station", EventAttribute.getSingleStationFromPath(Event::getPpth, true),
147             EventAttribute.voidSetter(), StringType::new, StringType.class);
148
149     /**
150      * Planned Previous stations.
151      */
152     public static final EventAttribute<String, StringType> PLANNED_PREVIOUS_STATIONS = new EventAttribute<>(
153             "planned-previous-stations", EventAttribute.getIntermediateStationsFromPath(Event::getPpth, true),
154             EventAttribute.voidSetter(), StringType::new, StringType.class);
155
156     /**
157      * Planned Target station.
158      */
159     public static final EventAttribute<String, StringType> PLANNED_TARGET_STATION = new EventAttribute<>(
160             "planned-target-station", EventAttribute.getSingleStationFromPath(Event::getPpth, false),
161             EventAttribute.voidSetter(), StringType::new, StringType.class);
162
163     /**
164      * Planned Following stations.
165      */
166     public static final EventAttribute<String, StringType> PLANNED_FOLLOWING_STATIONS = new EventAttribute<>(
167             "planned-following-stations", EventAttribute.getIntermediateStationsFromPath(Event::getPpth, false),
168             EventAttribute.voidSetter(), StringType::new, StringType.class);
169
170     /**
171      * Changed Start station.
172      */
173     public static final EventAttribute<String, StringType> CHANGED_START_STATION = new EventAttribute<>(
174             "changed-start-station", EventAttribute.getSingleStationFromPath(Event::getCpth, true),
175             EventAttribute.voidSetter(), StringType::new, StringType.class);
176
177     /**
178      * Changed Previous stations.
179      */
180     public static final EventAttribute<String, StringType> CHANGED_PREVIOUS_STATIONS = new EventAttribute<>(
181             "changed-previous-stations", EventAttribute.getIntermediateStationsFromPath(Event::getCpth, true),
182             EventAttribute.voidSetter(), StringType::new, StringType.class);
183
184     /**
185      * Changed Target station.
186      */
187     public static final EventAttribute<String, StringType> CHANGED_TARGET_STATION = new EventAttribute<>(
188             "changed-target-station", EventAttribute.getSingleStationFromPath(Event::getCpth, false),
189             EventAttribute.voidSetter(), StringType::new, StringType.class);
190
191     /**
192      * Changed Following stations.
193      */
194     public static final EventAttribute<String, StringType> CHANGED_FOLLOWING_STATIONS = new EventAttribute<>(
195             "changed-following-stations", EventAttribute.getIntermediateStationsFromPath(Event::getCpth, false),
196             EventAttribute.voidSetter(), StringType::new, StringType.class);
197
198     /**
199      * List containing all known {@link EventAttribute}.
200      */
201     public static final List<EventAttribute<?, ?>> ALL_ATTRIBUTES = Arrays.asList(PPTH, CPTH, PP, CP, PT, CT, PS, CS,
202             HI, CLT, WINGS, TRA, PDE, CDE, DC, L, MESSAGES);
203
204     private static final SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat("yyMMddHHmm");
205
206     /**
207      * Creates an new {@link EventAttribute}.
208      *
209      * @param getter Function to get the raw value.
210      * @param setter Function to set the raw value.
211      * @param getState Function to get the Value as {@link State}.
212      */
213     private EventAttribute(final String channelTypeName, //
214             final Function<Event, @Nullable VALUE_TYPE> getter, //
215             final BiConsumer<Event, VALUE_TYPE> setter, //
216             final Function<VALUE_TYPE, @Nullable STATE_TYPE> getState, //
217             final Class<STATE_TYPE> stateType) {
218         super(channelTypeName, getter, setter, getState, stateType);
219     }
220
221     private static StringType fromEventStatus(final EventStatus value) {
222         return new StringType(value.value());
223     }
224
225     private static OnOffType parseHidden(@Nullable Integer value) {
226         return OnOffType.from(value != null && value == 1);
227     }
228
229     private static Function<Event, @Nullable Date> getDate(final Function<Event, @Nullable String> getValue) {
230         return (final Event event) -> {
231             return parseDate(getValue.apply(event));
232         };
233     }
234
235     private static BiConsumer<Event, Date> setDate(final BiConsumer<Event, String> setter) {
236         return (final Event event, final Date value) -> {
237             synchronized (DATETIME_FORMAT) {
238                 String formattedDate = DATETIME_FORMAT.format(value);
239                 setter.accept(event, formattedDate);
240             }
241         };
242     }
243
244     private static void setMessages(Event event, List<Message> messages) {
245         event.getM().clear();
246         event.getM().addAll(messages);
247     }
248
249     @Nullable
250     private static synchronized Date parseDate(@Nullable final String dateValue) {
251         if ((dateValue == null) || dateValue.isEmpty()) {
252             return null;
253         }
254         try {
255             synchronized (DATETIME_FORMAT) {
256                 return DATETIME_FORMAT.parse(dateValue);
257             }
258         } catch (final ParseException e) {
259             return null;
260         }
261     }
262
263     @Nullable
264     private static DateTimeType createDateTimeType(final @Nullable Date value) {
265         if (value == null) {
266             return null;
267         } else {
268             final ZonedDateTime d = ZonedDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault());
269             return new DateTimeType(d);
270         }
271     }
272
273     /**
274      * Maps the status codes from the messages into status texts.
275      */
276     @Nullable
277     private static StringType mapMessages(final @Nullable List<Message> messages) {
278         if (messages == null || messages.isEmpty()) {
279             return StringType.EMPTY;
280         } else {
281             final String messageTexts = messages //
282                     .stream()//
283                     .filter((Message message) -> message.getC() != null) //
284                     .map(Message::getC) //
285                     .distinct() //
286                     .map(MessageCodes::getMessage) //
287                     .filter((String messageText) -> !messageText.isEmpty()) //
288                     .collect(Collectors.joining(" - "));
289
290             return new StringType(messageTexts);
291         }
292     }
293
294     private static Function<Event, @Nullable List<Message>> getMessages() {
295         return new Function<Event, @Nullable List<Message>>() {
296
297             @Override
298             public @Nullable List<Message> apply(Event t) {
299                 if (t.getM().isEmpty()) {
300                     return null;
301                 } else {
302                     return t.getM();
303                 }
304             }
305         };
306     }
307
308     /**
309      * Returns an single station from an path value (i.e. pipe separated value of stations).
310      * 
311      * @param getPath Getter for the path.
312      * @param returnFirst if <code>true</code> the first value will be returned, <code>false</code> will return the last
313      *            value.
314      */
315     private static Function<Event, @Nullable String> getSingleStationFromPath(
316             final Function<Event, @Nullable String> getPath, boolean returnFirst) {
317         return (final Event event) -> {
318             String path = getPath.apply(event);
319             if (path == null || path.isEmpty()) {
320                 return null;
321             }
322
323             final String[] stations = splitPath(path);
324             if (returnFirst) {
325                 return stations[0];
326             } else {
327                 return stations[stations.length - 1];
328             }
329         };
330     }
331
332     /**
333      * Returns all intermediate stations from an path. The first or last station will be omitted. The values will be
334      * separated by an single dash -.
335      * 
336      * @param getPath Getter for the path.
337      * @param removeFirst if <code>true</code> the first value will be removed, <code>false</code> will remove the last
338      *            value.
339      */
340     private static Function<Event, @Nullable String> getIntermediateStationsFromPath(
341             final Function<Event, @Nullable String> getPath, boolean removeFirst) {
342         return (final Event event) -> {
343             final String path = getPath.apply(event);
344             if (path == null || path.isEmpty()) {
345                 return null;
346             }
347             final String[] stationValues = splitPath(path);
348             Stream<String> stations = Arrays.stream(stationValues);
349             if (removeFirst) {
350                 stations = stations.skip(1);
351             } else {
352                 stations = stations.limit(stationValues.length - 1);
353             }
354             return stations.collect(Collectors.joining(" - "));
355         };
356     }
357
358     /**
359      * Setter that does nothing.
360      * Used for derived attributes that can't be set.
361      */
362     private static <VALUE_TYPE> BiConsumer<Event, VALUE_TYPE> voidSetter() {
363         return new BiConsumer<Event, VALUE_TYPE>() {
364
365             @Override
366             public void accept(Event t, VALUE_TYPE u) {
367             }
368         };
369     }
370
371     private static String[] splitPath(final String path) {
372         return path.split("\\|");
373     }
374
375     /**
376      * Returns an {@link EventAttribute} for the given channel-type and {@link EventType}.
377      */
378     @Nullable
379     public static EventAttribute<?, ?> getByChannelName(final String channelName, EventType eventType) {
380         switch (channelName) {
381             case "planned-path":
382                 return PPTH;
383             case "changed-path":
384                 return CPTH;
385             case "planned-platform":
386                 return PP;
387             case "changed-platform":
388                 return CP;
389             case "planned-time":
390                 return PT;
391             case "changed-time":
392                 return CT;
393             case "planned-status":
394                 return PS;
395             case "changed-status":
396                 return CS;
397             case "hidden":
398                 return HI;
399             case "cancellation-time":
400                 return CLT;
401             case "wings":
402                 return WINGS;
403             case "transition":
404                 return TRA;
405             case "planned-distant-endpoint":
406                 return PDE;
407             case "changed-distant-endpoint":
408                 return CDE;
409             case "distant-change":
410                 return DC;
411             case "line":
412                 return L;
413             case "messages":
414                 return MESSAGES;
415             case "planned-final-station":
416                 return eventType == EventType.ARRIVAL ? PLANNED_START_STATION : PLANNED_TARGET_STATION;
417             case "planned-intermediate-stations":
418                 return eventType == EventType.ARRIVAL ? PLANNED_PREVIOUS_STATIONS : PLANNED_FOLLOWING_STATIONS;
419             case "changed-final-station":
420                 return eventType == EventType.ARRIVAL ? CHANGED_START_STATION : CHANGED_TARGET_STATION;
421             case "changed-intermediate-stations":
422                 return eventType == EventType.ARRIVAL ? CHANGED_PREVIOUS_STATIONS : CHANGED_FOLLOWING_STATIONS;
423             default:
424                 return null;
425         }
426     }
427 }