]> git.basschouten.com Git - openhab-addons.git/blob
51cffdca38db0ff7cdf6747cee24d34318d0b79c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.mqtt.generic.tools;
14
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.TreeMap;
18 import java.util.concurrent.CompletableFuture;
19 import java.util.function.Consumer;
20 import java.util.function.Function;
21 import java.util.stream.Collectors;
22 import java.util.stream.Stream;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26
27 /**
28  * <p>
29  * In some MQTT conventions there are topics dedicated to list further subtopics.
30  * We need to watch those topics and maintain observable lists for those children.
31  * </p>
32  *
33  * <p>
34  * This class consists of a mapping ID->subtopic. Apply an array of subtopics
35  * via the {@link #apply(String[], Function, Function, Consumer)} method.
36  * </p>
37  *
38  * <p>
39  * Restore children from configuration by using {@link #put(String, Object)}.
40  * </p>
41  *
42  * For example in homie 3.x these topics are meant to be watched:
43  *
44  * <pre>
45  * * homie/mydevice/$nodes
46  * * homie/mydevice/mynode/$properties
47  * </pre>
48  *
49  * <p>
50  * An example value of "homie/mydevice/$nodes" could be "lamp1,lamp2,switch", which means there are
51  * "homie/mydevice/lamp1","homie/mydevice/lamp2" and "homie/mydevice/switch" existing and this map
52  * would contain 3 entries [lamp1->Node, lamp2->Node, switch->Node].
53  * </p>
54  *
55  * @author David Graeff - Initial contribution
56  *
57  * @param <T> Any object
58  */
59 @NonNullByDefault
60 public class ChildMap<T> {
61     protected Map<String, T> map = new TreeMap<>();
62
63     public Stream<T> stream() {
64         return map.values().stream();
65     }
66
67     /**
68      * Modifies the map in way that it matches the entries of the given childIDs.
69      *
70      * @param childIDs The list of IDs that should be in the map. Everything else currently in the map will be removed.
71      * @param addedAction A function where the newly added child is given as an argument to perform any actions on it.
72      *            A future is expected as a return value that completes as soon as said action is performed.
73      * @param supplyNewChild A function where the ID of a new child is given and the created child is
74      *            expected as a
75      *            result.
76      * @param removedCallback A callback, that is called whenever a child got removed by the
77      *            {@link #apply(String[], Function, Function, Consumer)} method.
78      * @return Complete successfully if all "addedAction" complete successfully, otherwise complete exceptionally.
79      */
80     public CompletableFuture<@Nullable Void> apply(String[] childIDs,
81             final Function<T, CompletableFuture<Void>> addedAction, final Function<String, T> supplyNewChild,
82             final Consumer<T> removedCallback) {
83         Set<String> arrayValues = Stream.of(childIDs).collect(Collectors.toSet());
84
85         // Add all entries to the map, that are not in there yet.
86         final Map<String, T> newSubnodes = arrayValues.stream().filter(entry -> !this.map.containsKey(entry))
87                 .collect(Collectors.toMap(k -> k, supplyNewChild));
88         this.map.putAll(newSubnodes);
89
90         // Remove any entries that are not listed in the 'childIDs'.
91         this.map.entrySet().removeIf(entry -> {
92             if (!arrayValues.contains(entry.getKey())) {
93                 removedCallback.accept(entry.getValue());
94                 return true;
95             }
96             return false;
97         });
98
99         // Apply the 'addedAction' function for all new entries.
100         return CompletableFuture
101                 .allOf(newSubnodes.values().stream().map(addedAction).toArray(CompletableFuture[]::new));
102     }
103
104     /**
105      * Return the size of this map.
106      */
107     public int size() {
108         return map.size();
109     }
110
111     /**
112      * Get the item with the given id
113      *
114      * @param key The id
115      * @return The item
116      */
117     public T get(@Nullable String key) {
118         return map.get(key);
119     }
120
121     /**
122      * Clear the map
123      */
124     public void clear() {
125         map.clear();
126     }
127
128     /**
129      * Use this method only to restore a child from configuration.
130      *
131      * @param key The ID
132      * @param value The subnode object
133      */
134     public void put(String key, T value) {
135         map.put(key, value);
136     }
137 }