package net.java.nativelibsupport; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.List; import java.util.Properties; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import net.java.nativelibsupport.natives_config.Library; import net.java.nativelibsupport.natives_config.Library.Os; import net.java.nativelibsupport.natives_config.Library.Os.Cpu; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.filesystems.JarFileSystem; /** * Utility class for native library deployment. * @author Michael Bien */ public class NativeLibSupport { private static WeakReference<JAXBContext> reference; private NativeLibSupport() { } /** * Deployes, re-deployes or does nothing dependent on the currently deployed native libraries. * @param jarName the name of the library.jar * @param configFile InputStream which reads the xml config file * @param distFolder The distribution folder containing the library.jar and all native libraries in sub folders * @throws DeploymentException Thrown when libraries were not successfully deployed. */ public static void deploy(String jarName, InputStream configFile, File distributionFolder) throws LibDeploymentException { deploy(jarName, configFile, distributionFolder, null); } /** * Deployes, re-deployes or does nothing dependent on the currently deployed native libraries. * @param jarName the name of the library.jar * @param configFile InputStream which reads the xml config file * @param distFolder The distribution folder containing the library.jar and all native libraries in sub folders * @param archive The zip archive in which the native libraries are stored * @throws DeploymentException Thrown when libraries were not successfully deployed. */ public static void deploy(String jarName, InputStream configFile, File distributionFolder, String archive) throws LibDeploymentException { assert jarName!=null; assert configFile!=null; assert distributionFolder!=null; try{ JarFileSystem jarSystem = new JarFileSystem(); // read jogl version from manifest and compare with deployed version jarSystem.setJarFile(new File(distributionFolder+File.separator+jarName)); String jarVersion = jarSystem.getManifest().getMainAttributes().getValue("Implementation-Version"); if(jarVersion == null) throw new NullPointerException(jarSystem.getJarFile()+" has no 'Implementation-Version' property in manifest file"); String root = distributionFolder.getCanonicalPath(); root = root.substring(0, root.lastIndexOf(File.separator)); String libFolderPath = root + File.separator + "modules" + File.separator + "lib"; // read property file with deployed libraries version entries File propertyFile = new File(libFolderPath + File.separator + "deployed-natives.properties"); Properties properties = new Properties(); if(propertyFile.exists()) { properties.load(new FileInputStream(propertyFile)); }else{ propertyFile.getParentFile().mkdirs(); propertyFile.createNewFile(); } String deployedLibVersion = properties.getProperty(jarName, null); // check if we've already deployed if(deployedLibVersion == null || !jarVersion.equals(deployedLibVersion)) { // load native libraries configuration file Object obj = null; try { // jaxb context loading is expensive load it only once JAXBContext jc = getJAXBContext(); Unmarshaller unmarshaller = jc.createUnmarshaller(); obj = unmarshaller.unmarshal(configFile); } catch (JAXBException ex) { // Logger.getLogger(NativeLibSupport.class.getName()).log(Level.SEVERE, null, ex); throw new RuntimeException("error reading deployment file", ex); } if(obj instanceof Library == false) { throw new IllegalArgumentException("wrong root element in config file. 'Library' expected but got "+obj); } Library lib = (Library)obj; String osName = System.getProperty("os.name"); String cpuName = System.getProperty("os.arch"); // assamble path to platform dependent library folder StringBuilder path = new StringBuilder(); path.append(distributionFolder.getName()); path.append(File.separatorChar); FileObject libSource = null; // find library storage if(archive != null && archive.endsWith(".zip")) { path.append(archive); JarFileSystem jar = new JarFileSystem(); jar.setJarFile(new File(root+File.separator+path.toString())); path.delete(0, path.length()); path.append(lib.getName()); if(!lib.isFlat()) path.append(File.separatorChar); assambleLibPath(lib, osName, cpuName, path); path.append(".jar"); libSource = jar.findResource(path.toString()); }else{ path.append(lib.getName()); if(!lib.isFlat()) path.append(File.separatorChar); assambleLibPath(lib, osName, cpuName, path); libSource = FileUtil.toFileObject( new File(root+File.separator+path.toString())); } if(libSource != null) { FileObject libTargetFolder = FileUtil.createFolder(new File(libFolderPath)); if(libSource.isFolder()) { copyFolderEntries(libSource, libTargetFolder); }else{ extractArchive(libSource, libTargetFolder); } // update deployed version property properties.put(jarName, jarVersion); properties.store(new FileOutputStream(propertyFile), "deployed native libraries (remove entry and restart to force re-deployment)"); Logger.getLogger(NativeLibSupport.class.getName()).info( "deployed "+jarName+" version: "+jarVersion ); }else{ String os = System.getProperty("os.name"); String arch = System.getProperty("os.arch"); throw new LibDeploymentException( String.format("The %1$s native libraries are either not available"+ " for this system (OS: %2$s CPU: %3$s) or an error accrued while deploying", lib.getName(), os, arch)); } }else{ Logger.getLogger(NativeLibSupport.class.getName()).info( jarName+" version "+jarVersion +" is up to date"); } }catch(Exception ex) { throw new LibDeploymentException("can not deploy "+ jarName +" natives", ex); } } /** * overwrites files */ private static final void copyFolderEntries(FileObject src, FileObject targetFolder) throws IOException { FileObject[] entries = src.getChildren(); for (int i = 0; i < entries.length; i++) { FileObject entry = entries[i]; // delete old files (overwrite) FileObject old = targetFolder.getFileObject(entry.getNameExt()); if(old != null) { Logger.getLogger(NativeLibSupport.class.getName()).info( "remove old file: "+old.getPath() ); old.delete(); } // copy into target folder Logger.getLogger(NativeLibSupport.class.getName()).info( "copy "+entry.getPath() +" to "+targetFolder.getPath() ); FileUtil.copyFile(entry, targetFolder, entry.getName()); } } private static void extractArchive(FileObject libSource, FileObject libTargetFolder) throws IOException { FileUtil.extractJar(libTargetFolder, libSource.getInputStream()); } private final static void assambleLibPath(Library lib, String osName, String cpuName, StringBuilder nativesFolderPath) { List<Os> oses = lib.getOs(); for (Os os : oses) { Matcher osMatcher = Pattern.compile(os.getRegex()).matcher(osName); if(osMatcher.find()) { nativesFolderPath.append(os.getFolder()); if(!lib.isFlat()) nativesFolderPath.append(File.separatorChar); List<Cpu> cpus = os.getCpu(); for (Cpu cpu : cpus) { Matcher cpuMatcher = Pattern.compile(cpu.getRegex()).matcher(cpuName); if(cpuMatcher.find()) { nativesFolderPath.append(cpu.getFolder()); break; } } break; } } } private static final synchronized JAXBContext getJAXBContext() throws JAXBException { JAXBContext jc; if(reference == null) { jc = createJAXBContext(); reference = new WeakReference<JAXBContext>(jc); }else{ jc = reference.get(); if(jc == null) { jc = createJAXBContext(); reference = new WeakReference<JAXBContext>(jc); } } // System.out.println(jc); return jc; } private final static JAXBContext createJAXBContext() throws JAXBException { return JAXBContext.newInstance( "net.java.nativelibsupport.natives_config", NativeLibSupport.class.getClassLoader() ); } }