This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new 02132ee97 Add method ConcurrentInitializer#isInitialized() (#1120)
02132ee97 is described below

commit 02132ee97bf0d57c85aa45bb64d6eb8dfbe0cf5e
Author: Benjamin Confino <benja...@uk.ibm.com>
AuthorDate: Sat Oct 14 13:00:56 2023 +0100

    Add method ConcurrentInitializer#isInitialized() (#1120)
    
    * use NO_INIT to better handle initialize returning null
    
    * add isInitialized method to all ConcurrentInitializer classes and test
    
    * Update AtomicInitializer.java
    
    * Update AtomicSafeInitializer.java
    
    ---------
    
    Co-authored-by: Gary Gregory <garydgreg...@users.noreply.github.com>
---
 .../concurrent/AbstractConcurrentInitializer.java  |  9 ++++
 .../lang3/concurrent/AtomicInitializer.java        | 19 +++++--
 .../lang3/concurrent/AtomicSafeInitializer.java    | 16 +++++-
 .../lang3/concurrent/BackgroundInitializer.java    | 23 +++++++++
 .../lang3/concurrent/ConstantInitializer.java      | 11 ++++
 .../concurrent/MultiBackgroundInitializer.java     | 16 ++++++
 .../AbstractConcurrentInitializerTest.java         | 16 ++++++
 .../lang3/concurrent/AtomicInitializerTest.java    | 25 ++++++++++
 .../concurrent/AtomicSafeInitializerTest.java      | 21 ++++++++
 .../concurrent/BackgroundInitializerTest.java      | 30 +++++++++++
 .../lang3/concurrent/ConstantInitializerTest.java  | 10 ++++
 .../concurrent/MultiBackgroundInitializerTest.java | 58 ++++++++++++++++++++++
 12 files changed, 249 insertions(+), 5 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java
 
b/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java
index 8e064a73f..7786c8057 100644
--- 
a/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java
+++ 
b/src/main/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializer.java
@@ -36,4 +36,13 @@ public abstract class AbstractConcurrentInitializer<T, E 
extends Exception> impl
      */
     protected abstract T initialize() throws E;
 
+    /**
+     * Returns true if initialization has been completed. If initialization 
threw an exception this will return false, but it will return true if a 
subsequent
+     * call to initialize completes successfully. If the implementation of 
ConcurrentInitializer can initialize multiple objects, this will only return 
true if
+     * all objects have been initialized.
+     *
+     * @return true if all initialization is complete, otherwise false
+     */
+    protected abstract boolean isInitialized();
+
 }
diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java 
b/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java
index 4560fd424..9c20bd5ac 100644
--- a/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java
+++ b/src/main/java/org/apache/commons/lang3/concurrent/AtomicInitializer.java
@@ -63,8 +63,10 @@ import java.util.concurrent.atomic.AtomicReference;
  * @param <T> the type of the object managed by this initializer class
  */
 public abstract class AtomicInitializer<T> extends 
AbstractConcurrentInitializer<T, RuntimeException> {
+
+    private static final Object NO_INIT = new Object();
     /** Holds the reference to the managed object. */
-    private final AtomicReference<T> reference = new AtomicReference<>();
+    private final AtomicReference<T> reference = new AtomicReference<>((T) 
NO_INIT);
 
     /**
      * Returns the object managed by this initializer. The object is created if
@@ -79,9 +81,9 @@ public abstract class AtomicInitializer<T> extends 
AbstractConcurrentInitializer
     public T get() throws ConcurrentException {
         T result = reference.get();
 
-        if (result == null) {
+        if (result == (T) NO_INIT) {
             result = initialize();
-            if (!reference.compareAndSet(null, result)) {
+            if (!reference.compareAndSet((T) NO_INIT, result)) {
                 // another thread has initialized the reference
                 result = reference.get();
             }
@@ -89,4 +91,15 @@ public abstract class AtomicInitializer<T> extends 
AbstractConcurrentInitializer
 
         return result;
     }
+
+    /**
+     * Tests whether this instance is initialized. Once initialized, always 
returns true.
+     *
+     * @return whether this instance is initialized. Once initialized, always 
returns true.
+     * @since 3.14.0
+     */
+    @Override
+    public boolean isInitialized() {
+        return reference.get() != NO_INIT;
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java 
b/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java
index 87a09ae44..e6093339d 100644
--- 
a/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java
+++ 
b/src/main/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializer.java
@@ -53,12 +53,13 @@ import java.util.concurrent.atomic.AtomicReference;
  */
 public abstract class AtomicSafeInitializer<T> extends 
AbstractConcurrentInitializer<T, RuntimeException> {
 
+    private static final Object NO_INIT = new Object();
     /** A guard which ensures that initialize() is called only once. */
     private final AtomicReference<AtomicSafeInitializer<T>> factory =
             new AtomicReference<>();
 
     /** Holds the reference to the managed object. */
-    private final AtomicReference<T> reference = new AtomicReference<>();
+    private final AtomicReference<T> reference = new AtomicReference<>((T) 
NO_INIT);
 
     /**
      * Gets (and initialize, if not initialized yet) the required object
@@ -71,7 +72,7 @@ public abstract class AtomicSafeInitializer<T> extends 
AbstractConcurrentInitial
     public final T get() throws ConcurrentException {
         T result;
 
-        while ((result = reference.get()) == null) {
+        while ((result = reference.get()) == (T) NO_INIT) {
             if (factory.compareAndSet(null, this)) {
                 reference.set(initialize());
             }
@@ -79,4 +80,15 @@ public abstract class AtomicSafeInitializer<T> extends 
AbstractConcurrentInitial
 
         return result;
     }
+
+    /**
+     * Tests whether this instance is initialized. Once initialized, always 
returns true.
+     *
+     * @return whether this instance is initialized. Once initialized, always 
returns true.
+     * @since 3.14.0
+     */
+    @Override
+    public boolean isInitialized() {
+        return reference.get() != NO_INIT;
+    }
 }
diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java 
b/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java
index dba81d5a8..91ee1c015 100644
--- 
a/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java
+++ 
b/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java
@@ -17,6 +17,7 @@
 package org.apache.commons.lang3.concurrent;
 
 import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -123,6 +124,28 @@ public abstract class BackgroundInitializer<T> extends 
AbstractConcurrentInitial
         return externalExecutor;
     }
 
+    /**
+     * Tests whether this instance is initialized. Once initialized, always 
returns true.
+     * If initialization failed then the failure will be cached and this will 
never return
+     * true.
+     *
+     * @return true if initialization completed successfully, otherwise false
+     * @since 3.14.0
+     */
+    @Override
+    public synchronized boolean isInitialized() {
+        if (future == null || ! future.isDone() ) {
+            return false;
+        }
+
+        try {
+            future.get();
+            return true;
+        } catch (CancellationException | ExecutionException | 
InterruptedException e) {
+            return false;
+        }
+    }
+
     /**
      * Returns a flag whether this {@link BackgroundInitializer} has already
      * been started.
diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java 
b/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java
index ea6c6a502..9f52aa009 100644
--- a/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java
+++ b/src/main/java/org/apache/commons/lang3/concurrent/ConstantInitializer.java
@@ -80,6 +80,17 @@ public class ConstantInitializer<T> implements 
ConcurrentInitializer<T> {
         return getObject();
     }
 
+    /**
+     * As a {@link ConstantInitializer} is initialized on construction this 
will
+     * always return true.
+     *
+     * @return true.
+     * @since 3.14.0
+     */
+    public boolean isInitialized() {
+        return true;
+    }
+
     /**
      * Returns a hash code for this object. This implementation returns the 
hash
      * code of the managed object.
diff --git 
a/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java
 
b/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java
index 4f5abaad1..f294ef407 100644
--- 
a/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java
+++ 
b/src/main/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializer.java
@@ -199,6 +199,22 @@ public class MultiBackgroundInitializer
         return new MultiBackgroundInitializerResults(inits, results, excepts);
     }
 
+    /**
+     * Tests whether this all child {@code BackgroundInitializer} objects are 
initialized.
+     * Once initialized, always returns true.
+     *
+     * @return whether all child {@code BackgroundInitializer} objects 
instance are initialized. Once initialized, always returns true. If there are 
no child {@code BackgroundInitializer} objects return false.
+     * @since 3.14.0
+     */
+    @Override
+    public boolean isInitialized() {
+        if (childInitializers.isEmpty()) {
+            return false;
+        }
+
+        return 
childInitializers.values().stream().allMatch(BackgroundInitializer::isInitialized);
+    }
+
     /**
      * A data class for storing the results of the background initialization
      * performed by {@link MultiBackgroundInitializer}. Objects of this inner
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java
 
b/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java
index d0b1db398..6f575ff54 100644
--- 
a/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/concurrent/AbstractConcurrentInitializerTest.java
@@ -17,7 +17,9 @@
 package org.apache.commons.lang3.concurrent;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.concurrent.CountDownLatch;
 
@@ -46,6 +48,20 @@ public abstract class AbstractConcurrentInitializerTest 
extends AbstractLangTest
         assertNotNull(createInitializer().get(), "No managed object");
     }
 
+    /**
+     * Tests a simple invocation of the isInitialized() method.
+     */
+    @Test
+    public void testisInitialized() throws Throwable {
+        final ConcurrentInitializer<Object> initializer = createInitializer();
+        if (initializer instanceof AbstractConcurrentInitializer) {
+            AbstractConcurrentInitializer castedInitializer = 
(AbstractConcurrentInitializer) initializer;
+            assertFalse(castedInitializer.isInitialized(), "was initialized 
before get()");
+            assertNotNull(castedInitializer.get(), "No managed object");
+            assertTrue(castedInitializer.isInitialized(), "was not initialized 
after get()");
+        }
+    }
+
     /**
      * Tests whether sequential get() invocations always return the same
      * instance.
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java 
b/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java
index 9b05a1ae3..cdf42bd1a 100644
--- 
a/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/concurrent/AtomicInitializerTest.java
@@ -16,6 +16,12 @@
  */
 package org.apache.commons.lang3.concurrent;
 
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.jupiter.api.Test;
+
 /**
  * Test class for {@code AtomicInitializer}.
  */
@@ -34,4 +40,23 @@ public class AtomicInitializerTest extends 
AbstractConcurrentInitializerTest {
             }
         };
     }
+
+    @Test
+    public void testGetThatReturnsNullFirstTime() throws ConcurrentException {
+        final AtomicInitializer<Object> initializer = new 
AtomicInitializer<Object>() {
+            final AtomicBoolean firstRun = new AtomicBoolean(true);
+
+            @Override
+            protected Object initialize() {
+                if (firstRun.getAndSet(false)) {
+                    return null;
+                } else {
+                    return new Object();
+                }
+            }
+        };
+
+        assertNull(initializer.get());
+        assertNull(initializer.get());
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java
 
b/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java
index 6739ef758..8b0393472 100644
--- 
a/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/concurrent/AtomicSafeInitializerTest.java
@@ -17,7 +17,9 @@
 package org.apache.commons.lang3.concurrent;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -58,6 +60,25 @@ public class AtomicSafeInitializerTest extends 
AbstractConcurrentInitializerTest
         assertEquals(1, initializer.initCounter.get(), "Wrong number of 
invocations");
     }
 
+    @Test
+    public void testGetThatReturnsNullFirstTime() throws ConcurrentException {
+        final AtomicSafeInitializer<Object> initializer = new 
AtomicSafeInitializer<Object>() {
+            final AtomicBoolean firstRun = new AtomicBoolean(true);
+
+            @Override
+            protected Object initialize() {
+                if (firstRun.getAndSet(false)) {
+                    return null;
+                } else {
+                    return new Object();
+                }
+            }
+        };
+
+        assertNull(initializer.get());
+        assertNull(initializer.get());
+    }
+
     /**
      * A concrete test implementation of {@code AtomicSafeInitializer} which 
also serves as a simple example.
      * <p>
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java
 
b/src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java
index 895a2247c..90193f2af 100644
--- 
a/src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/concurrent/BackgroundInitializerTest.java
@@ -267,6 +267,21 @@ public class BackgroundInitializerTest extends 
AbstractLangTest {
         assertTrue(init.isStarted(), "Not started");
     }
 
+    /**
+     * Tests isInitialized() before and after the background task has finished.
+     */
+    @Test
+    public void testIsInitialized() throws ConcurrentException {
+        final BackgroundInitializerTestImpl init = new 
BackgroundInitializerTestImpl();
+        init.enableLatch();
+        init.start();
+        assertTrue(init.isStarted(), "Not started"); //Started and Initialized 
should return opposite values
+        assertFalse(init.isInitialized(), "Initalized before releasing latch");
+        init.releaseLatch();
+        init.get(); //to ensure the initialize thread has completed.
+        assertTrue(init.isInitialized(), "Not initalized after releasing 
latch");
+    }
+
     /**
      * A concrete implementation of BackgroundInitializer. It also overloads
      * some methods that simplify testing.
@@ -282,6 +297,10 @@ public class BackgroundInitializerTest extends 
AbstractLangTest {
         /** The number of invocations of initialize(). */
         volatile int initializeCalls;
 
+        /** A latch tests can use to control when initialize completes. */
+        final CountDownLatch latch = new CountDownLatch(1);
+        boolean waitForLatch = false;
+
         BackgroundInitializerTestImpl() {
         }
 
@@ -289,6 +308,14 @@ public class BackgroundInitializerTest extends 
AbstractLangTest {
             super(exec);
         }
 
+        public void enableLatch() {
+            waitForLatch = true;
+        }
+
+        public void releaseLatch() {
+            latch.countDown();
+        }
+
         /**
          * Records this invocation. Optionally throws an exception or sleeps a
          * while.
@@ -303,6 +330,9 @@ public class BackgroundInitializerTest extends 
AbstractLangTest {
             if (shouldSleep) {
                 ThreadUtils.sleep(Duration.ofMinutes(1));
             }
+            if (waitForLatch) {
+                latch.await();
+            }
             return Integer.valueOf(++initializeCalls);
         }
     }
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java
 
b/src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java
index c296f056f..eddc2cbbf 100644
--- 
a/src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/concurrent/ConstantInitializerTest.java
@@ -130,4 +130,14 @@ public class ConstantInitializerTest extends 
AbstractLangTest {
         final String s = new ConstantInitializer<>(null).toString();
         assertTrue(s.indexOf("object = null") > 0, "Object not found: " + s);
     }
+
+    /**
+     * Tests a simple invocation of the isInitialized() method.
+     */
+    @Test
+    public void testisInitialized() {
+        assertTrue(init.isInitialized(), "was not initialized before get()");
+        assertEquals(VALUE, init.getObject(), "Wrong object");
+        assertTrue(init.isInitialized(), "was not initialized after get()");
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java
 
b/src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java
index c054c8396..f861e0302 100644
--- 
a/src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/concurrent/MultiBackgroundInitializerTest.java
@@ -21,9 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -42,6 +44,9 @@ public class MultiBackgroundInitializerTest extends 
AbstractLangTest {
     /** The initializer to be tested. */
     private MultiBackgroundInitializer initializer;
 
+    /** A short time to wait for background threads to run. */
+    private static final long PERIOD_MILLIS = 50;
+
     @BeforeEach
     public void setUp() {
         initializer = new MultiBackgroundInitializer();
@@ -366,6 +371,43 @@ public class MultiBackgroundInitializerTest extends 
AbstractLangTest {
         assertTrue(exec.isShutdown(), "Executor not shutdown");
     }
 
+    @Test
+    public void testIsInitialized()
+            throws ConcurrentException, InterruptedException {
+        final ChildBackgroundInitializer childOne = new 
ChildBackgroundInitializer();
+        final ChildBackgroundInitializer childTwo = new 
ChildBackgroundInitializer();
+
+        childOne.enableLatch();
+        childTwo.enableLatch();
+
+        assertFalse(initializer.isInitialized(), "Initalized without having 
anything to initalize");
+
+        initializer.addInitializer("child one", childOne);
+        initializer.addInitializer("child two", childTwo);
+        initializer.start();
+
+        long startTime = System.currentTimeMillis();
+        long waitTime = 3000;
+        long endTime = startTime + waitTime;
+        //wait for the children to start
+        while (! childOne.isStarted() || ! childTwo.isStarted()) {
+            if (System.currentTimeMillis() > endTime) {
+                fail("children never started");
+                Thread.sleep(PERIOD_MILLIS);
+            }
+        }
+
+        assertFalse(initializer.isInitialized(), "Initalized with two children 
running");
+
+        childOne.releaseLatch();
+        childOne.get(); //ensure this child finishes initializing
+        assertFalse(initializer.isInitialized(), "Initalized with one child 
running");
+
+        childTwo.releaseLatch();
+        childTwo.get(); //ensure this child finishes initializing
+        assertTrue(initializer.isInitialized(), "Not initalized with no 
children running");
+    }
+
     /**
      * A concrete implementation of {@code BackgroundInitializer} used for
      * defining background tasks for {@code MultiBackgroundInitializer}.
@@ -381,6 +423,18 @@ public class MultiBackgroundInitializerTest extends 
AbstractLangTest {
         /** An exception to be thrown by initialize(). */
         Exception ex;
 
+        /** A latch tests can use to control when initialize completes. */
+        final CountDownLatch latch = new CountDownLatch(1);
+        boolean waitForLatch = false;
+
+        public void enableLatch() {
+            waitForLatch = true;
+        }
+
+        public void releaseLatch() {
+            latch.countDown();
+        }
+
         /**
          * Records this invocation. Optionally throws an exception.
          */
@@ -393,6 +447,10 @@ public class MultiBackgroundInitializerTest extends 
AbstractLangTest {
                 throw ex;
             }
 
+            if (waitForLatch) {
+                latch.await();
+            }
+
             return Integer.valueOf(initializeCalls);
         }
     }

Reply via email to