Skip to content

Commit

Permalink
(feat) MemoizingSupplier.isMemoized
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-pelykh committed Jan 8, 2025
1 parent d1a3cd5 commit 56ac6ff
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,16 @@ static class SerializableThrowingSupplier extends ThrowingSupplier implements Se
private static final long serialVersionUID = 0L;
}

static void checkMemoize(CountingSupplier countingSupplier, Supplier<Integer> memoizedSupplier) {
static void checkMemoize(CountingSupplier countingSupplier,
Suppliers.MemoizingSupplier<Integer> memoizedSupplier) {
// the underlying supplier hasn't executed yet
assertEquals(0, countingSupplier.calls);

assertFalse(memoizedSupplier.isMemoized());
assertEquals(10, (int) memoizedSupplier.get());

// now it has
assertEquals(1, countingSupplier.calls);

assertTrue(memoizedSupplier.isMemoized());
assertEquals(10, (int) memoizedSupplier.get());

// it still should only have executed once due to memoization
Expand All @@ -102,7 +103,7 @@ public void testMemoize() {
}

private void memoizeTest(CountingSupplier countingSupplier) {
Supplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
Suppliers.MemoizingSupplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
checkMemoize(countingSupplier, memoizedSupplier);
}

Expand Down Expand Up @@ -139,7 +140,7 @@ private void memoizeExceptionThrownTest(ThrowingSupplier throwingSupplier) {
@GwtIncompatible // SerializableTester
public void testMemoizeNonSerializable() throws Exception {
CountingSupplier countingSupplier = new CountingSupplier();
Supplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
Suppliers.MemoizingSupplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
assertThat(memoizedSupplier.toString()).isEqualTo("Suppliers.memoize(CountingSupplier)");
checkMemoize(countingSupplier, memoizedSupplier);
// Calls to the original memoized supplier shouldn't affect its copy.
Expand All @@ -156,19 +157,19 @@ public void testMemoizeNonSerializable() throws Exception {
@GwtIncompatible // SerializableTester
public void testMemoizeSerializable() throws Exception {
SerializableCountingSupplier countingSupplier = new SerializableCountingSupplier();
Supplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
Suppliers.MemoizingSupplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
assertThat(memoizedSupplier.toString()).isEqualTo("Suppliers.memoize(CountingSupplier)");
checkMemoize(countingSupplier, memoizedSupplier);
// Calls to the original memoized supplier shouldn't affect its copy.
Object unused = memoizedSupplier.get();
assertThat(memoizedSupplier.toString())
.isEqualTo("Suppliers.memoize(<supplier that returned 10>)");

Supplier<Integer> copy = reserialize(memoizedSupplier);
Suppliers.MemoizingSupplier<Integer> copy = reserialize(memoizedSupplier);
Object unused2 = memoizedSupplier.get();

CountingSupplier countingCopy =
(CountingSupplier) ((Suppliers.MemoizingSupplier<Integer>) copy).delegate;
(CountingSupplier) ((Suppliers.SerializableMemoizingSupplier<Integer>) copy).delegate;
checkMemoize(countingCopy, copy);
}

Expand Down
41 changes: 34 additions & 7 deletions android/guava/src/com/google/common/base/Suppliers.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,34 @@ public String toString() {
* <p>If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is
* returned directly.
*/
public static <T extends @Nullable Object> Supplier<T> memoize(Supplier<T> delegate) {
public static <T extends @Nullable Object> MemoizingSupplier<T> memoize(Supplier<T> delegate) {
if (delegate instanceof NonSerializableMemoizingSupplier
|| delegate instanceof MemoizingSupplier) {
return delegate;
|| delegate instanceof SerializableMemoizingSupplier) {
return (MemoizingSupplier<T>) delegate;
}
return delegate instanceof Serializable
? new MemoizingSupplier<T>(delegate)
? new SerializableMemoizingSupplier<T>(delegate)
: new NonSerializableMemoizingSupplier<T>(delegate);
}

/**
* A supplier that memoizes the result of the first call to {@link #get()} and returns the same
* result on subsequent calls to {@link #get()}.
*
* @author Alexey Pelykh
*/
@ElementTypesAreNonnullByDefault
public interface MemoizingSupplier<T extends @Nullable Object> extends Supplier<T> {
/**
* Returns {@code true} if the supplier has been initialized, i.e. if the first call to
* {@link #get()} has been made or if the supplier has been explicitly initialized.
*/
boolean isMemoized();
}

@VisibleForTesting
static class MemoizingSupplier<T extends @Nullable Object> implements Supplier<T>, Serializable {
static class SerializableMemoizingSupplier<T extends @Nullable Object>
implements MemoizingSupplier<T>, Serializable {
private transient Object lock = new Object();

final Supplier<T> delegate;
Expand All @@ -128,7 +144,7 @@ static class MemoizingSupplier<T extends @Nullable Object> implements Supplier<T
// on volatile read of "initialized".
transient @Nullable T value;

MemoizingSupplier(Supplier<T> delegate) {
SerializableMemoizingSupplier(Supplier<T> delegate) {
this.delegate = checkNotNull(delegate);
}

Expand All @@ -152,6 +168,11 @@ public T get() {
return uncheckedCastNullableTToT(value);
}

@Override
public boolean isMemoized() {
return initialized;
}

@Override
public String toString() {
return "Suppliers.memoize("
Expand All @@ -170,7 +191,8 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
}

@VisibleForTesting
static class NonSerializableMemoizingSupplier<T extends @Nullable Object> implements Supplier<T> {
static class NonSerializableMemoizingSupplier<T extends @Nullable Object>
implements MemoizingSupplier<T> {
private final Object lock = new Object();

@SuppressWarnings("UnnecessaryLambda") // Must be a fixed singleton object
Expand Down Expand Up @@ -206,6 +228,11 @@ public T get() {
return uncheckedCastNullableTToT(value);
}

@Override
public boolean isMemoized() {
return delegate == SUCCESSFULLY_COMPUTED;
}

@Override
public String toString() {
Supplier<T> delegate = this.delegate;
Expand Down
17 changes: 9 additions & 8 deletions guava-tests/test/com/google/common/base/SuppliersTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,16 @@ static class SerializableThrowingSupplier extends ThrowingSupplier implements Se
private static final long serialVersionUID = 0L;
}

static void checkMemoize(CountingSupplier countingSupplier, Supplier<Integer> memoizedSupplier) {
static void checkMemoize(CountingSupplier countingSupplier,
Suppliers.MemoizingSupplier<Integer> memoizedSupplier) {
// the underlying supplier hasn't executed yet
assertEquals(0, countingSupplier.calls);

assertFalse(memoizedSupplier.isMemoized());
assertEquals(10, (int) memoizedSupplier.get());

// now it has
assertEquals(1, countingSupplier.calls);

assertTrue(memoizedSupplier.isMemoized());
assertEquals(10, (int) memoizedSupplier.get());

// it still should only have executed once due to memoization
Expand All @@ -102,7 +103,7 @@ public void testMemoize() {
}

private void memoizeTest(CountingSupplier countingSupplier) {
Supplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
Suppliers.MemoizingSupplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
checkMemoize(countingSupplier, memoizedSupplier);
}

Expand Down Expand Up @@ -139,7 +140,7 @@ private void memoizeExceptionThrownTest(ThrowingSupplier throwingSupplier) {
@GwtIncompatible // SerializableTester
public void testMemoizeNonSerializable() throws Exception {
CountingSupplier countingSupplier = new CountingSupplier();
Supplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
Suppliers.MemoizingSupplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
assertThat(memoizedSupplier.toString()).isEqualTo("Suppliers.memoize(CountingSupplier)");
checkMemoize(countingSupplier, memoizedSupplier);
// Calls to the original memoized supplier shouldn't affect its copy.
Expand All @@ -156,19 +157,19 @@ public void testMemoizeNonSerializable() throws Exception {
@GwtIncompatible // SerializableTester
public void testMemoizeSerializable() throws Exception {
SerializableCountingSupplier countingSupplier = new SerializableCountingSupplier();
Supplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
Suppliers.MemoizingSupplier<Integer> memoizedSupplier = Suppliers.memoize(countingSupplier);
assertThat(memoizedSupplier.toString()).isEqualTo("Suppliers.memoize(CountingSupplier)");
checkMemoize(countingSupplier, memoizedSupplier);
// Calls to the original memoized supplier shouldn't affect its copy.
Object unused = memoizedSupplier.get();
assertThat(memoizedSupplier.toString())
.isEqualTo("Suppliers.memoize(<supplier that returned 10>)");

Supplier<Integer> copy = reserialize(memoizedSupplier);
Suppliers.MemoizingSupplier<Integer> copy = reserialize(memoizedSupplier);
Object unused2 = memoizedSupplier.get();

CountingSupplier countingCopy =
(CountingSupplier) ((Suppliers.MemoizingSupplier<Integer>) copy).delegate;
(CountingSupplier) ((Suppliers.SerializableMemoizingSupplier<Integer>) copy).delegate;
checkMemoize(countingCopy, copy);
}

Expand Down
41 changes: 34 additions & 7 deletions guava/src/com/google/common/base/Suppliers.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,34 @@ public String toString() {
* <p>If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is
* returned directly.
*/
public static <T extends @Nullable Object> Supplier<T> memoize(Supplier<T> delegate) {
public static <T extends @Nullable Object> MemoizingSupplier<T> memoize(Supplier<T> delegate) {
if (delegate instanceof NonSerializableMemoizingSupplier
|| delegate instanceof MemoizingSupplier) {
return delegate;
|| delegate instanceof SerializableMemoizingSupplier) {
return (MemoizingSupplier<T>) delegate;
}
return delegate instanceof Serializable
? new MemoizingSupplier<T>(delegate)
? new SerializableMemoizingSupplier<T>(delegate)
: new NonSerializableMemoizingSupplier<T>(delegate);
}

/**
* A supplier that memoizes the result of the first call to {@link #get()} and returns the same
* result on subsequent calls to {@link #get()}.
*
* @author Alexey Pelykh
*/
@ElementTypesAreNonnullByDefault
public interface MemoizingSupplier<T extends @Nullable Object> extends Supplier<T> {
/**
* Returns {@code true} if the supplier has been initialized, i.e. if the first call to
* {@link #get()} has been made or if the supplier has been explicitly initialized.
*/
boolean isMemoized();
}

@VisibleForTesting
static class MemoizingSupplier<T extends @Nullable Object> implements Supplier<T>, Serializable {
static class SerializableMemoizingSupplier<T extends @Nullable Object>
implements MemoizingSupplier<T>, Serializable {
private transient Object lock = new Object();

final Supplier<T> delegate;
Expand All @@ -128,7 +144,7 @@ static class MemoizingSupplier<T extends @Nullable Object> implements Supplier<T
// on volatile read of "initialized".
transient @Nullable T value;

MemoizingSupplier(Supplier<T> delegate) {
SerializableMemoizingSupplier(Supplier<T> delegate) {
this.delegate = checkNotNull(delegate);
}

Expand All @@ -152,6 +168,11 @@ public T get() {
return uncheckedCastNullableTToT(value);
}

@Override
public boolean isMemoized() {
return initialized;
}

@Override
public String toString() {
return "Suppliers.memoize("
Expand All @@ -170,7 +191,8 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
}

@VisibleForTesting
static class NonSerializableMemoizingSupplier<T extends @Nullable Object> implements Supplier<T> {
static class NonSerializableMemoizingSupplier<T extends @Nullable Object>
implements MemoizingSupplier<T> {
private final Object lock = new Object();

@SuppressWarnings("UnnecessaryLambda") // Must be a fixed singleton object
Expand Down Expand Up @@ -206,6 +228,11 @@ public T get() {
return uncheckedCastNullableTToT(value);
}

@Override
public boolean isMemoized() {
return delegate == SUCCESSFULLY_COMPUTED;
}

@Override
public String toString() {
Supplier<T> delegate = this.delegate;
Expand Down

0 comments on commit 56ac6ff

Please sign in to comment.