From 389fb37c56d82473ae925d3c8cbb1d4c40819722 Mon Sep 17 00:00:00 2001
From: Gary Gregory
Date: Mon, 21 Mar 2022 10:00:41 -0400
Subject: [PATCH] Add Memoizer(Function) and Memoizer(Function, boolean).
---
src/changes/changes.xml | 1 +
.../commons/lang3/concurrent/Memoizer.java | 34 +++++-
...rTest.java => MemoizerComputableTest.java} | 32 +++---
.../concurrent/MemoizerFunctionTest.java | 108 ++++++++++++++++++
4 files changed, 158 insertions(+), 17 deletions(-)
rename src/test/java/org/apache/commons/lang3/concurrent/{MemoizerTest.java => MemoizerComputableTest.java} (99%)
create mode 100644 src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index e0197e39b0d..c13545e62b7 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -123,6 +123,7 @@ The type attribute can be add,update,fix,remove.
Add JavaVersion.JAVA_18.
Add TimeZones.toTimeZone(TimeZone).
Add FutureTasks.
+ Add Memoizer(Function) and Memoizer(Function, boolean).
Bump spotbugs-maven-plugin from 4.2.0 to 4.5.0.0 #735, #808, #822, #834.
Bump actions/cache from v2.1.4 to v2.1.7 #742, #752, #764, #833.
diff --git a/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java b/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java
index d53318de278..6fccc822dd4 100644
--- a/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java
+++ b/src/main/java/org/apache/commons/lang3/concurrent/Memoizer.java
@@ -55,7 +55,7 @@ public class Memoizer implements Computable {
* Constructs a Memoizer for the provided Computable calculation.
*
*
- * If a calculation is thrown an exception for any reason, this exception will be cached and returned for all future
+ * If a calculation throws an exception for any reason, this exception will be cached and returned for all future
* calls with the provided parameter.
*
*
@@ -80,6 +80,38 @@ public Memoizer(final Computable computable, final boolean recalculate) {
this.mappingFunction = k -> FutureTasks.run(() -> computable.compute(k));
}
+ /**
+ *
+ * Constructs a Memoizer for the provided Function calculation.
+ *
+ *
+ * If a calculation throws an exception for any reason, this exception will be cached and returned for all future
+ * calls with the provided parameter.
+ *
+ *
+ * @param function the function whose results should be memorized
+ * @since 2.13.0
+ */
+ public Memoizer(final Function function) {
+ this(function, false);
+ }
+
+ /**
+ *
+ * Constructs a Memoizer for the provided Function calculation, with the option of whether a Function that
+ * experiences an error should recalculate on subsequent calls or return the same cached exception.
+ *
+ *
+ * @param function the computation whose results should be memorized
+ * @param recalculate determines whether the computation should be recalculated on subsequent calls if the previous call
+ * failed
+ * @since 2.13.0
+ */
+ public Memoizer(final Function function, final boolean recalculate) {
+ this.recalculate = recalculate;
+ this.mappingFunction = k -> FutureTasks.run(() -> function.apply(k));
+ }
+
/**
*
* This method will return the result of the calculation and cache it, if it has not previously been calculated.
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/MemoizerTest.java b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerComputableTest.java
similarity index 99%
rename from src/test/java/org/apache/commons/lang3/concurrent/MemoizerTest.java
rename to src/test/java/org/apache/commons/lang3/concurrent/MemoizerComputableTest.java
index d45800d49c8..d34bedbcdca 100644
--- a/src/test/java/org/apache/commons/lang3/concurrent/MemoizerTest.java
+++ b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerComputableTest.java
@@ -25,7 +25,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-public class MemoizerTest {
+public class MemoizerComputableTest {
private Computable computable;
@@ -34,17 +34,6 @@ public void setUpComputableMock() {
computable = EasyMock.mock(Computable.class);
}
- @Test
- public void testOnlyCallComputableOnceIfDoesNotThrowException() throws Exception {
- final Integer input = 1;
- final Memoizer memoizer = new Memoizer<>(computable);
- expect(computable.compute(input)).andReturn(input);
- replay(computable);
-
- assertEquals(input, memoizer.compute(input), "Should call computable first time");
- assertEquals(input, memoizer.compute(input), "Should not call the computable the second time");
- }
-
@Test
public void testDefaultBehaviourNotToRecalculateExecutionExceptions() throws Exception {
final Integer input = 1;
@@ -83,14 +72,14 @@ public void testDoesRecalculateWhenSetToTrue() throws Exception {
}
@Test
- public void testWhenComputableThrowsRuntimeException() throws Exception {
+ public void testOnlyCallComputableOnceIfDoesNotThrowException() throws Exception {
final Integer input = 1;
final Memoizer memoizer = new Memoizer<>(computable);
- final RuntimeException runtimeException = new RuntimeException("Some runtime exception");
- expect(computable.compute(input)).andThrow(runtimeException);
+ expect(computable.compute(input)).andReturn(input);
replay(computable);
- assertThrows(RuntimeException.class, () -> memoizer.compute(input));
+ assertEquals(input, memoizer.compute(input), "Should call computable first time");
+ assertEquals(input, memoizer.compute(input), "Should not call the computable the second time");
}
@Test
@@ -103,4 +92,15 @@ public void testWhenComputableThrowsError() throws Exception {
assertThrows(Error.class, () -> memoizer.compute(input));
}
+
+ @Test
+ public void testWhenComputableThrowsRuntimeException() throws Exception {
+ final Integer input = 1;
+ final Memoizer memoizer = new Memoizer<>(computable);
+ final RuntimeException runtimeException = new RuntimeException("Some runtime exception");
+ expect(computable.compute(input)).andThrow(runtimeException);
+ replay(computable);
+
+ assertThrows(RuntimeException.class, () -> memoizer.compute(input));
+ }
}
diff --git a/src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java
new file mode 100644
index 00000000000..d9419ee6dac
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/concurrent/MemoizerFunctionTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.concurrent;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.function.Function;
+
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class MemoizerFunctionTest {
+
+ private Function function;
+
+ @BeforeEach
+ public void setUpComputableMock() {
+ function = EasyMock.mock(Function.class);
+ }
+
+ @Test
+ public void testDefaultBehaviourNotToRecalculateExecutionExceptions() throws Exception {
+ final Integer input = 1;
+ final Memoizer memoizer = new Memoizer<>(function);
+ final IllegalArgumentException interruptedException = new IllegalArgumentException();
+ expect(function.apply(input)).andThrow(interruptedException);
+ replay(function);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertThrows(IllegalArgumentException.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testDoesNotRecalculateWhenSetToFalse() throws Exception {
+ final Integer input = 1;
+ final Memoizer memoizer = new Memoizer<>(function, false);
+ final IllegalArgumentException interruptedException = new IllegalArgumentException();
+ expect(function.apply(input)).andThrow(interruptedException);
+ replay(function);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertThrows(IllegalArgumentException.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testDoesRecalculateWhenSetToTrue() throws Exception {
+ final Integer input = 1;
+ final Integer answer = 3;
+ final Memoizer memoizer = new Memoizer<>(function, true);
+ final IllegalArgumentException interruptedException = new IllegalArgumentException();
+ expect(function.apply(input)).andThrow(interruptedException).andReturn(answer);
+ replay(function);
+
+ assertThrows(Throwable.class, () -> memoizer.compute(input));
+ assertEquals(answer, memoizer.compute(input));
+ }
+
+ @Test
+ public void testOnlyCallComputableOnceIfDoesNotThrowException() throws Exception {
+ final Integer input = 1;
+ final Memoizer memoizer = new Memoizer<>(function);
+ expect(function.apply(input)).andReturn(input);
+ replay(function);
+
+ assertEquals(input, memoizer.compute(input), "Should call computable first time");
+ assertEquals(input, memoizer.compute(input), "Should not call the computable the second time");
+ }
+
+ @Test
+ public void testWhenComputableThrowsError() throws Exception {
+ final Integer input = 1;
+ final Memoizer memoizer = new Memoizer<>(function);
+ final Error error = new Error();
+ expect(function.apply(input)).andThrow(error);
+ replay(function);
+
+ assertThrows(Error.class, () -> memoizer.compute(input));
+ }
+
+ @Test
+ public void testWhenComputableThrowsRuntimeException() throws Exception {
+ final Integer input = 1;
+ final Memoizer memoizer = new Memoizer<>(function);
+ final RuntimeException runtimeException = new RuntimeException("Some runtime exception");
+ expect(function.apply(input)).andThrow(runtimeException);
+ replay(function);
+
+ assertThrows(RuntimeException.class, () -> memoizer.compute(input));
+ }
+}