package com.aimmac23.node.jna; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.commons.io.FileUtils; import com.google.common.collect.FluentIterable; import com.google.common.io.Files; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.NativeLibrary; public class JnaLibraryLoader { private static final Logger log = Logger.getLogger(JnaLibraryLoader.class.getName()); static private LibVPX libVPX; static private YUVLib yuvLib; static private LibMKV libMKV; static private EncoderInterface encoder; // optional dependency static private XvfbScreenshotInterface xvfbInterface; private static void addNativePath(String path) { NativeLibrary.addSearchPath("vpx", path); NativeLibrary.addSearchPath("yuv", path); NativeLibrary.addSearchPath("mkv", path); NativeLibrary.addSearchPath("interface", path); NativeLibrary.addSearchPath("xvfb_interface", path); } private static File extractJNABinariesIfAvailable() { InputStream zipStream = JnaLibraryLoader.class.getClassLoader().getResourceAsStream("native.zip"); if(zipStream == null) { throw new IllegalStateException("Native code zip file not found"); } String targetDirectory = System.getProperty("java.io.tmpdir") + File.separator + "nativeCode-" + System.currentTimeMillis() + File.separator; File target = new File(targetDirectory); if(target.exists()) { target.delete(); } target.mkdir(); try { ZipInputStream zipInputStream = new ZipInputStream(zipStream); ZipEntry entry = null; while((entry = zipInputStream.getNextEntry()) != null){ File extractedFilePath = new File(targetDirectory + entry.getName()); if(entry.isDirectory()) { extractedFilePath.mkdirs(); continue; } FileOutputStream outputStream = new FileOutputStream(extractedFilePath); byte[] buffer = new byte[1024]; int size = 0; while((size = zipInputStream.read(buffer)) != -1){ outputStream.write(buffer, 0 , size); } outputStream.flush(); outputStream.close(); } zipInputStream.close(); zipStream.close(); addNativePath(targetDirectory); // at this point, we will have extracted the native libraries into a new temporary folder, containing directories // "32bit" and "64bit". There is no platform-independent way to figure out what bit-size this JVM uses, and no way to // change the search path of JNA once added, so we're going to copy things onto the search path instead and see what happens. return target; } catch(IOException e) { log.info("Caught IOException"); throw new IllegalStateException("Could not extract native libraries", e); } } public static void init() { File targetDirectory = extractJNABinariesIfAvailable(); addShutdownCleanupHook(targetDirectory); // this makes things work in Eclipse String classpath = System.getProperty("java.class.path"); String[] classpathEntries = classpath.split(File.pathSeparator); for(String entry : classpathEntries) { addNativePath(entry); } try { tryBitDepth(BitDepth.BIT_64, targetDirectory); } catch(Error e) { log.info("Could not load 64 bit native libraries - attempting 32 bit instead"); deleteAllFilesInDirectory(targetDirectory); tryBitDepth(BitDepth.BIT_32, targetDirectory); } } private static void deleteAllFilesInDirectory(File directory) { File[] files = directory.listFiles(); for(File file : files) { if(file.isFile()) { file.delete(); } } } private static void tryLoadLibraries() { libVPX = (LibVPX) Native.loadLibrary("vpx", LibVPX.class); yuvLib = (YUVLib) Native.loadLibrary("yuv", YUVLib.class); libMKV = (LibMKV) Native.loadLibrary("mkv", LibMKV.class); encoder = (EncoderInterface) Native.loadLibrary("interface", EncoderInterface.class); } private static void tryBitDepth(BitDepth depth, File targetDirectory) { File sourceDirectory = new File(targetDirectory, depth.getDirectoryName()); if(!sourceDirectory.exists()) { throw new IllegalStateException("Native code directory not found for bit depth: " + depth); } File[] filesToCopy = sourceDirectory.listFiles(); for(File file : filesToCopy) { File destinationLocation = new File(targetDirectory, file.getName()); try { FileUtils.copyFile(file, destinationLocation); } catch(IOException e) { throw new IllegalStateException("Could not copy file: " + file + " to: " + destinationLocation, e); } } tryLoadLibraries(); } private static void disposeLibrary(Library lib, String name) { if(lib != null) { NativeLibrary.getInstance(name).dispose(); } } private static synchronized void addShutdownCleanupHook(final File extractedDirectory) { Thread shutdownThread = new Thread(new Runnable() { @Override public void run() { // close the native libraries in reverse order disposeLibrary(xvfbInterface, "xvfb_interface"); disposeLibrary(encoder, "interface"); disposeLibrary(libMKV, "mkv"); disposeLibrary(yuvLib, "yuv"); disposeLibrary(libVPX, "vpx"); // Java File doesn't want to recursively delete things Iterator<File> iterator = Files.fileTreeTraverser().postOrderTraversal(extractedDirectory).iterator(); while(iterator.hasNext()) { iterator.next().delete(); } } }, "JNA Shutdown Hook Thread"); Runtime.getRuntime().addShutdownHook(shutdownThread); } public static EncoderInterface getEncoder() { return encoder; } public static YUVLib getYuvLib() { return yuvLib; } public static LibVPX getLibVPX() { return libVPX; } public static XvfbScreenshotInterface getXvfbInterface() { if(xvfbInterface == null) { xvfbInterface = (XvfbScreenshotInterface) Native.loadLibrary("xvfb_interface", XvfbScreenshotInterface.class); } return xvfbInterface; } private static enum BitDepth { BIT_32("32bit"), BIT_64("64bit"); private String directoryName; private BitDepth(String directoryName) { this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } } }