summaryrefslogtreecommitdiffstats
path: root/src/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java')
-rw-r--r--src/java/com/jogamp/common/util/JarUtil.java347
-rw-r--r--src/java/com/jogamp/common/util/cache/TempFileCache.java480
-rw-r--r--src/java/com/jogamp/common/util/cache/TempJarCache.java224
3 files changed, 1051 insertions, 0 deletions
diff --git a/src/java/com/jogamp/common/util/JarUtil.java b/src/java/com/jogamp/common/util/JarUtil.java
new file mode 100644
index 0000000..a2e6be5
--- /dev/null
+++ b/src/java/com/jogamp/common/util/JarUtil.java
@@ -0,0 +1,347 @@
+/**
+ * Copyright 2011 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community OR
+ * CONTRIBUTORS 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.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.common.util;
+
+import java.io.BufferedInputStream;
+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.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.cert.Certificate;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import com.jogamp.common.os.NativeLibrary;
+
+import jogamp.common.Debug;
+
+public class JarUtil {
+ private static final boolean VERBOSE = Debug.isPropertyDefined("jogamp.debug.JARUtil", true, AccessController.getContext());
+
+ /**
+ * @param clazzBinName com.jogamp.common.util.cache.TempJarCache
+ * @param cl
+ * @return jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/
+ * @throws IOException
+ * @see {@link IOUtil#getClassURL(String, ClassLoader)}
+ */
+ public static URL getJarURL(String clazzBinName, ClassLoader cl) throws IOException {
+ URL url = IOUtil.getClassURL(clazzBinName, cl);
+ if(null != url) {
+ String urlS = url.toExternalForm();
+ // from
+ // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class
+ // to
+ // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/
+ urlS = urlS.substring(0, urlS.lastIndexOf('!')+2); // include !/
+ return new URL(urlS);
+ }
+ return null;
+ }
+
+ /**
+ *
+ * @param jarURL jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class
+ * @return file:/usr/local/projects/JOGL/gluegen/build-x86_64/
+ * @throws IOException
+ */
+ public static URL getJarURLDirname(URL jarURL) throws IOException {
+ String urlS = jarURL.toExternalForm();
+ // from
+ // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/com/jogamp/common/util/cache/TempJarCache.class
+ // to
+ // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar
+ urlS = urlS.substring(0, urlS.lastIndexOf('!')); // exclude !/
+
+ // from
+ // jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar
+ // to
+ // file:/usr/local/projects/JOGL/gluegen/build-x86_64/
+ urlS = urlS.substring(4, urlS.lastIndexOf('/')+1); // include / exclude jar:
+ return new URL(urlS);
+ }
+
+ /**
+ *
+ * @param baseUrl file:/usr/local/projects/JOGL/gluegen/build-x86_64/
+ * @param jarFileName gluegen-rt.jar
+ * @return jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/
+ * @throws IOException
+ */
+ public static URL getJarURL(URL baseUrl, String jarFileName) throws IOException {
+ return new URL("jar:"+baseUrl.toExternalForm()+jarFileName+"!/");
+ }
+
+ /**
+ *
+ * @param clazzBinName com.jogamp.common.util.cache.TempJarCache
+ * @param cl domain
+ * @return JarFile containing the named class within the given ClassLoader
+ * @throws IOException
+ * @see {@link #getJarURL(String, ClassLoader)}
+ */
+ public static JarFile getJarFile(String clazzBinName, ClassLoader cl) throws IOException {
+ return getJarFile(getJarURL(clazzBinName, cl), cl);
+ }
+
+ /**
+ *
+ * @param jarURL jar:file:/usr/local/projects/JOGL/gluegen/build-x86_64/gluegen-rt.jar!/
+ * @param cl domain
+ * @return JarFile as named by URL within the given ClassLoader
+ * @throws IOException
+ */
+ public static JarFile getJarFile(URL jarUrl, ClassLoader cl) throws IOException {
+ if(null != jarUrl) {
+ URLConnection urlc = jarUrl.openConnection();
+ if(urlc instanceof JarURLConnection) {
+ JarURLConnection jarConnection = (JarURLConnection)jarUrl.openConnection();
+ JarFile jarFile = jarConnection.getJarFile();
+ return jarFile;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return a map from native-lib-base-name to entry-name.
+ */
+ public static Map<String, String> getNativeLibNames(JarFile jarFile) {
+ if (VERBOSE) {
+ System.err.println("getNativeLibNames: "+jarFile);
+ }
+
+ Map<String,String> nameMap = new HashMap<String, String>();
+ Enumeration<JarEntry> entries = jarFile.entries();
+
+ while (entries.hasMoreElements()) {
+ final JarEntry entry = entries.nextElement();
+ final String entryName = entry.getName();
+ final String baseName = NativeLibrary.isValidNativeLibraryName(entryName, false);
+
+ if(null != baseName) {
+ nameMap.put(baseName, entryName);
+ }
+ }
+
+ return nameMap;
+ }
+
+ /**
+ * Extract the files of the given jar file.
+ * <p>
+ * If <code>extractNativeLibraries</code> is true,
+ * native libraries are added to the given <code>nativeLibMap</code>
+ * with the base name to temp file location.<br>
+ * A file is identified as a native library,
+ * if it's name complies with the running platform's native library naming scheme.<br>
+ * Root entries are favored over non root entries in case of naming collisions.<br>
+ * Example on a Unix like machine:<br>
+ * <pre>
+ * mylib.jar!/sub1/libsour.so -> sour (mapped, unique name)
+ * mylib.jar!/sub1/libsweet.so (dropped, root entry favored)
+ * mylib.jar!/libsweet.so -> sweet (mapped, root entry favored)
+ * mylib.jar!/sweet.dll -> (dropped, not a unix library name)
+ * </pre>
+ * </p>
+ * <p>
+ * In order to be compatible with Java Web Start, we need
+ * to extract all root entries from the jar file.<br>
+ * In this case, set all flags to true <code>extractNativeLibraries </code>.
+ * <code>extractClassFiles</code>, <code>extractOtherFiles</code>.
+ * </p>
+ *
+ * @param dest
+ * @param nativeLibMap
+ * @param jarFile
+ * @param deepDirectoryTraversal
+ * @param extractNativeLibraries
+ * @param extractClassFiles
+ * @param extractOtherFiles
+ * @return
+ * @throws IOException
+ */
+ public static int extract(File dest, Map<String, String> nativeLibMap,
+ JarFile jarFile,
+ boolean extractNativeLibraries,
+ boolean extractClassFiles,
+ boolean extractOtherFiles) throws IOException {
+
+ if (VERBOSE) {
+ System.err.println("extractNativeLibs: "+jarFile.getName()+" -> "+dest+
+ ", extractNativeLibraries "+extractNativeLibraries+
+ ", extractClassFiles"+extractClassFiles+
+ ", extractOtherFiles "+extractOtherFiles);
+ }
+ int num = 0;
+
+ Enumeration<JarEntry> entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = (JarEntry) entries.nextElement();
+ String entryName = entry.getName();
+
+ // Match entries with correct prefix and suffix (ignoring case)
+ final String libBaseName = NativeLibrary.isValidNativeLibraryName(entryName, false);
+ final boolean isNativeLib = null != libBaseName;
+ if(isNativeLib && !extractNativeLibraries) {
+ if (VERBOSE) {
+ System.err.println("JarEntry : " + entryName + " native-lib skipped");
+ }
+ continue;
+ }
+
+ final boolean isClassFile = entryName.endsWith(".class");
+ if(isClassFile && !extractClassFiles) {
+ if (VERBOSE) {
+ System.err.println("JarEntry : " + entryName + " class-file skipped");
+ }
+ continue;
+ }
+
+ if(!isNativeLib && !isClassFile && !extractOtherFiles) {
+ if (VERBOSE) {
+ System.err.println("JarEntry : " + entryName + " other-file skipped");
+ }
+ continue;
+ }
+
+ boolean isDir = entryName.endsWith("/");
+
+ boolean isRootEntry = entryName.indexOf('/') == -1 &&
+ entryName.indexOf(File.separatorChar) == -1;
+
+ // strip prefix & suffix
+ final File destFile = new File(dest, entryName);
+ if(isDir) {
+ destFile.mkdir();
+ if (VERBOSE) {
+ System.err.println("MKDIR: " + entryName + " -> " + destFile );
+ }
+ } else {
+ final InputStream in = new BufferedInputStream(jarFile.getInputStream(entry));
+ final OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile));
+ int numBytes = -1;
+ try {
+ numBytes = IOUtil.copyStream2Stream(in, out, -1);
+ } finally {
+ in.close();
+ out.close();
+ }
+ boolean addedAsNativeLib = false;
+ if (numBytes>0) {
+ num++;
+ if (isNativeLib && ( isRootEntry || !nativeLibMap.containsKey(libBaseName) ) ) {
+ nativeLibMap.put(libBaseName, destFile.getAbsolutePath());
+ addedAsNativeLib = true;
+ }
+ }
+ if (VERBOSE) {
+ System.err.println("EXTRACT["+num+"]: [" + libBaseName + " -> ] " + entryName + " -> " + destFile + ": "+numBytes+" bytes, addedAsNativeLib: "+addedAsNativeLib);
+ }
+ }
+ }
+ return num;
+ }
+
+ /**
+ * Validate the certificates for each native Lib in the jar file.
+ * Throws an IOException if any certificate is not valid.
+ * <pre>
+ Certificate[] appletLauncherCerts = Something.class.getProtectionDomain().
+ getCodeSource().getCertificates();
+ </pre>
+ */
+ public static void validateCertificates(Certificate[] appletLauncherCerts, JarFile jarFile)
+ throws IOException {
+
+ if (VERBOSE) {
+ System.err.println("validateCertificates:");
+ }
+
+ byte[] buf = new byte[1000];
+ Enumeration<JarEntry> entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = (JarEntry) entries.nextElement();
+ String entryName = entry.getName();
+
+ if (VERBOSE) {
+ System.err.println("Validate JarEntry : " + entryName);
+ }
+
+ if (!checkNativeCertificates(appletLauncherCerts, jarFile, entry, buf)) {
+ throw new IOException("Cannot validate certificate for " + entryName);
+ }
+ }
+
+ }
+
+ /**
+ * Check the certificates with the ones in the jar file
+ * (all must match).
+ */
+ private static boolean checkNativeCertificates(Certificate[] launchedCerts,
+ JarFile jar, JarEntry entry, byte[] buf) throws IOException {
+
+ // API states that we must read all of the data from the entry's
+ // InputStream in order to be able to get its certificates
+
+ InputStream is = jar.getInputStream(entry);
+ while (is.read(buf) > 0) { }
+ is.close();
+
+ if (launchedCerts == null || launchedCerts.length == 0) {
+ throw new RuntimeException("Null certificates passed");
+ }
+
+ // Get the certificates for the JAR entry
+ Certificate[] nativeCerts = entry.getCertificates();
+ if (nativeCerts == null || nativeCerts.length == 0) {
+ return false;
+ }
+
+ int checked = 0;
+ for (int i = 0; i < launchedCerts.length; i++) {
+ for (int j = 0; j < nativeCerts.length; j++) {
+ if (nativeCerts[j].equals(launchedCerts[i])){
+ checked++;
+ break;
+ }
+ }
+ }
+ return (checked == launchedCerts.length);
+ }
+}
diff --git a/src/java/com/jogamp/common/util/cache/TempFileCache.java b/src/java/com/jogamp/common/util/cache/TempFileCache.java
new file mode 100644
index 0000000..2145239
--- /dev/null
+++ b/src/java/com/jogamp/common/util/cache/TempFileCache.java
@@ -0,0 +1,480 @@
+/**
+ * Copyright 2011 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community OR
+ * CONTRIBUTORS 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.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.common.util.cache;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.security.AccessController;
+
+import jogamp.common.Debug;
+
+public class TempFileCache {
+ private static final boolean VERBOSE = Debug.isPropertyDefined("jogamp.debug.TempFileCache", true, AccessController.getContext());
+
+ // Lifecycle: For all JVMs, ClassLoader and times.
+ private static final String tmpDirPrefix = "jogamp.tmp.cache";
+
+ // Get the value of the tmproot system property
+ // Lifecycle: For all JVMs and ClassLoader
+ private static final String tmpRootPropName = "jnlp.jogamp.tmp.cache.root";
+
+ // Flag indicating that we got a fatal error in the static initializer.
+ private static boolean staticInitError = false;
+
+ private static File tmpBaseDir;
+
+ // String representing the name of the temp root directory relative to the
+ // tmpBaseDir. Its value is "jlnNNNNN", which is the unique filename created
+ // by File.createTempFile() without the ".tmp" extension.
+ //
+ // Lifecycle: For all JVMs and ClassLoader
+ //
+ private static String tmpRootPropValue;
+
+ private static File tmpRootDir;
+
+ // Flag indicating that we got a fatal error in the initializer.
+ private boolean initError = false;
+
+ private File individualTmpDir;
+
+ static {
+ // Create / initialize the temp root directory, starting the Reaper
+ // thread to reclaim old installations if necessary. If we get an
+ // exception, set an error code.
+ try {
+ initTmpRoot();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ staticInitError = true;
+ }
+ }
+
+ /**
+ * Documented way to kick off static initialization
+ * @return true is static initialization was successful
+ */
+ public static boolean initSingleton() {
+ return !staticInitError;
+ }
+
+ /**
+ * This method is called by the static initializer to create / initialize
+ * the temp root directory that will hold the temp directories for this
+ * instance of the JVM. This is done as follows:
+ *
+ * 1. Synchronize on a global lock. Note that for this purpose we will
+ * use System.out in the absence of a true global lock facility.
+ * We are careful not to hold this lock too long.
+ *
+ * 2. Check for the existence of the "jnlp.applet.launcher.tmproot"
+ * system property.
+ *
+ * a. If set, then some other thread in a different ClassLoader has
+ * already created the tmprootdir, so we just need to
+ * use it. The remaining steps are skipped.
+ *
+ * b. If not set, then we are the first thread in this JVM to run,
+ * and we need to create the the tmprootdir.
+ *
+ * 3. Create the tmprootdir, along with the appropriate locks.
+ * Note that we perform the operations in the following order,
+ * prior to creating tmprootdir itself, to work around the fact that
+ * the file creation and file lock steps are not atomic, and we need
+ * to ensure that a newly-created tmprootdir isn't reaped by a
+ * concurrently running JVM.
+ *
+ * create jlnNNNN.tmp using File.createTempFile()
+ * lock jlnNNNN.tmp
+ * create jlnNNNN.lck while holding the lock on the .tmp file
+ * lock jlnNNNN.lck
+ *
+ * Since the Reaper thread will enumerate the list of *.lck files
+ * before starting, we can guarantee that if there exists a *.lck file
+ * for an active process, then the corresponding *.tmp file is locked
+ * by that active process. This guarantee lets us avoid reaping an
+ * active process' files.
+ *
+ * 4. Set the "jnlp.applet.launcher.tmproot" system property.
+ *
+ * 5. Add a shutdown hook to cleanup jlnNNNN.lck and jlnNNNN.tmp. We
+ * don't actually expect that this shutdown hook will ever be called,
+ * but the act of doing this, ensures that the locks never get
+ * garbage-collected, which is necessary for correct behavior when
+ * the first ClassLoader is later unloaded, while subsequent Applets
+ * are still running.
+ *
+ * 6. Start the Reaper thread to cleanup old installations.
+ */
+ private static void initTmpRoot() throws IOException {
+ if (VERBOSE) {
+ System.err.println("TempFileCache Static Initialization ----------------------------------------------");
+ System.err.println("Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode()));
+ }
+
+ synchronized (System.out) {
+
+ // Get the name of the tmpbase directory.
+ String tmpBaseName = System.getProperty("java.io.tmpdir") + File.separator + tmpDirPrefix ;
+ tmpBaseDir = new File(tmpBaseName);
+
+ tmpRootPropValue = System.getProperty(tmpRootPropName);
+
+ if (tmpRootPropValue == null) {
+ // Create the tmpbase directory if it doesn't already exist
+ tmpBaseDir.mkdir();
+ if (!tmpBaseDir.isDirectory()) {
+ throw new IOException("Cannot create directory " + tmpBaseDir);
+ }
+
+ // Create ${tmpbase}/jlnNNNN.tmp then lock the file
+ File tmpFile = File.createTempFile("jln", ".tmp", tmpBaseDir);
+ if (VERBOSE) {
+ System.err.println("tmpFile = " + tmpFile.getAbsolutePath());
+ }
+ final FileOutputStream tmpOut = new FileOutputStream(tmpFile);
+ final FileChannel tmpChannel = tmpOut.getChannel();
+ final FileLock tmpLock = tmpChannel.lock();
+
+ // Strip off the ".tmp" to get the name of the tmprootdir
+ String tmpFileName = tmpFile.getAbsolutePath();
+ String tmpRootName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp"));
+
+ // create ${tmpbase}/jlnNNNN.lck then lock the file
+ String lckFileName = tmpRootName + ".lck";
+ File lckFile = new File(lckFileName);
+ if (VERBOSE) {
+ System.err.println("lckFile = " + lckFile.getAbsolutePath());
+ }
+ lckFile.createNewFile();
+ final FileOutputStream lckOut = new FileOutputStream(lckFile);
+ final FileChannel lckChannel = lckOut.getChannel();
+ final FileLock lckLock = lckChannel.lock();
+
+ // Create tmprootdir
+ tmpRootDir = new File(tmpRootName);
+ if (VERBOSE) {
+ System.err.println("tmpRootDir = " + tmpRootDir.getAbsolutePath());
+ }
+ if (!tmpRootDir.mkdir()) {
+ throw new IOException("Cannot create " + tmpRootDir);
+ }
+
+ // Add shutdown hook to cleanup the OutputStream, FileChannel,
+ // and FileLock for the jlnNNNN.lck and jlnNNNN.lck files.
+ // We do this so that the locks never get garbage-collected.
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ /* @Override */
+ public void run() {
+ // NOTE: we don't really expect that this code will ever
+ // be called. If it does, we will close the output
+ // stream, which will in turn close the channel.
+ // We will then release the lock.
+ try {
+ tmpOut.close();
+ tmpLock.release();
+ lckOut.close();
+ lckLock.release();
+ } catch (IOException ex) {
+ // Do nothing
+ }
+ }
+ });
+
+ // Set the system property...
+ tmpRootPropValue = tmpRootName.substring(tmpRootName.lastIndexOf(File.separator) + 1);
+ System.setProperty(tmpRootPropName, tmpRootPropValue);
+ if (VERBOSE) {
+ System.err.println("Setting " + tmpRootPropName + "=" + tmpRootPropValue);
+ }
+
+ // Start a new Reaper thread to do stuff...
+ Thread reaperThread = new Thread() {
+ /* @Override */
+ public void run() {
+ deleteOldTempDirs();
+ }
+ };
+ reaperThread.setName("AppletLauncher-Reaper");
+ reaperThread.start();
+ } else {
+ // Make sure that the property is not set to an illegal value
+ if (tmpRootPropValue.indexOf('/') >= 0 ||
+ tmpRootPropValue.indexOf(File.separatorChar) >= 0) {
+ throw new IOException("Illegal value of: " + tmpRootPropName);
+ }
+
+ // Set tmpRootDir = ${tmpbase}/${jnlp.applet.launcher.tmproot}
+ if (VERBOSE) {
+ System.err.println("Using existing value of: " +
+ tmpRootPropName + "=" + tmpRootPropValue);
+ }
+ tmpRootDir = new File(tmpBaseDir, tmpRootPropValue);
+ if (VERBOSE) {
+ System.err.println("tmpRootDir = " + tmpRootDir.getAbsolutePath());
+ }
+ if (!tmpRootDir.isDirectory()) {
+ throw new IOException("Cannot access " + tmpRootDir);
+ }
+ }
+ }
+ if (VERBOSE) {
+ System.err.println("------------------------------------------------------------------ (static ok: "+(!staticInitError)+")");
+ }
+ }
+
+ /**
+ * Called by the Reaper thread to delete old temp directories
+ * Only one of these threads will run per JVM invocation.
+ */
+ private static void deleteOldTempDirs() {
+ if (VERBOSE) {
+ System.err.println("*** Reaper: deleteOldTempDirs in " +
+ tmpBaseDir.getAbsolutePath());
+ }
+
+ // enumerate list of jnl*.lck files, ignore our own jlnNNNN file
+ final String ourLockFile = tmpRootPropValue + ".lck";
+ FilenameFilter lckFilter = new FilenameFilter() {
+ /* @Override */
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".lck") && !name.equals(ourLockFile);
+ }
+ };
+
+ // For each file <file>.lck in the list we will first try to lock
+ // <file>.tmp if that succeeds then we will try to lock <file>.lck
+ // (which should always succeed unless there is a problem). If we can
+ // get the lock on both files, then it must be an old installation, and
+ // we will delete it.
+ String[] fileNames = tmpBaseDir.list(lckFilter);
+ if (fileNames != null) {
+ for (int i = 0; i < fileNames.length; i++) {
+ String lckFileName = fileNames[i];
+ String tmpDirName = lckFileName.substring(0, lckFileName.lastIndexOf(".lck"));
+ String tmpFileName = tmpDirName + ".tmp";
+
+ File lckFile = new File(tmpBaseDir, lckFileName);
+ File tmpFile = new File(tmpBaseDir, tmpFileName);
+ File tmpDir = new File(tmpBaseDir, tmpDirName);
+
+ if (lckFile.exists() && tmpFile.exists() && tmpDir.isDirectory()) {
+ FileOutputStream tmpOut = null;
+ FileChannel tmpChannel = null;
+ FileLock tmpLock = null;
+
+ try {
+ tmpOut = new FileOutputStream(tmpFile);
+ tmpChannel = tmpOut.getChannel();
+ tmpLock = tmpChannel.tryLock();
+ } catch (Exception ex) {
+ // Ignore exceptions
+ if (VERBOSE) {
+ ex.printStackTrace();
+ }
+ }
+
+ if (tmpLock != null) {
+ FileOutputStream lckOut = null;
+ FileChannel lckChannel = null;
+ FileLock lckLock = null;
+
+ try {
+ lckOut = new FileOutputStream(lckFile);
+ lckChannel = lckOut.getChannel();
+ lckLock = lckChannel.tryLock();
+ } catch (Exception ex) {
+ if (VERBOSE) {
+ ex.printStackTrace();
+ }
+ }
+
+ if (lckLock != null) {
+ // Recursively remove the old tmpDir and all of
+ // its contents
+ removeAll(tmpDir);
+
+ // Close the streams and delete the .lck and .tmp
+ // files. Note that there is a slight race condition
+ // in that another process could open a stream at
+ // the same time we are trying to delete it, which will
+ // prevent deletion, but we won't worry about it, since
+ // the worst that will happen is we might have an
+ // occasional 0-byte .lck or .tmp file left around
+ try {
+ lckOut.close();
+ } catch (IOException ex) {
+ }
+ lckFile.delete();
+ try {
+ tmpOut.close();
+ } catch (IOException ex) {
+ }
+ tmpFile.delete();
+ } else {
+ try {
+ // Close the file and channel for the *.lck file
+ if (lckOut != null) {
+ lckOut.close();
+ }
+ // Close the file/channel and release the lock
+ // on the *.tmp file
+ tmpOut.close();
+ tmpLock.release();
+ } catch (IOException ex) {
+ if (VERBOSE) {
+ ex.printStackTrace();
+ }
+ }
+ }
+ }
+ } else {
+ if (VERBOSE) {
+ System.err.println(" Skipping: " + tmpDir.getAbsolutePath());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the specified file or directory. If "path" is a directory, then
+ * recursively remove all entries, then remove the directory itself.
+ */
+ private static void removeAll(File path) {
+ if (VERBOSE) {
+ System.err.println("removeAll(" + path + ")");
+ }
+
+ if (path.isDirectory()) {
+ // Recursively remove all files/directories in this directory
+ File[] list = path.listFiles();
+ if (list != null) {
+ for (int i = 0; i < list.length; i++) {
+ removeAll(list[i]);
+ }
+ }
+ }
+
+ path.delete();
+ }
+
+ public TempFileCache () {
+ if (VERBOSE) {
+ System.err.println("new TempFileCache() --------------------- (static ok: "+(!staticInitError)+")");
+ System.err.println("Thread: "+Thread.currentThread().getName()+", CL 0x"+Integer.toHexString(TempFileCache.class.getClassLoader().hashCode())+", this 0x"+Integer.toHexString(hashCode()));
+ }
+ if(!staticInitError) {
+ try {
+ createTmpDir();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ initError = true;
+ }
+ }
+ if (VERBOSE) {
+ System.err.println("tempDir: "+individualTmpDir+" (ok: "+(!initError)+")");
+ System.err.println("----------------------------------------------------------");
+ }
+ }
+
+ /**
+ * @return true is static and object initialization was successful
+ */
+ public boolean isValid() { return !staticInitError && !initError; }
+
+ /**
+ * Base temp directory used by TempFileCache.
+ * Lifecycle: For all JVMs, ClassLoader and times.
+ *
+ * This is set to:
+ *
+ * ${java.io.tmpdir}/<tmpDirPrefix>
+ *
+ *
+ * @return
+ */
+ public File getBaseDir() { return tmpBaseDir; }
+
+ /**
+ * Root temp directory for this JVM instance. Used to store individual
+ * directories.
+ *
+ * Lifecycle: For all JVMs and ClassLoader
+ *
+ * <tmpBaseDir>/<tmpRootPropValue>
+ *
+ * Use Case: Per ClassLoader files, eg. native libraries.
+ *
+ * Old temp directories are cleaned up the next time a JVM is launched that
+ * uses TempFileCache.
+ *
+ *
+ * @return
+ */
+ public File getRootDir() { return tmpRootDir; }
+
+ /**
+ * Temporary directory for individual files (eg. native libraries of one ClassLoader instance).
+ * The directory name is:
+ *
+ * Lifecycle: Within each JVM .. use case dependent, ie. per ClassLoader
+ *
+ * <tmpRootDir>/jlnMMMMM
+ *
+ * where jlnMMMMM is the unique filename created by File.createTempFile()
+ * without the ".tmp" extension.
+ *
+ *
+ * @return
+ */
+ public File getTempDir() { return individualTmpDir; }
+
+
+ /**
+ * Create the temp directory in tmpRootDir. To do this, we create a temp
+ * file with a ".tmp" extension, and then create a directory of the
+ * same name but without the ".tmp". The temp file, directory, and all
+ * files in the directory will be reaped the next time this is started.
+ * We avoid deleteOnExit, because it doesn't work reliably.
+ */
+ private void createTmpDir() throws IOException {
+
+ File tmpFile = File.createTempFile("jln", ".tmp", tmpRootDir);
+ String tmpFileName = tmpFile.getAbsolutePath();
+ String tmpDirName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp"));
+ individualTmpDir = new File(tmpDirName);
+ if (!individualTmpDir.mkdir()) {
+ throw new IOException("Cannot create " + individualTmpDir);
+ }
+ }
+}
diff --git a/src/java/com/jogamp/common/util/cache/TempJarCache.java b/src/java/com/jogamp/common/util/cache/TempJarCache.java
new file mode 100644
index 0000000..efdf113
--- /dev/null
+++ b/src/java/com/jogamp/common/util/cache/TempJarCache.java
@@ -0,0 +1,224 @@
+/**
+ * Copyright 2011 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community OR
+ * CONTRIBUTORS 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.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.common.util.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarFile;
+
+
+import com.jogamp.common.os.NativeLibrary;
+import com.jogamp.common.util.JarUtil;
+
+public class TempJarCache {
+ // A HashMap of native libraries that can be loaded with System.load()
+ // The key is the string name of the library as passed into the loadLibrary
+ // call; it is the file name without the directory or the platform-dependent
+ // library prefix and suffix. The value is the absolute path name to the
+ // unpacked library file in nativeTmpDir.
+ private static Map<String, String> nativeLibMap;
+
+ // Set of native jar files added
+ private static Set<JarFile> nativeLibJars;
+ private static Set<JarFile> classFileJars;
+ private static Set<JarFile> resourceFileJars;
+
+ private static TempFileCache tmpFileCache;
+
+ private static boolean staticInitError = false;
+
+ static {
+ staticInitError = !TempFileCache.initSingleton();
+
+ if(!staticInitError) {
+ tmpFileCache = new TempFileCache();
+ staticInitError = !tmpFileCache.isValid();
+ }
+
+ if(!staticInitError) {
+ // Initialize the collections of resources
+ nativeLibMap = new HashMap<String, String>();
+ nativeLibJars = new HashSet<JarFile>();
+ classFileJars = new HashSet<JarFile>();
+ resourceFileJars = new HashSet<JarFile>();
+ }
+ }
+
+ /**
+ * Documented way to kick off static initialization
+ * @return true is static initialization was successful
+ */
+ public static boolean initSingleton() {
+ return isValid();
+ }
+
+ /**
+ * @return true is static initialization was successful
+ */
+ public static boolean isValid() {
+ return !staticInitError;
+ }
+
+ public static TempFileCache getTempFileCache() {
+ return tmpFileCache;
+ }
+
+ public static boolean contains(JarFile jarFile) throws IOException {
+ return nativeLibJars.contains(jarFile);
+ }
+
+ /**
+ * Adds native libraries, if not yet added.
+ *
+ * @param jarFile
+ * @return
+ * @throws IOException
+ */
+ public static boolean addNativeLibs(JarFile jarFile) throws IOException {
+ if(!nativeLibJars.contains(jarFile)) {
+ JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile,
+ true, false, false);
+ nativeLibJars.add(jarFile);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds native classes, if not yet added.
+ *
+ * TODO class access pending
+ * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
+ *
+ * @param jarFile
+ * @return
+ * @throws IOException
+ */
+ public static boolean addClasses(JarFile jarFile) throws IOException {
+ if(!classFileJars.contains(jarFile)) {
+ JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile,
+ false, true, false);
+ classFileJars.add(jarFile);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds native resources, if not yet added.
+ *
+ * @param jarFile
+ * @return
+ * @throws IOException
+ */
+ public static boolean addResources(JarFile jarFile) throws IOException {
+ if(!resourceFileJars.contains(jarFile)) {
+ JarUtil.extract(tmpFileCache.getTempDir(), null, jarFile,
+ false, false, true);
+ resourceFileJars.add(jarFile);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adds all types, native libraries, class files and other files (resources),
+ * if not yet added.
+ *
+ * TODO class access pending
+ * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
+ *
+ * @param jarFile
+ * @return
+ * @throws IOException
+ */
+ public static boolean addAll(JarFile jarFile) throws IOException {
+ if(!nativeLibJars.contains(jarFile) ||
+ !classFileJars.contains(jarFile) ||
+ !resourceFileJars.contains(jarFile)) {
+ final boolean extractNativeLibraries = !nativeLibJars.contains(jarFile);
+ final boolean extractClassFiles = !classFileJars.contains(jarFile);
+ final boolean extractOtherFiles = !resourceFileJars.contains(jarFile);
+ JarUtil.extract(tmpFileCache.getTempDir(), nativeLibMap, jarFile,
+ extractNativeLibraries, extractClassFiles, extractOtherFiles);
+ if(extractNativeLibraries) {
+ nativeLibJars.add(jarFile);
+ }
+ if(extractClassFiles) {
+ classFileJars.add(jarFile);
+ }
+ if(extractOtherFiles) {
+ resourceFileJars.add(jarFile);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static String findLibrary(String libName) {
+ // try with mapped library basename first
+ String path = nativeLibMap.get(libName);
+ if(null == path) {
+ // if valid library name, try absolute path in temp-dir
+ if(null != NativeLibrary.isValidNativeLibraryName(libName, false)) {
+ final File f = new File(tmpFileCache.getTempDir(), libName);
+ if(f.exists()) {
+ path = f.getAbsolutePath();
+ }
+ }
+ }
+ return path;
+ }
+
+ /** TODO class access pending
+ * needs Classloader.defineClass(..) access, ie. own derivation - will do when needed ..
+ public static Class<?> findClass(String name, ClassLoader cl) throws IOException, ClassFormatError {
+ final File f = new File(nativeTmpFileCache.getTempDir(), IOUtil.getClassFileName(name));
+ if(f.exists()) {
+ Class.forName(fname, initialize, loader)
+ URL url = new URL(f.getAbsolutePath());
+ byte[] b = IOUtil.copyStream2ByteArray(new BufferedInputStream( url.openStream() ));
+ MyClassLoader mcl = new MyClassLoader(cl);
+ return mcl.defineClass(name, b, 0, b.length);
+ }
+ return null;
+ } */
+
+ public static String findResource(String name) {
+ final File f = new File(tmpFileCache.getTempDir(), name);
+ if(f.exists()) {
+ return f.getAbsolutePath();
+ }
+ return null;
+ }
+
+}