]> git.basschouten.com Git - openhab-addons.git/blob
a5593eecace40845ad084073a34731d318cd816f
[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 future A future that completes as soon as all children have their added-action performed.
71      * @param childIDs The list of IDs that should be in the map. Everything else currently in the map will be removed.
72      * @param addedAction A function where the newly added child is given as an argument to perform any actions on it.
73      *            A future is expected as a return value that completes as soon as said action is performed.
74      * @param supplyNewChild A function where the ID of a new child is given and the created child is
75      *            expected as a
76      *            result.
77      * @param removedCallback A callback, that is called whenever a child got removed by the
78      *            {@link #apply(CompletableFuture, String[], Function)} method.
79      * @return Complete successfully if all "addedAction" complete successfully, otherwise complete exceptionally.
80      */
81     public CompletableFuture<@Nullable Void> apply(String[] childIDs,
82             final Function<T, CompletableFuture<Void>> addedAction, final Function<String, T> supplyNewChild,
83             final Consumer<T> removedCallback) {
84         Set<String> arrayValues = Stream.of(childIDs).collect(Collectors.toSet());
85
86         // Add all entries to the map, that are not in there yet.
87         final Map<String, T> newSubnodes = arrayValues.stream().filter(entry -> !this.map.containsKey(entry))
88                 .collect(Collectors.toMap(k -> k, supplyNewChild));
89         this.map.putAll(newSubnodes);
90
91         // Remove any entries that are not listed in the 'childIDs'.
92         this.map.entrySet().removeIf(entry -> {
93             if (!arrayValues.contains(entry.getKey())) {
94                 removedCallback.accept(entry.getValue());
95                 return true;
96             }
97             return false;
98         });
99
100         // Apply the 'addedAction' function for all new entries.
101         return CompletableFuture
102                 .allOf(newSubnodes.values().stream().map(addedAction).toArray(CompletableFuture[]::new));
103     }
104
105     /**
106      * Return the size of this map.
107      */
108     public int size() {
109         return map.size();
110     }
111
112     /**
113      * Get the item with the given id
114      *
115      * @param key The id
116      * @return The item
117      */
118     public T get(@Nullable String key) {
119         return map.get(key);
120     }
121
122     /**
123      * Clear the map
124      */
125     public void clear() {
126         map.clear();
127     }
128
129     /**
130      * Use this method only to restore a child from configuration.
131      *
132      * @param key The ID
133      * @param value The subnode object
134      */
135     public void put(String key, T value) {
136         map.put(key, value);
137     }
138 }