diff --git a/src/main/java/com/fathzer/jchess/uci/helper/DeferredReadMoveLibrary.java b/src/main/java/com/fathzer/jchess/uci/helper/DeferredReadMoveLibrary.java
new file mode 100644
index 0000000..4df1663
--- /dev/null
+++ b/src/main/java/com/fathzer/jchess/uci/helper/DeferredReadMoveLibrary.java
@@ -0,0 +1,99 @@
+package com.fathzer.jchess.uci.helper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Optional;
+
+import com.fathzer.games.MoveGenerator;
+import com.fathzer.games.ai.evaluation.EvaluatedMove;
+import com.fathzer.games.movelibrary.MoveLibrary;
+
+/** A move library that encapsulates another move library and read its content outside of its constructor.
+ *
This typically allows an UCI engine to instantiate a move library during the launch process, and
+ * deferred the effective read of the moves during the isReady command execution as specified in
+ * UCI protocol.
+ * @param The type of a move
+ * @param The type of the move generator used by the UCI engine
+ */
+public class DeferredReadMoveLibrary> implements MoveLibrary {
+ /** A interface that can read an object from an URL and sends an IOException if sommething goes wrong.
+ * @param The type of the read object
+ */
+ @FunctionalInterface
+ public static interface IOReader {
+ /** Reads an object.
+ * @param url The url where to read the object
+ * @return The object
+ * @throws IOException If something went wrong
+ */
+ T read(URL url) throws IOException;
+ }
+
+ private final String url;
+ private final IOReader> reader;
+ private MoveLibrary internal;
+
+ /** Constructor.
+ * @param url The url where to read the MoveLibrary
+ * @param reader The reader to read the library.
+ */
+ public DeferredReadMoveLibrary(String url, IOReader> reader) {
+ if (url==null || reader==null) {
+ throw new IllegalArgumentException();
+ }
+ this.url = url;
+ this.reader = reader;
+ }
+
+ /** {@inheritDoc}
+ *
If the move library is not initialized, an empty optional is returned.
+ */
+ @Override
+ public Optional> apply(B board) {
+ if (internal==null) {
+ return Optional.empty();
+ }
+ return internal.apply(board);
+ }
+
+ /** Tests if this move library should be initialized.
+ * @return false if initialization has been made and succeeded.
+ * @see #init()
+ */
+ public boolean isInitRequired() {
+ return internal==null;
+ }
+
+ /** Initializes this move library.
+ * @throws IOException
+ */
+ public void init() throws IOException {
+ if (isInitRequired()) {
+ internal = reader.read(toURL(this.url));
+ }
+ }
+
+ static URL toURL(String path) throws IOException {
+ URL url;
+ try {
+ url = new URL(path);
+ } catch (MalformedURLException e) {
+ File file = new File(path);
+ if (!file.exists()) {
+ throw new FileNotFoundException();
+ }
+ url = file.toURI().toURL();
+ }
+ return url;
+ }
+
+ /** Gets the URL where the data should be/has been read.
+ * @return A String
+ */
+ public String getUrl() {
+ return url;
+ }
+}
diff --git a/src/test/java/com/fathzer/jchess/uci/helper/DeferredReadBookTest.java b/src/test/java/com/fathzer/jchess/uci/helper/DeferredReadBookTest.java
new file mode 100644
index 0000000..b42a4f2
--- /dev/null
+++ b/src/test/java/com/fathzer/jchess/uci/helper/DeferredReadBookTest.java
@@ -0,0 +1,50 @@
+package com.fathzer.jchess.uci.helper;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+
+import com.fathzer.games.MoveGenerator;
+import com.fathzer.games.ai.evaluation.EvaluatedMove;
+import com.fathzer.games.ai.evaluation.Evaluation;
+import com.fathzer.games.movelibrary.MoveLibrary;
+
+class DeferredReadMoveLibraryTest {
+
+ @SuppressWarnings("unchecked")
+ private final MoveGenerator mv = mock(MoveGenerator.class);
+
+ @Test
+ void test() throws Exception {
+ String httpsURL = "https://myApp.org/file.gz";
+ assertEquals(URI.create(httpsURL).toURL(), DeferredReadMoveLibrary.toURL(httpsURL));
+
+ assertThrows(IOException.class, () -> DeferredReadMoveLibrary.toURL("httpx://myApp.org/file.gz"));
+ assertThrows(IOException.class, () -> DeferredReadMoveLibrary.toURL("src/test/resources/unknownFile.json"));
+
+ final String path = "src/test/resources/FakeOpenings.txt";
+ final DeferredReadMoveLibrary> rb = new DeferredReadMoveLibrary<>(path, this::readOpenings);
+ assertTrue(rb.isInitRequired());
+ assertTrue(rb.apply(mv).isEmpty());
+ rb.init();
+ assertFalse(rb.isInitRequired());
+ assertFalse(rb.apply(mv).isEmpty());
+ // A second init call should no throw any exception
+ rb.init();
+ }
+
+ private MoveLibrary> readOpenings(URL url) {
+ return new MoveLibrary>() {
+ @Override
+ public Optional> apply(MoveGenerator board) {
+ return mv.equals(board) ? Optional.of(new EvaluatedMove<>("ok", Evaluation.score(100))) : Optional.empty();
+ }
+ };
+ }
+}
diff --git a/src/test/resources/FakeOpenings.txt b/src/test/resources/FakeOpenings.txt
new file mode 100644
index 0000000..3ad2607
--- /dev/null
+++ b/src/test/resources/FakeOpenings.txt
@@ -0,0 +1 @@
+File required by src/test/com.fathzer.jchess.uci.helper.DeferredReadBookTest.java
\ No newline at end of file