]> git.basschouten.com Git - openhab-addons.git/blob
4f4e24c26dbe5cfe40dbaf4b282a53a40f488874
[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.network.internal.toberemoved.cache;
14
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.concurrent.TimeUnit;
18 import java.util.function.Consumer;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22
23 /**
24  * Complementary class to {@link org.openhab.core.cache.ExpiringCache}, implementing an async variant
25  * of an expiring cache. Returns the cached value immediately to the callback if not expired yet, otherwise issue
26  * a fetch and notify callback implementors asynchronously.
27  *
28  * @author David Graeff - Initial contribution
29  *
30  * @param <V> the type of the cached value
31  */
32 @NonNullByDefault
33 public class ExpiringCacheAsync<V> {
34     private final long expiry;
35     private ExpiringCacheUpdate cacheUpdater;
36     long expiresAt = 0;
37     private boolean refreshRequested = false;
38     private V value;
39     private final List<Consumer<V>> waitingCacheCallbacks = new LinkedList<>();
40
41     /**
42      * Implement the requestCacheUpdate method which will be called when the cache
43      * needs an updated value. Call {@see setValue} to update the cached value.
44      */
45     public static interface ExpiringCacheUpdate {
46         void requestCacheUpdate();
47     }
48
49     /**
50      * Create a new instance.
51      *
52      * @param expiry the duration in milliseconds for how long the value stays valid. Must be greater than 0.
53      * @param cacheUpdater The cache will use this callback if a new value is needed. Must not be null.
54      * @throws IllegalArgumentException For an expire value <=0 or a null cacheUpdater.
55      */
56     public ExpiringCacheAsync(long expiry, @Nullable ExpiringCacheUpdate cacheUpdater) throws IllegalArgumentException {
57         if (expiry <= 0) {
58             throw new IllegalArgumentException("Cache expire time must be greater than 0");
59         }
60         if (cacheUpdater == null) {
61             throw new IllegalArgumentException("A cache updater is necessary");
62         }
63         this.expiry = TimeUnit.MILLISECONDS.toNanos(expiry);
64         this.cacheUpdater = cacheUpdater;
65     }
66
67     /**
68      * Returns the value - possibly from the cache, if it is still valid.
69      *
70      * @return the value
71      */
72     public void getValue(Consumer<V> callback) {
73         if (isExpired()) {
74             refreshValue(callback);
75         } else {
76             callback.accept(value);
77         }
78     }
79
80     /**
81      * Invalidates the value in the cache.
82      */
83     public void invalidateValue() {
84         expiresAt = 0;
85     }
86
87     /**
88      * Updates the cached value with the given one.
89      *
90      * @param newValue The new value. All listeners, registered by getValueAsync() and refreshValue(), will be notified
91      *            of the new value.
92      */
93     public void setValue(V newValue) {
94         refreshRequested = false;
95         value = newValue;
96         expiresAt = getCurrentNanoTime() + expiry;
97         // Inform all callback handlers of the new value and clear the list
98         for (Consumer<V> callback : waitingCacheCallbacks) {
99             callback.accept(value);
100         }
101         waitingCacheCallbacks.clear();
102     }
103
104     /**
105      * Returns an arbitrary time reference in nanoseconds.
106      * This is used for the cache to determine if a value has expired.
107      */
108     public long getCurrentNanoTime() {
109         return System.nanoTime();
110     }
111
112     /**
113      * Refreshes and returns the value asynchronously.
114      *
115      * @return the new value
116      */
117     private void refreshValue(Consumer<V> callback) {
118         waitingCacheCallbacks.add(callback);
119         if (refreshRequested) {
120             return;
121         }
122         refreshRequested = true;
123         expiresAt = 0;
124         cacheUpdater.requestCacheUpdate();
125     }
126
127     /**
128      * Checks if the value is expired.
129      *
130      * @return true if the value is expired
131      */
132     public boolean isExpired() {
133         return expiresAt < getCurrentNanoTime();
134     }
135
136     /**
137      * Return the raw value, no matter if it is already
138      * expired or still valid.
139      */
140     public V getExpiredValue() {
141         return value;
142     }
143 }