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