Skip to content

Commit

Permalink
(feat) MemoizingSupplier.isInitialized
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-pelykh committed Oct 21, 2024
1 parent fea7a36 commit 3897edd
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 @@ -80,15 +80,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.isInitialized());
assertEquals(10, (int) memoizedSupplier.get());

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

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

// it still should only have executed once due to memoization
Expand All @@ -101,7 +102,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 @@ -138,7 +139,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 @@ -155,19 +156,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 @@ -110,18 +110,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 isInitialized();
}

@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 @@ -130,7 +146,7 @@ static class MemoizingSupplier<T extends @Nullable Object> implements Supplier<T
// on volatile read of "initialized".
@CheckForNull transient T value;

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

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

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

@Override
public String toString() {
return "Suppliers.memoize("
Expand All @@ -172,7 +193,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 @@ -208,6 +230,11 @@ public T get() {
return uncheckedCastNullableTToT(value);
}

@Override
public boolean isInitialized() {
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 @@ -80,15 +80,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.isInitialized());
assertEquals(10, (int) memoizedSupplier.get());

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

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

// it still should only have executed once due to memoization
Expand All @@ -101,7 +102,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 @@ -138,7 +139,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 @@ -155,19 +156,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 @@ -110,18 +110,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 isInitialized();
}

@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 @@ -130,7 +146,7 @@ static class MemoizingSupplier<T extends @Nullable Object> implements Supplier<T
// on volatile read of "initialized".
@CheckForNull transient T value;

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

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

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

@Override
public String toString() {
return "Suppliers.memoize("
Expand All @@ -172,7 +193,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 @@ -208,6 +230,11 @@ public T get() {
return uncheckedCastNullableTToT(value);
}

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

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

0 comments on commit 3897edd

Please sign in to comment.