/**
  * Loads specified JAR.
  *
  * @param jarFileInfo
  * @throws IOException
  */
 private void loadJar(JarFileInfo jarFileInfo) throws IOException {
   lstJarFile.add(jarFileInfo);
   try {
     Enumeration<JarEntry> en = jarFileInfo.jarFile.entries();
     final String EXT_JAR = ".jar";
     while (en.hasMoreElements()) {
       JarEntry je = en.nextElement();
       if (je.isDirectory()) {
         continue;
       }
       String s = je.getName().toLowerCase(); // JarEntry name
       if (s.lastIndexOf(EXT_JAR) == s.length() - EXT_JAR.length()) {
         JarEntryInfo inf = new JarEntryInfo(jarFileInfo, je);
         File fileTemp = createTempFile(inf);
         logInfo(
             LogArea.JAR,
             "Loading inner JAR %s from temp file %s",
             inf.jarEntry,
             getFilename4Log(fileTemp));
         loadJar(new JarFileInfo(new JarFile(fileTemp), inf.getName(), jarFileInfo, fileTemp));
       }
     }
   } catch (JarClassLoaderException e) {
     throw new RuntimeException("ERROR on loading inner JAR: " + e.getMessageAll());
   }
 } // loadJar()
 /**
  * Loads class from a JAR and searches for all jar-in-jar.
  *
  * @param sClassName class to load.
  * @return Loaded class.
  * @throws JarClassLoaderException.
  */
 private Class<?> findJarClass(String sClassName) throws JarClassLoaderException {
   // http://java.sun.com/developer/onlineTraining/Security/Fundamentals
   //       /magercises/ClassLoader/solution/FileClassLoader.java
   Class<?> c = hmClass.get(sClassName);
   if (c != null) {
     return c;
   }
   // Char '/' works for Win32 and Unix.
   String sName = sClassName.replace('.', '/') + ".class";
   JarEntryInfo inf = findJarEntry(sName);
   String jarSimpleName = null;
   if (inf != null) {
     jarSimpleName = inf.jarFileInfo.simpleName;
     definePackage(sClassName, inf);
     byte[] a_by = inf.getJarBytes();
     try {
       c = defineClass(sClassName, a_by, 0, a_by.length, pd);
     } catch (ClassFormatError e) {
       throw new JarClassLoaderException(null, e);
     }
   }
   if (c == null) {
     throw new JarClassLoaderException(sClassName);
   }
   hmClass.put(sClassName, c);
   logInfo(
       LogArea.CLASS,
       "Loaded %s by %s from JAR %s",
       sClassName,
       getClass().getName(),
       jarSimpleName);
   return c;
 } // findJarClass()
 /**
  * Using temp files (one per inner JAR/DLL) solves many issues: 1. There are no ways to load JAR
  * defined in a JarEntry directly into the JarFile object (see also #6 below). 2. Cannot use
  * memory-mapped files because they are using nio channels, which are not supported by JarFile
  * ctor. 3. JarFile object keeps opened JAR files handlers for fast access. 4. Deep resource in a
  * jar-in-jar does not have well defined URL. Making temp file with JAR solves this problem. 5.
  * Similar issues with native libraries: <code>ClassLoader.findLibrary()</code> accepts ONLY
  * string with absolute path to the file with native library. 6. Option
  * "java.protocol.handler.pkgs" does not allow access to nested JARs(?).
  *
  * @param inf JAR entry information.
  * @return temporary file object presenting JAR entry.
  * @throws JarClassLoaderException
  */
 private File createTempFile(JarEntryInfo inf) throws JarClassLoaderException {
   // Temp files directory:
   //   WinXP: C:/Documents and Settings/username/Local Settings/Temp/JarClassLoader
   //    Unix: /var/tmp/JarClassLoader
   if (dirTemp == null) {
     File dir = new File(System.getProperty("java.io.tmpdir"), TMP_SUB_DIRECTORY);
     if (!dir.exists()) {
       dir.mkdir();
     }
     chmod777(dir); // Unix - allow temp directory RW access to all users.
     if (!dir.exists() || !dir.isDirectory()) {
       throw new JarClassLoaderException("Cannot create temp directory " + dir.getAbsolutePath());
     }
     dirTemp = dir;
   }
   File fileTmp = null;
   try {
     fileTmp = File.createTempFile(inf.getName() + ".", null, dirTemp);
     fileTmp.deleteOnExit();
     chmod777(fileTmp); // Unix - allow temp file deletion by any user
     byte[] a_by = inf.getJarBytes();
     BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(fileTmp));
     os.write(a_by);
     os.close();
     return fileTmp;
   } catch (IOException e) {
     throw new JarClassLoaderException(
         String.format("Cannot create temp file '%s' for %s", fileTmp, inf.jarEntry), e);
   }
 } // createTempFile()
 /**
  * @see java.lang.ClassLoader#findResource(java.lang.String)
  * @return A URL object for reading the resource, or null if the resource could not be found.
  *     Example URL: jar:file:C:\...\some.jar!/resources/InnerText.txt
  */
 @Override
 protected URL findResource(String sName) {
   logDebug(LogArea.RESOURCE, "findResource: %s", sName);
   if (isLaunchedFromJar()) {
     JarEntryInfo inf = findJarEntry(normalizeResourceName(sName));
     if (inf != null) {
       URL url = inf.getURL();
       logInfo(LogArea.RESOURCE, "found resource: %s", url);
       return url;
     }
     logInfo(LogArea.RESOURCE, "not found resource: %s", sName);
     return null;
   }
   return super.findResource(sName);
 } // findResource()
 /**
  * @see java.lang.ClassLoader#findResources(java.lang.String)
  * @return An enumeration of {@link java.net.URL <tt>URL</tt>} objects for the resources
  */
 @Override
 public Enumeration<URL> findResources(String sName) throws IOException {
   logDebug(LogArea.RESOURCE, "getResources: %s", sName);
   if (isLaunchedFromJar()) {
     List<JarEntryInfo> lstJarEntry = findJarEntries(normalizeResourceName(sName));
     List<URL> lstURL = new ArrayList<URL>();
     for (JarEntryInfo inf : lstJarEntry) {
       URL url = inf.getURL();
       if (url != null) {
         lstURL.add(url);
       }
     }
     return Collections.enumeration(lstURL);
   }
   return super.findResources(sName);
 } // findResources()