All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.util.SQLiteLibraryLoader Maven / Gradle / Ivy

package org.robolectric.shadows.util;

import com.almworks.sqlite4java.SQLite;
import com.almworks.sqlite4java.SQLiteException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.ByteSource;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;

/** Initializes sqlite native libraries. */
public class SQLiteLibraryLoader {
  private static SQLiteLibraryLoader instance;
  private static final String SQLITE4JAVA = "sqlite4java";
  private static final String OS_WIN = "win32", OS_LINUX = "linux", OS_MAC = "osx";
  private static final String OS_ARCH_ARM64 = "aarch64";
  private static final String SYSTEM_PROPERTY_OS_NAME = "os.name";
  private static final String SYSTEM_PROPERTY_OS_ARCH = "os.arch";

  private final LibraryNameMapper libraryNameMapper;
  private boolean loaded;

  public SQLiteLibraryLoader() {
    this(DEFAULT_MAPPER);
  }

  public SQLiteLibraryLoader(LibraryNameMapper mapper) {
    libraryNameMapper = mapper;
  }

  private static final LibraryNameMapper DEFAULT_MAPPER = System::mapLibraryName;

  public static synchronized void load() {
    if (instance == null) {
      instance = new SQLiteLibraryLoader();
    }
    instance.doLoad();
  }

  public void doLoad() {
    if (loaded) {
      return;
    }
    final long startTime = System.currentTimeMillis();
    File tempDir = Files.createTempDir();
    tempDir.deleteOnExit();
    File extractedLibraryPath = new File(tempDir, getLibName());
    try (FileOutputStream outputStream = new FileOutputStream(extractedLibraryPath)) {
      getLibraryByteSource().copyTo(outputStream);
    } catch (IOException e) {
      throw new RuntimeException("Cannot extract SQLite library into " + extractedLibraryPath, e);
    }
    loadFromDirectory(tempDir);
    logWithTime("SQLite natives prepared in", startTime);
  }

  public String getLibClasspathResourceName() {
    return "sqlite4java/" + getNativesResourcesPathPart() + "/" + getLibName();
  }

  private ByteSource getLibraryByteSource() {
    return Resources.asByteSource(Resources.getResource(getLibClasspathResourceName()));
  }

  private void logWithTime(final String message, final long startTime) {
    log(message + " " + (System.currentTimeMillis() - startTime));
  }

  private void log(final String message) {
    org.robolectric.util.Logger.debug(message);
  }

  @VisibleForTesting
  public boolean isLoaded() {
    return loaded;
  }

  public static boolean isOsSupported() {
    String prefix = getOsPrefix();
    String arch = getArchitecture();
    // We know macOS with aarch64 arch is not supported by sqlite4java now.
    return !(OS_MAC.equals(prefix) && OS_ARCH_ARM64.equals(arch));
  }

  private void loadFromDirectory(final File libPath) {
    // configure less verbose logging
    Logger.getLogger("com.almworks.sqlite4java").setLevel(Level.WARNING);

    SQLite.setLibraryPath(libPath.getAbsolutePath());
    try {
      log(
          "SQLite version: library "
              + SQLite.getLibraryVersion()
              + " / core "
              + SQLite.getSQLiteVersion());
    } catch (SQLiteException e) {
      throw new RuntimeException(e);
    }
    loaded = true;
  }

  private String getLibName() {
    return libraryNameMapper.mapLibraryName(SQLITE4JAVA);
  }

  private String getNativesResourcesPathPart() {
    String prefix = getOsPrefix();
    String suffix = getArchitectureSuffix(prefix);
    if (suffix != null) {
      return prefix + "-" + suffix;
    } else {
      return prefix;
    }
  }

  private static String getOsPrefix() {
    String name = System.getProperty(SYSTEM_PROPERTY_OS_NAME).toLowerCase(Locale.US);
    if (name.contains("win")) {
      return OS_WIN;
    } else if (name.contains("linux")) {
      return OS_LINUX;
    } else if (name.contains("mac")) {
      return OS_MAC;
    } else {
      throw new UnsupportedOperationException(
          "Platform '" + name + "' is not supported by SQLite library");
    }
  }

  private static String getArchitectureSuffix(String prefix) {
    String arch = getArchitecture();
    switch (prefix) {
      case OS_MAC:
        // Current sqlite4java doesn't support macOS aarch64.
        if (!OS_ARCH_ARM64.equals(arch)) {
          return null;
        }
        break;
      case OS_LINUX:
        switch (arch) {
          case "i386":
          case "x86":
            return "i386";
          case "x86_64":
          case "amd64":
            return "amd64";
        }
        break;
      case OS_WIN:
        switch (arch) {
          case "x86":
            return "x86";
          case "x86_64":
          case "amd64":
            return "x64";
        }
        break;
    }
    throw new UnsupportedOperationException(
        "Architecture '" + arch + "' is not supported by SQLite library");
  }

  private static String getArchitecture() {
    return System.getProperty(SYSTEM_PROPERTY_OS_ARCH).toLowerCase(Locale.US).replaceAll("\\W", "");
  }

  public interface LibraryNameMapper {
    String mapLibraryName(String name);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy