2 * Copyright (c) 2010-2022 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.linky.internal.api;
15 import java.time.LocalDateTime;
16 import java.time.format.DateTimeFormatter;
17 import java.time.temporal.ChronoUnit;
18 import java.util.Optional;
19 import java.util.function.Supplier;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * This is a simple expiring and reloading cache implementation.
29 * There must be provided an action in order to retrieve/calculate the value. This action will be called only if the
30 * answer from the last calculation is not valid anymore, i.e. if it is expired.
32 * The cache expires after the current day; it is possible to shift the beginning time of the day.
34 * Soft Reference is not used to store the cached value because JVM Garbage Collector is clearing it too much often.
36 * @author Laurent Garnier - Initial contribution
38 * @param <V> the type of the value
41 public class ExpiringDayCache<V> {
42 private final Logger logger = LoggerFactory.getLogger(ExpiringDayCache.class);
44 private final String name;
45 private final int beginningHour;
46 private final Supplier<@Nullable V> action;
48 private @Nullable V value;
49 private LocalDateTime expiresAt;
52 * Create a new instance.
54 * @param name the name of this cache
55 * @param beginningHour the hour in the day at which the validity period is starting
56 * @param action the action to retrieve/calculate the value
58 public ExpiringDayCache(String name, int beginningHour, Supplier<@Nullable V> action) {
60 this.beginningHour = beginningHour;
61 this.expiresAt = calcAlreadyExpired();
66 * Returns the value - possibly from the cache, if it is still valid.
68 public synchronized Optional<V> getValue() {
70 V cachedValue = value;
71 if (cachedValue == null || isExpired()) {
72 logger.debug("getValue from cache \"{}\" is requiring a fresh value", name);
73 cachedValue = refreshValue();
75 logger.debug("getValue from cache \"{}\" is returning a cached value", name);
77 return Optional.ofNullable(cachedValue);
81 * Refreshes and returns the value in the cache.
83 * @return the new value
85 public synchronized @Nullable V refreshValue() {
87 expiresAt = calcNextExpiresAt();
92 * Checks if the value is expired.
94 * @return true if the value is expired
96 public boolean isExpired() {
97 return !LocalDateTime.now().isBefore(expiresAt);
100 private LocalDateTime calcNextExpiresAt() {
101 LocalDateTime now = LocalDateTime.now();
102 LocalDateTime limit = now.withHour(beginningHour).truncatedTo(ChronoUnit.HOURS);
103 LocalDateTime result = now.isBefore(limit) ? limit : limit.plusDays(1);
104 logger.debug("calcNextExpiresAt result = {}", result.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
108 private LocalDateTime calcAlreadyExpired() {
109 return LocalDateTime.now().minusDays(1);