2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.network.internal.toberemoved.cache;
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.concurrent.TimeUnit;
18 import java.util.function.Consumer;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
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.
28 * @author David Graeff - Initial contribution
30 * @param <V> the type of the cached value
33 public class ExpiringCacheAsync<V> {
34 private final long expiry;
35 private ExpiringCacheUpdate cacheUpdater;
37 private boolean refreshRequested = false;
39 private final List<Consumer<V>> waitingCacheCallbacks = new LinkedList<>();
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.
45 public static interface ExpiringCacheUpdate {
46 void requestCacheUpdate();
50 * Create a new instance.
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 {@literal <=0} or a null cacheUpdater.
56 public ExpiringCacheAsync(long expiry, @Nullable ExpiringCacheUpdate cacheUpdater) throws IllegalArgumentException {
58 throw new IllegalArgumentException("Cache expire time must be greater than 0");
60 if (cacheUpdater == null) {
61 throw new IllegalArgumentException("A cache updater is necessary");
63 this.expiry = TimeUnit.MILLISECONDS.toNanos(expiry);
64 this.cacheUpdater = cacheUpdater;
68 * Returns the value - possibly from the cache, if it is still valid.
70 * @param callback callback to return the value
72 public void getValue(Consumer<V> callback) {
74 refreshValue(callback);
76 callback.accept(value);
81 * Invalidates the value in the cache.
83 public void invalidateValue() {
88 * Updates the cached value with the given one.
90 * @param newValue The new value. All listeners, registered by getValueAsync() and refreshValue(), will be notified
93 public void setValue(V newValue) {
94 refreshRequested = false;
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);
101 waitingCacheCallbacks.clear();
105 * Returns an arbitrary time reference in nanoseconds.
106 * This is used for the cache to determine if a value has expired.
108 public long getCurrentNanoTime() {
109 return System.nanoTime();
113 * Refreshes and returns the value asynchronously.
115 * @return the new value
117 private void refreshValue(Consumer<V> callback) {
118 waitingCacheCallbacks.add(callback);
119 if (refreshRequested) {
122 refreshRequested = true;
124 cacheUpdater.requestCacheUpdate();
128 * Checks if the value is expired.
130 * @return true if the value is expired
132 public boolean isExpired() {
133 return expiresAt < getCurrentNanoTime();
137 * Return the raw value, no matter if it is already
138 * expired or still valid.
140 public V getExpiredValue() {