/*
 * Class NativeUtils is published under the The MIT License:
 *
 * Copyright (c) 2012 Adam Heinrich <adam@adamh.cz>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package cz.adamh.utils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.ProviderNotFoundException;
import java.nio.file.StandardCopyOption;

/**
 * A simple library class which helps with loading dynamic libraries stored in
 * the
 * JAR archive. These libraries usualy contain implementation of some methods in
 * native code (using JNI - Java Native Interface).
 *
 * @see http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar
 * @see https://github.com/adamheinrich/native-utils
 *
 */
public class NativeUtils {

	/**
	 * The minimum length a prefix for a file has to have according to
	 * {@link File#createTempFile(String, String)}}.
	 */
	private static final int MIN_PREFIX_LENGTH = 3;

	/**
	 * Temporary directory which will contain the DLLs.
	 */
	private static File temporaryDir;

	/**
	 * Private constructor - this class will never be instanced
	 */
	private NativeUtils() {}

	/**
	 * Loads library from current JAR archive
	 *
	 * The file from JAR is copied into system temporary directory and then
	 * loaded. The temporary file is deleted after
	 * exiting.
	 * Method uses String as filename because the pathname is "abstract", not
	 * system-dependent.
	 *
	 * @param path
	 *            The path of file inside JAR as absolute path (beginning with
	 *            '/'), e.g. /package/File.ext
	 * @throws IOException
	 *             If temporary file creation or read/write operation fails
	 * @throws IllegalArgumentException
	 *             If source file (param path) does not exist
	 * @throws IllegalArgumentException
	 *             If the path is not absolute or if the filename is shorter
	 *             than three characters
	 *             (restriction of
	 *             {@link File#createTempFile(java.lang.String, java.lang.String)}).
	 * @throws FileNotFoundException
	 *             If the file could not be found inside the JAR.
	 */
	public static void loadLibraryFromJar(final String path) throws IOException {

		if (!path.startsWith("/"))
			throw new IllegalArgumentException("The path has to be absolute (start with '/').");

		// Obtain filename from path
		final String[] parts = path.split("/");
		final String filename = parts.length > 1 ? parts[parts.length - 1] : null;

		// Check if the filename is okay
		if (filename == null || filename.length() < NativeUtils.MIN_PREFIX_LENGTH)
			throw new IllegalArgumentException("The filename has to be at least 3 characters long.");

		// Prepare temporary file
		if (NativeUtils.temporaryDir == null) {
			NativeUtils.temporaryDir = NativeUtils.createTempDirectory("nativeutils");
			NativeUtils.temporaryDir.deleteOnExit();
		}

		final File temp = new File(NativeUtils.temporaryDir, filename);

		try (InputStream is = NativeUtils.class.getResourceAsStream(path)) {
			Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
		} catch (final IOException e) {
			temp.delete();
			throw e;
		} catch (final NullPointerException e) {
			temp.delete();
			throw new FileNotFoundException("File " + path + " was not found inside JAR.");
		}

		try {
			System.load(temp.getAbsolutePath());
		} finally {
			if (NativeUtils.isPosixCompliant())
				// Assume POSIX compliant file system, can be deleted after loading
				temp.delete();
			else
				// Assume non-POSIX, and don't delete until last file descriptor closed
				temp.deleteOnExit();
		}
	}

	private static boolean isPosixCompliant() {
		try {
			if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix"))
				return true;
			return false;
		} catch (FileSystemNotFoundException | ProviderNotFoundException | SecurityException e) {
			return false;
		}
	}

	private static File createTempDirectory(final String prefix) throws IOException {
		final String tempDir = System.getProperty("java.io.tmpdir");
		final File generatedDir = new File(tempDir, prefix + System.nanoTime());

		if (!generatedDir.mkdir())
			throw new IOException("Failed to create temp directory " + generatedDir.getName());

		return generatedDir;
	}
}