From 922917e2bef5277b383622b98653c9c699d03779 Mon Sep 17 00:00:00 2001 From: andreblanke Date: Tue, 24 Oct 2017 21:49:48 +0200 Subject: [PATCH 1/2] Refactor NativeUtils.java --- src/main/java/cz/adamh/utils/NativeUtils.java | 85 +++++++++---------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/src/main/java/cz/adamh/utils/NativeUtils.java b/src/main/java/cz/adamh/utils/NativeUtils.java index bc6f768..54e3cbd 100644 --- a/src/main/java/cz/adamh/utils/NativeUtils.java +++ b/src/main/java/cz/adamh/utils/NativeUtils.java @@ -26,7 +26,9 @@ package cz.adamh.utils; import java.io.*; 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 @@ -38,7 +40,12 @@ import java.nio.file.ProviderNotFoundException; * */ 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; + /** * Private constructor - this class will never be instanced */ @@ -48,13 +55,16 @@ public class 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. + * 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 {@see File#createTempFile(java.lang.String, java.lang.String)}). + * @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(String path) throws IOException { @@ -72,11 +82,11 @@ public class NativeUtils { if (filename != null) { parts = filename.split("\\.", 2); prefix = parts[0]; - suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null; // Thanks, davs! :-) + suffix = (parts.length > 1) ? "." + parts[1] : null; // Thanks, davs! :-) } // Check if the filename is okay - if (filename == null || prefix.length() < 3) { + if (filename == null || prefix.length() < MIN_PREFIX_LENGTH) { throw new IllegalArgumentException("The filename has to be at least 3 characters long."); } @@ -87,54 +97,41 @@ public class NativeUtils { throw new FileNotFoundException("File " + temp.getAbsolutePath() + " does not exist."); } - boolean tempFileIsPosix = false; - try { - if (FileSystems.getDefault() - .supportedFileAttributeViews() - .contains("posix")) { - // Assume POSIX compliant file system, can be deleted after loading. - tempFileIsPosix = true; - } - } catch (FileSystemNotFoundException - | ProviderNotFoundException - | SecurityException e) { - // Assume non-POSIX, and don't delete until last file descriptor closed. - } - - // Prepare buffer for data copying - byte[] buffer = new byte[1024]; - int readBytes; - - // Open and check input stream - InputStream is = NativeUtils.class.getResourceAsStream(path); - if (is == null) { + try (InputStream is = NativeUtils.class.getResourceAsStream(path)) { + Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + temp.delete(); + throw e; + } catch (NullPointerException e) { temp.delete(); throw new FileNotFoundException("File " + path + " was not found inside JAR."); } - // Open output stream and copy data between source file in JAR and the temporary file - OutputStream os = new FileOutputStream(temp); try { - while ((readBytes = is.read(buffer)) != -1) { - os.write(buffer, 0, readBytes); - } - } catch (Throwable e) { - temp.delete(); - throw e; - } finally { - // If read/write fails, close streams safely before throwing an exception - os.close(); - is.close(); - } - - try { - // Load the library System.load(temp.getAbsolutePath()); } finally { - if (tempFileIsPosix) + if (isPosixCompliant()) { + // Assume POSIX compliant file system, can be deleted after loading temp.delete(); - else + } 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; } } } From 9311279ed3697a53b3212a61ed2ca0c7d8d07e96 Mon Sep 17 00:00:00 2001 From: andreblanke Date: Tue, 24 Oct 2017 22:13:38 +0200 Subject: [PATCH 2/2] Add basic unit tests --- pom.xml | 8 +++++ .../java/cz/adamh/utils/NativeUtilsTest.java | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/test/java/cz/adamh/utils/NativeUtilsTest.java diff --git a/pom.xml b/pom.xml index 7d619d5..0bbd948 100644 --- a/pom.xml +++ b/pom.xml @@ -31,5 +31,13 @@ + + + junit + junit + 4.12 + test + + \ No newline at end of file diff --git a/src/test/java/cz/adamh/utils/NativeUtilsTest.java b/src/test/java/cz/adamh/utils/NativeUtilsTest.java new file mode 100644 index 0000000..ca1cd02 --- /dev/null +++ b/src/test/java/cz/adamh/utils/NativeUtilsTest.java @@ -0,0 +1,29 @@ +package cz.adamh.utils; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.junit.Test; + +public class NativeUtilsTest { + + @Test(expected=IllegalArgumentException.class) + public void testLoadLibraryIllegalPath() throws IOException { + NativeUtils.loadLibraryFromJar("libtest.so"); + } + + @Test(expected=IllegalArgumentException.class) + public void testLoadLibraryIllegalPrefix() throws IOException { + NativeUtils.loadLibraryFromJar("/l"); + } + + @Test(expected= FileNotFoundException.class) + public void testLoadLibraryNonExistentPath() throws IOException { + NativeUtils.loadLibraryFromJar("/libtest.so"); + } + + @Test(expected=NullPointerException.class) + public void testLoadLibraryNullPath() throws IOException { + NativeUtils.loadLibraryFromJar(null); + } +}