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

com.caoccao.javet.interop.loader.JavetLibLoader Maven / Gradle / Ivy

Go to download

Javet is Java + V8 (JAVa + V + EighT). It is an awesome way of embedding Node.js and V8 in Java.

There is a newer version: 4.0.0
Show newest version
/*
 * Copyright (c) 2021-2023. caoccao.com Sam Cao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.caoccao.javet.interop.loader;

import com.caoccao.javet.enums.JSRuntimeType;
import com.caoccao.javet.exceptions.JavetError;
import com.caoccao.javet.exceptions.JavetException;
import com.caoccao.javet.interfaces.IJavetLogger;
import com.caoccao.javet.utils.JavetDefaultLogger;
import com.caoccao.javet.utils.JavetOSUtils;
import com.caoccao.javet.utils.JavetStringUtils;
import com.caoccao.javet.utils.SimpleMap;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Objects;

/**
 * The type Javet lib loader is the one that actually loads the JNI library based on JS runtime type.
 * It is managed by a dedicated classloader (not the default classloader) so that unload is possible.
 * It is not supposed to be called directly by the applications via the default classloader.
 * 

* It accepts a listener which can specify: * (1) To deploy the JNI library to a custom location to work around the permission issues. * (2) To skip the JNI library deployment to improve the performance and reduce the jar file size. * * @since 0.8.0 */ public final class JavetLibLoader { /** * The constant LIB_VERSION. * * @since 0.8.0 */ public static final String LIB_VERSION = "2.1.0"; private static final String ANDROID_ABI_ARM = "armeabi-v7a"; private static final String ANDROID_ABI_ARM64 = "arm64-v8a"; private static final String ANDROID_ABI_X86 = "x86"; private static final String ANDROID_ABI_X86_64 = "x86_64"; private static final String ARCH_ARM = "arm"; private static final String ARCH_ARM64 = "arm64"; private static final String ARCH_X86 = "x86"; private static final String ARCH_X86_64 = "x86_64"; private static final int BUFFER_LENGTH = 4096; private static final String CHMOD = "chmod"; private static final String DOT = "."; private static final String LIB_FILE_EXTENSION_ANDROID = "so"; private static final String LIB_FILE_EXTENSION_LINUX = "so"; private static final String LIB_FILE_EXTENSION_MACOS = "dylib"; private static final String LIB_FILE_EXTENSION_WINDOWS = "dll"; private static final String LIB_FILE_NAME_FORMAT = "libjavet-{0}-{1}-{2}.v.{3}.{4}"; private static final String LIB_FILE_NAME_FOR_ANDROID_FORMAT = "libjavet-{0}-{1}.v.{2}.{3}"; private static final String LIB_FILE_NAME_PREFIX = "lib"; private static final IJavetLogger LOGGER = new JavetDefaultLogger(JavetLibLoader.class.getName()); private static final long MIN_LAST_MODIFIED_GAP_IN_MILLIS = 60L * 1000L; // 1 minute private static final String OS_ANDROID = "android"; private static final String OS_LINUX = "linux"; private static final String OS_MACOS = "macos"; private static final String OS_WINDOWS = "windows"; private static final String RESOURCE_NAME_FORMAT = "/{0}"; private static final String XRR = "755"; private static IJavetLibLoadingListener libLoadingListener = new JavetLibLoadingListener(); private final JSRuntimeType jsRuntimeType; private volatile boolean loaded; /** * Instantiates a new Javet lib loader. * * @param jsRuntimeType the js runtime type * @since 0.8.0 */ public JavetLibLoader(JSRuntimeType jsRuntimeType) { Objects.requireNonNull(jsRuntimeType); this.jsRuntimeType = jsRuntimeType; loaded = false; } /** * Gets lib loading listener. * * @return the lib loading listener * @since 1.0.1 */ public static IJavetLibLoadingListener getLibLoadingListener() { return libLoadingListener; } /** * Sets lib loading listener. * * @param libLoadingListener the lib loading listener * @since 1.0.1 */ public static void setLibLoadingListener(IJavetLibLoadingListener libLoadingListener) { JavetLibLoader.libLoadingListener = Objects.requireNonNull(libLoadingListener); } private void deployLibFile(String resourceFileName, File libFile) { boolean isLibFileLocked = false; if (libFile.exists() && libFile.canWrite()) { try { //noinspection ResultOfMethodCallIgnored libFile.delete(); } catch (Throwable t) { isLibFileLocked = true; LOGGER.logWarn("Failed to delete {0} because it is locked.", libFile.getAbsolutePath()); } } if (!isLibFileLocked) { byte[] buffer = new byte[BUFFER_LENGTH]; try (InputStream inputStream = JavetLibLoader.class.getResourceAsStream(resourceFileName); FileOutputStream outputStream = new FileOutputStream(libFile.getAbsolutePath())) { if (inputStream != null) { while (true) { int length = inputStream.read(buffer); if (length == -1) { break; } outputStream.write(buffer, 0, length); } if (JavetOSUtils.IS_LINUX || JavetOSUtils.IS_MACOS || JavetOSUtils.IS_ANDROID) { try { Runtime.getRuntime().exec(new String[]{CHMOD, XRR, libFile.getAbsolutePath()}).waitFor(); } catch (Throwable ignored) { } } } } catch (Throwable t) { LOGGER.logWarn("Failed to write to {0} because it is locked.", libFile.getAbsolutePath()); } } } private String getAndroidABI() { if (JavetOSUtils.IS_ANDROID) { if (JavetOSUtils.IS_ARM) { return ANDROID_ABI_ARM; } else if (JavetOSUtils.IS_ARM64) { return ANDROID_ABI_ARM64; } else if (JavetOSUtils.IS_X86) { return ANDROID_ABI_X86; } else if (JavetOSUtils.IS_X86_64) { return ANDROID_ABI_X86_64; } } return null; } private String getFileExtension() { if (JavetOSUtils.IS_WINDOWS) { return LIB_FILE_EXTENSION_WINDOWS; } else if (JavetOSUtils.IS_LINUX) { return LIB_FILE_EXTENSION_LINUX; } else if (JavetOSUtils.IS_MACOS) { return LIB_FILE_EXTENSION_MACOS; } else if (JavetOSUtils.IS_ANDROID) { return LIB_FILE_EXTENSION_ANDROID; } return null; } /** * Gets js runtime type. * * @return the js runtime type * @since 0.8.0 */ public JSRuntimeType getJSRuntimeType() { return jsRuntimeType; } /** * Gets lib file name. * * @return the lib file name * @throws JavetException the javet exception * @since 1.0.1 */ public String getLibFileName() throws JavetException { String fileExtension = getFileExtension(); String osName = getOSName(); if (fileExtension == null || osName == null) { throw new JavetException( JavetError.OSNotSupported, SimpleMap.of(JavetError.PARAMETER_OS, JavetOSUtils.OS_NAME)); } if (JavetOSUtils.IS_ANDROID) { return MessageFormat.format( LIB_FILE_NAME_FOR_ANDROID_FORMAT, jsRuntimeType.getName(), osName, LIB_VERSION, fileExtension); } else { String osArch = getOSArch(); if (osArch == null) { throw new JavetException( JavetError.OSNotSupported, SimpleMap.of(JavetError.PARAMETER_OS, JavetOSUtils.OS_ARCH)); } return MessageFormat.format( LIB_FILE_NAME_FORMAT, jsRuntimeType.getName(), osName, osArch, LIB_VERSION, fileExtension); } } private String getNormalizedLibFilePath(String libFilePath) { boolean prefixToBeNormalized = false; if (JavetOSUtils.IS_LINUX) { prefixToBeNormalized = true; if (libFilePath.endsWith(DOT + LIB_FILE_EXTENSION_LINUX)) { libFilePath = libFilePath.substring( 0, libFilePath.length() - DOT.length() - LIB_FILE_EXTENSION_LINUX.length()); } } else if (JavetOSUtils.IS_ANDROID) { prefixToBeNormalized = true; if (libFilePath.endsWith(DOT + LIB_FILE_EXTENSION_ANDROID)) { libFilePath = libFilePath.substring( 0, libFilePath.length() - DOT.length() - LIB_FILE_EXTENSION_ANDROID.length()); } } else if (JavetOSUtils.IS_MACOS) { prefixToBeNormalized = true; if (libFilePath.endsWith(DOT + LIB_FILE_EXTENSION_MACOS)) { libFilePath = libFilePath.substring( 0, libFilePath.length() - DOT.length() - LIB_FILE_EXTENSION_MACOS.length()); } } if (prefixToBeNormalized && libFilePath.startsWith(LIB_FILE_NAME_PREFIX)) { libFilePath = libFilePath.substring(LIB_FILE_NAME_PREFIX.length()); } return libFilePath; } private String getOSArch() { if (JavetOSUtils.IS_WINDOWS) { return ARCH_X86_64; } else if (JavetOSUtils.IS_LINUX) { return ARCH_X86_64; } else if (JavetOSUtils.IS_MACOS) { return JavetOSUtils.IS_ARM64 ? ARCH_ARM64 : ARCH_X86_64; } else if (JavetOSUtils.IS_ANDROID) { if (JavetOSUtils.IS_ARM) { return ARCH_ARM; } else if (JavetOSUtils.IS_ARM64) { return ARCH_ARM64; } else if (JavetOSUtils.IS_X86) { return ARCH_X86; } else if (JavetOSUtils.IS_X86_64) { return ARCH_X86_64; } } return null; } private String getOSName() { if (JavetOSUtils.IS_WINDOWS) { return OS_WINDOWS; } else if (JavetOSUtils.IS_LINUX) { return OS_LINUX; } else if (JavetOSUtils.IS_MACOS) { return OS_MACOS; } else if (JavetOSUtils.IS_ANDROID) { return OS_ANDROID; } return null; } /** * Gets resource file name. * * @return the resource file name * @throws JavetException the javet exception * @since 0.8.0 */ public String getResourceFileName() throws JavetException { String resourceFileName = MessageFormat.format(RESOURCE_NAME_FORMAT, JavetOSUtils.IS_ANDROID ? JavetStringUtils.join("/", LIB_FILE_NAME_PREFIX, getAndroidABI(), getLibFileName()) : getLibFileName()); if (JavetLibLoader.class.getResource(resourceFileName) == null) { throw new JavetException( JavetError.LibraryNotFound, SimpleMap.of(JavetError.PARAMETER_PATH, resourceFileName)); } return resourceFileName; } /** * Is loaded. * * @return true : loaded, false: not loaded * @since 0.8.0 */ public boolean isLoaded() { return loaded; } /** * Load. * * @throws JavetException the javet exception * @since 0.8.0 */ public void load() throws JavetException { if (!loaded) { String libFilePath = null; try { boolean isLibInSystemPath = libLoadingListener.isLibInSystemPath(jsRuntimeType); boolean isDeploy = libLoadingListener.isDeploy(jsRuntimeType); if (isLibInSystemPath) { libFilePath = getLibFileName(); } else if (isDeploy) { File libPath = libLoadingListener.getLibPath(jsRuntimeType); Objects.requireNonNull(libPath, "Lib path cannot be null"); String resourceFileName = getResourceFileName(); File rootLibPath; if (JavetOSUtils.IS_ANDROID) { rootLibPath = libPath; } else { rootLibPath = new File(libPath, Long.toString(JavetOSUtils.PROCESS_ID)); } if (!rootLibPath.exists()) { if (!rootLibPath.mkdirs()) { LOGGER.logError("Failed to create {0}.", rootLibPath.getAbsolutePath()); } } purge(libPath); File libFile = new File(rootLibPath, getLibFileName()).getAbsoluteFile(); deployLibFile(resourceFileName, libFile); libFilePath = libFile.getAbsolutePath(); } else { File libPath = libLoadingListener.getLibPath(jsRuntimeType); Objects.requireNonNull(libPath, "Lib path cannot be null"); libFilePath = new File(libPath, getLibFileName()).getAbsolutePath(); } try { if (isLibInSystemPath) { System.loadLibrary(getNormalizedLibFilePath(libFilePath)); } else { System.load(libFilePath); } loaded = true; } catch (Throwable t) { if (libLoadingListener.isSuppressingError(jsRuntimeType)) { LOGGER.warn(t.getMessage()); loaded = true; } else { throw t; } } } catch (Throwable t) { LOGGER.logError(t, t.getMessage()); throw new JavetException( JavetError.FailedToReadPath, SimpleMap.of(JavetError.PARAMETER_PATH, libFilePath), t); } } } private void purge(File rootLibPath) { try { if (rootLibPath.exists()) { if (rootLibPath.isDirectory()) { File[] files = rootLibPath.listFiles(); if (files != null && files.length > 0) { for (File libFileOrPath : files) { if (libFileOrPath.lastModified() + MIN_LAST_MODIFIED_GAP_IN_MILLIS > System.currentTimeMillis()) { continue; } boolean toBeDeleted = false; if (libFileOrPath.isDirectory()) { try { File[] libFiles = libFileOrPath.listFiles(); if (libFiles != null && libFiles.length > 0) { for (File libFile : libFiles) { if (libFile.delete()) { LOGGER.logDebug("Deleted {0}.", libFile.getAbsolutePath()); } else { LOGGER.logDebug("{0} is locked.", libFile.getAbsolutePath()); toBeDeleted = true; break; } } } else { toBeDeleted = true; } } catch (Throwable t) { LOGGER.logError(t, "Failed to delete {0}.", libFileOrPath.getAbsolutePath()); } } else if (libFileOrPath.isFile()) { toBeDeleted = true; } if (toBeDeleted) { if (libFileOrPath.delete()) { LOGGER.logDebug("Deleted {0}.", libFileOrPath.getAbsolutePath()); } else { LOGGER.logDebug("{0} is locked.", libFileOrPath.getAbsolutePath()); } } } } } else { if (!rootLibPath.delete()) { LOGGER.logError("Failed to delete {0}.", rootLibPath.getAbsolutePath()); } } } } catch (Throwable t) { LOGGER.logError(t, "Failed to clean up {0}.", rootLibPath.getAbsolutePath()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy