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}