Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) MemoizingSupplier.isMemoized #7450

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.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 @@ -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 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 @@ -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 isMemoized() {
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 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 @@ -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.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 @@ -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 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 @@ -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 isMemoized() {
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 isMemoized() {
return delegate == SUCCESSFULLY_COMPUTED;
}

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