001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2023 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License.  You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.function;
015
016import java.time.Duration;
017
018import java.util.function.Supplier;
019
020/**
021 * A utility class containing useful operations for {@link Supplier}s.
022 *
023 * @author <a href="https://about.me/lairdnelson/" target="_top">Laird Nelson</a>
024 */
025public final class Suppliers {
026
027  /**
028   * Creates a new {@link Suppliers}.
029   */
030  private Suppliers() {
031    super();
032  }
033
034  /**
035   * <a href="https://en.wikipedia.org/wiki/Memoization" target="_top"><em>Memoizes</em></a> the supplied {@link
036   * Supplier} and returns the memoization.
037   *
038   * <p>The memoization will not call the supplied {@link Supplier}'s {@link Supplier#get() get()} method until its own
039   * {@link Supplier#get() get()} method is called.</p>
040   *
041   * @param <R> the return type of the supplied {@link Supplier}
042   *
043   * @param s the {@link Supplier} to memoize; must not be {@code null}
044   *
045   * @return a memoized version of the supplied {@link Supplier}; never {@code null}
046   *
047   * @exception NullPointerException if {@code s} is {@code null}
048   */
049  @SuppressWarnings("unchecked")
050  public static final <R> Supplier<R> memoize(final Supplier<? extends R> s) {
051    if (s.getClass().getEnclosingClass() == Suppliers.class && s.getClass().isAnonymousClass()) {
052      // Already memoized.
053      return (Supplier<R>)s;
054    }
055    return new Supplier<>() {
056      private Supplier<R> d = this::compute; // deliberately not final; no need for volatile; construction semantics
057      private boolean initialized; // deliberately not final; no need for volatile; always accessed under lock
058      private final synchronized R compute() {
059        if (!this.initialized) {
060          final R r = s.get();
061          this.d = () -> r; // memoization
062          this.initialized = true;
063        }
064        return this.d.get();
065      }
066      @Override
067      public final R get() {
068        return this.d.get(); // no need for volatile; d is either synchronized or "immutable"
069      }
070    };
071  }
072
073  /**
074   * <a href="https://en.wikipedia.org/wiki/Memoization" target="_top"><em>Memoizes</em></a> the supplied {@link
075   * Supplier} for a time equal to that represented by the supplied {@link Duration} and returns the memoization.
076   *
077   * <p>The memoization will not call the supplied {@link Supplier}'s {@link Supplier#get() get()} method until its own
078   * {@link Supplier#get() get()} method is called.</p>
079   *
080   * @param <R> the return type of the supplied {@link Supplier}
081   *
082   * @param s the {@link Supplier} to memoize; must not be {@code null}
083   *
084   * @param d the {@link Duration} expressing how long the memoization will survive; must not be {@code null}
085   *
086   * @return a memoized version of the supplied {@link Supplier}; never {@code null}
087   *
088   * @exception NullPointerException if either argument is {@code null}
089   *
090   * @see #memoize(Supplier, long)
091   */
092  public static final <R> Supplier<R> memoize(final Supplier<? extends R> s, final Duration d) {
093    return memoize(s, d.toNanos());
094  }
095
096  /**
097   * <a href="https://en.wikipedia.org/wiki/Memoization" target="_top"><em>Memoizes</em></a> the supplied {@link
098   * Supplier} for a time equal to that represented by the supplied number of nanoseconds and returns the memoization.
099   *
100   * <p>The memoization will not call the supplied {@link Supplier}'s {@link Supplier#get() get()} method until its own
101   * {@link Supplier#get() get()} method is called.</p>
102   *
103   * @param <R> the return type of the supplied {@link Supplier}
104   *
105   * @param s the {@link Supplier} to memoize; must not be {@code null}
106   *
107   * @param durationNanos the duration, in nanoseconds, expressing how long the memoization will survive
108   *
109   * @return a memoized version of the supplied {@link Supplier}; never {@code null}
110   *
111   * @exception NullPointerException if either argument is {@code null}
112   */
113  public static final <R> Supplier<R> memoize(final Supplier<? extends R> s, final long durationNanos) {
114    return new Supplier<>() {
115      private volatile long expiresAt;
116      private volatile R r;
117      @Override
118      public final R get() {
119        final long expiresAt = this.expiresAt; // volatile read
120        final long now = System.nanoTime();
121        if (expiresAt == 0L || now - expiresAt >= 0L) {
122          // first time or expiration
123          synchronized (this) {
124            // double-checked locking
125            if (expiresAt == this.expiresAt) { // volatile read
126              this.r = s.get(); // volatile write; memoization
127              this.expiresAt = now + durationNanos; // volatile write
128            }
129          }
130        }
131        return this.r; // volatile read
132      }
133    };
134  }
135
136}