View Javadoc
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  }