1 /*
2 * Copyright 2019-2025 The OSHI Project Contributors
3 * SPDX-License-Identifier: MIT
4 */
5 package oshi.util;
6
7 import java.util.concurrent.TimeUnit;
8 import java.util.function.Supplier;
9
10 import oshi.annotation.concurrent.ThreadSafe;
11
12 /**
13 * A memoized function stores the output corresponding to some set of specific inputs. Subsequent calls with remembered
14 * inputs return the remembered result rather than recalculating it.
15 */
16 @ThreadSafe
17 public final class Memoizer {
18
19 private static final Supplier<Long> DEFAULT_EXPIRATION_NANOS = memoize(Memoizer::queryExpirationConfig,
20 TimeUnit.MINUTES.toNanos(1));
21
22 private Memoizer() {
23 }
24
25 private static long queryExpirationConfig() {
26 return TimeUnit.MILLISECONDS.toNanos(GlobalConfig.get(GlobalConfig.OSHI_UTIL_MEMOIZER_EXPIRATION, 300));
27 }
28
29 public static long installedAppsExpiration() {
30 return TimeUnit.MINUTES.toNanos(1);
31 }
32
33 /**
34 * Default exipiration of memoized values in nanoseconds, which will refresh after this time elapses. Update by
35 * setting {@link GlobalConfig} property <code>oshi.util.memoizer.expiration</code> to a value in milliseconds.
36 *
37 * @return The number of nanoseconds to keep memoized values before refreshing
38 */
39 public static long defaultExpiration() {
40 return DEFAULT_EXPIRATION_NANOS.get();
41 }
42
43 /**
44 * Store a supplier in a delegate function to be computed once, and only again after time to live (ttl) has expired.
45 *
46 * @param <T> The type of object supplied
47 * @param original The {@link java.util.function.Supplier} to memoize
48 * @param ttlNanos Time in nanoseconds to retain calculation. If negative, retain indefinitely.
49 * @return A memoized version of the supplier
50 */
51 public static <T> Supplier<T> memoize(Supplier<T> original, long ttlNanos) {
52 // Adapted from Guava's ExpiringMemoizingSupplier
53 return new Supplier<T>() {
54 private final Supplier<T> delegate = original;
55 private volatile T value; // NOSONAR squid:S3077
56 private volatile long expirationNanos;
57
58 @Override
59 public T get() {
60 long nanos = expirationNanos;
61 long now = System.nanoTime();
62 if (nanos == 0 || (ttlNanos >= 0 && now - nanos >= 0)) {
63 synchronized (this) {
64 if (nanos == expirationNanos) { // recheck for lost race
65 T t = delegate.get();
66 value = t;
67 nanos = now + ttlNanos;
68 expirationNanos = (nanos == 0) ? 1 : nanos;
69 return t;
70 }
71 }
72 }
73 return value;
74 }
75 };
76 }
77
78 /**
79 * Store a supplier in a delegate function to be computed only once.
80 *
81 * @param <T> The type of object supplied
82 * @param original The {@link java.util.function.Supplier} to memoize
83 * @return A memoized version of the supplier
84 */
85 public static <T> Supplier<T> memoize(Supplier<T> original) {
86 return memoize(original, -1L);
87 }
88 }