/** * Copyright (c) 2011, JFXtras * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Tom Eugelink * */ abstract public class ApplicationFx extends javafx.application.Application { // logger logger final static Logger logger = Logger .getLogger(ApplicationFx.class.getName()); /** * Do some prelaunch things like * * @param args */ public static void prelaunch() { prelaunchUnpackOSFilesToBinDirectory(); } /** * Unpack files from the jar to ../lib so JavaFX's loader can find them. An * alternative is to unpack the JAR to a temp folder and have * java.library.path point to it. JavaFX first tries ../bin and if not foud * then System.loadLibrary. * * @param args */ public static void prelaunchUnpackOSFilesToBinDirectory() { try { // find the JavaFX windows binaries jar File lJavaFXBinFile = findResourceFile("jfxmedia.dll"); if (lJavaFXBinFile == null) findResourceFile("jfxmedia.so"); if (lJavaFXBinFile == null) return; if (logger.isLoggable(Level.FINE)) logger.fine("Using JavaFX OS libraries from " + lJavaFXBinFile.getAbsolutePath()); // create an temp unpack directory // first create a temp file (because Java only supports temp files) File lJFXtrasTempdir = File.createTempFile("jfxtras_", ""); // delete the temp file if (!lJFXtrasTempdir.delete()) throw new IOException( "Could not delete temp file in order to recreate it as a dir: " + lJFXtrasTempdir.getAbsolutePath()); // create a fixed temp dir in the parent directory // Using a fixed temp directory prevents every run of a JavaFX / // JFXtras application to create, unpack and leave behind a new // directory // And further more it will improve startup time, because it if the // directory already exist, unpacking is skipped (small risc // involved here when unpacking went wrong the previous time) lJFXtrasTempdir = new File(lJFXtrasTempdir.getParent(), "jfxtras"); if (lJFXtrasTempdir.exists() == false && !lJFXtrasTempdir.mkdirs()) throw new IOException("Could not create dir: " + lJFXtrasTempdir.getAbsolutePath()); // create the unpack directory for the bin jar inside the tempdir // There may be multiple versions of the bin, so each version is // unpacked to a separate directory. File lUnpackToDir = new File(lJFXtrasTempdir, lJavaFXBinFile.getName() + ".unpacked"); if (logger.isLoggable(Level.FINE)) logger.fine("Unpacking to " + lUnpackToDir.getAbsolutePath()); // create it if (lUnpackToDir.exists()) { lUnpackToDir.mkdirs(); } // unpack the jar to the bin directory @SuppressWarnings("resource") JarFile lJarFile = new JarFile(lJavaFXBinFile); for (Enumeration<JarEntry> lEnum = lJarFile.entries(); lEnum .hasMoreElements();) { // each element JarEntry lJarEntry = lEnum.nextElement(); File lLibFile = new File(lUnpackToDir, lJarEntry.getName()); // if directory, create it if (lJarEntry.isDirectory()) { if (lLibFile.exists() == false) lLibFile.mkdirs(); continue; } // copy entry contents if (lJarEntry.getTime() == -1 // if the jar does not know how // old it is || lLibFile.exists() == false // if the destination does // not exist || lJarEntry.getTime() > lLibFile.lastModified() // if // the // jar // file // is // newer // that // the // destination ) { if (logger.isLoggable(Level.FINE)) logger.fine("Unpacking " + lLibFile.getAbsolutePath()); try { InputStream lInputStream = lJarFile .getInputStream(lJarEntry); OutputStream lOutputStream = new BufferedOutputStream( new FileOutputStream(lLibFile)); try { // copy copy(lInputStream, lOutputStream); // if the jar knows how old it is, copy that // information so we know if we can skip it next // time if (lJarEntry.getTime() != -1) lLibFile.setLastModified(lJarEntry.getTime()); } finally { lInputStream.close(); lOutputStream.close(); } } catch (Throwable e) { if (logger.isLoggable(Level.INFO)) logger.info(e.getMessage()); } } else if (logger.isLoggable(Level.FINE)) logger.fine("File is already up to date: " + lLibFile.getAbsolutePath()); } // add the bin directory to java.libary.path String lJavaLibraryPathId = "java.library.path"; String lJavaLibraryPath = System.getProperty(lJavaLibraryPathId); lJavaLibraryPath = lUnpackToDir.getAbsolutePath() + File.pathSeparator + (lJavaLibraryPath == null ? "" : lJavaLibraryPath); System.setProperty(lJavaLibraryPathId, lJavaLibraryPath); // Changing the system property after Java is started doesn't have // any effect, since the property is evaluated very early and // cached. // But the guys over at JDIC discovered a way how to work around it. // A bit dirty, but it'll do the job. // http://blog.cedarsoft.com/2010/11/setting-java-library-path-programmatically/ Field lFieldSysPath = ClassLoader.class .getDeclaredField("sys_paths"); lFieldSysPath.setAccessible(true); lFieldSysPath.set(null, null); } catch (Throwable e) { logger.warning(e.getMessage()); } } /** * find a resource and and then the file it is in * * @param resource * @return */ static private File findResourceFile(String resource) { try { // find the JavaFX runtime JAR URL lURL = null; for (Enumeration<URL> lEnum = ApplicationFx.class.getClassLoader() .getResources(resource); lEnum.hasMoreElements();) { lURL = lEnum.nextElement(); break; } if (lURL == null) return null; // not found if (lURL.toString().startsWith("jar:") == false) return null; // not in a jar if (lURL.toString().startsWith("jar:file:") == false) return null; // not in a jar as a file // extract file String lFilename = lURL.getFile(); if (lFilename.contains(".jar!/")) { lFilename = lFilename.substring(0, lFilename.indexOf(".jar!/") + 4); // strip the in-jar // part } if (lFilename.startsWith("file:/") == false) return null; // not a file if (lFilename.startsWith("file:///") == true) lFilename = lFilename.substring(8); // strip file:/// if (lFilename.startsWith("file:/") == true) lFilename = lFilename.substring(6); // strip file:/ lFilename = URLDecoder.decode(lFilename, "UTF-8"); // create file File lFile = new File(lFilename); // done return lFile; } catch (IOException e) { throw new RuntimeException(e); } } /** * copy all bytes from one stream to another (by now this should be a * standard util method) * * @param in * @param out * @throws IOException */ static public void copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) >= 0) { out.write(buffer, 0, len); } in.close(); out.close(); } }