diff --git a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java index c32eaadf9..cbfb2b2a4 100644 --- a/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java +++ b/bindings/java/src/main/java/org/github/tursodatabase/core/LimboDB.java @@ -2,6 +2,10 @@ package org.github.tursodatabase.core; import static org.github.tursodatabase.utils.ByteArrayUtils.stringToUtf8ByteArray; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.sql.SQLException; import java.util.concurrent.locks.ReentrantLock; import org.github.tursodatabase.LimboErrorCode; @@ -30,17 +34,136 @@ public final class LimboDB extends AbstractDB { } } - /** Loads the SQLite interface backend. */ + /** + * Enum representing different architectures and their corresponding library paths and file + * extensions. + */ + enum Architecture { + MACOS_ARM64("libs/macos_arm64/lib_limbo_java.dylib", ".dylib"), + MACOS_X86("libs/macos_x86/lib_limbo_java.dylib", ".dylib"), + WINDOWS("libs/windows/lib_limbo_java.dll", ".dll"), + UNSUPPORTED("", ""); + + private final String libPath; + private final String fileExtension; + + Architecture(String libPath, String fileExtension) { + this.libPath = libPath; + this.fileExtension = fileExtension; + } + + public String getLibPath() { + return libPath; + } + + public String getFileExtension() { + return fileExtension; + } + + public static Architecture detect() { + String osName = System.getProperty("os.name").toLowerCase(); + String osArch = System.getProperty("os.arch").toLowerCase(); + + if (osName.contains("mac")) { + if (osArch.contains("aarch64") || osArch.contains("arm64")) { + return MACOS_ARM64; + } else if (osArch.contains("x86_64") || osArch.contains("amd64")) { + return MACOS_X86; + } + } else if (osName.contains("win")) { + return WINDOWS; + } + + return UNSUPPORTED; + } + } + + /** + * This method attempts to load the native library required for Limbo operations. It first tries + * to load the library from the system's library path using {@link #defaultLoad()}. If that fails, + * it attempts to load the library from the JAR file using {@link #loadFromJar()}. If either + * method succeeds, the `isLoaded` flag is set to true. If both methods fail, an {@link + * InternalError} is thrown indicating that the necessary native library could not be loaded. + * + * @throws InternalError if the native library cannot be loaded from either the system path or the + * JAR file. + */ public static void load() { if (isLoaded) { return; } + if (defaultLoad() || loadFromJar()) { + isLoaded = true; + return; + } + + throw new InternalError("Unable to load necessary native library"); + } + + /** + * Load the native library from the system path. + * + *

This method attempts to load the native library named "_limbo_java" from the system's + * library path. If the library is successfully loaded, the `isLoaded` flag is set to true. + * + * @return true if the library was successfully loaded, false otherwise. + */ + private static boolean defaultLoad() { try { System.loadLibrary("_limbo_java"); - } finally { - isLoaded = true; + return true; + } catch (Throwable t) { + logger.info("Unable to load from default path: {}", String.valueOf(t)); } + + return false; + } + + /** + * Load the native library from the JAR file. + * + *

By default, native libraries are packaged within the JAR file. This method extracts the + * appropriate native library for the current operating system and architecture from the JAR and + * loads it. + * + * @return true if the library was successfully loaded, false otherwise. + */ + private static boolean loadFromJar() { + Architecture arch = Architecture.detect(); + if (arch == Architecture.UNSUPPORTED) { + logger.info("Unsupported OS or architecture"); + return false; + } + + try { + InputStream is = LimboDB.class.getClassLoader().getResourceAsStream(arch.getLibPath()); + assert is != null; + File file = convertInputStreamToFile(is, arch); + System.load(file.getPath()); + return true; + } catch (Throwable t) { + logger.info("Unable to load from jar: {}", String.valueOf(t)); + } + + return false; + } + + private static File convertInputStreamToFile(InputStream is, Architecture arch) + throws IOException { + File tempFile = File.createTempFile("lib", arch.getFileExtension()); + tempFile.deleteOnExit(); + + try (FileOutputStream os = new FileOutputStream(tempFile)) { + int read; + byte[] bytes = new byte[1024]; + + while ((read = is.read(bytes)) != -1) { + os.write(bytes, 0, read); + } + } + + return tempFile; } /**